Skip to content

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

Open
Guimove wants to merge 4 commits into
stagingfrom
feat/variable-override-ux
Open

Guimove wants to merge 4 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, so the scope-aware message q-core is adding shows up as is.

The rules mirror q-core's override validation and live in @qovery/domains/variables/util (canOverrideConflictingVariable). 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.

Dependency: q-core's 409 existing_variable needs variable_type, is_secret and mount_path (q-core!4207). Without them the toast falls back to the link, so this PR is safe to merge first.

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 override of unknown kind, different secret kind, missing type in the body, 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 toast offers "Create an override" when the existing variable sits at a strictly higher scope, resolves to the same kind (VALUE/FILE, or an OVERRIDE whose overridden variable reveals the kind) as the request, has the same is_secret, and matches the requested key; it opens the override modal prefilled with the typed value and scope. Other conflicts—including a mount path clash with another key—keep the link to the existing variable.
  • The override modal is remounted fresh from the parent variable so it doesn't inherit stale state like file format, mount path, or the 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.
  • Other useCreateVariable() callers keep today's behavior: the override action only appears when an onCreateOverride handler is passed.

Dependency: the action requires variable_type and is_secret in the 409 body's existing_variable. Until q-core sends them, the toast falls back to the link, so this PR is safe to merge first.

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

View guided diff Turn on auto-fix

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.
@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 ec45943

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

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


☁️ Nx Cloud last updated this comment at 2026-10-22 10:46:37 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 92.30769% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 52.24%. Comparing base (ab54bc2) to head (ec45943).

Files with missing lines Patch % Lines
...b/hooks/use-create-variable/use-create-variable.ts 89.47% 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    53.39%   52.24%   -1.15%     
===========================================
  Files         1444      592     -852     
  Lines        32446    16409   -16037     
  Branches      9657     5198    -4459     
===========================================
- Hits         17323     8573    -8750     
+ Misses       12384     6394    -5990     
+ Partials      2739     1442    -1297     
Flag Coverage Δ
unittests 52.24% <92.30%> (-1.15%) ⬇️

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.

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.
@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
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