fix(agents): render recent runs oldest-to-newest on the Agents page - #31645
fix(agents): render recent runs oldest-to-newest on the Agents page#31645aniketkatkar97 wants to merge 3 commits into
Conversation
The agent card's run dots and the run-history drawer's rail rendered their runs in whatever order the API returned. The backend serves pipeline statuses newest-first (IngestionPipelineRepository uses OrderBy.DESC), so both surfaces read right-to-left, with the latest run on the left. The legacy IngestionRecentRuns badge row already normalises to ascending and treats the last element as the latest. This aligns the two newer ServiceAgents surfaces with that convention: - buildRecentRuns takes the newest five statuses, then reverses them. Windowing before reversing matters: capping an already-ascending list would keep the five oldest runs. The sort runs on filter's copy because every other consumer reads pipelineStatuses[0] as the latest run. - AgentCard highlights the last dot instead of the first, and exposes data-run-status so order is assertable without parsing a translated title. - useAgentRuns sorts the raw PipelineStatus list by timestamp before mapping. AgentRun.startedAt is an already-formatted display string, so the mapped runs cannot be ordered. - RunHistoryDrawer defaults its selection to the newest (now last) run and scrolls the rail to keep the selected card in view, since the rail only fits four or five of the ten cards. It drives scrollLeft rather than calling scrollIntoView, which would also scroll the drawer vertically. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ServiceAgentsDeploymentSummary.spec.ts` was absent from the mapping that covers `src/components/ServiceAgents/**`, so a change to that directory only ran the spec when the spec itself was edited. It now also carries the recent-run ordering coverage, which is exactly the kind of regression the mapping exists to catch. `ServiceAgentsPauseResume.spec.ts` and `ServiceAgentsRefresh.spec.ts` have the same gap but are unrelated to this change and are left alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically. Maintainers can bypass this check by adding the |
✅ Playwright Results — workflow succeededValidated commit ✅ 622 passed · ❌ 0 failed · 🟡 1 flaky · ⏭️ 0 skipped · 🧰 0 lifecycle flaky PerformanceBlocking targets: ✅ met · Optimization targets: 🟡 in progress Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting. 🕒 Full workflow signal wall (to summary) 56m 32s ⏱️ Max setup 3m 43s · max shard execution 18m 39s · max shard-job elapsed before upload 23m 18s · reporting 7s 🌐 218.33 requests/attempt · 2.72 app boots/UI scenario · 17.16% common-shard skew Optimization targets still in progress:
🟡 1 flaky test(s) (passed on retry)
How to debug locally# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip # view trace |
|
| Count | Rule |
|---|---|
| 6 | i18next/no-literal-string |
| 4 | sonarjs/cyclomatic-complexity |
| 4 | sonarjs/no-duplicate-string |
| 1 | sonarjs/expression-complexity |
| 1 | jsx-a11y/control-has-associated-label |
| 1 | react/no-array-index-key |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/ServiceAgents/components/AgentCard.component.tsx:73:4 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 28 which is greater than 10 authorized.","cost":18,"secondaryLocations":[{"line":73,"column":3,"endLine":73,"endColumn" |
| 🟡 | src/components/ServiceAgents/components/AgentCard.component.tsx:163:14 |
sonarjs/expression-complexity |
Reduce the number of conditional operators (6) used in the expression (maximum allowed 3). |
| 🟡 | src/components/ServiceAgents/components/AgentCard.component.tsx:221:19 |
jsx-a11y/control-has-associated-label |
A control must be associated with a text label. |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:26:15 |
i18next/no-literal-string |
disallow literal string: AgentOverflowMenu |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:31:41 |
i18next/no-literal-string |
disallow literal string: StatusPill |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:35:41 |
i18next/no-literal-string |
disallow literal string: ProgressBar |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:39:41 |
i18next/no-literal-string |
disallow literal string: Metric |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:150:29 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/components/ServiceAgents/components/AgentCard.test.tsx:253:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/components/ServiceAgents/components/RunHistoryDrawer.component.tsx:176:4 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 14 which is greater than 10 authorized.","cost":4,"secondaryLocations":[{"line":176,"column":3,"endLine":176,"endColumn |
| 🟡 | src/components/ServiceAgents/components/RunHistoryDrawer.component.tsx:272:57 |
i18next/no-literal-string |
disallow literal string: {run.startedAt} ({getUtcOffsetLabel()}) ·{' '} {t('message.ran-for-duration', { du |
| 🟡 | src/components/ServiceAgents/components/RunHistoryDrawer.component.tsx:350:26 |
react/no-array-index-key |
Do not use Array index in keys |
| 🟡 | src/components/ServiceAgents/components/RunHistoryDrawer.test.tsx:25:41 |
i18next/no-literal-string |
disallow literal string: RunStepRow |
| 🟡 | src/components/ServiceAgents/components/RunHistoryDrawer.test.tsx:159:28 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/components/ServiceAgents/utils/agentsDataMapper.test.ts:785:25 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/ServiceAgents/utils/agentsDataMapper.ts:147:69 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 14 which is greater than 10 authorized.","cost":4,"secondaryLocations":[{"line":147,"column":68,"endLine":147,"endColum |
| 🟡 | src/components/ServiceAgents/utils/agentsDataMapper.ts:267:72 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 12 which is greater than 10 authorized.","cost":2,"secondaryLocations":[{"line":267,"column":71,"endLine":267,"endColum |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
|
Code Review ✅ ApprovedNormalizes Agent recent runs to render oldest-to-newest chronologically across the UI and updates related tests. No issues found. OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |



Describe your changes:
No linked issue — reported directly. Please link one if you would like the metadata gate to pass.
Recent runs are supposed to read chronologically left → right (oldest leftmost, newest rightmost). The legacy Ant badge row
IngestionRecentRunswas fixed to do that in #23572, but the newer ServiceAgents surfaces never got the same treatment: they render whatever order the API returns, and the backend serves pipeline statuses newest-first (IngestionPipelineRepositoryusesOrderBy.DESCplus areverseOrder()comparator).So on the Agents tab both surfaces read right → left, with the latest run on the left:
buildRecentRunsonly did.filter().slice(0, 5), no sort, andAgentCardgave full opacity toindex === 0.useAgentRunsdidres.data.map(mapPipelineStatusToRun)with no sort, and the drawer defaulted its selection toruns[0].This aligns both with the
IngestionRecentRunsconvention, so ordering is one convention across the app.Type of change:
High-level design:
Order is normalised once, at the data boundary (mapper + hook); the two renderers that assumed index 0 was the latest were adjusted to the last index.
utils/agentsDataMapper.tsbuildRecentRunssorts descending bytimestamp, takes the newest 5, then reverses.components/AgentCard.component.tsxindex === 0to the last index; addsdata-run-status.hooks/useAgentRuns.tsPipelineStatus[]ascending bytimestampbefore mapping.components/RunHistoryDrawer.component.tsxruns.at(-1); scrolls the rail to keep the selected card in view.Decisions worth calling out:
pipeline.pipelineStatusesis shared, andmapPipelineToAgent,AgentsStatusWidgetUtilsanduseEntityLogsall read[0]as the latest run.filterruns beforesortso the sort operates on its copy. There is a regression test asserting the caller's array is not reordered.AgentRun.startedAtis an already-formatted display string and cannot be ordered, souseAgentRunssortsPipelineStatus[]before mapping.scrollLeft, notscrollIntoView. The rail fits four or five of the ten cards, so the newest run is now outside the initial scroll window. The drawer body isoverflow-y-auto, soscrollIntoViewwould also move it vertically.Alternatives rejected: reversing at render time in each component (leaves the two surfaces free to drift apart again, which is how this bug happened), and
flex-row-reverseon the rail (breaks tab order and does not fix the underlying data order).Also included:
ServiceAgentsDeploymentSummary.spec.tswas missing from theimpact-map.jsonentry that coverssrc/components/ServiceAgents/**, so it only ran when the spec itself was edited. It now carries the ordering coverage, so it is mapped. (ServiceAgentsPauseResume.spec.tsandServiceAgentsRefresh.spec.tshave the same gap but are unrelated to this change and were left alone.)Tests:
Use cases covered
pipelineStatuses[0]as the latest run (status pill, current run id, last-run timestamp) are unaffected.Unit tests
utils/agentsDataMapper.test.ts— ascending order from a newest-first response; ordering from an unsorted response; newest-five window; caller's array not reordered whilecurrentRunId/lastRunAtstill resolve to the newest run.components/AgentCard.test.tsx— dot order viadata-run-status; only the rightmost dot undimmed.components/RunHistoryDrawer.test.tsx— cards oldest-first, rightmost selected;initialRunIdstill wins over the default.yarn test src/components/ServiceAgents→ 13 suites / 222 tests passed.Backend integration tests
Ingestion integration tests
Playwright (UI) tests
playwright/e2e/Features/ServiceAgentsDeploymentSummary.spec.ts— newService Agents recent run orderingdescribe with two tests. Extends the existingmockAgentsTabharness to support a multi-run history and adds thepipelineStatusroute the drawer reads (previously unmocked).Expected substring: "Partial Success"/Received string: "FailedNov 15, 2023..."on the first card — i.e. the exact reported symptom.Manual testing performed
:8585.UI screen recording / screenshots:
Ordering change only; the Playwright assertions above pin the before/after (leftmost card was
Failed— the newest run — and is nowPartial Success, the oldest). Happy to attach a recording if reviewers want one.Checklist:
I have read the CONTRIBUTING document.
My PR title is
Fixes <issue-number>: <short explanation>— no issue exists yet.My PR is linked to a GitHub issue via
Fixes #<issue-number>above — no issue exists yet.I have commented on my code, particularly in hard-to-understand areas.
For JSON Schema changes: not applicable.
For UI changes: see the note above in place of a recording.
I have added tests (unit / integration / Playwright as applicable) and listed them above.
I have added a test that covers the exact scenario we are fixing.
🤖 Generated with Claude Code
Greptile Summary
The PR normalizes recent agent runs into chronological display order while preserving newest-run selection and the newest-five window.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR API[Pipeline statuses: newest first] --> Sort[Sort by timestamp] Sort --> Window[Keep newest runs] Window --> Order[Present oldest to newest] Order --> Card[Agent card dots] Order --> Drawer[Run-history rail] Drawer --> Latest[Select rightmost newest run]Reviews (2): Last reviewed commit: "Merge branch 'main' into recent-runs-sor..." | Re-trigger Greptile