feat(variables): [QOV-2445] create an override from a conflict and show which scope wins
Jira: QOV-2445
Related changes:
- q-core: https://gitlab.com/qovery/backend/q-core/-/merge_requests/4207
- OpenAPI: https://github.com/Qovery/qovery-openapi-spec/pull/1237
- Console: https://github.com/Qovery/console/pull/3141
- CLI: https://github.com/Qovery/qovery-cli/pull/747
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
VALUEorFILEvariable of the same kind as the request (a file when the request has a mount path, a value otherwise), - has the same
is_secretas 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, noexisting_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 linton 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 anOVERRIDEwhose overridden variable reveals the kind) as the request, has the sameis_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 anonCreateOverridehandler 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.