Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7200 +/- ##
==========================================
- Coverage 64.97% 64.96% -0.01%
==========================================
Files 511 513 +2
Lines 38419 38541 +122
==========================================
+ Hits 24964 25040 +76
- Misses 13455 13501 +46 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
11 tasks
jsbroks
force-pushed
the
justin/round-progress-bar
branch
4 times, most recently
from
September 14, 2026 16:16
4ec57a7 to
bc38544
Compare
jsbroks
force-pushed
the
justin/stage-fleet-tab
branch
3 times, most recently
from
September 14, 2026 16:45
d15428f to
24d3524
Compare
✅ Deploy Preview for docs-kargo-io ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
jsbroks
force-pushed
the
justin/stage-fleet-tab
branch
from
September 14, 2026 22:07
1b6024d to
de4fd7f
Compare
Targets had no REST surface: the UI could see a Stage's targets block and
its PromotionRequests, but never the Targets themselves. This adds
GET /v1beta1/projects/{project}/targets
GET /v1beta1/projects/{project}/targets/{target}
following the PromotionRequest endpoints: the list seeds a follow-up watch
(`?watch=true`, `resourceVersion`) through the uncached reader and sorts by
name.
Two filters narrow the list. `labelSelector` is a Kubernetes label selector
the API server applies itself, for both the list and the watch. `stage`
names a Stage and returns only the Targets it governs. A Stage's selectors
are a union that no single label selector can express, so that filter is
evaluated in-process, against each listed Target and each watch event. A
classic Stage governs no Targets and yields an empty list; a missing Stage
is a 404; a malformed selector is a 400.
The selector parsing and the any-selector-matches test move into pkg/api as
TargetSelectorsForStage and AnySelectorMatches, and ListTargetsForStage now
parses through the former, so the controller and the API server share one
definition of governance.
Regenerates swagger.json and the Go and TypeScript clients.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Justin Brooks <[email protected]>
(cherry picked from commit 4b16070)
jsbroks
force-pushed
the
justin/stage-fleet-tab
branch
from
September 14, 2026 22:12
900f1da to
9fa5046
Compare
jsbroks
marked this pull request as ready for review
September 14, 2026 22:21
11 tasks
A target-aware Stage's drawer had no view of the Targets it governs. The Promotions tab shows its PromotionRequests; nothing listed the Targets themselves with where each stands. The Fleet tab shows one row per Target the Stage governs: name and labels, the Freight the Target is running for this Stage, its health, the phase of the child Promotion in the Stage's latest round with a link to it, and when the round finished. Rows with trouble sort first. The round's progress bar sits above the rows it summarizes, drawn from the rows' own phases so the two cannot disagree. Governance comes from the Target list endpoint's `stage` filter, which evaluates the Stage's selectors exactly as the controller does. Freight and health per Target read the Target's own status for this Stage, which the controller writes when a child Promotion succeeds; a Target whose current collection differs from the Stage's is marked behind. Until a Promotion has succeeded, the Freight column shows what the round is promoting. The round's outcome comes from useCurrentRound, which resolves the request the Stage is promoting through -- current, else last, else newest -- from the same PromotionRequest list the Promotions tab shows, so the two share one fetch and one watch. Targets stay live through a seeded watch scoped to the Stage. A Target the round did not name -- one that joined the Stage after the round was created -- reads "not in latest round". A round that ended before creating a child shows the round's phase and its Ready message. The tab is first in order and is the initial tab for target-aware Stages; classic Stages do not show it and open on Promotions as before. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
…tabs The Promotions tab warned when a target-aware Stage's latest PromotionRequest could not progress, quoting the request's Ready message under the heading "This Stage promotes to Targets". The Fleet tab, which now opens first, showed the same round as a red bar and a column of Errored rows with the reason buried in a tooltip. One screen said two things, and neither said them plainly. The explanation moves above the tabs, so the Fleet and Promotions tabs both sit under it, and it is worded for the person reading it. When the reconciler reports that fan-out is not available in the installation, the alert says so in plain language with a sentence on what that means for the Stage. Any other reason passes through under "Promotion to this Stage cannot progress". While a round is blocked the Fleet tab shows no progress bar, since the bar would only restate the same failure per Target. A round that did fan out and then had children fail is not blocked, even though the fan-out controller also sets Ready to False for it; its Targets show the outcome. So roundBlock yields nothing for a request that recorded any Target or summary, and the alert is reserved for rounds that never started. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
The Fleet tab opened with a bare progress bar and a "2 / 3" figure floating above the table. It had no label, nothing tied it to the Freight it described, and its fixed width matched nothing else in the drawer, so it read as out of place. The round is now a card in the style of the Requested Freight cards above the tabs -- the same grey ground and two-pixel border -- with labelled fields for the Freight the round promotes, its phase, when it finished, and how many Targets succeeded. The bar becomes the card's footer, spanning it. It is still drawn from the rows' own phases, so the card and the table beneath it cannot disagree. A blocked round shows no card, since the drawer explains the block above the tabs. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
…uccesses Two changes to the Fleet tab for rounds beyond a handful of Targets. The round card said only "37 of 40 succeeded", leaving the three failures as a red sliver of the bar. It now carries one chip per phase present in the round, in the order the bar draws them and with the presentation the Promotions tab already uses, so every count is legible. The card also names the PromotionRequest it summarizes, so a reader can find the round on the Promotions tab. Past five Targets the tab grows a filter -- by name or label, and the same phase chips as a toggle to show one phase at a time -- and, when a round has trouble, its succeeded rows fold into a single line naming the first few Targets and how many more, expandable in place. The failures become the table and the green becomes one row. A round with no trouble, a round with fewer than six successes, and a filtered view all stay flat. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
When a target-aware Stage's latest round could not fan out, the drawer already says why above the tabs. The Fleet tab beneath it still painted every Target Errored in red with an empty Health column and a finish time for a round that never started, so the tab read as an outage under a banner saying nothing ran. Under a blocked round the tab now lists the governed Targets with a grey "Not attempted" in the Promotion column, drops the Health and When columns, which have nothing to say, shows no round card and no phase chips, and captions the list as the Targets this Stage governs. The filter by name or label stays for large fleets. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
jsbroks
force-pushed
the
justin/stage-fleet-tab
branch
from
September 14, 2026 22:58
9fa5046 to
76978fd
Compare
rpelczar
reviewed
Sep 15, 2026
|
|
||
| const targetsQuery = useListTargets(projectName, { stage: stageName }); | ||
| useWatchTargets(projectName, stageName, !!targetsQuery.data); | ||
| const round = useCurrentRound(projectName, stage); |
Contributor
There was a problem hiding this comment.
This triggers a watch on Promotion Requests. We call this hook here, and in the parent component.
Let's pass round param instead of initializing it a second time. I think we can get rid of the stage param.
Review of the Fleet tab asked for two changes. Grouping Targets by status folded a troubled round's successes into one line; a filter is enough, so the fold goes. And the quick filters above the table listed every phase as a chip, which is a design the rest of the UI does not use; filters belong in the column headers, as Ant tables recommend and as the other filterable tables here do. The Target column filters by name or label from a text box in its header menu; the Promotion column filters by phase and the Health column by health state, each listing only the values present with their counts. The filter row above the table goes with the fold, and the round card carries the per-phase chips again, since nothing else now does. Co-Authored-By: Claude Fable 5.1 <[email protected]> Signed-off-by: Justin Brooks <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #7183, which added the round progress bar and is now on main.
Summary
A target-aware Stage's drawer had no view of the Targets it governs. The Promotions tab shows its PromotionRequests; nothing listed the Targets themselves with where each stands. This adds a Fleet tab, first in order and the initial tab for target-aware Stages, and the Target endpoints it reads from.
API
Cherry-picked from #7138:
GET /v1beta1/projects/{project}/targetsandGET .../targets/{target}, following the PromotionRequest endpoints (list-then-watch with resourceVersion seeding, sorted by name).labelSelectoris applied by the Kubernetes API server, for the list and the watch.stagereturns only the Targets that Stage governs. A Stage's selectors are a union, which no single label selector expresses, so this filter is evaluated in-process against each Target and each watch event. A classic Stage yields an empty list; a missing Stage is a 404; a malformed selector is a 400.pkg/api(TargetSelectorsForStage,AnySelectorMatches);ListTargetsForStageparses through the former, so the controller and the API server share one definition of governance.swagger.jsonand the Go and TypeScript clients are regenerated, now including the Target status types from feat(api): record per-Stage current Freight, health checks, and health on Target status #7174. The user cluster roles already grant read on Targets.Fleet tab
One row per Target the Stage governs, from the
stagefilter above:Target.status.stages[<stage>].currentFreight(feat(api): record per-Stage current Freight, health checks, and health on Target status #7174), one link per origin. A Target whose collection differs from the Stage's current one is markedbehind. Until a Promotion has succeeded, the column shows what the round is promoting.status.stages[<stage>].health, with the shared health icon.Rows with trouble sort first. Above them, a card in the style of the Requested Freight cards summarizes the round: its Freight, the PromotionRequest's name, phase, finish time, how many Targets succeeded, one chip per phase present, and the progress bar from #7183 as the card's footer. Card and bar are drawn from the rows' own phases rather than the request's summary, so the card and the table cannot disagree.
The table filters from its column headers, as the UI's other filterable tables do: Target by name or label from a text box, Promotion by phase, and Health by health state, each listing only the values present with counts. Targets stay live through a seeded watch scoped to the Stage. The round comes from a new
useCurrentRoundhook that resolves the request the Stage is promoting through (current, else last, else newest) from the same PromotionRequest list the Promotions tab shows, sharing its fetch and watch.Classic Stages do not show the tab and open on Promotions as before. Once a user picks a tab, that choice sticks for the drawer's lifetime.
When a round cannot fan out
The Promotions tab used to warn on its own when the Stage's latest request could not progress, quoting the Ready message. That explanation now sits above the tabs, so the Fleet and Promotions tabs both read under it, and it is worded for the person reading it. When the reconciler reports that fan-out is not available in the installation, the alert says so in plain language with a sentence on what it means for the Stage. Any other reason passes through under "Promotion to this Stage cannot progress". While a round is blocked the Fleet tab shows no round card and no phase chips; its rows read "Not attempted" in grey, and the Health and When columns, which have nothing to say, drop out.
A round that did fan out and then had children fail is not blocked, even though the controller also sets Ready to False for it.
roundBlockyields nothing for a request that recorded any Target or summary, so the alert is reserved for rounds that never started and a partially failed round keeps its bar and rows.Verified
go test -raceonpkg/serverandpkg/api, golangci-lint clean.pnpm typecheck,pnpm lint,pnpm vitest run(34 files, 407 tests).fleet-utils.test.tscovers row construction, phase fallback, Freight and health from Target status, the behind marker, sort order, the bar's summary, and the Target text match;promotion-request.test.tscoversroundBlock.Not in this PR
Deep-linkable tabs, a Targets page, and the bar on other surfaces.
Checklist
Eligibility
Quality
AI Use Disclosure
This PR was written:
Sign-Off
All commits:
git commit -s) (required)git commit -S) (encouraged)🤖 Generated with Claude Code