Skip to content

feat(api): add ListStageSummaries and WatchStageSummaries RPCs#6159

Draft
jacobboykin wants to merge 10 commits intoakuity:mainfrom
jacobboykin:jboykin/stage-summary-api
Draft

feat(api): add ListStageSummaries and WatchStageSummaries RPCs#6159
jacobboykin wants to merge 10 commits intoakuity:mainfrom
jacobboykin:jboykin/stage-summary-api

Conversation

@jacobboykin
Copy link
Copy Markdown
Member

@jacobboykin jacobboykin commented Apr 23, 2026

Summary

Adds a lightweight Stage projection for clients rendering list and graph views of many Stages at once. Instead of a new endpoint, a summary flag is threaded through the existing ListStages and WatchStages RPCs. When set, heavy fields are stripped from each returned Stage in place:

  • status.freightHistory truncated to the current element (index 0)
  • spec.promotionTemplate.spec.steps[*].config cleared (kind/as/name kept)
  • status.health.output cleared — fetch lazily via the new GetStageHealthOutputs RPC for Stages currently in viewport

The UI continues to derive hasVerification from spec.verification != nil and promotionStepCount from len(spec.promotionTemplate.spec.steps)

API

  • ListStages / WatchStages gain summary: bool
  • ListStagesResponse and WatchStagesRequest gain resource_version for list-then-watch
  • New GetStageHealthOutputs(project, stage_names[]) RPC + REST endpoint for lazy per-Stage health output fetch (batch cap 1000)

Structure

Reusable Stage-domain helpers are exported in pkg/api/stage.go alongside the existing GetStage, ListFreightAvailableToStage, etc.:

  • api.StripStageForSummary(*Stage) — in-place summary projection
  • api.ListStageHealthOutputs(ctx, c, project, names) — batched health-output fetch

Server handlers are thin adapters.

Measurements

Measured on a live deployment of this branch behind the real ingress with seeded production-shape status, summary=true delivers up to 13x gzipped wire reduction on ListStages, ~5x reduction in JS heap retained per cached list (17.8 MB down to 3.7 MB), and ~3.5x faster JSON.parse (20.5 ms down to 5.9 ms). A heavy real stage with 778 promotion steps shrinks 10x (170 KB to 17 KB) with step skeletons preserved so the UI can still derive step kind and count without falling back to GetStage.

Adds a lightweight Stage projection for list and graph views that need
metadata and current state for many Stages at once but do not need the
full Stage CR. The summary omits FreightHistory entries beyond the
current FreightCollection, PromotionTemplate step configuration, and
Verification configuration. Use GetStage to retrieve the full Stage
resource when detail fields are needed.

Both endpoints are available in ConnectRPC and REST
(GET /v1beta1/projects/{project}/stage-summaries, including
?watch=true for SSE). Requests accept an optional freightOrigins
filter and a resourceVersion for the standard list-then-watch pattern.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 23, 2026

Deploy Preview for docs-kargo-io ready!

Name Link
🔨 Latest commit 493ec53
🔍 Latest deploy log https://app.netlify.com/projects/docs-kargo-io/deploys/69ebba83f3d6bc00088a4196
😎 Deploy Preview https://deploy-preview-6159.docs.kargo.io
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 79.83871% with 25 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.75%. Comparing base (91a058b) to head (493ec53).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
pkg/server/get_stage_health_outputs_v1alpha1.go 71.05% 8 Missing and 3 partials ⚠️
pkg/server/list_stages_v1alpha1.go 71.42% 4 Missing and 4 partials ⚠️
pkg/server/watch_stages_v1alpha1.go 25.00% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6159      +/-   ##
==========================================
+ Coverage   57.67%   57.75%   +0.08%     
==========================================
  Files         474      476       +2     
  Lines       40506    40628     +122     
==========================================
+ Hits        23362    23465     +103     
- Misses      15749    15762      +13     
- Partials     1395     1401       +6     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jacobboykin

This comment was marked as outdated.

jacobboykin and others added 3 commits April 23, 2026 19:41
Buf lint enforces lower_snake_case for proto field names. Updated the
new StageSummary / ListStageSummaries / WatchStageSummaries messages
accordingly. Generated Go struct names (StageSummaries, FreightOrigins,
etc.) and protojson wire format (camelCase via the json= tag) are
unchanged. Swagger and TypeScript bindings regenerated to match.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
…geSummary

Adds a batch RPC that returns the raw health output blob for a specified
set of Stages in a project, and updates stageToSummary to always leave
Status.Health.Output nil in the summary response. Intended for clients
that use ListStageSummaries for the stage list and want to resolve
per-argocd-app health only for the Stages currently in viewport (React
Flow virtualizes to ~10-30 visible nodes at a time).

The raw health output is typically ~2 KB per Stage and is the single
largest remaining field in StageSummary. Moving it behind a lazy fetch
drops the summary to roughly 1-2 KB per stage, reducing both wire
transfer and the heap footprint of the cached list.

The new endpoint is available in ConnectRPC and REST
(GET /v1beta1/projects/{project}/stage-health-outputs?stageNames=a&stageNames=b).
Stages that do not exist or have no recorded health output are omitted
from the response map; the endpoint has best-effort semantics.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
Comment on lines +105 to +119
// @id ListStageSummaries
// @Summary List Stage Summaries
// @Description List a lightweight projection of Stage resources from a
// @Description project's namespace. Intended for UI list and graph views that
// @Description need metadata and current state for many Stages at once but do
// @Description not need full FreightHistory, PromotionTemplate steps, or
// @Description Verification configuration. Use GetStage for detail fields.
// @Tags Core, Project-Level
// @Security BearerAuth
// @Produce json
// @Param project path string true "Project name"
// @Param freightOrigins query []string false "Warehouse name(s) to filter by" collectionFormat(multi)
// @Success 200 {object} svcv1alpha1.ListStageSummariesResponse
// @Router /v1beta1/projects/{project}/stage-summaries [get]
func (s *server) listStageSummaries(c *gin.Context) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a defensible reason for this being a dedicated endpoint instead of just adding an optional summary=true query param to the existing endpoint, in which case, you'll just drop the fields that "summary" excludes?

jacobboykin and others added 6 commits April 24, 2026 12:10
Reconciles with akuity#6153's Warehouse-filter helper: drops the duplicate
stageMatchesAnyWarehouse and warehouseNameSet helpers and routes the
summary list/watch paths through the shared []string-based matcher in
list_stages_v1alpha1.go, which also checks Origin.Kind explicitly.
filterStagesByWarehouses normalizes with nonEmptyStrings so
?freightOrigins=&freightOrigins= still behaves as no filter.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
Signed-off-by: Kent Rancourt <kent.rancourt@gmail.com>
…y flag

Replaces the dedicated ListStageSummaries / WatchStageSummaries RPCs and
their StageSummary / StageSpecSummary / StageStatusSummary messages with
a `summary` bool on ListStagesRequest / WatchStagesRequest. When set,
heavy fields are stripped from each returned Stage in place:

- status.freightHistory truncated to the current element (index 0)
- spec.promotionTemplate.spec.steps[*].config cleared (kind/as/name kept)
- status.health.output cleared (use GetStageHealthOutputs for lazy fetch)

The UI continues to derive has-verification from
`spec.verification != nil` and promotion-step-count from
`len(spec.promotionTemplate.spec.steps)`, so no derived fields or
dedicated projection types are needed.

ListStagesResponse gains resource_version and WatchStagesRequest gains
summary and resource_version so list-then-watch works across both full
and summary modes.

GetStageHealthOutputs is unchanged — it remains a separate batch RPC
for lazy-loading argocd health blobs per viewport.

Addresses review feedback asking why a dedicated endpoint was used
instead of a flag on the existing ListStages/WatchStages pair.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
Promotes two helpers that were living in pkg/server to pkg/api/stage.go,
alongside GetStage, ListFreightAvailableToStage, and the rest of the
Stage-domain operations. Both are now exported and unit-tested:

- StripStageForSummary: in-place projection that clears heavy fields
  (freightHistory[1..], promotionTemplate step configs, health.output)
  from a Stage. Previously a package-private helper in
  pkg/server/list_stages_v1alpha1.go.

- ListStageHealthOutputs: given a project and a set of Stage names,
  returns map[name]raw-health-output. Previously inlined (twice — once
  for the ConnectRPC handler and once for the REST handler) in
  pkg/server/get_stage_health_outputs_v1alpha1.go, alongside a
  private uniqueNonEmptyStrings helper that is now folded into the
  exported function.

The server handlers are now thin adapters that validate transport-level
concerns (batch-size cap) and delegate domain work to pkg/api. This
matches the convention akuity#6163 establishes for StageMatchesAnyWarehouse /
ListStagesByWarehouses.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
…n/stage-summary-api

Applies Kent's pkg/api Stage-domain consolidation ahead of its merge so
this branch is ready to land cleanly once akuity#6163 is in main.

Kent's PR promotes stageMatchesAnyWarehouse + listStagesByWarehouses
from pkg/server into pkg/api (exported, unit-tested, options-struct
shaped). Our REST listStages handler now calls api.ListStagesByWarehouses
directly. Our ConnectRPC ListStages handler does its list inline so it
can surface list.ResourceVersion for the list-then-watch response field;
filtering still goes through api.StageMatchesAnyWarehouse to keep the
matching logic in one place.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
… cache update

- Remove the meta/v1/generated.proto import from service.proto; it was
  only used by the StageSummary messages that this PR replaced with a
  summary flag on ListStages.
- The new required ListStagesResponse.resource_version field requires
  UI callers constructing the response shape to populate it. The
  pipeline watcher's cache-update path has no authoritative RV on hand
  (it's merging incremental Stage watch events), so it passes an empty
  string. The list-then-watch RV flow itself continues to be handled
  by ListStages's own response, not by this cache-update path.

Signed-off-by: Jacob Boykin <boykinmusic@gmail.com>
Signed-off-by: Jacob Boykin <jacob.boykin@akuity.io>
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.

2 participants