Handle ERROR status

The error status is slightly different and has a free-form string.
Parse this and put it in the results.  We put this behind a tooltip on
the ERROR status; not terribly discoverable so further work is
welcome.

Change-Id: I10fe614ad6d47964ca0d45f32cf4ce105c6ec76f
diff --git a/zuul-results-summary/zuul-results-summary.js b/zuul-results-summary/zuul-results-summary.js
index 2259362..559c3d5 100644
--- a/zuul-results-summary/zuul-results-summary.js
+++ b/zuul-results-summary/zuul-results-summary.js
@@ -87,6 +87,10 @@
       color: red;
     }
 
+    .status-ERROR {
+      color: red;
+    }
+
     .status-RETRY_LIMIT {
       color: red;
     }
@@ -142,7 +146,8 @@
       <tr>
        <template is="dom-if" if="{{job.link}}"><td><a href="{{job.link}}">[[job.job]]</a></td></template>
        <template is="dom-if" if="{{!job.link}}"><td><span style="color: var(--secondary-text-color)">[[job.job]]</span></td></template>
-       <td><span class$="status-[[job.result]]">[[job.result]]</span></td>
+       <template is="dom-if" if="{{job.errormsg}}"><td><span title="[[job.errormsg]]" class$="status-[[job.result]]">[[job.result]]</span></td></template>
+       <template is="dom-if" if="{{!job.errormsg}}"><td><span class$="status-[[job.result]]">[[job.result]]</span></td></template>
        <td>[[job.time]]</td>
       </tr>
      </template>
@@ -262,21 +267,31 @@
         }
       }
 
-      // Find each result line, e.g. :
-      //   - openstack-tox-py35 http://... : SUCCESS in 2m 45
+      // Find each result line
       const results = [];
       const lines = message.message.split('\n');
-      const resultRe = /^- (?<job>[^ ]+) (?:(?<link>https?:\/\/[^ ]+)|[^ ]+) : (?<result>[^ ]+)( in (?<time>.*))?/;
+      // 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) {
-          // Note SKIPPED jobs have no time and an invalid placeholder
-          // URL.  Since there's no point linking this URL, null
-          // that match out and it will just use the job name but not
-          // make it a <a>.
-          if (result.groups.result === 'SKIPPED') {
+          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);
         }
       });