in zuul-results-summary/zuul-results-summary.js [252:374]
_processMessages(change) {
/*
* change-view-tab-content gets passed ChangeInfo object [1],
* registered in the property "change". We walk the list of
* messages with some regexps to extract into a data structure
* stored in __table
*
* __table is an [] of objects
*
* author: "<string> CI"
* date: Date object of date message posted, useful for
* sorting, diffs, etc.
* gr_date: original message timestamp sutiable to pass to
* gr-date-formatter
* revision: the revision the patchset was made against
* rechecks: the number of times we've seen the same
* ci run for the same revision
* status: one of <succeeded|failed>
* pipeline: string of reporting pipeline
* (may be undefined for some CI)
* results: [] of objects
* job: job name
* link: raw URL link to logs
* result: one of <SUCCESS|FAILURE>
* time: duration of run in human string (e.g. 2m 5s)
*
* This is then presented by the template
*
* [1] https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
*/
this.__table = [];
change.messages.forEach(message => {
if (! (this._match_message_via_tag(message) ||
this._match_message_via_regex(message))) {
return;
}
const date = new Date(message.date);
const revision = message._revision_number;
const sp = this._get_status_and_pipeline(message);
if (!sp) {
// This shouldn't happen as we've validated it is a Zuul message.
return;
}
const status = sp[0];
const pipeline = sp[1];
// We only want the latest entry for each CI system in
// each pipeline
const existing = this.__table.findIndex(entry =>
(entry.author_id === message.author._account_id) &&
(entry.pipeline === pipeline));
// If this is a comment by the same CI on the same pipeline and
// the same revision, it's considered a "recheck" ... i.e. likely
// manually triggered to run again. Take a note of this.
let rechecks = 0;
if (existing !== -1) {
if (this.__table[existing].revision === revision) {
rechecks = this.__table[existing].rechecks + 1;
}
}
// Find each result line
const results = [];
const lines = message.message.split('\n');
// We have to match a few different things ...
// A "standard" line is like
// - passing-job http://... : SUCCESS in 2m 45s
// Skipped jobs don't have a time, e.g.
// - skipped-job http://... : SKIPPED
// Error status has a string before the time
// - error-job http://... : ERROR A freeform string in 2m 45s
const resultRe = /^- (?<job>[^ ]+) (?:(?<link>https?:\/\/[^ ]+)|[^ ]+) : ((ERROR (?<errormsg>.*?) in (?<errtime>.*))|(?<result>[^ ]+)( in (?<time>.*))?)/;
lines.forEach(line => {
const result = resultRe.exec(line);
if (result) {
if (result.groups.result === "SKIPPED") {
result.groups.link = null;
}
// Note you can't duplicate match group names, even if
// it's behind an | statement like above. So for error
// matches we copy things into the right place to display.
if (result.groups.errormsg) {
result.groups.result = "ERROR";
result.groups.time = result.groups.errtime;
}
results.push(result.groups);
}
});
const table = {
author_name: message.author.name,
author_id: message.author._account_id,
revision,
rechecks,
date,
gr_date: message.date,
status,
succeeded: status === 'succeeded',
pipeline,
results,
};
if (existing === -1) {
this.__table.push(table);
} else {
this.__table[existing] = table;
}
// Sort first by listed priority, then by date
this.__table.sort((a, b) => {
// >>> 0 is just a trick to convert -1 to uint max
// of 2^32-1
const p_a = ZUUL_PRIORITY.indexOf(a.author_id) >>> 0;
const p_b = ZUUL_PRIORITY.indexOf(b.author_id) >>> 0;
const priority = p_a - p_b;
const date = b.date - a.date;
return priority || date;
});
});
}