Show summary for flows similar to checks
https://imgur.com/a/Sf4v6LM
Bug: Google b/431939712
Release-Notes: skip
Change-Id: Ia864ecfa1bfa838ba09bdd222f0e3e18a11823b2
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
index 017adca..591845a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary.ts
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
import './gr-checks-chip';
+import './gr-summary-chip';
import '../gr-comments-summary/gr-comments-summary';
import '../../shared/gr-icon/gr-icon';
import '../../checks/gr-checks-action';
@@ -29,6 +30,7 @@
} from '../../../models/checks/checks-util';
import {getMentionedThreads, isUnresolved} from '../../../utils/comment-util';
import {AccountInfo, CommentThread, DropdownLink} from '../../../types/common';
+import {FlowInfo, FlowStageState} from '../../../api/rest-api';
import {Tab} from '../../../constants/constants';
import {ChecksTabState} from '../../../types/events';
import {modifierPressed} from '../../../utils/dom-util';
@@ -43,6 +45,7 @@
import {userModelToken} from '../../../models/user/user-model';
import {assertIsDefined} from '../../../utils/common-util';
import {GrAiPromptDialog} from '../gr-ai-prompt-dialog/gr-ai-prompt-dialog';
+import {flowsModelToken} from '../../../models/flows/flows-model';
function handleSpaceOrEnter(e: KeyboardEvent, handler: () => void) {
if (modifierPressed(e)) return;
@@ -98,6 +101,9 @@
@state()
draftCount = 0;
+ @state()
+ flows: FlowInfo[] = [];
+
@query('#aiPromptModal')
aiPromptModal?: HTMLDialogElement;
@@ -114,6 +120,8 @@
private readonly getChangeModel = resolve(this, changeModelToken);
+ private readonly getFlowsModel = resolve(this, flowsModelToken);
+
private readonly reporting = getAppContext().reportingService;
constructor() {
@@ -189,6 +197,11 @@
this.mentionCount = unresolvedThreadsMentioningSelf.length;
}
);
+ subscribe(
+ this,
+ () => this.getFlowsModel().flows$,
+ x => (this.flows = x)
+ );
}
static override get styles() {
@@ -584,7 +597,7 @@
</div>
</td>
</tr>
- ${this.renderChecksSummary()}
+ ${this.renderChecksSummary()} ${this.renderFlowsSummary()}
</table>
</div>
<dialog id="aiPromptModal" tabindex="-1">
@@ -596,6 +609,81 @@
`;
}
+ private getFlowOverallStatus(flow: FlowInfo): FlowStageState {
+ if (
+ flow.stages.some(
+ stage =>
+ stage.state === FlowStageState.FAILED ||
+ stage.state === FlowStageState.TERMINATED
+ )
+ ) {
+ return FlowStageState.FAILED;
+ }
+ if (
+ flow.stages.some(
+ stage =>
+ stage.state === FlowStageState.PENDING ||
+ stage.state === FlowStageState.TERMINATED
+ )
+ ) {
+ return FlowStageState.PENDING; // Using PENDING to represent running/in-progress
+ }
+ if (flow.stages.every(stage => stage.state === FlowStageState.DONE)) {
+ return FlowStageState.DONE;
+ }
+ return FlowStageState.PENDING; // Default or unknown state
+ }
+
+ private renderFlowsSummary() {
+ if (this.flows.length === 0) return nothing;
+ const handler = () => fireShowTab(this, Tab.FLOWS, true);
+ const failed = this.flows.filter(
+ f => this.getFlowOverallStatus(f) === FlowStageState.FAILED
+ ).length;
+ const running = this.flows.filter(
+ f => this.getFlowOverallStatus(f) === FlowStageState.PENDING
+ ).length;
+ const done = this.flows.filter(
+ f => this.getFlowOverallStatus(f) === FlowStageState.DONE
+ ).length;
+ return html`
+ <tr>
+ <td class="key">Flows</td>
+ <td class="value">
+ <div class="flowsSummary">
+ ${failed > 0
+ ? html`<gr-checks-chip
+ .statusOrCategory=${Category.ERROR}
+ .text=${`${failed}`}
+ @click=${handler}
+ @keydown=${(e: KeyboardEvent) =>
+ handleSpaceOrEnter(e, handler)}
+ ></gr-checks-chip>`
+ : ''}
+ ${running > 0
+ ? html`<gr-checks-chip
+ .statusOrCategory=${RunStatus.RUNNING}
+ .text=${`${running}`}
+ @click=${handler}
+ @keydown=${(e: KeyboardEvent) =>
+ handleSpaceOrEnter(e, handler)}
+ ></gr-checks-chip>`
+ : ''}
+ ${done > 0
+ ? html`<gr-checks-chip
+ .statusOrCategory=${Category.SUCCESS}
+ .text=${`${done}`}
+ @click=${handler}
+ @keydown=${(e: KeyboardEvent) =>
+ handleSpaceOrEnter(e, handler)}
+ ></gr-checks-chip>`
+ : ''}
+ </div>
+ </td>
+ </tr>
+ `;
+ }
+
private renderChecksSummary() {
const hasNonRunningChip = this.runs.some(
run => hasCompletedWithoutResults(run) || hasResults(run)
diff --git a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary_test.ts b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary_test.ts
index 2770ada..fd60d95 100644
--- a/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-summary/gr-change-summary_test.ts
@@ -16,7 +16,7 @@
createDraft,
createRun,
} from '../../../test/test-data-generators';
-import {Timestamp} from '../../../api/rest-api';
+import {FlowInfo, FlowStageState, Timestamp} from '../../../api/rest-api';
import {testResolver} from '../../../test/common-test-setup';
import {UserModel, userModelToken} from '../../../models/user/user-model';
import {
@@ -26,16 +26,29 @@
import {GrChecksChip} from './gr-checks-chip';
import {CheckRun} from '../../../models/checks/checks-model';
import {Category, RunStatus} from '../../../api/checks';
+import {FlowsModel, flowsModelToken} from '../../../models/flows/flows-model';
+
+function createFlow(partial: Partial<FlowInfo> = {}): FlowInfo {
+ return {
+ uuid: 'test-uuid',
+ owner: createAccountWithEmail(),
+ created: '2020-01-01 00:00:00.000000000' as Timestamp,
+ stages: [],
+ ...partial,
+ };
+}
suite('gr-change-summary test', () => {
let element: GrChangeSummary;
let commentsModel: CommentsModel;
let userModel: UserModel;
+ let flowsModel: FlowsModel;
setup(async () => {
element = await fixture(html`<gr-change-summary></gr-change-summary>`);
commentsModel = testResolver(commentsModelToken);
userModel = testResolver(userModelToken);
+ flowsModel = testResolver(flowsModelToken);
});
test('is defined', () => {
@@ -58,7 +71,8 @@
await element.updateComplete;
assert.shadowDom.equal(
element,
- /* HTML */ `<div>
+ /* HTML */ `
+ <div>
<table class="info">
<tbody>
<tr>
@@ -86,7 +100,8 @@
<dialog id="aiPromptModal" tabindex="-1">
<gr-ai-prompt-dialog id="aiPromptDialog" role="dialog">
</gr-ai-prompt-dialog>
- </dialog> `
+ </dialog>
+ `
);
});
@@ -181,6 +196,62 @@
});
});
+ suite('flows summary', () => {
+ test('renders', async () => {
+ flowsModel.setState({
+ flows: [
+ createFlow({
+ stages: [
+ {expression: {condition: ''}, state: FlowStageState.PENDING},
+ ],
+ }),
+ createFlow({
+ stages: [{expression: {condition: ''}, state: FlowStageState.DONE}],
+ }),
+ createFlow({
+ stages: [{expression: {condition: ''}, state: FlowStageState.DONE}],
+ }),
+ createFlow({
+ stages: [
+ {expression: {condition: ''}, state: FlowStageState.FAILED},
+ ],
+ }),
+ createFlow({
+ stages: [
+ {expression: {condition: ''}, state: FlowStageState.FAILED},
+ ],
+ }),
+ createFlow({
+ stages: [
+ {expression: {condition: ''}, state: FlowStageState.FAILED},
+ ],
+ }),
+ ],
+ loading: false,
+ });
+ await element.updateComplete;
+ const flowsSummary = queryAndAssert(element, '.flowsSummary');
+ assert.dom.equal(
+ flowsSummary,
+ /* HTML */ `
+ <div class="flowsSummary">
+ <gr-checks-chip> </gr-checks-chip>
+ <gr-checks-chip> </gr-checks-chip>
+ <gr-checks-chip> </gr-checks-chip>
+ </div>
+ `
+ );
+ const chips = queryAll<GrChecksChip>(element, 'gr-checks-chip');
+ assert.equal(chips.length, 3);
+ assert.equal(chips[0].statusOrCategory, Category.ERROR);
+ assert.equal(chips[0].text, '3');
+ assert.equal(chips[1].statusOrCategory, RunStatus.RUNNING);
+ assert.equal(chips[1].text, '1');
+ assert.equal(chips[2].statusOrCategory, Category.SUCCESS);
+ assert.equal(chips[2].text, '2');
+ });
+ });
+
test('renders mentions summary', async () => {
commentsModel.setState({
drafts: {