UX-179 - rpcn: save pipelines as drafts - #2624
Conversation
Save used to mean Start, and was gated on validation passing. So work in
progress could not be parked — an invalid config would not save at all, and a
refresh or a misclicked Back button lost everything typed — and a finished
config could not be saved without going live.
A pipeline can now be saved as a draft: persisted, not running, zero compute,
and stored exactly as typed even when it does not lint. Validation moves to the
moment it matters, which is Start.
API (proto/redpanda/api/dataplane/v1/pipeline.proto), all additive:
- Pipeline.State.STATE_DRAFT, omitted from ListPipelines unless
Filter.include_drafts is set, so clients written before drafts existed never
receive a state they cannot render.
- PipelineCreate.draft, and PipelineUpdate.draft as an *assertion* rather than
a transition: it means "I am editing a draft", and the update fails with
FAILED_PRECONDITION if the pipeline has since been started. Without that,
"Save draft" could silently deploy a config to a running pipeline because a
teammate started it mid-edit.
- Filter.states for state filtering, and Pipeline.created_by / create_time /
update_time so a shared draft pool is attributable and its staleness visible.
- config_yaml loses its field-level `required` rule; the service enforces it
for anything that is not a draft, with a sentence instead of a proto field
path. Pipeline keeps the invariant as a message-level CEL rule.
Console:
- Split save actions per context: Save draft (new pipelines and drafts),
Save and start, Save (stopped), Apply and restart (running — saying "Save"
would hide the restart, and there is no apply-later to make that untrue).
- Drafts are ordinary rows in the pipeline list: Draft chip, Drafts tab with a
count, sorted first and by last edited, resume / start / delete actions, and
"Edited 5m ago · by someone" in place of the id.
- A draft's own page explains itself instead of offering monitoring it cannot
have, and starting one routes lint failures into the editor where they are
fixable.
- Lint results are warnings on a draft and block only Start; an unnamed draft
is auto-named rather than refused.
- localStorage is repurposed from "drafts" to what it is actually good for:
crash recovery for the editor buffer, offered back after a refresh and
dropped on a successful save.
- New Changes lane (YAML | Visual | Changes N) diffing the saved config
against the editor, with the components touched listed and clickable, and a
header line stating what applying will cost.
Deliberate decisions, with reasoning, in frontend/specs/rp-connect-pipeline-drafts.md:
draft as a distinct state rather than an unapplied-changes flag; annotation
storage rather than a CRD field; drafts count against the pipeline quota; drafts
are named and names may collide; org-wide visibility because pipeline RBAC has no
ownership predicate; no expiry, staleness shown instead. It also records the
feature's real limit — a draft ends at first start, so "save without going live"
is solved for pipelines that have never run and not for the ones that have — and
the design for closing that with a pending revision.
Ships behind enable-rpcn-pipeline-drafts. The flag must stay off until the
redpanda-connect-api carrying draft support is deployed: an older API ignores
`draft` on create and deploys what the user asked to park. Note that
backend/pkg/protogen must be regenerated even though no Go here implements
PipelineService — console-enterprise proxies through these types, and Connect's
JSON codec discards unknown fields rather than passing them through.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Autosave restore resets the form without keepDirty, so a settings-only restore arms the unsaved-changes guard (regression test added). - Regenerate proto/gen/openapi for the pipeline proto changes; CI dirty-checks it alongside the protogen dirs. - Spec: deploy order is free while the flag is off; only the flag flip is ordered. - Trim comments to the constraint, not the rationale. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest Buf updates on your PR. Results from workflow Buf CI / validate (pull_request).
|
✅ Clean — no registry drift, off-token colours, or ad-hoc classesApp:
Generated by lookout audit-changes. |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Also lands under frontend/**, which the verify and dispatch workflows filter on; the previous empty commit ran none of them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adversarial review — findingsWent through this looking for what breaks rather than whether it reads well. Findings only, HIGH → LOW. A lot of this is careful work — the lane-switch commit-before-unmount, revealing by store request instead of poking a disposed editor, the deliberate choice to compare server clocks only for staleness, the exhaustive HIGHH1.
|
|
Thanks for the review, addressed/fixed most feedback. For M12 this will require a backend change, and this branch is following the convention used elsewhere in the app. In utils/tsx-utils.tsx computes exactly Date.now() - serverTs and is used across topics, brokers. The branch matched the existing convention. We could do a follow up to fix this throughout, but it is a bit of an edge case. |
| // FAILED_PRECONDITION if the pipeline has since been started. An update never | ||
| // changes whether a pipeline is a draft; false is rejected, and StartPipeline | ||
| // promotes a draft. Leave unset to update whatever is stored. | ||
| optional bool draft = 9; |
There was a problem hiding this comment.
can you have a draft that's not technically valid YAML so that you can leave it and fix it before it can move to a non-draft state?
There was a problem hiding this comment.
Yes, you can save invalid YAML as a draft
Summary
Save no longer means deploy. A pipeline can be saved as a draft: stored as written, never validated, never running, and started later once it lints. Around that the editor gets crash recovery and a way to see what a save will change.
dataplane/v1/pipeline.proto, additive):STATE_DRAFT,PipelineCreate.draft,PipelineUpdate.draft, list filtersstates/include_drafts, and output-onlycreated_by,create_time,update_time. Drafts stay out ofListPipelinesunless asked for, so older clients never see a state they can't render.localStorage, so a crash or a closed tab doesn't lose work. The next visit offers to restore them and warns if someone saved the pipeline in the meantime.The reasoning behind the design choices is in
frontend/specs/rp-connect-pipeline-drafts.md.Rollout
Behind
enable-rpcn-pipeline-drafts, off by default here and in cloud-ui. The service side is redpanda-data/cloudv2#29544.Deploy order doesn't matter while the flag is off. Once it's on, every hop (console, console-enterprise, redpanda-connect-api) has to be new: an old one drops
draftand deploys the pipeline for real. The UI checks the state that comes back and tells the user if that happened. Flip the flag last.backend/pkg/protogenandproto/gen/openapiare regenerated because console-enterprise proxies this service through the Go types and would otherwise strip the new fields.