Conversation
…urce graph Rollout nodes now read canaryService/stableService/activeService/previewService and canary weights from spec/status, and the Service->Rollout matcher fixes a real bug where canary/stable Services never connected at all (their selector targets the live rollouts-pod-template-hash, which never appears in the Rollout's static template labels — name matching is used instead, falling back to the old selector match for a Rollout's ordinary/primary Service). Pods and ReplicaSets owned by a Rollout are tagged with a trafficRole (canary/stable/active/preview) by comparing their pod-template-hash against the Rollout's live status pointers. Rollout-owned ReplicaSets that still have replicas bypass the IncludeReplicaSets collapse so the canary/stable split is visible without the noisy-history toggle.
…c edges Pod/ReplicaSet/Service nodes carrying a server-set trafficRole (canary, stable, active, preview) get a small accent badge in the existing header pill row — accent1 for the being-tested side (canary/preview), accent2 for the serving side (stable/active). Canary/stable/active/preview Service->Rollout edges now animate in the main resources-view topology, not just the separate Network Flow view — that view builds its own Ingress/Gateway/Service/Pod graph and never includes Rollout edges at all, so gating on isTrafficView would mean these edges never animate. Detected via the edge's fixed label vocabulary set server-side (Canary/Stable with a weight suffix, or bare Active/Preview).
…ership Service->Rollout exposes edges already animated for a canary/stable/active/ preview role; ownership edges (Rollout->ReplicaSet, ReplicaSet->Pod) stopped short of that even though the target node already carries the same trafficRole. Detected via a nodeId->trafficRole lookup built from the node list (ownership edges carry no label to key off, unlike the exposes edges), so the "active" path now reads continuously from the Service all the way down to the pods actually serving that role - live-verified against the blue-green scenario on kind-radar-rollouts-demo, user-reported gap.
…y role Service->Rollout edges already carried a "Canary · 20%" style label; the animated arrows one hop further down (Rollout->ReplicaSet, ReplicaSet->Pod) carried the same visual treatment but no text, so a blinking edge told the user nothing about which revision was carrying which share of traffic - exactly the gap called out live-testing canary: "the blinking arrows aren't that useful if they all blink throughout all the steps." rolloutTrafficEdgeLabel is the one place this label string is built now (previously inlined only at the Service-matching site); reused at both new edge sites via the trafficRole each already computes for its own node.
The rollout-in-progress banner (WorkloadRolloutNotice, shown on the Topology tab and others while a Rollout/Deployment/StatefulSet/DaemonSet is actively rolling out) said only "Step 3" - no hint what that step actually does, and no traffic-weight number, even though the same information is already formatted for the drawer's overview page via canaryStepLabel. Now reuses that exact function so the two surfaces describe a step identically, and appends the live canary weight when the controller has reported one. pkg/health/workload_rollout.go carries a parallel Go port (canaryStepLabel) for cross-language parity with the TS version - the two are checked against the same golden fixture (workload_rollout_vectors.json, updated here for the 2 vectors whose expected detail text changed), not duplicated by accident.
… plugin Confirmed live: status.canary.weights is NEVER populated for a "basic canary" Rollout (no Istio/SMI/ALB/NGINX trafficRouting configured) - the controller drives the split by scaling the canary ReplicaSet's replica count to approximate the last setWeight step reached, and simply never writes that status field. Every Rollout in the demo fixture is basic canary, so every traffic edge showed a bare "Canary"/"Stable" role with no percentage for the entire time it was progressing - user-reported live while testing. canaryStepWeight falls back to walking backward from currentStepIndex to the most recent setWeight step when status.canary.weights is absent. Returns nothing once fully promoted (currentStepIndex past the end of steps) - there's no active split left to report, and the settled "Stable" label correctly shows no percentage there (100% implied).
… once useTopology's query had no refetchInterval here and none is set by default - it fetched once when the drawer opened and then never refreshed. Every symptom reported live-testing canary traced back to this single gap: weight labels stuck at their initial value, a Pod's role never flipping canary->stable, the old ReplicaSet never disappearing once scaled down, an AnalysisRun staying "Running" long after it finished on the cluster - none of it was a rendering bug, the tab just never asked the backend for new data. Matches the existing useTrace "drawer feeling live" polling idiom (5s interval).
PR Summary by QodoVisualize Argo Rollouts traffic in topology
AI Description
Diagram
High-Level Assessment
Files changed (10)
|
Code Review by Qodo
1.
|
Two real bugs, both confirmed live and cross-checked against the real Argo Rollouts status schema: - rolloutTrafficRole checked stableRS/currentPodHash before activeSelector/previewSelector. The first pair are generic, strategy-agnostic status fields the Rollout controller maintains for EVERY Rollout (canary or blueGreen), so a blueGreen Rollout's ReplicaSets/Pods had real values there too — every blueGreen revision in the graph got badged canary/stable instead of active/preview. Reordered to check activeSelector/previewSelector first; they live under status.blueGreen, which is only ever populated for a blueGreen-strategy Rollout, so a canary Rollout's hash can never coincidentally match either. - The large-group PodGroup path (>5 pods sharing an app label, regardless of which ReplicaSet owns them — the normal shape once a Rollout mid-transition splits stable/canary across more than 5 pods) used only group.Pods[0] for both the trafficRole badge and the owner edge, misrepresenting the whole group with one arbitrary pod's role and connecting it to only one of the ReplicaSets actually present. Now scans every pod: sets trafficRole only when every pod in the group agrees (a wrong badge is worse than no badge), and draws an owner edge to every distinct ReplicaSet the group's pods actually belong to. Also rewrote a test comment that described the fallback's prior (unfixed) behavior instead of just why it exists. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
canaryStepLabel's Go port reused templateNames (built for
templateName/clusterTemplateName AnalysisTemplate refs) for
experiment.templates too — but those are RolloutExperimentTemplate pod
template variants ("baseline"/"canary", etc.), which carry their
identifier under `name`, not templateName/clusterTemplateName. The TS
version this is meant to mirror already reads `name` there; Go emitted
"Experiment for 5m" instead of "Experiment: baseline, canary for 5m",
silently dropping the template names.
Added experimentTemplateNames for the correct shape and a new golden
fixture vector — no existing vector exercised an experiment step, on
either side, which is how this parity gap slipped through the shared
cross-language test in the first place. Verified via real test runs on
both languages against the same fixture, not hand-computed: both now
produce the identical string.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…or the animation cutoff buildEdges gates its large-graph performance safeguard (disabling edge animations above a node-count threshold) on a nodeCount parameter. The visual-only-sync refresh path passed prev.length — the PREVIOUS EDGES array's length, not nodes — while the real layout path a few lines up correctly passes the node array's length. A tree-shaped graph commonly has fewer edges than nodes, so a graph at or above the threshold could read as under it on every status-driven refresh, re-enabling animations (including the newly added rollout traffic ones) and defeating the safeguard on exactly the large graphs it exists to protect. workingNodes — the real node list — is already in scope at this call site; used its length instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 758cd4e. Configure here.
…owner onto every pod A large PodGroup spanning more than one owner (a Rollout's canary + stable ReplicaSets both over the 5-pod individual-display threshold) already draws one collapsed edge per distinct owner correctly, but expanding the group on the frontend connected every one of those owner edges to every individual pod — a canary pod would render as owned by the stable ReplicaSet too, and vice versa. Each pod now carries an ownerKey (from its own OwnerReferences) and, once the backend resolves each owner to its real edge source(s), an ownerIds list — so expansion reconnects a pod only to its actual owner(s), falling back to every source when a pod has none (a plain single-owner group, the common case, is unaffected). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Thanks for this, the drawer Topology tab with the canary/stable split is a real improvement, and catching the Service selector vs rollouts-pod-template-hash mismatch was a good find.
Found 4 things that I think should be fixed before we merge this (and also you need to rebase - some conflicts with main, sorry about that...):
- Main Topology view orphans Rollout pods. The main view hides ReplicaSet by default (
web/src/App.tsxDEFAULT_VISIBLE_KINDS), and the SSE topology is built withIncludeReplicaSets=false. Main emits a Rollout→Pod shortcut edge in that mode; this branch replaces it with RS→Pod only, so with default filters every Rollout-owned pod floats with no edges and the Rollout has no children. Easy to see onmake rollouts-demo. Please keep the shortcut: the existing pattern is the CronJob→Pod edge increatePodOwnerEdges, emitted withSkipIfKindVisible: "ReplicaSet"alongside the RS→Pod edge, and carrying the same role label. - Settled Rollouts should be quiet. When
stableRS == currentPodHash(oractiveSelector == previewSelectorfor blue-green) nothing is in flight, but every RS and pod still gets a Stable/Active badge and every edge from the Services down to the pods animates permanently, and the RS layer stays expanded where Deployments collapse. Treat that state as settled: keep the edges (the canary/stable Services should still connect to the Rollout by name), but no roles, labels or animation, and the same RS collapse Deployments get. Roles and animation come back only while a transition is in progress. This also stops a blue-green's retained old RS from reading "Stable" during post-promotion. - Weights on basic canaries should be the actual split, not
setWeight. With notrafficRoutingthe controller only approximates the target through replica counts (3 replicas at setWeight 20 is really 25%), and an aborted rollout still walks back to the lastsetWeightand shows "Stable · 50%" while stable is serving 100% (canary-degradedon the demo cluster). Deriving canary/stable from the live ReplicaSets' replicas (what Argo's CLI calls ActualWeight) fixes both; keepstatus.canary.weightswhen it's present. - Shortcut edges in the drawer. Only the main view's kind filter honors
skipIfKindVisible; the drawer's neighborhood graph doesn't, so after (1) the Topology tab would draw both Rollout→Pod and Rollout→RS→Pod during a transition (CronJob→Job→Pod has the same quirk today). Please have the graph drop a shortcut edge whenever a node of itsskipIfKindVisiblekind is present, so both views get the same picture.
I think some tests will need fixing to match the new behavior.
Smaller, but if you wanna do while you're at it:
- I think we can drop the percentage from the RS->Pod edges (the pod badge already carries it and three stacked "Stable 75%" labels overlap).
- the health detail's
% canary trafficsuffix reads onlystatus.canary.weightsso it never shows for the basic canaries the topology just handled, either fall back the same way or drop it - move
canaryStepLabelintoutils/workload-rollout.tsto avoid the utils->renderer->utils import cycle - preferably trim comments a bit - avoid history like "without this fallback … showed", no "confirmed on the demo cluster") - maybe useful for review, but not really for the next reader after this PR is merged
The last few items I can do as a followup later, if you wanna just take the main items.

The Rollout deployment-plugin work (renderer-level: step timeline, AnalysisRun history, etc.) told the "what's this rollout doing" story in the resource drawer. This tells the same story graphically in the main cluster Topology view — which ReplicaSets/Pods are canary vs. stable (or active vs. preview for blue-green), which Services route to which, and how much traffic each side is getting.
spec.template.metadata.labels— but canary/stable Service selectors are keyed on the liverollouts-pod-template-hash, which isn't in that static label set, so these edges silently never rendered. Replaced with direct name-matching againstspec.strategy.canary.canaryService/.stableService(and the blue-green equivalents), which the Rollout already declares.trafficRolecomputed server-side at Pod/ReplicaSet/Service node build time (canary/stable/active/preview), read off the samerollouts-pod-template-hashlabel those nodes already carry — no new K8s API calls.Canary · 20%/Stable · 80%) on the Service→workload edges: readsstatus.canary.weightswhen a service-mesh trafficRouting plugin is configured, falling back to deriving the split from the current step'ssetWeightfor "basic canary" rollouts (the common case —status.canary.weightsis only ever populated with a mesh plugin, confirmed against real Argo Rollouts behavior).IncludeReplicaSetscollapse when live (spec.replicas > 0) — surfaces exactly the revisions actually carrying traffic without touching the broader Deployment-history noise-reduction setting.Test plan
go test ./pkg/topology/...(new coverage: Service→Rollout edge + weight label for both strategies, trafficRole on Pod/RS/Service, zero-replica old ReplicaSets stay hidden, step-weight fallback)make tscnpx vitest run(k8s-ui)🤖 Generated with Claude Code
Note
Medium Risk
Touches topology graph construction and edge rendering for production workloads; changes are well-tested but incorrect traffic-role logic could mislabel blue-green vs canary revisions.
Overview
Adds end-to-end Argo Rollouts traffic visualization in the resources topology: server-side
trafficRole(canary/stable/active/preview) on Services, ReplicaSets, and Pods, weighted edge labels likeCanary · 20%, and matching badges plus animated dashed edges on the graph (including outside Network Flow view).Topology builder fixes and behavior: Service→Rollout links now match named canary/stable/active/preview Services instead of label selectors (which missed live
rollouts-pod-template-hash). Live Rollout-owned ReplicaSets stay visible when global ReplicaSet collapse is off; ownership edges carry the same traffic labels down to pods. Large mixed-owner PodGroups get correct multi-owner edges and per-podownerIdsso expansion does not wire canary pods to stable ReplicaSets.Rollout health copy: Go and TypeScript
argoStepDetailnow share rich canary step labels (pause, experiment, setWeight, etc.) and optional canary traffic %, kept in sync via golden vectors.Workload drawer: The per-resource Topology tab polls every 5s while expanded so weights and roles update during a rollout.
Reviewed by Cursor Bugbot for commit 7e3b82b. Bugbot is set up for automated code reviews on this repo. Configure here.