Skip to content

feat(variables): [QOV-2445] create an override from a conflict and show which scope wins - #3141

Merged
Guimove merged 7 commits into
stagingfrom
feat/variable-override-ux
Oct 30, 2026
Merged

Guimove merged 7 commits into
stagingfrom
feat/variable-override-ux

Conversation

@Guimove

@Guimove Guimove commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Jira: QOV-2445

Related changes:

Two changes that make variable inheritance easier to follow. We keep the explicit OVERRIDE type and fix the UX around it.

"Create an override" on a creation conflict

When POST /variable returns a 409 status, the toast now offers Create an override if the existing variable:

  • has the same key (a 409 can also come from a mount path clash with another variable; that case keeps the link),
  • sits at a strictly higher scope than the one being created (project or environment above a service, project above environment),
  • is a VALUE or FILE variable of the same kind as the request (a file when the request has a mount path, a value otherwise),
  • has the same is_secret as the request.

Clicking it swaps the creation modal for the existing override modal, with the existing variable as parent and the value and scope the user had typed. Every other conflict keeps the "Go to the conflicting variable" link. That includes an existing OVERRIDE: q-core stores overrides without a mount path and the 409 body doesn't include the overridden variable, so the Console can't tell a file override from a value override. The toast description is the API's detail, which says which scope holds the existing variable.

The rules mirror q-core's override validation and live in @qovery/domains/variables/util (canOverrideConflictingVariable). The 409 body is typed with the generated VariableConflictResponse and ExistingVariableResponse; there is no hand-written type left. useCreateVariable() takes an optional onCreateOverride; the other callers (service and job creation flows, external secrets tab) don't pass it and keep today's behavior.

Dependencies, all merged: q-core!4207 sends variable_type, is_secret and mount_path in the 409 body and is deployed to production (v1.772.0). qovery-openapi-spec#1237 adds override_if_exists and #1248 describes the 409 body. qovery-typescript-axios is bumped to 1.1.1030.

Provenance in the service variable list

The service list already returned rows from every visible scope, with an OVERRIDE badge and the parent key under the override. Nothing marked the parent row, so you couldn't tell which value applied. Now:

  • the overridden row is muted and labelled "Overridden in service" (or "environment"),
  • the override row shows "Overrides environment" (or "project") instead of repeating the key.

Both labels scroll to the other row.

Testing

  • yarn nx test domains-variables-util --maxWorkers=2: 18 passed.
  • yarn nx test domains-variables-feature --maxWorkers=2: 80 passed. New hook spec covers the eligible conflict, same scope, another key (mount path clash), an existing override, different secret kind, a body without location ids, no handler, no existing_variable, and non-409 errors. Modal and list specs cover the prefill, the modal swap (fresh form, guard cleared), and both labels.
  • Variable list snapshot updated after review: two new rows in the fixture, the row count, radix id renumbering.
  • nx run-many -t lint on both variables libs: clean. Prettier on the changed files: clean.
  • Not run in a browser.

Summary by cubic

Makes variable inheritance easier to follow by converting eligible creation conflicts into override creation and by labeling which scope wins in the variable list.

  • On a 409, the conflict toast offers "Create an override" when the existing variable sits at a strictly higher scope, matches the requested key and is_secret, and is the same kind (VALUE/FILE, or an OVERRIDE whose overridden variable reveals the kind) as the request. Other conflicts—including a mount path clash with another key—keep the link to the existing variable.
  • The override modal is remounted from the parent variable so it doesn't inherit stale state (file format, mount path, interpolation toggle) from the creation form it replaces; the unsaved-changes guard armed by the creation form is cleared first.
  • In the service variable list, the overridden parent row is muted and labeled "Overridden in service" or "environment"; the override row shows "Overrides environment" or "project". Both labels scroll to the other row.
  • The override action only appears when an onCreateOverride handler is passed, so other useCreateVariable() callers keep today's behavior.
  • The 409 body is now typed from the generated client (VariableConflictResponse/ExistingVariableResponse), where variable_type and is_secret are always sent; the older-API fallbacks are gone. Bumps qovery-typescript-axios to 1.1.1030.

Written for commit ba8bb48. Summary will update on new commits.

View guided diff Turn on auto-fix

@RemiBonnet

Copy link
Copy Markdown
Member

Qovery Preview

Qovery can create a Preview Environment for this PR.
To trigger its creation, please post a comment with one of the following command.

Command Blueprint environment
/qovery preview cc1de7de-94e5-40f8-8e45-bc8986d9dfec storybook
/qovery preview 964984c5-fba6-4371-88d3-a5daf99642ab test-prod
/qovery preview 28c47145-c8e7-4b9d-8d9e-c65c95b48425 staging
/qovery preview {all|UUID1,UUID2,...} To preview multiple environments

This comment has been generated from Qovery AI 🤖.
Below, a word from its wisdom :

Programs must be written for people to read, and only incidentally for machines to execute

@nx-cloud

nx-cloud Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit ba8bb48

Command Status Duration Result
nx run console:build --parallel=3 --configurati... ✅ Succeeded 1m 14s View ↗
nx affected --target=test --parallel=3 --config... ✅ Succeeded 4m 49s View ↗
nx affected --target=lint --parallel=3 ✅ Succeeded 3m 25s View ↗
nx-cloud record -- yarn nx format:check ✅ Succeeded 2s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-10-30 15:31:20 UTC

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.80328% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.85%. Comparing base (38cf66c) to head (ba8bb48).

Files with missing lines Patch % Lines
...b/hooks/use-create-variable/use-create-variable.ts 90.00% 1 Missing and 1 partial ⚠️
...es/feature/src/lib/variable-list/variable-list.tsx 77.77% 1 Missing and 1 partial ⚠️
libs/domains/variables/util/src/index.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           staging    #3141       +/-   ##
============================================
- Coverage    88.10%   53.85%   -34.26%     
============================================
  Files           15     1435     +1420     
  Lines          328    32361    +32033     
  Branches        84     9663     +9579     
============================================
+ Hits           289    17427    +17138     
- Misses          28    12224    +12196     
- Partials        11     2710     +2699     
Flag Coverage Δ
unittests 53.85% <91.80%> (-34.26%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 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.

@Guimove Guimove changed the title feat(variables): create an override from a conflict and show which scope wins feat(variables): [QOV-2445] create an override from a conflict and show which scope wins Oct 22, 2026
@Guimove
Guimove force-pushed the feat/variable-override-ux branch from ec45943 to 7ba6299 Compare October 30, 2026 09:54
When POST /variable returns 409 and the existing variable sits at a strictly
higher scope, is a value, file or override, and has the same secret kind, the
conflict toast offers "Create an override". It opens the override modal with
the existing variable as parent and the value and scope the user typed. Other
conflicts keep the link to the existing variable.
A variable overridden at a lower scope is muted and labelled "Overridden in
<scope>"; the override is labelled "Overrides <scope>". Both labels scroll to
the other row.
A 409 can also come from a mount path clash with a variable of another key;
that case now keeps the link to the existing variable instead of offering an
override of an unrelated variable.

The override modal replaced the creation modal at the same tree position, so
it reused the creation form state (file format, mount path, interpolation
toggle). A distinct key now forces a remount so the override form starts from
the parent variable.
…e variable kind

- Detect the 409 with isHttpStatus instead of the code the interceptor copies
  from the body.
- Only offer an override when the request and the existing variable are the
  same kind (file or value). An existing override only carries its kind through
  the variable it overrides, which the 409 body does not include, so it keeps
  the link.
- Clear the unsaved-changes guard armed by the creation form before swapping in
  the override form.
Use VariableConflictResponse and ExistingVariableResponse from qovery-typescript-axios instead of the local ConflictingVariableResponse type. variable_type and is_secret are now always sent, so the checks for an older API go away. An existing override still falls back to the link: the body doesn't include the variable it overrides.

@rmnbrd rmnbrd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Tested and approved 🤝

@Guimove
Guimove merged commit 4a27688 into staging Oct 30, 2026
9 of 10 checks passed
@Guimove
Guimove deleted the feat/variable-override-ux branch October 30, 2026 16:09
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.

3 participants