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