Add handling for run actions in new Checks UI
Change-Id: I8d463eb27c406832e63d2a779fde4de8377e12eb
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index 859d82d..87d7a2a 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -224,8 +224,8 @@
export type ActionCallback = (
change: number,
patchset: number,
- attempt: number,
- externalId: string,
+ attempt: number | undefined,
+ externalId: string | undefined,
/** Identical to 'checkName' property of CheckRun. */
checkName: string,
/** Identical to 'name' property of Action entity. */
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
index 6638c5f..09a2319 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-runs.ts
@@ -18,11 +18,12 @@
import {classMap} from 'lit-html/directives/class-map';
import {css, customElement, property} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
-import {CheckRun, RunStatus} from '../../api/checks';
+import {Action, CheckRun, RunStatus} from '../../api/checks';
import {sharedStyles} from '../../styles/shared-styles';
import {
compareByWorstCategory,
iconForRun,
+ primaryRunAction,
} from '../../services/checks/checks-util';
import {
allRuns$,
@@ -59,6 +60,33 @@
);
}
+export interface ActionTriggeredEventDetail {
+ action: Action;
+ run: CheckRun;
+}
+
+export type ActionTriggeredEvent = CustomEvent<ActionTriggeredEventDetail>;
+
+declare global {
+ interface HTMLElementEventMap {
+ 'action-triggered': ActionTriggeredEvent;
+ }
+}
+
+function fireActionTriggered(
+ target: EventTarget,
+ action: Action,
+ run: CheckRun
+) {
+ target.dispatchEvent(
+ new CustomEvent('action-triggered', {
+ detail: {action, run},
+ composed: true,
+ bubbles: true,
+ })
+ );
+}
+
@customElement('gr-checks-run')
export class GrChecksRun extends GrLitElement {
static get styles() {
@@ -70,14 +98,17 @@
--thick-border: 6px;
}
.chip {
- display: block;
- font-weight: var(--font-weight-bold);
+ display: flex;
+ justify-content: space-between;
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: var(--spacing-s) var(--spacing-m);
margin-top: var(--spacing-s);
cursor: default;
}
+ .name {
+ font-weight: var(--font-weight-bold);
+ }
.chip.error {
border-left: var(--thick-border) solid var(--error-foreground);
}
@@ -117,6 +148,14 @@
div.chip.selected iron-icon {
color: var(--selected-foreground);
}
+ gr-button.action {
+ --padding: var(--spacing-xs) var(--spacing-m);
+ /* The button should fit into the 20px line-height. The negative
+ margin provides the extra space needed for the vertical padding.
+ Alternatively we could have set the vertical padding to 0, but
+ that would not have been a nice click target. */
+ margin: calc(0px - var(--spacing-xs));
+ }
`,
];
}
@@ -130,20 +169,39 @@
render() {
const icon = this.selected ? 'check-circle' : iconForRun(this.run);
const classes = {chip: true, [icon]: true, selected: this.selected};
+ const action = primaryRunAction(this.run);
return html`
- <div @click="${this._handleChipClick}" class="${classMap(classes)}">
- <iron-icon icon="gr-icons:${icon}"></iron-icon>
- <span>${this.run.checkName}</span>
+ <div @click="${this.handleChipClick}" class="${classMap(classes)}">
+ <div class="left">
+ <iron-icon icon="gr-icons:${icon}"></iron-icon>
+ <span class="name">${this.run.checkName}</span>
+ </div>
+ <div class="right">
+ ${action
+ ? html`<gr-button
+ class="action"
+ link
+ @click="${(e: MouseEvent) => this.handleAction(e, action)}"
+ >${action.name}</gr-button
+ >`
+ : ''}
+ </div>
</div>
`;
}
- _handleChipClick(e: MouseEvent) {
+ private handleChipClick(e: MouseEvent) {
e.stopPropagation();
e.preventDefault();
fireRunSelected(this, this.run.checkName);
}
+
+ private handleAction(e: MouseEvent, action: Action) {
+ e.stopPropagation();
+ e.preventDefault();
+ fireActionTriggered(this, action, this.run);
+ }
}
@customElement('gr-checks-runs')
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
index 4844593..d31ec53 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-tab.ts
@@ -17,13 +17,14 @@
import {html} from 'lit-html';
import {css, customElement, property} from 'lit-element';
import {GrLitElement} from '../lit/gr-lit-element';
-import {CheckResult, CheckRun} from '../../api/checks';
+import {Action, CheckResult, CheckRun} from '../../api/checks';
import {allResults$, allRuns$} from '../../services/checks/checks-model';
import './gr-checks-runs';
import './gr-checks-results';
import {sharedStyles} from '../../styles/shared-styles';
-import {currentPatchNum$} from '../../services/change/change-model';
-import {PatchSetNum} from '../../types/common';
+import {changeNum$, currentPatchNum$} from '../../services/change/change-model';
+import {NumericChangeId, PatchSetNum} from '../../types/common';
+import {ActionTriggeredEvent} from './gr-checks-runs';
/**
* The "Checks" tab on the Gerrit change page. Gets its data from plugins that
@@ -39,11 +40,19 @@
@property()
currentPatchNum: PatchSetNum | undefined = undefined;
+ @property()
+ changeNum: NumericChangeId | undefined = undefined;
+
constructor() {
super();
this.subscribe('runs', allRuns$);
this.subscribe('results', allResults$);
this.subscribe('currentPatchNum', currentPatchNum$);
+ this.subscribe('changeNum', changeNum$);
+
+ this.addEventListener('action-triggered', (e: ActionTriggeredEvent) =>
+ this.handleActionTriggered(e.detail.action, e.detail.run)
+ );
}
static get styles() {
@@ -70,7 +79,7 @@
display: flex;
}
.runs {
- min-width: 250px;
+ min-width: 300px;
min-height: 400px;
border-right: 1px solid var(--border-color);
}
@@ -105,6 +114,22 @@
</div>
`;
}
+
+ private handleActionTriggered(action: Action, run: CheckRun) {
+ if (!this.changeNum) return;
+ if (!this.currentPatchNum) return;
+ // TODO(brohlfs): The callback is supposed to be returning a promise.
+ // A toast should be displayed until the promise completes. And then the
+ // data should be updated.
+ action.callback(
+ this.changeNum,
+ this.currentPatchNum as number,
+ run.attempt,
+ run.externalId,
+ run.checkName,
+ action.name
+ );
+ }
}
declare global {
diff --git a/polygerrit-ui/app/services/checks/checks-util.ts b/polygerrit-ui/app/services/checks/checks-util.ts
index 86b9b47..30b82b6 100644
--- a/polygerrit-ui/app/services/checks/checks-util.ts
+++ b/polygerrit-ui/app/services/checks/checks-util.ts
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {Category, CheckRun, RunStatus} from '../../api/checks';
+import {Action, Category, CheckRun, RunStatus} from '../../api/checks';
import {assertNever} from '../../utils/common-util';
export function worstCategory(run: CheckRun) {
@@ -37,6 +37,42 @@
}
}
+enum PRIMARY_STATUS_ACTIONS {
+ RERUN = 'rerun',
+ RUN = 'run',
+ CANCEL = 'cancel',
+}
+
+export function toCanonicalAction(action: Action, status: RunStatus) {
+ let name = action.name.toLowerCase();
+ if (status === RunStatus.COMPLETED && (name === 'run' || name === 're-run')) {
+ name = PRIMARY_STATUS_ACTIONS.RERUN;
+ }
+ if (status === RunStatus.RUNNING && name === 'stop') {
+ name = PRIMARY_STATUS_ACTIONS.CANCEL;
+ }
+ return {...action, name};
+}
+
+export function primaryActionName(status: RunStatus) {
+ switch (status) {
+ case RunStatus.COMPLETED:
+ return PRIMARY_STATUS_ACTIONS.RERUN;
+ case RunStatus.RUNNABLE:
+ return PRIMARY_STATUS_ACTIONS.RUN;
+ case RunStatus.RUNNING:
+ return PRIMARY_STATUS_ACTIONS.CANCEL;
+ default:
+ assertNever(status, `Unsupported status: ${status}`);
+ }
+}
+
+export function primaryRunAction(run: CheckRun): Action | undefined {
+ return (run.actions ?? [])
+ .map(action => toCanonicalAction(action, run.status))
+ .filter(action => action.name === primaryActionName(run.status))[0];
+}
+
export function iconForRun(run: CheckRun) {
const category = worstCategory(run);
return category ? iconForCategory(category) : iconForStatus(run.status);