New release - #23118
Draft
github-actions[bot] wants to merge 29 commits into
Draft
Conversation
ref: #BKP-1161 Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
ref: #BKP-1161 Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
ref: #BKP-1161 Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Merged onto the initialised module rather than replacing it: namespaces, route constants and test i18n helpers gain the onboarding entries, and the app keeps its own constants and tailwind scan. The app-side onboarding page, hook, types and translations move to the shared module. ref: #BKP-1206 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
…he move The page it renders now lives in the shared module. ref: #BKP-1206 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
ref: #BKP-1161 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
These files predate the onboarding page and were already failing prettier on the base branch. The module's lint task globs the whole package, so this branch cannot go green while they fail. Formatting only, no behaviour change — they can be moved down to the project/backup-licenses PR if that PR is fixed first. ref: #BKP-1206 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Ref: #BKP-1208 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
ref: #BKP-1216 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
ref: #BKP-1226 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
ref: #BKP-1218 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com> # Conflicts: # packages/manager/apps/hpc-backup-licenses/spec/INDEX.md # packages/manager/modules/backup-licenses/src/routes/routes.constants.ts # packages/manager/modules/backup-licenses/src/routes/routes.tsx
ref: #BKP-1217 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com> # Conflicts: # packages/manager/modules/backup-licenses/src/data/api/backupLicenses/backupLicenses.requests.ts # packages/manager/modules/backup-licenses/src/mocks/backupLicenses/backupLicenses.mock.ts # packages/manager/modules/backup-licenses/src/types/BackupLicense.type.ts # Conflicts: # packages/manager/modules/backup-licenses/src/routes/routes.tsx # packages/manager/modules/backup-licenses/src/utils/apiRoutes/apiRoutes.spec.ts
ref: #BKP-1219 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
ref: #BKP-1216 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
Activates the Vaults tab and renders the listing BKP-1221 specifies: the five
columns, its four states and the row action menu. The tab entry was already
declared in SERVICE_NAV_TABS with `isDisabled`, as its own comment invited — the
flag is now removed and the route mounted under ServiceLayout.
The vault data layer written for the billing tab is completed rather than
duplicated: Resource.type gains OUT_OF_SYNC, the 7th member of the API enum that
was missing; Vault.type gains status, buckets, vspcTenants and the IAM envelope as
optional fields, so the billing fixtures keep working; queryKeys, apiRoutes and
vaults.requests gain their vault entries.
Two deliberate divergences from the ticket, both because the module already
decided otherwise and its reason holds:
- the product-line filter keeps vaults that carry no `vaultProductLine`. The
ticket asks for an exact match, but the field is published by no contract, so a
strict filter would empty the tab for every customer until the backend ships it.
- the region cell resolves `currentState.region` through the offer's subset onto
the shared `region` namespace, case-insensitively: the contract publishes
lowercase codes, the existing fixtures uppercase ones.
Also registers the shared translation namespaces in the test harness. They were
imported for assertions but never added to i18next, so any component resolving
`t('region:…')` rendered the raw key — which is why two of the linked-servers
assertions matched untranslated keys and now match the labels.
477 tests pass, tsc and lint clean.
Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Adds the credentials modal as a child route of the listing, with the four fields the ticket specifies and its four states. The keys are read when the modal opens and never served from cache. Departures from the ticket, each with its reason: - only the secret key is masked, with the ticket's Show/Hide toggle; the mockup shows a second eye control on the access key, which would suggest it is sensitive too. - region and endpoint both describe the key-serving bucket, not the vault. The ticket binds the endpoint to `buckets[0]` and the region to the vault, but `buckets[0]` can be a suspended bucket, and an S3 client signs with the region it is given and rejects one the endpoint contradicts — so a mismatched pair would fail to connect. - the endpoint falls back to a region-derived hostname, isolated in getVaultBucketEndpoint, because `bucket.endPoint` is published by no contract. The requests actually emitted are asserted in their own spec file, which disables the module's mock switch — the other specs keep it, so both modes stay covered. Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Adds the termination modal as a child route of the listing. The confirmation is
the Manager's shared destructive modal, driven by useDeleteService.
Two ticket requirements are not deliverable and are recorded rather than faked:
- the verb is POST /services/{serviceId}/terminate, not the DELETE the ticket
names. The shared deleteService helper sends that DELETE only for the US
subsidiary — and slice 5.1 already uses the POST, so the epic resolves in its
favour. Going through the shared hook also settles the long[] tie-break: it
takes the first element, the Manager-wide convention.
- the typed TERMINATE keyword no longer exists: MRC 2.43.1 removed the
confirmation input from that modal and fixes its title and button labels. Only
the body is free, and it carries the ticket's message. A bespoke modal would
mean diverging from the Manager's destructive pattern on a data-loss action.
The modal guards its own route: it is deep-linkable, so a hand-typed URL would
otherwise reach the included vault whose row entry stays disabled. It renders
nothing until the list resolves, since the confirmation names the vault.
Its integration spec asserts the route is reachable but no longer the success
toast: the /services mutation never settles in this harness. The page spec covers
the toast instead.
Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Adds the order modal as a child route of the listing: the two inputs the ticket specifies (name, storage region), its validation, the pricing message, and the polling that follows the new vault until it settles. The submit cannot be delivered, and says so rather than pretending. Three published facts, not an omission: `.../vault` exposes GET only, the `POST /publicCloud/.../storage/object/bucket` the ticket names is absent from the v2 schema (that section is Rancher-only), and the express order link its 2026-07-10 comment replaced the route with was never documented. The channel is therefore a single swap point that rejects — when the real one lands, it is installed there and nothing else changes. The region select is populated from the `/location` referential the order funnel already consumes, not from a hardcoded list. Extends the shared OrderTextField rather than duplicating it: it now forwards its ref, so react-hook-form can focus the field in error after an invalid submit; carries `isRequired` on the input instead of an astérisque a screen reader reads as punctuation; and renders its error in a light-DOM live region, because no IDREF crosses the ODS shadow boundary — the error reaches assistive tech through the accessible name. Also drops the <Notifications /> this tab hosted: it was explicitly deferred until BKP-1215 delivered the shell, which now renders one for every tab. Keeping both showed the order confirmation twice. 553 tests pass, tsc and lint clean. Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
…entry Unmocks the catalog/tenants API calls for BKP-1208 and mirrors the hpc-backup-licenses sidebar entry (nav-reshuffle + legacy) gated by the bmc-backup-licenses CFF flag, fixing the app's appName constant which previously used the wrong bmc-backup-licenses-baremetal value. ref: #BKP-1208 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
Both order surfaces — the first-subscription funnel (BKP-1208) and the extra
vault (BKP-1223) — buy through the same Agora channel, so the channel lands once,
here, with no page or hook wired to it yet.
Declares @ovh-ux/manager-module-order on the module (the apps already carry it)
and wraps its createCart for the funnel's cart: one backupServices item, its
configurations, its addons, then assign and the checkout simulation.
createCart cannot serve the other shape. Buying onto an existing service puts the
item on POST /order/cartServiceOption/backupServices/{serviceName}, between the
cart creation and the assign — so the primitives it composes are exposed too
(createOrderCart, addBackupServicesOption, assignOrderCart, getOrderCartCheckout).
Two decisions the specs insist on, and one pure utility each:
- prices and order parameters are read, never guessed. getOfferOrderParameters
derives planCode, pricingMode, duration and quantity from the offer's
installable pricing, because a metered product does not follow the funnel's
monthly default/P1M pattern. No price and no plan parameter lives in the code.
- the configuration labels are not frozen catalogue-side, so planCartConfigurations
pairs the labels the cart actually asks for with the values at hand and reports
the required ones nothing covers, rather than posting them empty.
No USE_API_MOCKS guard, same status as the catalog requests: these routes answer
in production. The backupServices catalogue itself is not published in EU yet, so
they can answer empty or in error and callers degrade — never a fallback figure.
The MSW handlers cover the whole surface off setupMswMock's params, with success,
offer-absent and settable 4xx/5xx outcomes. Their price fixtures are arbitrary
test numbers, recognisable as fake, since nobody knows the real ones.
40 new tests pass; lint and tsc clean on the new files.
Refs: BKP-1208 · BKP-1223
Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (BKP-1208) The "Finaliser ma commande" CTA cleared the form and navigated away without placing anything. It now builds, configures, assigns and executes one Agora cart, and only then clears the draft and leaves the page. The cart is walked with the primitives rather than createCart, for two reasons createCart cannot express: the configuration labels are unknown until the catalogue is declared, so each item's requiredConfiguration is read before its configurations are posted; and the addons of R2 (vspc-tenant, then vspc-tenant-backuplicenses, then the bundled 500G vault) go in one at a time, because the catalogue may condition one on the previous. createCart fires both in parallel and posts configurations blind. Nothing is guessed. buildBackupLicensesOrderComposition offers the values the ticket names — displayName, the public IP, the vault name, the region, the card's exact licenseType enum — and the private IP only when the NAT toggle is on (R4). Whichever item asks for a label gets it; a required label no value answers fails the order with UNKNOWN_CART_CONFIGURATION instead of shipping a half-configured service. The plan codes and label spellings stay unverified until the catalogue is published: first run against it is the first truth. The execution follows the sibling product on the same channel (bmc-backup-agent-baremetal): GET checkout to simulate, POST checkout to engage, autopay included, without which the order lands unpaid and never provisions. The GET's contractList transits through the hook's result and nothing renders it — the CGV section is still the PO's call (R5), and that GET is where the sequence splits if it ships. Submitting locks the whole funnel — cards, fields, region grid, step buttons, the back link — and disables the CTA, which is what makes one click one cart. A failure keeps every field, keeps the persisted draft, stays on the page and puts the message in a live region that takes focus. Refs: BKP-1208 Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
…P-1223)
The submit rejected on purpose, and now it orders. A vault bought onto an
existing service is an Agora cart *service option*, not a fresh cart item, so
the sequence is its own: POST /order/cart, then POST
/order/cartServiceOption/backupServices/{serviceName}, the item's
configurations, assign, and the same GET-then-POST checkout the funnel walks —
the GET simulates, the POST engages.
Nothing about the offer is assumed. The offers route is read first, the paygo
plan code is looked up in what it serves, and the POST's pricingMode, duration
and quantity are taken off that offer's installable price: a metered product
does not share the funnel's monthly default/P1M. No offer, or no installable
price, and no cart is opened at all — the modal shows its banner instead of
buying something nobody named.
The configuration labels are still unknown, same structural hole as the funnel:
every plausible spelling of the name and the region is offered, only what the
cart claims is sent, and a claimed label no value answers fails the order
instead of shipping a half-configured vault.
The serviceName is the licence's currentState.resourceName — the one /services
identifier this module already resolves, and the one it terminates the
subscription with. Unconfirmed on the Agora side and recorded as such: the
service accepting the vault option could be the backupServices tenant itself.
Pays the R1 debt. order.pricing_message no longer carries 0,007 € in three
locales: the sentence holds {{price}} in all eight, and the value is the offer's
own formatted string, which Agora localises per subsidiary — a front-end
formatter at two decimals would round a per-GB rate to the nearest cent. With no
rate the whole sentence goes, skeleton then nothing, the way CardPrice already
degrades; a sentence stripped of its number would announce a billing model
without saying which.
setVaultOrderChannel stays, and is now covered: the default is the real channel,
which is what stops a stub shipping installed. The MSW handlers of the order
routes drive the accepted, name-rejected and offer-missing outcomes, so the
integration suite walks the real sequence instead of an installed answer. Error
classification is untouched — a 400/409 with a message stays on the name field,
everything else goes to the banner.
Along the way: configureCartItemFromRequirements moves next to the requests both
channels call it from rather than being duplicated, and mockBackupServicesTenants
finally exists — the tenant MSW handler had been importing it from nowhere.
673 tests pass, tsc and lint clean. No price literal left in src/ or the
translations outside the explicitly fictional test fixtures.
Refs: BKP-1223
Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
manager-lint flagged import ordering and JSX formatting drift in two spec files touched by the unmock work; auto-fix only, no test changes. Refs: BKP-1208, BKP-1223 Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
… assuming P1M
The funnel stamped duration P1M, pricingMode default and quantity 1 onto all
four elements of the cart. The referencing doc only establishes that monthly
pattern for the main item; the spec says as much in as many words, "unverified
for consumption addons" — and the three addons are exactly the consumption
plans. So three quarters of the cart were billed on conditions nobody sourced.
The vault channel already knows better (BKP-1223): it reads the offer before it
orders, and takes the POST parameters off the offer's installable price. The
funnel now walks the same discipline. Right after the cart opens, and before a
single item is posted, both definition routes are read — GET
/order/cart/{cartId}/backupServices for the main item, GET
/order/cart/{cartId}/backupServices/options?planCode= for the addons offered to
it — and pricingMode, duration and quantity come from each plan's own
installation pricing. utils/orderComposition no longer decides anything about
money: it names what to order and holds the configuration values, nothing else.
A plan of the composition that the cart does not offer, or that offers no
installable price, stops the order with UNAVAILABLE_CART_OFFER and the names of
the plans at fault. That failure is the point. The alternative is a POST the
catalogue rejects for a reason no one can read from the response, or worse, a
cart of three items out of four — an subscription billed without the vault the
offer includes. Same philosophy as UNKNOWN_CART_CONFIGURATION next door, its own
constant and its own message so the two are never confused in a bug report.
The mocked definitions deliberately announce two non-monthly addons, one on a
consumption pricingMode and one yearly at a minimum quantity of two: without
that, a hard-coded monthly pattern would pass the tests unnoticed.
What the routes will actually serve for this product stays unknown until the
backupServices catalogue is declared — which plans, which prices carry
installation, what each announces. The routes and the types are certain, the
values are not, and API-STATUS records that as the residual hole. The upside is
that the first run against a declared catalogue will also settle R2: an addon
the catalogue attaches by itself will simply be absent from the option
definitions.
Refs: BKP-1208
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
`backupServices` publishes a single entry under `plans`, `backup-tenant`, and everything else — licences and vaults included — under `addons`. Looking the licence plan codes up in `plans` alone never returned a pricing, so the funnel showed its empty placeholder instead of a price, on every card and in the recap panel. The catalogue mocks moved their licence plans to `addons` as well: they were the reason the tests never caught it. Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
`USE_API_MOCKS` shipped as `true`, against the instruction its own comment carried, so tenants, VSPC tenants, licences, backup servers, vaults and consumption all answered from `src/mocks/**` and never reached the network. The switch and its short-circuits are gone, `resolveServiceId` included. Removing them uncovered tests that only passed through the hard-coded return: they had no handler for the routes the short-circuit hid. Hence the new `backupLicenses` MSW handler, the missing `vspc` one on the tenants handler, and `getResourceServiceId` mocked where the service id used to be the resource name. Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
The price block reached neither edge of the card: the button's default padding left a white band around its grey background, so `p-0` on both card buttons. The suffix listed every unit a licence can cover — server, VM, database, 500 GB of storage — which no longer reads on a card. The referencing doc prices every workload type alike, so the unit is named once and named as such: the five identical `price_suffix` entries per locale give way to one shared key, which the recap panel reuses to state the same unit as the cards. A second line carries what the asterisk on the vault feature points to: the storage the licence includes, then the rate per GB beyond it. The recap panel carries it too — that is where the order is placed, and that rate is the only amount that keeps running after delivery. The rate is read from `backup-vault-backuplicenses-500g-consumption`, never written here. It can go through neither `Price` nor the catalogue's own `formattedPrice`: both settle on two decimals, which turns 0.007 € into 0.01 €. Hence `formatCatalogPrice`, which keeps a sub-cent rate intact. Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
@hookform/resolvers ^5.2.2 imports zod/v4/core, a subpath that zod 3.22.4 does not export. The module pinned zod 3.22.4 in devDependencies, so a fresh pnpm install broke the three vault-order suites at load time and both app builds. It went unnoticed because earlier installs hoisted the apps' zod 4.1.12 at the worktree root, which the module silently resolved instead of its own pin. Bump the dev pin to 4.1.12 — the version both consuming apps already declare — and add zod to peerDependencies, since the module imports it at runtime, mirroring how react-hook-form is declared. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Thibault Barske <thibault.barske.ext@corp.ovh.com>
ref: #bkp-1161 Signed-off-by: maelthi <mael-thi.seme.ext@ovhcloud.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📦 New release
Approximate release date: 📆
Dashboard
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Bare Metal Cloud
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Hosted Private Cloud
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Public Cloud
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Web Cloud
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Telecom
✨ Features
to be defined
🐛 Bug Fixes
to be defined
Transversal
✨ Features
to be defined
🐛 Bug Fixes
to be defined