UI: Add chips to ease navigation to the Tasks tab
On changes with a lot of label scores or changed files, users
have to scroll to view status of their tasks. This change adds
a chip (under the commit message, similar to comment chips)
each for 'needs' (aka ready) and 'blocked' (aka failed) tasks
to ease navigation and to show a quick summary of tasks. Here
are some highlights:
- Chips are shown only after the tasks have loaded
- Clicking the chips changes focus to the Tasks tab
- Each chip shows the count of respective tasks
- Chips are hidden if the count of respective tasks is 0
- Warning style is used for 'needs' chip and error for 'blocked'
Also, update the eslint ecmaVersion[1] to 2020 to support optional
chaining.
[1] https://eslint.org/blog/2020/07/eslint-v7.5.0-released/#optional-chaining-support
Change-Id: I3e69232661577b64064ce03627cf7efb86728c81
diff --git a/.eslintrc.json b/.eslintrc.json
index 0c290fa..358c95d 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -4,7 +4,7 @@
"google"
],
"parserOptions": {
- "ecmaVersion": 8,
+ "ecmaVersion": 2020,
"sourceType": "module"
},
"env": {
diff --git a/gr-task-plugin/gr-task-chip.js b/gr-task-plugin/gr-task-chip.js
new file mode 100644
index 0000000..8e46bd0
--- /dev/null
+++ b/gr-task-plugin/gr-task-chip.js
@@ -0,0 +1,66 @@
+/**
+ * @license
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import './gr-task-plugin.js';
+import {htmlTemplate} from './gr-task-chip_html.js';
+
+class GrTaskChip extends Polymer.Element {
+ static get is() {
+ return 'gr-task-chip';
+ }
+
+ static get template() {
+ return htmlTemplate;
+ }
+
+ static get properties() {
+ return {
+ chip_style: {
+ type: String,
+ notify: true,
+ value: 'ready',
+ },
+ };
+ }
+
+ _setTasksTabActive() {
+ // TODO: Identify a better way as current implementation is fragile
+ const endPointDecorators = document.querySelector('gr-app')
+ .shadowRoot.querySelector('gr-app-element')
+ .shadowRoot.querySelector('main')
+ .querySelector('gr-change-view')
+ .shadowRoot.querySelector('#mainContent')
+ .getElementsByTagName('gr-endpoint-decorator');
+ if (endPointDecorators) {
+ for (let i = 0; i <= endPointDecorators.length; i++) {
+ const el = endPointDecorators[i]
+ ?.shadowRoot?.querySelector('gr-task-plugin');
+ if (el) {
+ el.shadowRoot.querySelector('paper-tabs')
+ .querySelector('paper-tab').scrollIntoView();
+ break;
+ }
+ }
+ }
+ }
+
+ _onChipClick() {
+ this._setTasksTabActive();
+ }
+}
+
+customElements.define(GrTaskChip.is, GrTaskChip);
\ No newline at end of file
diff --git a/gr-task-plugin/gr-task-chip_html.js b/gr-task-plugin/gr-task-chip_html.js
new file mode 100644
index 0000000..af7338f
--- /dev/null
+++ b/gr-task-plugin/gr-task-chip_html.js
@@ -0,0 +1,72 @@
+/**
+ * @license
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+export const htmlTemplate = Polymer.html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
+ <style include="shared-styles">
+ .taskSummaryChip {
+ color: var(--chip-color);
+ cursor: pointer;
+ display: inline-block;
+ padding: var(--spacing-xxs) var(--spacing-m) var(--spacing-xxs)
+ var(--spacing-s);
+ margin-right: var(--spacing-s);
+ border-radius: 12px;
+ border: 1px solid gray;
+ vertical-align: top;
+ /* centered position of 20px chips in 24px line-height inline flow */
+ vertical-align: top;
+ position: relative;
+ top: 2px;
+ }
+ .taskSummaryChip.ready {
+ border-color: var(--warning-foreground);
+ background: var(--warning-background);
+ }
+ .taskSummaryChip.ready:hover {
+ background: var(--warning-background-hover);
+ box-shadow: var(--elevation-level-1);
+ }
+ .taskSummaryChip.ready:focus-within {
+ background: var(--warning-background-focus);
+ }
+ .taskSummaryChip.fail {
+ color: var(--error-foreground);
+ border-color: var(--error-foreground);
+ background: var(--error-background);
+ }
+ .taskSummaryChip.fail:hover {
+ background: var(--error-background-hover);
+ box-shadow: var(--elevation-level-1);
+ }
+ .taskSummaryChip.fail:focus-within {
+ background: var(--error-background-focus);
+ }
+ .font-small {
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-normal);
+ line-height: var(--line-height-small);
+ }
+ </style>
+ <button
+ class$="taskSummaryChip font-small [[chip_style]]"
+ on-click="_onChipClick">
+ <slot></slot>
+ </button>
+`;
diff --git a/gr-task-plugin/gr-task-plugin.js b/gr-task-plugin/gr-task-plugin.js
index aa0e5f3..4813d10 100644
--- a/gr-task-plugin/gr-task-plugin.js
+++ b/gr-task-plugin/gr-task-plugin.js
@@ -123,6 +123,13 @@
if (taskPluginInfo) {
this._tasks = this._addTasks(taskPluginInfo.roots);
+ document.dispatchEvent(new CustomEvent('tasks-loaded', {
+ detail: {
+ ready_count: this._ready_count,
+ fail_count: this._fail_count,
+ },
+ composed: true, bubbles: true,
+ }));
}
}
}
diff --git a/gr-task-plugin/gr-task-summary.js b/gr-task-plugin/gr-task-summary.js
new file mode 100644
index 0000000..6e54268
--- /dev/null
+++ b/gr-task-plugin/gr-task-summary.js
@@ -0,0 +1,60 @@
+/**
+ * @license
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import {htmlTemplate} from './gr-task-summary_html.js';
+import './gr-task-chip.js';
+
+class GrTaskSummary extends Polymer.Element {
+ static get is() {
+ return 'gr-task-summary';
+ }
+
+ static get template() {
+ return htmlTemplate;
+ }
+
+ static get properties() {
+ return {
+ ready_count: {
+ type: Number,
+ notify: true,
+ value: 0,
+ },
+
+ fail_count: {
+ type: Number,
+ notify: true,
+ value: 0,
+ },
+ };
+ }
+
+ /** @override */
+ ready() {
+ super.ready();
+ document.addEventListener('tasks-loaded', e => {
+ this.ready_count = e.detail.ready_count;
+ this.fail_count = e.detail.fail_count;
+ });
+ }
+
+ _can_show(ready_count, fail_count) {
+ return ready_count || fail_count;
+ }
+}
+
+customElements.define(GrTaskSummary.is, GrTaskSummary);
\ No newline at end of file
diff --git a/gr-task-plugin/gr-task-summary_html.js b/gr-task-plugin/gr-task-summary_html.js
new file mode 100644
index 0000000..18afc66
--- /dev/null
+++ b/gr-task-plugin/gr-task-summary_html.js
@@ -0,0 +1,80 @@
+/**
+ * @license
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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.
+ */
+
+export const htmlTemplate = Polymer.html`
+ <style include="gr-a11y-styles">
+ /* Workaround for empty style block - see https://github.com/Polymer/tools/issues/408 */
+ </style>
+ <style include="shared-styles">
+ :host {
+ display: block;
+ color: var(--deemphasized-text-color);
+ max-width: 625px;
+ margin-bottom: var(--spacing-m);
+ }
+ .zeroState {
+ color: var(--deemphasized-text-color);
+ }
+ .loading.zeroState {
+ margin-right: var(--spacing-m);
+ }
+ div.error,
+ .login {
+ display: flex;
+ color: var(--primary-text-color);
+ padding: 0 var(--spacing-s);
+ margin: var(--spacing-xs) 0;
+ width: 490px;
+ }
+ div.error {
+ background-color: var(--error-background);
+ }
+ div.error .right {
+ overflow: hidden;
+ }
+ div.error .right .message {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ td.key {
+ padding-right: var(--spacing-l);
+ padding-bottom: var(--spacing-s);
+ line-height: calc(var(--line-height-normal) + var(--spacing-s));
+ }
+ td.value {
+ padding-right: var(--spacing-l);
+ padding-bottom: var(--spacing-s);
+ line-height: calc(var(--line-height-normal) + var(--spacing-s));
+ display: flex;
+ }
+ div {
+ margin-left: var(--spacing-m);
+ }
+ </style>
+ <div class="task_summary" hidden$="[[!_can_show(ready_count, fail_count)]]">
+ <table>
+ <tr>
+ <td class="key">Tasks</td>
+ <td class="value">
+ <gr-task-chip chip_style="ready" hidden$="[[!ready_count]]">[[ready_count]] needs</gr-task-chip>
+ <gr-task-chip chip_style="fail" hidden$="[[!fail_count]]">[[fail_count]] blocked</gr-task-chip>
+ </td>
+ </tr>
+ </table>
+ </div>
+`;
diff --git a/gr-task-plugin/plugin.js b/gr-task-plugin/plugin.js
index 59d05b0..2219f12 100644
--- a/gr-task-plugin/plugin.js
+++ b/gr-task-plugin/plugin.js
@@ -16,8 +16,11 @@
*/
import './gr-task-plugin.js';
+import './gr-task-summary.js';
Gerrit.install(plugin => {
plugin.registerCustomComponent(
'change-view-integration', 'gr-task-plugin');
+ plugin.registerCustomComponent(
+ 'commit-container', 'gr-task-summary');
});