Skip to content

Follow-ups from the Argo Rollouts review: two bugs, a blue-green verdict, a collision test - #1767

Merged
hisco merged 4 commits into
mainfrom
radar-rollouts-followups
Sep 16, 2026
Merged

hisco merged 4 commits into
mainfrom
radar-rollouts-followups

Conversation

@hisco

@hisco hisco commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Four follow-ups from reviewing #1625, which is already merged. Two bugs, one
addition, one test. They are independent, so any can be dropped.

A refused analysis-history read looked like an empty one. A caller without
list analysisruns got no AnalysisRun History section at all, which is exactly
what a Rollout that never ran an analysis looks like. The section now renders
either way and a refusal says so, through the LookupFailureNote the other
reverse lookups already use. It is marked partial when rows are on screen,
because React Query keeps the last good data through a failed refetch, so a
session that loses its grant mid-way would otherwise print a flat refusal above
a visible list.

The history list reshuffled between loads. It sorted on creation time only,
and Argo creates a step run and a background run on the same reconcile, so their
order was left to chance. Ties now fall back to step index, then to runs that
have one, then to name. That makes the step index decide what an operator sees,
so parseInt64 got stricter in the same commit: Sscanf read "3abc" as 3
and reported no error, which would sort a run under a step it does not belong to.

A blue-green phase could not show its own verdict. Reading one meant going
back to the Analysis section and matching by name. The phase now carries its
verdict and links the run, the canary step badge links its run too, and the
blue-green dot picks up a failed or inconclusive tone the way a canary step
already did. Nothing is removed. The Analysis section still lists every
populated slot.

The Experiment plural collision had no test. Katib ships Experiment at
kubeflow.org sharing the plural with Argo Rollouts, and only an apiVersion gate
keeps the Argo status mapping off it. Removing that gate failed nothing.

Worth a careful look

The third one deliberately stops short. An earlier version also filtered the
Analysis section down to slots no timeline had shown, and that dropped the step
run on an aborted canary: the controller moves currentStepIndex off the
analysis step while currentStepAnalysisRunStatus keeps the failed run, so the
run that caused the abort had nowhere left to appear. Consolidating those
surfaces is still worth doing, but it needs the filter to derive from the same
condition the timeline renders on, and it is a product call about a section that
arrived four weeks ago in #1380.

Testing

Ran against a kind cluster with Argo Rollouts and the rollouts-demo fixtures,
covering canary, blue-green, aborted and workloadRef Rollouts, plus a Katib
Experiment CRD for the collision. Confirmed the aborted canary renders
identically to main.

Each new test was checked against the old behaviour: the partial-read case fails
without the incomplete flag, the ordering case fails with the step-index
tiebreak removed, the malformed-label case fails with Sscanf restored, and the
collision case fails with the apiVersion gate removed.

go test ./pkg/rollouts ./internal/server, npx vitest run in packages/k8s-ui
(3731 passing), tsc --noEmit in web, and gofmt -l all clean.


Note

Low Risk
UI and list-sorting changes in Rollouts surfaces with targeted tests; no auth or data-mutation paths touched.

Overview
Argo Rollouts follow-ups: clearer failure handling for analysis history, stabler backend ordering, and richer progression UI.

AnalysisRun history no longer disappears when the list API fails (e.g. missing list analysisruns). RolloutRenderer accepts analysisRunHistoryError, shows the existing LookupFailureNote, auto-expands the section on error, and marks data as possibly incomplete when React Query still has cached rows. The web shell forwards the fetch error from useRolloutAnalysisRuns.

History ordering in ListAnalysisRuns uses deterministic tie-breaks (step index, then name) when runs share a creation timestamp, and rejects malformed step-index labels via strict strconv.ParseInt instead of permissive parsing.

Progression timelines surface analysis verdicts inline: blue-green phases carry pre/post-promotion analysis status with navigable badges and dot health tones; canary step analysis badges link to the AnalysisRun when navigation is available.

Collision coverage: tests lock Argo experiments status to AnalysisPhase vocabulary while Katib experiments at kubeflow.org do not get Argo health levels.

Reviewed by Cursor Bugbot for commit ff77e5f. Bugbot is set up for automated code reviews on this repo. Configure here.

@hisco
hisco requested a review from nadaverell as a code owner September 15, 2026 12:57
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix Rollout analysis history, ordering, and timeline verdicts

🐞 Bug fix ✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Distinguishes unreadable analysis history from empty history, including stale partial results.
• Stabilizes same-second AnalysisRun ordering and rejects malformed step-index labels.
• Adds linked analysis verdicts to timelines and guards Experiment plural collisions.
Diagram

graph TD
  K8S["Kubernetes API"] --> API["Rollouts API"] --> SORT["Sorted history"] --> QUERY["Web query"] --> RENDER["Rollout renderer"]
  RENDER --> HISTORY["History section"]
  RENDER --> TIMELINE["Strategy timeline"] --> RUN["AnalysisRun view"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Consolidate analysis surfaces
  • ➕ Avoids repeating AnalysisRuns between timelines and the history section.
  • ➕ Creates a single place for operators to inspect analysis outcomes.
  • ➖ Could hide the failed run that caused an aborted canary.
  • ➖ Requires timeline visibility rules and product behavior to be defined together.
  • ➖ Expands independent fixes into a higher-risk redesign.

Recommendation: Keep the PR's incremental approach. Propagating query errors, defining a total history order, and enriching timelines address the observed problems without removing the comprehensive Analysis section. Defer consolidation until filtering can use the same visibility rules as each timeline, especially for aborted canaries.

Files changed (7) +245 / -38

Enhancement (2) +103 / -33
RolloutRenderer.tsxExpose history failures and blue-green analysis details +44/-12

Expose history failures and blue-green analysis details

• Accepts history-fetch errors and renders LookupFailureNote so denied reads are distinguishable from empty history, including partial stale results. Carries blue-green analysis status into progression phases and supplies navigation context to the timeline.

packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx

CanaryStepTimeline.tsxAdd linked verdicts to rollout strategy timelines +59/-21

Add linked verdicts to rollout strategy timelines

• Makes canary analysis verdict badges navigate to their AnalysisRuns when possible. Blue-green phases now display verdict badges, link their runs, and apply failed or inconclusive tones to the current phase dot.

packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx

Bug fix (2) +23 / -5
analysisruns.goDeterministically order AnalysisRun history +21/-4

Deterministically order AnalysisRun history

• Adds tie-breakers for same-second runs using descending step index, step-triggered runs before non-step runs, and ascending name. Replaces permissive scanning with strict integer parsing so malformed step labels cannot affect ordering.

pkg/rollouts/analysisruns.go

RolloutRenderer.tsxForward AnalysisRun query failures to the shared renderer +2/-1

Forward AnalysisRun query failures to the shared renderer

• Reads the React Query error from the rollout history hook and passes it alongside retained history data, enabling empty-versus-unreadable and partial-result messaging.

web/src/components/resources/renderers/RolloutRenderer.tsx

Tests (3) +119 / -0
RolloutRenderer.test.tsxCover unavailable and partially stale analysis history +31/-0

Cover unavailable and partially stale analysis history

• Adds server-rendered regression tests for forbidden history reads, retained rows after a failed refetch, and the genuinely empty-history case.

packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.test.tsx

ResourceRendererDispatch.test.tsxPin Experiment status dispatch to Argo resources +24/-0

Pin Experiment status dispatch to Argo resources

• Verifies that Argo Rollouts Experiments use AnalysisPhase health mapping while Katib Experiments sharing the same plural do not. This protects the existing apiVersion gate from regression.

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.test.tsx

analysisruns_test.goTest AnalysisRun ordering and strict label parsing +64/-0

Test AnalysisRun ordering and strict label parsing

• Covers step runs preceding background runs, later steps preceding earlier steps within the same second, and rejection of malformed step-index labels.

pkg/rollouts/analysisruns_test.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d3be39d. Configure here.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Parser comment preserves change history ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
parseInt64's new comment contrasts Sscanf with the replacement and says the step index now
controls ordering. A later reader gets implementation history rather than a durable explanation of
why malformed labels must be rejected.
Code

pkg/rollouts/analysisruns.go[R123-125]

+// Sscanf would read "3abc" as 3 and report no error, and the step index now
+// decides the order of same-second runs, so a malformed label has to be
+// rejected outright rather than silently truncated.
Evidence
Rule 3036538 disallows code comments that preserve explicit change history. The added comment names
the former Sscanf behavior and uses now to contrast it with the current ordering behavior.

Rule 3036538: Disallow references to tickets or PR history in code comments
pkg/rollouts/analysisruns.go[123-125]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `parseInt64` comment records the previous parser and describes current behavior using change-history language.
## Fix Focus Areas
- pkg/rollouts/analysisruns.go[123-125]
## Recommended Fix
Rewrite the comment to explain only the durable invariant: malformed step-index labels must be rejected because step indexes determine deterministic ordering.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Verdict badges bypass shared styling 📘 Rule violation ⚙ Maintainability
Description
CanaryStepTimeline and BlueGreenTimeline render analysis verdicts as custom button and span
elements with badge-sm and healthColors instead of the shared Badge component. Both linked and
unlinked verdict paths therefore maintain their own status-badge markup rather than inheriting the
component's semantic tone and interaction behavior.
Code

packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[R185-188]

+                    className={clsx('badge-sm hover:underline', healthColors[level])}
+                    title={phase.analysis.name}
+                  >
+                    {phase.analysis.status}
Evidence
Rule 3036677 requires badge-like status UI to use the shared Badge component with semantic
appearance props. The changed timelines instead construct buttons and spans with badge-sm and
healthColors, while the repository's Badge already supports semantic tones and clickable
rendering.

Rule 3036677: Use Badge components instead of hard-coded badge color strings
packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[89-103]
packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[179-192]
packages/k8s-ui/src/components/ui/Badge.tsx[296-313]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new analysis verdict controls recreate badge appearance with custom elements and color classes instead of using the shared semantic badge component.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[89-103]
- packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[179-192]
## Recommended Fix
Import `Badge` and render each verdict through it, passing the normalized analysis level through the semantic `tone` prop. Use `Badge`'s `onClick` and `title` support for navigable verdicts rather than constructing separate buttons.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Analysis history starts collapsed 📘 Rule violation ⚙ Maintainability
Description
RolloutRenderer sets defaultExpanded from analysisRunHistoryError, so a populated history
section is collapsed whenever the fetch succeeds. The section has non-empty normal-priority content
in that path but no explicit low-priority prop, reaching every successful analysis-history render.
Code

packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[R752-754]

+          // Collapsed when it holds history, because that is backward-looking.
+          // Open when it could not be read, so the reason does not need a click.
+          defaultExpanded={!!analysisRunHistoryError}
Evidence
Rule 3036709 requires non-empty renderer sections to explicitly set defaultExpanded to true unless
they carry a clear low-priority prop. The changed expression evaluates to false for successful
populated history, and the Section API exposes no priority property.

Rule 3036709: Renderer sections with content must explicitly set defaultExpanded to true
packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[748-760]
packages/k8s-ui/src/components/ui/drawer-components.tsx[79-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The populated AnalysisRun History renderer section explicitly starts collapsed even though it contains content and cannot be marked as low priority through the section API.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[748-760]
## Recommended Fix
Set `defaultExpanded={true}` for the AnalysisRun History section so successful history and lookup failures both satisfy the renderer-section expansion requirement.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
4. Refetch failures stay hidden ✓ Resolved 🐞 Bug ≡ Correctness
Description
RolloutRenderer passes the changing error state through Section.defaultExpanded, but Section
snapshots that prop into local state only on its first render. When a collapsed history section
retains rows and a refetch later fails, the already-mounted section stays closed, so the permission
or fault note is not shown unless the operator manually expands it.
Code

packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[754]

+          defaultExpanded={!!analysisRunHistoryError}
Evidence
The history renderer explicitly relies on defaultExpanded becoming true when an error appears,
while Section initializes its state from that value only once. The added retained-row test
documents the exact mid-session failure scenario, and LookupFailureNote confirms that the
permission or fault explanation is rendered inside the collapsible content.

packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[748-760]
packages/k8s-ui/src/components/ui/drawer-components.tsx[79-106]
packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.test.tsx[188-201]
packages/k8s-ui/src/components/resources/renderers/LookupFailureNote.tsx[18-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The analysis-history section remains collapsed when a refetch starts failing because `defaultExpanded` is only applied when `Section` mounts. This hides the new failure note during the retained-data scenario the change is intended to support.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[748-760]
- packages/k8s-ui/src/components/ui/drawer-components.tsx[87-88]
## Recommended Fix
Make the history section react to transitions between successful and failed fetch states. For example, give `Section` a stable key derived from whether `analysisRunHistoryError` is present so it remounts expanded when an error appears and collapsed again after recovery, or add an explicit controlled expansion mechanism to `Section`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread pkg/rollouts/analysisruns.go Outdated
Comment on lines +185 to +188
className={clsx('badge-sm hover:underline', healthColors[level])}
title={phase.analysis.name}
>
{phase.analysis.status}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Verdict badges bypass shared styling 📘 Rule violation ⚙ Maintainability

CanaryStepTimeline and BlueGreenTimeline render analysis verdicts as custom button and span
elements with badge-sm and healthColors instead of the shared Badge component. Both linked and
unlinked verdict paths therefore maintain their own status-badge markup rather than inheriting the
component's semantic tone and interaction behavior.
Agent Prompt
## Issue description
The new analysis verdict controls recreate badge appearance with custom elements and color classes instead of using the shared semantic badge component.

## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[89-103]
- packages/k8s-ui/src/components/resources/renderers/rollout/CanaryStepTimeline.tsx[179-192]

## Recommended Fix
Import `Badge` and render each verdict through it, passing the normalized analysis level through the semantic `tone` prop. Use `Badge`'s `onClick` and `title` support for navigable verdicts rather than constructing separate buttons.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +752 to +754
// Collapsed when it holds history, because that is backward-looking.
// Open when it could not be read, so the reason does not need a click.
defaultExpanded={!!analysisRunHistoryError}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Analysis history starts collapsed 📘 Rule violation ⚙ Maintainability

RolloutRenderer sets defaultExpanded from analysisRunHistoryError, so a populated history
section is collapsed whenever the fetch succeeds. The section has non-empty normal-priority content
in that path but no explicit low-priority prop, reaching every successful analysis-history render.
Agent Prompt
## Issue description
The populated AnalysisRun History renderer section explicitly starts collapsed even though it contains content and cannot be marked as low priority through the section API.

## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/RolloutRenderer.tsx[748-760]

## Recommended Fix
Set `defaultExpanded={true}` for the AnalysisRun History section so successful history and lookup failures both satisfy the renderer-section expansion requirement.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@hisco
hisco force-pushed the radar-rollouts-followups branch from d3be39d to c2861c9 Compare September 15, 2026 13:12
Katib ships an Experiment at kubeflow.org sharing the plural with Argo
Rollouts, and only an apiVersion gate keeps the Argo status mapping off it.
Nothing failed if that gate was removed. Two cases now cover it, alongside
the other colliding plurals in this file.
The history sorted on creation time alone. Argo creates a step run and a
background run on the same reconcile, so both carry the same second and
their order was left to chance, which reshuffled the list between loads.

Ties now fall back to step index, then to runs that have one at all, then
to name. A step-triggered run is what an operator is looking for, so it
leads, and a later step leads an earlier one.

That makes the step index decide what an operator sees, so reading it got
stricter in the same change. Sscanf accepted "3abc" as 3 and reported no
error, which would have sorted a run under a step it does not belong to.
The canary step badge said an analysis was Inconclusive but could not take
you to the run, and a blue-green phase could not say anything at all, so
reading either meant going back to the Analysis section and matching by
name.

The step badge now links to the AnalysisRun, and a blue-green phase carries
its verdict and link the same way, with the dot picking up a failed or
inconclusive tone exactly as a canary step does.

Nothing is removed. The Analysis section still lists every populated slot,
because the timeline only shows the step run while the current step is an
analysis step, and an aborted canary is not: the controller moves the index
off that step while the failed run's status stays behind.
A caller without `list analysisruns` got no AnalysisRun History section at
all, which looks exactly like a Rollout that has never run an analysis. The
section now appears either way, and a refused read says so through the same
LookupFailureNote the other reverse lookups use.

It opens by default only in that case, so the reason does not need a click,
and stays collapsed when it actually holds history.

The note is marked partial when rows are already on screen: React Query
keeps the last good data through a failed refetch, so a session that loses
its grant mid-way would otherwise print a flat refusal above a visible list.
@hisco
hisco force-pushed the radar-rollouts-followups branch from c2861c9 to ff77e5f Compare September 16, 2026 08:28
@hisco
hisco merged commit 6b51926 into main Sep 16, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant