_processMessages()

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;
      });
    });
  }