Add support for full logging of all actions in Quay, and the ability to view and filter these logs in the org’s admin view

This commit is contained in:
Joseph Schorr 2013-11-27 02:29:31 -05:00
parent d5c0f768c2
commit cca5daf097
16 changed files with 25024 additions and 16 deletions

View file

@ -1261,4 +1261,221 @@ RepositoryUsageChart.prototype.draw = function(container) {
this.arc_ = arc;
this.width_ = cw;
this.drawInternal_();
};
////////////////////////////////////////////////////////////////////////////////
/**
* A chart which displays the last seven days of actions in the account.
*/
function LogUsageChart(logData, titleMap) {
this.logs_ = logData;
this.titleMap_ = titleMap;
this.colorScale_ = d3.scale.category20();
}
/**
* Builds the D3-representation of the data.
*/
LogUsageChart.prototype.buildData_ = function() {
var parseDate = d3.time.format("%a, %d %b %Y %H:%M:%S GMT").parse
// Build entries for each kind of event that occurred, on each day. We have one
// entry per {kind, day} pair.
var map = {};
var entries = [];
for (var i = 0; i < this.logs_.length; ++i) {
var log = this.logs_[i];
var title = this.titleMap_[log.kind] || log.kind;
var datetime = parseDate(log.datetime);
var formatted = (datetime.getMonth() + 1) + '/' + datetime.getDate();
var adjusted = new Date(datetime.getFullYear(), datetime.getMonth(), datetime.getDate());
var key = title + '_' + formatted;
var found = map[key];
if (!found) {
found = {
'kind': log.kind,
'title': title,
'adjusted': adjusted,
'formatted': datetime.getDate(),
'count': 0
};
map[key] = found;
entries.push(found);
}
found['count']++;
}
this.entries_ = map;
// Build the data itself. We create a single entry for each possible kind of data, and then add (x, y) pairs
// for the number of times that kind of event occurred on a particular day.
var dataArray = [];
var dataMap = {};
var dateMap = {};
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
var key = entry.title;
var found = dataMap[key];
if (!found) {
found = {'key': key, 'values': [], 'kind': entry.kind};
dataMap[key] = found;
dataArray.push(found);
}
found.values.push({
'x': entry.adjusted,
'y': entry.count
});
dateMap[entry.adjusted.toString()] = entry.adjusted;
}
// Note: nvd3 has a bug that causes d3 to fail if there is not an entry for every single
// kind on each day that has data. Therefore, we pad those days with 0-length entries for each
// kind.
for (var i = 0; i < dataArray.length; ++i) {
var datum = dataArray[i];
for (var sDate in dateMap) {
if (!dateMap.hasOwnProperty(sDate)) {
continue;
}
var cDate = dateMap[sDate];
var found = false;
for (var j = 0; j < datum.values.length; ++j) {
if (datum.values[j]['x'].getDate() == cDate.getDate()) {
found = true;
break;
}
}
if (!found) {
datum.values.push({
'x': cDate,
'y': 0
});
}
}
datum.values.sort(function(a, b) {
return a['x'].getDate() - b['x'].getDate();
});
}
return this.data_ = dataArray;
};
/**
* Renders the tooltip when hovering over an element in the chart.
*/
LogUsageChart.prototype.renderTooltip_ = function(d, e) {
var entry = this.entries_[d + '_' + e];
if (!entry) {
entry = {'count': 0};
}
var s = entry.count == 1 ? '' : 's';
return d + ' - ' + entry.count + ' time' + s + ' on ' + e;
};
/**
* Returns the color used in the chart for log entries of the given
* kind.
*/
LogUsageChart.prototype.getColor = function(kind) {
var colors = this.colorScale_.range();
var index = 0;
for (var i = 0; i < this.data_.length; ++i) {
var datum = this.data_[i];
var key = this.titleMap_[kind] || kind;
if (datum.key == key) {
index = i;
break;
}
}
return colors[index];
};
LogUsageChart.prototype.handleStateChange_ = function(e) {
var allowed = {};
var disabled = e.disabled;
for (var i = 0; i < this.data_.length; ++i) {
if (!disabled[i]) {
allowed[this.data_[i].kind] = true;
}
}
$(this).trigger({
'type': 'filteringChanged',
'allowed': allowed
});
};
/**
* Draws the chart in the given container element.
*/
LogUsageChart.prototype.draw = function(container) {
// Returns a date offset from the given date by "days" Days.
var offsetDate = function(d, days) {
var copy = new Date(d.getTime());
copy.setDate(copy.getDate() + days);
return copy;
};
var that = this;
var data = this.buildData_();
nv.addGraph(function() {
// Build the chart itself.
var chart = nv.models.multiBarChart()
.margin({top: 30, right: 30, bottom: 50, left: 30})
.stacked(false)
.staggerLabels(false)
.tooltip(function(d, e) {
return that.renderTooltip_(d, e);
})
.color(that.colorScale_.range())
.groupSpacing(0.1);
chart.multibar.delay(0);
// Create the x-axis domain to encompass a week from today.
var domain = [];
var datetime = new Date();
datetime = new Date(datetime.getFullYear(), datetime.getMonth(), datetime.getDate());
for (var i = 7; i >= 0; --i) {
domain.push(offsetDate(datetime, -1 * i));
}
chart.xDomain(domain);
// Finish setting up the chart.
chart.xAxis
.tickFormat(d3.time.format("%m/%d"));
chart.yAxis
.tickFormat(d3.format(',f'));
d3.select('#bar-chart svg')
.datum(data)
.transition()
.duration(500)
.call(chart);
nv.utils.windowResize(chart.update);
chart.multibar.dispatch.on('elementClick', function(e) { window.console.log(e); });
chart.dispatch.on('stateChange', function(e) { that.handleStateChange_(e); });
return chart;
});
};