Skip to content

Commit f191a04

Browse files
authored
Merge pull request #4377 from cardstack/CS-10707-submission-ux-improvement-of-ci-checks
Submission workflow UX improvement of CI checks
2 parents b5cd4a5 + 0dc1299 commit f191a04

5 files changed

Lines changed: 88 additions & 19 deletions

File tree

packages/catalog-realm/pr-card/components/isolated/ci-section.gts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import GlimmerComponent from '@glimmer/component';
2+
import { cached } from '@glimmer/tracking';
23
import type { CiStatus, CiGroup } from '../../utils';
34

45
// ── Sub-components ──────────────────────────────────────────────────────
@@ -144,31 +145,41 @@ class CiStatusLabel extends GlimmerComponent<CiStatusLabelSignature> {
144145
interface CiSectionSignature {
145146
Args: {
146147
ciGroups: CiGroup[];
148+
isLoading?: boolean;
147149
};
148150
}
149151

150152
export class CiSection extends GlimmerComponent<CiSectionSignature> {
153+
@cached get flatItems() {
154+
return this.args.ciGroups.flatMap((g) => g.items);
155+
}
156+
151157
<template>
152158
<div class='ci-section'>
153159
<h2 class='section-heading'>CI Checks</h2>
154160

155-
{{#if @ciGroups.length}}
161+
{{#if this.flatItems.length}}
156162
<ul class='ci-group' role='list'>
157-
{{#each @ciGroups as |group|}}
158-
{{#each group.items as |item|}}
159-
<li class='ci-item'>
160-
<CiDot @state={{item.state}} />
161-
<div class='ci-item-detail'>
162-
<span class='ci-item-name'>{{item.name}}</span>
163-
<CiStatusLabel
164-
@state={{item.state}}
165-
@text={{item.statusText}}
166-
/>
167-
</div>
168-
</li>
169-
{{/each}}
163+
{{#each this.flatItems key='name' as |item|}}
164+
<li class='ci-item'>
165+
<CiDot @state={{item.state}} />
166+
<div class='ci-item-detail'>
167+
<span class='ci-item-name'>{{item.name}}</span>
168+
<CiStatusLabel
169+
@state={{item.state}}
170+
@text={{item.statusText}}
171+
/>
172+
</div>
173+
</li>
170174
{{/each}}
171175
</ul>
176+
{{else if @isLoading}}
177+
<div class='ci-item loading-state'>
178+
<CiDot @state='in_progress' />
179+
<div class='ci-item-detail'>
180+
<span class='ci-item-name loading-text'>Loading CI checks...</span>
181+
</div>
182+
</div>
172183
{{else}}
173184
<div class='empty-state'>
174185
<span class='empty-state-icon' aria-hidden='true'>
@@ -257,6 +268,12 @@ export class CiSection extends GlimmerComponent<CiSectionSignature> {
257268
font-size: var(--boxel-font-xs);
258269
color: var(--muted-foreground, #656d76);
259270
}
271+
.loading-state {
272+
border-radius: var(--radius, 6px);
273+
}
274+
.loading-text {
275+
color: var(--muted-foreground, #656d76);
276+
}
260277
</style>
261278
</template>
262279
}

packages/catalog-realm/pr-card/fields/ci-status-field.gts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ export class PrCiStatusField extends FieldDef {
8585
return this.ciItems.length;
8686
}
8787

88+
get isLoading() {
89+
return (
90+
this.checkRunEventData?.isLoading ||
91+
this.checkSuiteEventData?.isLoading
92+
) ?? false;
93+
}
94+
8895
get ciHeadline() {
8996
if (this.ciTotalCount === 0) return null;
9097
if (this.ciFailedCount > 0) return 'Some checks were not successful';
@@ -127,6 +134,15 @@ export class PrCiStatusField extends FieldDef {
127134
<span class='ci-subtitle'>{{this.ciSubtitle}}</span>
128135
</div>
129136
</div>
137+
{{else if this.isLoading}}
138+
<div class='ci-status-row ci-status-loading'>
139+
<span class='ci-donut ci-donut-loading'>
140+
<span class='ci-donut-hole'></span>
141+
</span>
142+
<div class='ci-status-text'>
143+
<span class='ci-headline'>Loading CI checks...</span>
144+
</div>
145+
</div>
130146
{{/if}}
131147

132148
<style scoped>
@@ -175,6 +191,17 @@ export class PrCiStatusField extends FieldDef {
175191
overflow: hidden;
176192
text-overflow: ellipsis;
177193
}
194+
.ci-donut-loading {
195+
background: var(--muted-foreground, #656d76);
196+
animation: ci-donut-pulse 1.2s ease-in-out infinite;
197+
}
198+
.ci-status-loading .ci-headline {
199+
color: var(--muted-foreground, #656d76);
200+
}
201+
@keyframes ci-donut-pulse {
202+
0%, 100% { opacity: 0.4; }
203+
50% { opacity: 1; }
204+
}
178205
</style>
179206
</template>
180207
};

packages/catalog-realm/pr-card/pr-card.gts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ class IsolatedTemplate extends Component<typeof PrCard> {
166166
return buildCiGroups(this.ciItems);
167167
}
168168

169+
get ciIsLoading() {
170+
return (
171+
this.checkRunEventData?.isLoading ||
172+
this.checkSuiteEventData?.isLoading
173+
) ?? false;
174+
}
175+
169176
// ── Reviews ──
170177
get latestReviewByReviewer() {
171178
return buildLatestReviewByReviewer(this.prReviewEventData?.instances ?? []);
@@ -266,7 +273,7 @@ class IsolatedTemplate extends Component<typeof PrCard> {
266273
{{! ── Body ── }}
267274
<div class='pr-body'>
268275
<section class='pr-status-columns'>
269-
<CiSection @ciGroups={{this.ciGroups}} />
276+
<CiSection @ciGroups={{this.ciGroups}} @isLoading={{this.ciIsLoading}} />
270277
<hr class='status-divider' />
271278
<ReviewSection
272279
@reviewState={{this.latestReviewState}}

packages/catalog-realm/pr-card/utils.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ export function buildCiItemFromEvent(event: any, type: CiEventType): CiItem {
122122
const statusText =
123123
conclusion != null
124124
? `${formatCiValue(status)} - ${formatCiValue(conclusion)}`
125-
: formatCiValue(status);
125+
: state === 'in_progress'
126+
? 'In Progress'
127+
: formatCiValue(status);
126128

127129
return {
128130
name,
@@ -192,7 +194,7 @@ function latestEventsByCheckId(
192194

193195
/**
194196
* Build CI items from check_run and check_suite event instances,
195-
* deduped by name and sorted by most recent.
197+
* deduped by name and sorted alphabetically for stable ordering.
196198
*/
197199
export function buildCiItems(
198200
checkRunInstances: any[],
@@ -216,6 +218,7 @@ export function buildCiItems(
216218
events.push({ event, type: 'check_run' });
217219
}
218220

221+
// Sort by most recent first so deduplication keeps the latest event per name
219222
events.sort(
220223
(a, b) => eventLastModified(b.event) - eventLastModified(a.event),
221224
);
@@ -231,6 +234,9 @@ export function buildCiItems(
231234
items.push(buildCiItemFromEvent(event, type));
232235
}
233236

237+
// Sort alphabetically by name for stable display order across refreshes
238+
items.sort((a, b) => a.name.localeCompare(b.name));
239+
234240
return items;
235241
}
236242

packages/catalog-realm/submission-workflow-card/submission-workflow-card.gts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ function resolveSubmissionWorkflowState(
9292
ciAllPassed: boolean,
9393
ciHasFailure: boolean,
9494
ciInProgress: boolean,
95+
ciIsLoading: boolean,
9596
reviewState: string | null,
9697
isMerged: boolean,
9798
isClosed: boolean,
@@ -119,6 +120,9 @@ function resolveSubmissionWorkflowState(
119120
if (hasPr && ciInProgress) {
120121
inProgress = true;
121122
statusDetail = 'Checks are running...';
123+
} else if (hasPr && ciIsLoading && !ciAllPassed && !ciHasFailure) {
124+
inProgress = true;
125+
statusDetail = 'Loading check status...';
122126
}
123127
break;
124128
case 'reviewer-approve':
@@ -416,6 +420,13 @@ export class SubmissionWorkflowCard extends CardDef {
416420
return this.ciItems.some((i) => i.state === 'in_progress');
417421
}
418422

423+
get ciIsLoading() {
424+
return (
425+
this.checkRunEventData?.isLoading ||
426+
this.checkSuiteEventData?.isLoading
427+
) ?? false;
428+
}
429+
419430
// ── Review state ──
420431
get latestReviewByReviewer() {
421432
return buildLatestReviewByReviewer(
@@ -436,6 +447,7 @@ export class SubmissionWorkflowCard extends CardDef {
436447
this.ciAllPassed,
437448
this.ciHasFailure,
438449
this.ciInProgress,
450+
this.ciIsLoading,
439451
this.reviewState,
440452
this.isMerged,
441453
this.isClosed,
@@ -491,7 +503,7 @@ export class SubmissionWorkflowCard extends CardDef {
491503

492504
{{! ── Step tracker ── }}
493505
<div class='sw-steps'>
494-
{{#each this.workflowState.steps as |step idx|}}
506+
{{#each this.workflowState.steps key="key" as |step idx|}}
495507
<div class={{concat 'sw-step ' step.status}}>
496508
<div class='sw-step-indicator'>
497509
{{#if (eq step.status 'completed')}}
@@ -611,7 +623,7 @@ export class SubmissionWorkflowCard extends CardDef {
611623
{{! Step summary }}
612624
<div class='sw-sidebar-section'>
613625
<div class='sw-sidebar-heading'>Steps</div>
614-
{{#each this.workflowState.steps as |step|}}
626+
{{#each this.workflowState.steps key="key" as |step|}}
615627
<div class={{concat 'sw-sidebar-step ' step.status}}>
616628
{{#if (eq step.status 'completed')}}
617629
<span class='sw-sidebar-icon completed'>

0 commit comments

Comments
 (0)