Further refactor
diff --git a/gr-zuul-summary-status/gr-zuul-summary-status-view-tab-header-view.js b/gr-zuul-summary-status/gr-zuul-summary-status-view-tab-header-view.js
deleted file mode 100644
index 81a680a..0000000
--- a/gr-zuul-summary-status/gr-zuul-summary-status-view-tab-header-view.js
+++ /dev/null
@@ -1,14 +0,0 @@
-class GrZuulSummaryStatusChangeViewTabHeaderView extends Polymer.Element {
-
- static get is() {
- return 'gr-zuul-summary-status-view-tab-header-view';
- }
-
- static get template() {
- return Polymer.html`Zuul Summary`;
- }
- }
-
-customElements.define(GrZuulSummaryStatusChangeViewTabHeaderView.is,
- GrZuulSummaryStatusChangeViewTabHeaderView);
-
diff --git a/gr-zuul-summary-status/gr-zuul-summary-status-view.js b/gr-zuul-summary-status/gr-zuul-summary-status-view.js
deleted file mode 100644
index 78e0495..0000000
--- a/gr-zuul-summary-status/gr-zuul-summary-status-view.js
+++ /dev/null
@@ -1,152 +0,0 @@
-class GrZuulSummaryStatusView extends Polymer.Element {
-
- static get is() {
- return 'gr-zuul-summary-status-view'
- }
-
- static get properties() {
- return {
- change: Object,
- revision: Object
- }
- }
-
- static get template() {
- return Polymer.html`
- <style>
- table {
- table-layout: fixed;
- width: 100%;
- border-collapse: collapse;
- }
-
- th, td {
- text-align: left;
- padding: 10px;
- }
-
- th {
- background-color: #f7ffff;
- }
-
- tr:nth-child(even) {
- background-color: #f2f2f2;
- }
-
- .status-SUCCESS {
- color: green;
- }
-
- .status-FAILURE {
- color: red;
- }
- </style>
-
- <template is="dom-repeat" items="[[__table]]">
- <table>
- <tr>
- <th>[[item.author]] in pipeline [[item.pipeline]] ([[item.rechecks]] rechecks)</th>
- <th>[[item.date]]</th>
- </tr>
- <template is="dom-repeat" items="[[item.results]]" as="job">
- <tr>
- <td><a href="[[job.link]]">[[job.job]]</a></td>
- <td><span class$="status-[[job.result]]">[[job.result]]</span> in [[job.time]]</td>
- </tr>
- </template>
- </table>
- </template>
-`;
- }
-
- ready() {
- super.ready();
- /*
- * 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 datastructure
- * stored in __table
- *
- * __table is an [] of objects
- *
- * author: "<string> CI"
- * date: date message posted
- * 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 = [];
- this.change.messages.forEach((element) => {
- if (element.author.name) {
- /* First check it looks like Zuul, or a third-party CI
- * Zuul author */
- let authorRe = /^(?<author>.* CI|Zuul)/;
- var author = authorRe.exec(element.author.name);
- if (author) {
-
- /* If we've already seen a comment by this CI,
- * take note of it and increase the rechecks */
- let existing = this.__table.findIndex((entry) => {
- entry.author === author });
- let rechecks = existing == -1 ? 0 : (this.__table[existing].rechecks + 1);
-
- var date = new Date(element.date);
- /* Look for the build status message, e.g.:
- * Build succeeded (check pipeline).
- */
- let statusRe = /^Build (?<status>\w+) \((?<pipeline>[\w]+) pipeline\)\./gm
- let status = statusRe.exec(element.message);
- if (!status) {
- /* Match non-pipeline CI comments, e.g.:
- * Build succeeded.
- */
- let statusRe = /^Build (?<status>\w+)\./gm
- status = statusRe.exec(element.message)
- }
- if (status) {
- var table = {
- 'author': author.groups.author,
- 'rechecks': rechecks,
- 'date': date.toLocaleString(),
- 'status': status.groups.status,
- 'pipeline': status.groups.pipeline,
- 'results': []
- }
- /* Find each result line, e.g. :
- * - openstack-tox-py35 http://... : SUCCESS in 2m 45
- */
- let lines = element.message.split("\n");
- let resultRe = /^- (?<job>[^ ]+) (?<link>[^ ]+) : (?<result>[^ ]+) in (?<time>.*)/
- lines.forEach((line) => {
- var result = resultRe.exec(line);
- if (result) {
- table.results.push(result.groups);
- }
- });
- if (existing == -1) {
- this.__table.push(table);
- } else {
- this.__table[existing] = table;
- }
- console.debug("Zuul Summary table now:");
- console.debug(this.__table);
- }
- }
- }
- });
- }
-
-
-}
-
-customElements.define(GrZuulSummaryStatusView.is,
- GrZuulSummaryStatusView)
diff --git a/gr-zuul-summary-status/gr-zuul-summary-status.js b/gr-zuul-summary-status/gr-zuul-summary-status.js
index 6047674..61de196 100644
--- a/gr-zuul-summary-status/gr-zuul-summary-status.js
+++ b/gr-zuul-summary-status/gr-zuul-summary-status.js
@@ -1,20 +1,237 @@
-import './gr-zuul-summary-status-view-tab-header-view.js';
-import './gr-zuul-summary-status-view.js';
+// Copyright (c) 2020 Red Hat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
-function installZuulSummaryStatus(plugin) {
+/*
+ * Tab contents
+ */
+class ZuulSummaryStatusTab extends Polymer.Element {
+ static get properties() {
+ return {
+ change: Object,
+ revision: Object
+ }
+ }
+
+ static get template() {
+ return Polymer.html`
+ <style>
+ table {
+ table-layout: fixed;
+ width: 100%;
+ border-collapse: collapse;
+ }
+
+ th, td {
+ text-align: left;
+ padding: 10px;
+ }
+
+ th {
+ background-color: #f7ffff;
+ font-weight: normal;
+ color: rgb(33, 33, 33);
+ }
+
+ tr:nth-child(even) {
+ background-color: #f2f2f2;
+ }
+
+ .status-SUCCESS {
+ color: green;
+ }
+
+ .status-FAILURE {
+ color: red;
+ }
+ </style>
+
+ <template is="dom-repeat" items="[[__table]]">
+ <table>
+ <tr>
+ <th><b>[[item.author_name]]</b> on revision <b>[[item.revision]]</b> in pipeline <b>[[item.pipeline]]</b></th>
+ <th><template is="dom-if" if="{{item.rechecks}}">[[item.rechecks]] rechecks</template></th>
+ <th><b>[[item.date]]</b></th>
+ </tr>
+ <template is="dom-repeat" items="[[item.results]]" as="job">
+ <tr>
+ <td><a href="[[job.link]]">[[job.job]]</a></td>
+ <td><span class$="status-[[job.result]]">[[job.result]]</span></td>
+ <td>[[job.time]]</td>
+ </tr>
+ </template>
+ </table>
+ </template>`;
+ }
+
+ _match_message_via_tag(message) {
+ return (message.tag &&
+ message.tag.startsWith('autogenerated:zuul')) ? true : false;
+ }
+
+ _match_message_via_regex(message) {
+ // TODO: allow this to be passed in via config
+ let authorRe = /^(?<author>.* CI|Zuul)/;
+ let author = authorRe.exec(message.author.name);
+ return author ? true : false;
+ }
+
+ _get_status_and_pipeline(message) {
+ /* Look for the full Zuul-3ish build status message, e.g.:
+ * Build succeeded (check pipeline).
+ */
+ let statusRe = /^Build (?<status>\w+) \((?<pipeline>[\w]+) pipeline\)\./gm
+ let statusMatch = statusRe.exec(message.message);
+ if (!statusMatch) {
+ /* Match non-pipeline CI comments, e.g.:
+ * Build succeeded.
+ */
+ let statusRe = /^Build (?<status>\w+)\./gm
+ statusMatch = statusRe.exec(message.message)
+ }
+ if (!statusMatch) {
+ return false; // we can't parse this
+ }
+
+ let status = statusMatch.groups.status
+ let pipeline = statusMatch.groups.pipeline ?
+ statusMatch.groups.pipeline : 'unknown';
+ return [status, pipeline]
+ }
+
+ ready() {
+ super.ready();
+ /*
+ * 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 datastructure
+ * stored in __table
+ *
+ * __table is an [] of objects
+ *
+ * author: "<string> CI"
+ * date: date message posted
+ * 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 = [];
+ this.change.messages.forEach((message) => {
+
+ if (! (this._match_message_via_tag(message) ||
+ this._match_message_via_regex(message))) {
+ return;
+ }
+
+ let date = new Date(message.date);
+ let revision = message._revision_number;
+ let sp = this._get_status_and_pipeline(message);
+ if (!sp) {
+ // This shouldn't happen as we've validated it is a Zuul message.
+ return;
+ }
+ let status = sp[0];
+ let pipeline = sp[1];
+
+ // We only want the latest entry for each CI system in
+ // each pipeline
+ let 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, e.g. :
+ * - openstack-tox-py35 http://... : SUCCESS in 2m 45
+ */
+ let results = [];
+ let lines = message.message.split("\n");
+ let resultRe = /^- (?<job>[^ ]+) (?<link>[^ ]+) : (?<result>[^ ]+) in (?<time>.*)/
+ lines.forEach((line) => {
+ let result = resultRe.exec(line);
+ if (result) {
+ results.push(result.groups);
+ }
+ });
+
+ let table = {
+ 'author_name': message.author.name,
+ 'author_id': message.author._account_id,
+ 'revision': revision,
+ 'rechecks': rechecks,
+ 'date': date.toLocaleString(),
+ 'status': status,
+ 'pipeline': pipeline,
+ 'results': results
+ }
+
+ if (existing == -1) {
+ this.__table.push(table);
+ } else {
+ this.__table[existing] = table;
+ }
+ console.debug("Zuul Summary table now:");
+ console.debug(this.__table);
+ });
+ }
+}
+
+customElements.define('zuul-summary-status-tab',
+ ZuulSummaryStatusTab);
+
+/*
+ * Tab Header Element
+ */
+class ZuulSummaryStatusTabHeader extends Polymer.Element {
+ static get template() {
+ return Polymer.html`Zuul Summary`;
+ }
+ }
+
+customElements.define('zuul-summary-status-tab-header',
+ ZuulSummaryStatusTabHeader);
+
+
+/*
+ * Install plugin
+ */
+
+Gerrit.install(plugin => {
plugin.registerDynamicCustomComponent(
'change-view-tab-header',
- 'gr-zuul-summary-status-view-tab-header-view'
+ 'zuul-summary-status-tab-header'
);
plugin.registerDynamicCustomComponent(
'change-view-tab-content',
- 'gr-zuul-summary-status-view'
+ 'zuul-summary-status-tab'
);
-}
-
-// Install the plugin
-Gerrit.install(plugin => {
- installZuulSummaryStatus(plugin);
});