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:
parent
d5c0f768c2
commit
cca5daf097
16 changed files with 25024 additions and 16 deletions
|
@ -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;
|
||||
});
|
||||
};
|
Reference in a new issue