Merge "Add support for disabled check actions"
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index 5d20f3f..e74eee1 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -231,7 +231,16 @@
    * primary actions might be rendered as buttons versus just menu entries in
    * an overflow menu.
    */
-  primary: boolean;
+  primary?: boolean;
+  /**
+   * Renders the action button in a disabled state. That can be useful for
+   * actions that are present most of the time, but sometimes don't apply. Then
+   * a grayed out button with a tooltip makes it easier for the user to
+   * understand why an expected action is not available. The tooltip should then
+   * contain a message about why the disabled state was set, not just about what
+   * the action would normally do.
+   */
+  disabled?: boolean;
   callback: ActionCallback;
 }
 
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-action.ts b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
new file mode 100644
index 0000000..6a7b24d
--- /dev/null
+++ b/polygerrit-ui/app/elements/checks/gr-checks-action.ts
@@ -0,0 +1,78 @@
+/**
+ * @license
+ * Copyright (C) 2021 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 {html} from 'lit-html';
+import {css, customElement, property} from 'lit-element';
+import {GrLitElement} from '../lit/gr-lit-element';
+import {Action} from '../../api/checks';
+import {checkRequiredProperty} from '../../utils/common-util';
+import {fireActionTriggered} from '../../services/checks/checks-util';
+
+@customElement('gr-checks-action')
+export class GrChecksAction extends GrLitElement {
+  @property()
+  action!: Action;
+
+  @property()
+  eventTarget?: EventTarget;
+
+  connectedCallback() {
+    super.connectedCallback();
+    checkRequiredProperty(this.action, 'action');
+  }
+
+  static get styles() {
+    return [
+      css`
+        :host {
+          display: inline-block;
+        }
+        gr-button {
+          --padding: var(--spacing-s) var(--spacing-m);
+        }
+        gr-button paper-tooltip {
+          text-transform: none;
+        }
+      `,
+    ];
+  }
+
+  render() {
+    return html`
+      <gr-button
+        link
+        ?disabled="${this.action.disabled}"
+        class="action"
+        @click="${this.handleClick}"
+      >
+        ${this.action.name}
+        <paper-tooltip ?hidden="${!this.action.tooltip}" offset="5"
+          >${this.action.tooltip}</paper-tooltip
+        >
+      </gr-button>
+    `;
+  }
+
+  handleClick() {
+    fireActionTriggered(this.eventTarget ?? this, this.action);
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-checks-action': GrChecksAction;
+  }
+}
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 1bbef18..42ce718 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -27,6 +27,7 @@
   TemplateResult,
 } from 'lit-element';
 import {GrLitElement} from '../lit/gr-lit-element';
+import './gr-checks-action';
 import './gr-checks-attempt';
 import '@polymer/paper-tooltip/paper-tooltip';
 import {
@@ -53,11 +54,7 @@
   iconForLink,
   tooltipForLink,
 } from '../../services/checks/checks-util';
-import {
-  assertIsDefined,
-  check,
-  checkRequiredProperty,
-} from '../../utils/common-util';
+import {assertIsDefined, check} from '../../utils/common-util';
 import {toggleClass, whenVisible} from '../../utils/dom-util';
 import {durationString} from '../../utils/date-util';
 import {charsOnly} from '../../utils/string-util';
@@ -363,6 +360,9 @@
     const overflowItems = actions.slice(2).map(action => {
       return {...action, id: action.name};
     });
+    const disabledItems = overflowItems
+      .filter(action => action.disabled)
+      .map(action => action.id);
     return html`<div class="actions">
       ${this.renderAction(actions[0])} ${this.renderAction(actions[1])}
       <gr-dropdown
@@ -375,6 +375,7 @@
           toggleClass(this, 'dropdown-open', e.detail.value)}"
         ?hidden="${overflowItems.length === 0}"
         .items="${overflowItems}"
+        .disabledIds="${disabledItems}"
       >
         <iron-icon icon="gr-icons:more-vert" aria-labelledby="moreMessage">
         </iron-icon>
@@ -790,6 +791,9 @@
     const overflowItems = this.actions.slice(2).map(action => {
       return {...action, id: action.name};
     });
+    const disabledItems = overflowItems
+      .filter(action => action.disabled)
+      .map(action => action.id);
     return html`
       ${this.renderAction(this.actions[0])}
       ${this.renderAction(this.actions[1])}
@@ -801,6 +805,7 @@
         @tap-item="${this.handleAction}"
         ?hidden="${overflowItems.length === 0}"
         .items="${overflowItems}"
+        .disabledIds="${disabledItems}"
       >
         <iron-icon icon="gr-icons:more-vert" aria-labelledby="moreMessage">
         </iron-icon>
@@ -1076,53 +1081,10 @@
   }
 }
 
-@customElement('gr-checks-action')
-export class GrChecksAction extends GrLitElement {
-  @property()
-  action!: Action;
-
-  connectedCallback() {
-    super.connectedCallback();
-    checkRequiredProperty(this.action, 'action');
-  }
-
-  static get styles() {
-    return [
-      css`
-        :host {
-          display: inline-block;
-        }
-        gr-button {
-          --padding: var(--spacing-s) var(--spacing-m);
-        }
-        gr-button paper-tooltip {
-          text-transform: none;
-        }
-      `,
-    ];
-  }
-
-  render() {
-    return html`
-      <gr-button link class="action" @click="${this.handleClick}">
-        ${this.action.name}
-        <paper-tooltip ?hidden="${!this.action.tooltip}" offset="5"
-          >${this.action.tooltip}</paper-tooltip
-        >
-      </gr-button>
-    `;
-  }
-
-  handleClick() {
-    fireActionTriggered(this, this.action);
-  }
-}
-
 declare global {
   interface HTMLElementTagNameMap {
     'gr-result-row': GrResultRow;
     'gr-result-expanded': GrResultExpanded;
     'gr-checks-results': GrChecksResults;
-    'gr-checks-action': GrChecksAction;
   }
 }
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
index ae6408d..10f036e 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run.ts
@@ -19,6 +19,7 @@
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-hovercard-run_html';
 import {customElement, property} from '@polymer/decorators';
+import './gr-checks-action';
 import {CheckRun} from '../../services/checks/checks-model';
 import {
   iconForCategory,
diff --git a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
index 08ceefe..c4402f5 100644
--- a/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
+++ b/polygerrit-ui/app/elements/checks/gr-hovercard-run_html.ts
@@ -202,7 +202,12 @@
       </div>
     </div>
     <template is="dom-repeat" items="[[computeActions(run)]]">
-      <div class="action"><gr-button link>[[item.name]]</gr-button></div>
+      <div class="action">
+        <gr-checks-action
+          event-target="[[_target]]"
+          action="[[item]]"
+        ></gr-checks-action>
+      </div>
     </template>
   </div>
 `;
diff --git a/polygerrit-ui/app/services/checks/checks-model.ts b/polygerrit-ui/app/services/checks/checks-model.ts
index ecd56cb..6275d69 100644
--- a/polygerrit-ui/app/services/checks/checks-model.ts
+++ b/polygerrit-ui/app/services/checks/checks-model.ts
@@ -257,19 +257,20 @@
           name: 'Ignore',
           tooltip: 'Ignore this result',
           primary: true,
-          callback: () => undefined,
+          callback: () => Promise.resolve({message: 'fake "ignore" triggered'}),
         },
         {
           name: 'Flag',
           tooltip: 'Flag this result as not useful',
           primary: true,
-          callback: () => undefined,
+          disabled: true,
+          callback: () => Promise.resolve({message: 'flag "flag" triggered'}),
         },
         {
           name: 'Upload',
           tooltip: 'Upload the result to the super cloud.',
           primary: false,
-          callback: () => undefined,
+          callback: () => Promise.resolve({message: 'fake "upload" triggered'}),
         },
       ],
       tags: [{name: 'INTERRUPTED'}, {name: 'WINDOWS'}],
@@ -334,17 +335,18 @@
       name: 'Re-Run',
       tooltip: 'More powerful run than before',
       primary: true,
-      callback: () => undefined,
+      callback: () => Promise.resolve({message: 'fake "re-run" triggered'}),
     },
     {
       name: 'Monetize',
       primary: true,
-      callback: () => undefined,
+      disabled: true,
+      callback: () => Promise.resolve({message: 'fake "monetize" triggered'}),
     },
     {
       name: 'Delete',
       primary: true,
-      callback: () => undefined,
+      callback: () => Promise.resolve({message: 'fake "delete" triggered'}),
     },
   ],
   results: [
@@ -446,29 +448,22 @@
   {
     name: 'Fake Action 1',
     primary: false,
+    disabled: true,
     tooltip: 'Tooltip for Fake Action 1',
-    callback: () => {
-      console.warn('fake action 1 triggered');
-      return undefined;
-    },
+    callback: () => Promise.resolve({message: 'fake action 1 triggered'}),
   },
   {
     name: 'Fake Action 2',
     primary: false,
+    disabled: true,
     tooltip: 'Tooltip for Fake Action 2',
-    callback: () => {
-      console.warn('fake action 2 triggered');
-      return undefined;
-    },
+    callback: () => Promise.resolve({message: 'fake action 2 triggered'}),
   },
   {
     name: 'Fake Action 3',
     primary: false,
     tooltip: 'Tooltip for Fake Action 3',
-    callback: () => {
-      console.warn('fake action 3 triggered');
-      return undefined;
-    },
+    callback: () => Promise.resolve({message: 'fake action 3 triggered'}),
   },
 ];
 
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 0a0881b..7eb2a5d 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -130,7 +130,7 @@
 
 export function primaryRunAction(run: CheckRun): Action | undefined {
   return runActions(run).filter(
-    action => action.name === primaryActionName(run.status)
+    action => !action.disabled && action.name === primaryActionName(run.status)
   )[0];
 }