Skip to content

Orchestrate the CRR Apply Actions chain with useChainedMutations - #1248

Merged
bert-e merged 1 commit into
development/4from
feature/ARTESCA-16787-crr-apply-actions-orchestrator
Jul 23, 2026
Merged

Orchestrate the CRR Apply Actions chain with useChainedMutations#1248
bert-e merged 1 commit into
development/4from
feature/ARTESCA-16787-crr-apply-actions-orchestrator

Conversation

@hervedombya

@hervedombya hervedombya commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

TL;DR

The CRR wizard's Apply Actions step now actually runs the full provisioning sequence — it drives the wizard-side steps and the crr-configurator destination stream as one chained sequence, instead of only rendering the backend stream.

Context

Part of the ARTESCA CRR Provisioning Wizard (ARTESCA-16787). The step contract (12 canonical rows) landed earlier; this PR makes the step orchestrate them for real: some steps run in the browser (certificate import, source account/bucket, location, replication rule), the middle ones run on the destination via the crr-configurator NDJSON stream.

Approach

useChainedMutations (the ISV pattern) runs the sequence. The chain has more links than visible rows — buildStepViews merges two state sources into the 12 contract rows:

Chain link Rendered as
import-destination-certificate (idempotent) row 1
create-source-account (when new) row 2
assume-source-role hidden — plumbing so the S3 hooks target the source account
create-source-bucket + create-source-bucket-versioning folded into row 3
configurator-setup (one link) expanded into rows 4–10 from the NDJSON step events
create-location (waits for overlay reconcile) row 11
create-replication-rule row 12

Two things worth knowing: the destination crr-role ARN from the stream's SetupResult is what makes the source replication rule's Role the composite s3-replication-role,<roleArn> pair the CRR procedure requires; and create-location only resolves once the overlay has reconciled, because the replication rule references the location by name.

Screenshots

Route /create-crr-configuration.

Apply Actions — the 12-row provisioning table, all pending before the run (Success / Failed + inline Retry appear per row as the chain progresses):

Apply Actions step

Configure — the step that feeds it (source account, destination connection + certificate, destination account, replication rule):

Configure step

Review focus

  • 🔴 ApplyActionsStep.tsx › useChainedMutations config + variable resolvers — the chain assembly and the data-flow from the stream SetupResult into create-location / create-replication-rule; a wrong resolver mis-provisions replication.
  • 🟡 steps.ts › buildStepViews / retryLinkIdForRow — merges configurator NDJSON events + wizard chain-link statuses into the 12 rows; per-row Retry re-runs the failed link of a folded row.
  • 🟡 useCreateCRRLocationMutation.ts — polls the running-config version so the location is reconciled before the replication rule runs (60s timeout, cancelled on unmount).

How to test

  1. Data Management → Locations → Start CRR Configuration.
  2. Fill Configure (source account, destination connection + certificate, destination account, enable the replication rule + bucket names), Check Connection, Continue.
  3. On Apply Actions, watch the 12 rows run top-to-bottom; each turns Success. If a step fails, that row shows Failed with an inline Retry that re-runs only that step (the destination rows re-run the configurator link).
  4. Re-run the wizard with an existing source account (source-account row disappears) and with the replication rule unchecked (source bucket / target bucket / replication rule rows disappear).

References

  • ARTESCA-16787 — CRR Provisioning Wizard.
  • Shared test mock __mocks__/@scality/data-browser-library gains useSetBucketVersioning / useSetBucketReplication (additive; existing ISV tests unaffected).

@bert-e

bert-e commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Hello hervedombya,

My role is to assist you with the merge of this
pull request. Please type @bert-e help to get information
on this process, or consult the user documentation.

Available options
name description privileged authored
/after_pull_request Wait for the given pull request id to be merged before continuing with the current one.
/bypass_author_approval Bypass the pull request author's approval
/bypass_build_status Bypass the build and test status
/bypass_commit_size Bypass the check on the size of the changeset TBA
/bypass_incompatible_branch Bypass the check on the source branch prefix
/bypass_jira_check Bypass the Jira issue check
/bypass_peer_approval Bypass the pull request peers' approval
/bypass_leader_approval Bypass the pull request leaders' approval
/approve Instruct Bert-E that the author has approved the pull request. ✍️
/create_pull_requests Allow the creation of integration pull requests.
/create_integration_branches Allow the creation of integration branches.
/no_octopus Prevent Wall-E from doing any octopus merge and use multiple consecutive merge instead
/unanimity Change review acceptance criteria from one reviewer at least to all reviewers
/wait Instruct Bert-E not to run until further notice.
Available commands
name description privileged
/help Print Bert-E's manual in the pull request.
/status Print Bert-E's current status in the pull request TBA
/clear Remove all comments from Bert-E from the history TBA
/retry Re-start a fresh build TBA
/build Re-start a fresh build TBA
/force_reset Delete integration branches & pull requests, and restart merge process from the beginning.
/reset Try to remove integration branches unless there are commits on them which do not appear on the source branch.

Status report is not available.

@bert-e

bert-e commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Waiting for approval

The following approvals are needed before I can proceed with the merge:

  • the author

  • one peer

Peer approvals must include at least 1 approval from the following list:

Comment thread src/react/locations/CRRSetupWizard/steps/ApplyActionsStep/steps.ts Outdated
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown

Well-structured PR — the chain orchestration, the two-source state merge in buildStepViews, and the CRR location/replication builders are clean. One concern:

- retryLinkIdForRow always returns the first link of a folded group (create-source-bucket), but when the second link (create-source-bucket-versioning) is the one that failed, calling retry() on the already-succeeded first link may no-op or re-run bucket creation unnecessarily.
- Consider targeting the first failed link within the folded group.

Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from 0669dbd to adbe78f Compare July 23, 2026 11:44
Comment thread src/react/locations/CRRSetupWizard/steps/ApplyActionsStep/ApplyActionsStep.tsx Outdated
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • Exit button enabled during wizard-side chain steps (ApplyActionsStep.tsx:303): disabled={setup.isLoading} only covers the configurator stream but the chain now includes steps that run outside it — notably create-location which polls for up to 60s. The user can navigate away mid-chain, orphaning the mutation and leaving the system partially provisioned.
    • Derive a chain-wide loading flag, e.g. disabled={setup.isLoading || steps.some(s => s.status === "pending")}
  • Component-level integration tests removed (ApplyActionsStep.test.tsx): The old tests covered step completion, inline error rendering, retry re-firing, and global stream errors. Only the initial-plan rendering tests remain — no test exercises the chain running to completion or retry behavior at the component level.
    • Add at least a happy-path chain-completion test and a retry test

Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from adbe78f to 0bda58b Compare July 23, 2026 11:58
{setup.isLoading && (
<Button type="button" variant="outline" label="Cancel" onClick={setup.cancel} />
)}
<Button type="button" variant="outline" label="Exit" disabled={setup.isLoading} onClick={() => prev(props)} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

disabled={setup.isLoading} only guards during the configurator stream step. During wizard-run chain links (import certificate, create account, create location, etc.), Exit remains enabled — the user can navigate away mid-chain. The in-flight mutation completes in the background but the chain stops, leaving the system partially provisioned. Re-entering the step restarts the chain from scratch, which can fail on non-idempotent steps (e.g. create-source-account → "entity already exists").

The same gap applies after the configurator stream finishes but before create-location / create-replication-rule complete — setup.isLoading is false again so Exit re-enables.

Consider deriving an isChainRunning flag from the chain steps and using it instead of setup.isLoading.

— Claude Code

};

return useMutation({
mutationFn: async (location: LocationV1) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The polling loop has no cancellation mechanism. If the component unmounts mid-poll (user clicks Exit or Cancel during this step), all 120 iterations continue running in the background. Consider threading an AbortSignal through and checking signal.aborted inside the loop to break early on unmount.

— Claude Code


return useMutation({
mutationFn: async ({ certificate }: { certificate: string }) => {
if (isCertificateAlreadyImported(zenkoCR, certificate)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

zenkoCR is captured at render time, not at mutation execution time. If a background refetch updates the Zenko CR between render and the chain reaching this step, the idempotency check uses stale data and a duplicate certificate entry could be appended. The window is small in practice (chain fires quickly after mount), but for full correctness the fresh state could be read inside the mutation via queryClient.fetchQuery(getZenkoCRQuery()).

— Claude Code

@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • Exit button is only disabled during the configurator stream (setup.isLoading), not during the other wizard-run chain links. The user can navigate away mid-chain, leaving the system partially provisioned with no easy recovery path for non-idempotent steps.
    - Derive an isChainRunning flag from the chain steps and use it for both the Exit disabled prop and the Continue isLoading prop.
    - The polling loop in useCreateCRRLocationMutation has no abort mechanism — it runs all 120 iterations if the component unmounts mid-poll.
    - Thread an AbortSignal through and check signal.aborted inside the loop.
    - useImportDestinationCertificateMutation captures zenkoCR at render time in its mutationFn closure, so the idempotency check can use stale data if a background refetch lands between render and execution.
    - Re-fetch inside the mutation via queryClient.fetchQuery.

    Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from 0bda58b to 3552317 Compare July 23, 2026 12:18
configs.push({ id: 'create-location', label: 'Create Location', mutation: createLocation });
resolvers['create-location'] = (prev) => {
const result = configuratorResult(prev);
return result ? buildCRRLocation(locationName, result) : undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The create-location and create-replication-rule resolvers silently return undefined when configuratorResult(prev) is missing. Since the chain runs sequentially and should stop on failure, this path should be unreachable — but if it is reached (e.g. a bug in useChainedMutations), the downstream mutation receives undefined and fails with a confusing runtime error rather than a descriptive one.

Consider throwing explicitly so the failure is diagnosable. Same applies to the create-replication-rule resolver at line 190.

— Claude Code

<Button type="button" variant="outline" label="Cancel" onClick={setup.cancel} />
)}
<Button type="button" variant="outline" label="Exit" disabled={isRunning} onClick={() => prev(props)} />
{setup.isLoading && <Button type="button" variant="outline" label="Cancel" onClick={setup.cancel} />}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

During non-configurator long-running steps (e.g. create-location polling up to 60s), isRunning is true so Exit is disabled, but Cancel only renders when setup.isLoading. The user has no way to interrupt a 60-second reconciliation wait. If this is intentional (to prevent partial provisioning on exit), worth a brief comment; otherwise consider extending Cancel to cover the full chain run.

— Claude Code

@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown

LGTM — well-structured chain orchestration with clean state merging and good unit test coverage on the pure-logic layers (steps.ts, crrLocation.ts, replicationConfiguration.ts).

Two minor observations posted inline:

- create-location / create-replication-rule resolvers silently return undefined when the configurator result is missing — consider an explicit throw for easier debugging
- Same pattern at line 190
- Exit is disabled during the full chain run but Cancel only covers the configurator stream step, leaving a gap during long-running wizard steps like create-location polling

Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from 3552317 to 8ba6f3e Compare July 23, 2026 12:38
'create-source-bucket': ['create-source-bucket', 'create-source-bucket-versioning'],
'create-location': ['create-location'],
'create-replication-rule': ['create-replication-rule'],
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If assume-source-role fails (STS unreachable, wrong ARN, propagation delay), the chain stops but no row shows an error — assume-source-role has no entry in WIZARD_ROW_LINKS, so the failure is invisible. The user sees the remaining rows stuck on "Pending..." with no indication of what went wrong and no Retry button.

Adding it to the create-source-bucket entry would surface the error and let Retry re-run the failed link:

Suggested change
};
const WIZARD_ROW_LINKS: Partial<Record<StepId, string[]>> = {
'import-destination-certificate': ['import-destination-certificate'],
'create-source-account': ['create-source-account'],
'create-source-bucket': ['assume-source-role', 'create-source-bucket', 'create-source-bucket-versioning'],
'create-location': ['create-location'],
'create-replication-rule': ['create-replication-rule'],
};

— Claude Code

@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • Hidden assume-source-role failure leaves the wizard stalled with no visible error — the step has no row in WIZARD_ROW_LINKS, so if it fails (STS unavailable, wrong ARN), all subsequent rows stay "Pending..." with no error message or Retry button.
    • Include 'assume-source-role' in the create-source-bucket entry of WIZARD_ROW_LINKS so the error surfaces on the bucket row and Retry re-runs the failed link.

Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from 8ba6f3e to a965620 Compare July 23, 2026 12:54
return useMutation({
mutationFn: async (location: LocationV1) => {
const referenceVersion = await runningConfigurationVersion();
await createLocation.mutateAsync(location);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Retry after a polling timeout will call createLocation.mutateAsync(location) again, attempting to re-create a location that already exists. Unlike the certificate import (which checks isCertificateAlreadyImported first), this mutation has no idempotency guard.

If the overlay is slow to reconcile and the 60s timeout fires, the location was already created successfully — the user just needs to re-poll, not re-create. On retry the management API will likely reject the duplicate, surfacing a confusing "already exists" error instead of resuming the wait.

Consider either catching the "already exists" response and continuing to poll, or splitting the logic so retry only re-polls when the location already exists.

— Claude Code

@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • useCreateCRRLocationMutation is not retry-safe after a polling timeout: the location was already created, so retrying will attempt to re-create it and likely fail with a confusing error instead of resuming the reconciliation poll.
    • Guard against the already-created case (catch + continue polling, or check existence first), similar to how useImportDestinationCertificateMutation handles idempotency.

Review by Claude Code

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.21348% with 37 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.79%. Comparing base (525e517) to head (a965620).
⚠️ Report is 66 commits behind head on development/4.

Files with missing lines Patch % Lines
...Wizard/steps/ApplyActionsStep/ApplyActionsStep.tsx 72.94% 23 Missing ⚠️
...RSetupWizard/hooks/useCreateCRRLocationMutation.ts 57.14% 12 Missing ⚠️
...RRSetupWizard/hooks/useAssumeSourceRoleMutation.ts 83.33% 1 Missing ⚠️
...d/hooks/useImportDestinationCertificateMutation.ts 92.85% 1 Missing ⚠️
Additional details and impacted files
@@                Coverage Diff                @@
##           development/4    #1248      +/-   ##
=================================================
+ Coverage          62.85%   63.79%   +0.94%     
=================================================
  Files                234      256      +22     
  Lines               8948     9454     +506     
  Branches            2435     2559     +124     
=================================================
+ Hits                5624     6031     +407     
- Misses              3314     3412      +98     
- Partials              10       11       +1     

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

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from a965620 to 2605298 Compare July 23, 2026 13:06
Comment thread src/react/locations/CRRSetupWizard/hooks/useCreateCRRLocationMutation.ts Outdated
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
  • isAlreadyExists regex in useCreateCRRLocationMutation.ts is overly broad — /already exist|exists/i matches any error containing "exists", not just "already exists", because regex | has the lowest precedence. Could silently swallow a real creation error on retry.
    - Use /already exists?/i to match only the intended "already exist" / "already exists" variants.

    Review by Claude Code

@hervedombya
hervedombya force-pushed the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch from 2605298 to 454b460 Compare July 23, 2026 13:13
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown

LGTM

Reviewed the chain assembly, variable resolvers, step-view merging logic, and all new hooks. The data flow from the configurator SetupResult into create-location / create-replication-rule is correct — the configuratorResult resolver reads from PreviousResults after the configurator chain link completes, so both the location details and the composite Role ARN are derived from the actual stream output. The folded source-bucket row (assume-role + create + versioning) correctly surfaces errors from any of the three links and retries the failed one. The reconcile-polling in useCreateCRRLocationMutation handles the already-exists / unmount / timeout edges cleanly.

Review by Claude Code

@hervedombya

Copy link
Copy Markdown
Contributor Author

/approve

@bert-e

bert-e commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

I have successfully merged the changeset of this pull request
into targetted development branches:

  • ✔️ development/4

The following branches have NOT changed:

  • development/1.4
  • development/1.5
  • development/1.6
  • development/2.0
  • development/2.1
  • development/2.2
  • development/3.0
  • development/3.1
  • development/3.2
  • development/4.0
  • development/4.1
  • development/4.2

Please check the status of the associated issue ARTESCA-16787.

Goodbye hervedombya.

The following options are set: approve

@bert-e
bert-e merged commit 454b460 into development/4 Jul 23, 2026
10 checks passed
@bert-e
bert-e deleted the feature/ARTESCA-16787-crr-apply-actions-orchestrator branch July 23, 2026 13:32
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