Skip to content

feat(NES-1637): add "Copy to collection" action to template cards in Collections#9237

Open
csiyang wants to merge 14 commits into
mainfrom
siyangcao/nes-1637-add-copy-to-collection-action-to-template-cards-in
Open

feat(NES-1637): add "Copy to collection" action to template cards in Collections#9237
csiyang wants to merge 14 commits into
mainfrom
siyangcao/nes-1637-add-copy-to-collection-action-to-template-cards-in

Conversation

@csiyang
Copy link
Copy Markdown
Contributor

@csiyang csiyang commented May 20, 2026

Summary

Adds a Copy to collection... action to template journey cards rendered inside a Collection on the gallery page. Submitting the dialog runs journeyDuplicate → optional useJourneyAiTranslateSubscriptiontemplateGalleryPageAssignJourney, with a single dialog-internal spinner and a Done state on success. Gated by the teamTemplateCollection LaunchDarkly flag and an InCollectionContext so the action only appears inside Collections.

Linear: NES-1637

What's in the PR

  • U1 (`318ce894b`) — `InCollectionContext` + provider wired around the Collection-grid `DraggableJourneysGrid` only; All-Templates grid untouched.
  • U2 (`4875af542`) — `CopyToCollectionDialog`: Formik form with collection dropdown (skip / loading / empty states), optional translation toggle + language picker, error / done states with `aria-live` regions, focus on dropdown, no `enableReinitialize`. 13 spec scenarios.
  • U3 (`a3876fc92`) — `CopyToCollectionMenuItem`: callback-driven orchestration (`handleSubmit` + `runAssign` + subscription `onComplete` → `runAssign`), `mountedRef` / `guardedClose`, single-flight + null-team guards, `refetchQueries({ include: ['GetAdminJourneys'] })` after assign success / failure and translation failure. 12 spec scenarios covering the full error matrix.
  • U4 (`2f628b2b5`) — Wired the menu item into `DefaultMenu` directly after `CopyToTeamMenuItem`, gated by `teamTemplateCollection === true && useInCollection() === true`. 4 new combination tests.
  • Plan (`02214bed4`) — `docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md`.

Test plan

Automated (passing locally, 67/67 across all relevant specs):

  • `InCollectionContext.spec.tsx` (3/3)
  • `CopyToCollectionDialog.spec.tsx` (13/13)
  • `CopyToCollectionMenuItem.spec.tsx` (12/12)
  • `DefaultMenu.spec.tsx` (21/21 — 17 pre-existing + 4 new combinations)
  • `TemplateGalleryPageList.spec.tsx` regression (6/6)
  • `tsc --noEmit -p apps/journeys-admin/tsconfig.json` clean

Manual QA (with `teamTemplateCollection` flag ON):

  • Open a Collection → journey card More menu shows `Copy to collection...` directly after `Copy to ...`
  • Same card outside a Collection (Active / Archived / Trashed / All Templates) does NOT show the new item
  • Submit no-translation → new card appears in target Collection card without manual refresh
  • Submit with translation → spinner stays through translation; success state on completion
  • Source collection is in the dropdown; copying into it works
  • Zero-collections team → disabled "No collections available" + disabled submit
  • Duplicate failure → "Failed to copy the journey. Please try again." inline
  • Translation failure → "An error occurred while translating." inline; copy appears in All Templates
  • Assign failure → assign-fail copy inline; copy appears in All Templates
  • DnD on the gallery page pauses while dialog is open
  • Verify the refetch observable assumption (deferred per plan): the `template: true` `GetAdminJourneys` is the active observable hit by the refetch
  • Verify drag-from-All-Templates recovery path (deferred per plan): if not supported, apply R10 contingency copy

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • "Copy to collection…" menu item on journey cards (appears in collection context when the feature flag is enabled).
    • Copy to Collection dialog: choose destination collection, optional translation language, and clear loading/error/done flows.
  • Tests

    • Extensive end-to-end tests for menu, dialog, translation subscription flows, single-submit behavior, loading/error/done states, and edge cases.
  • Documentation

    • Added design/implementation plan and best-practices guidance for subscription-chained dialog orchestration.

Review Change Stack

@linear
Copy link
Copy Markdown

linear Bot commented May 20, 2026

NES-1637

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2974aa5a-906c-4cfb-93c6-f1b4168e680b

📥 Commits

Reviewing files that changed from the base of the PR and between 3e01283 and 59ba63f.

📒 Files selected for processing (9)
  • apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.spec.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/index.ts
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.spec.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.tsx
  • docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md
  • docs/solutions/best-practices/subscription-bridged-dialog-orchestration-nes1637.md
  • docs/solutions/best-practices/template-gallery-page-collections-patterns-nes1539.md
✅ Files skipped from review due to trivial changes (3)
  • docs/solutions/best-practices/template-gallery-page-collections-patterns-nes1539.md
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/index.ts
  • docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md

Walkthrough

Adds a "Copy to collection…" menu item gated by the teamTemplateCollection flag and template-mode, a Formik-driven CopyToCollectionDialog (collection picker + optional language), a CopyToCollectionMenuItem orchestrator (duplicate → optional translate → assign) with mount-safety and refetch behavior, comprehensive tests across paths, and planning / best-practices docs.

Changes

Copy to collection feature

Layer / File(s) Summary
CopyToCollectionDialog component
apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.tsx, apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.spec.tsx, apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/index.ts
Dialog component with Formik form (collection dropdown, translation toggle, language autocomplete), Yup validation, and terminal states (error/done). Submits collectionId/title and optional language; blocks backdrop/escape when loading/translating. Tests cover rendering, submission (with/without translation), loading/empty states, double-submit guard, accessibility announcements, and loading-state suppression.
CopyToCollectionMenuItem orchestration
apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.tsx, apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.spec.tsx, apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/index.ts
Stateful orchestrator implementing duplicate → optional translation (subscription) → assign pipeline. Manages loading/error/done state, single-flight behavior, mount/unmount safety via refs, and refetches GetAdminJourneys after assignment or on translation error. Tests cover happy paths, translation flow, single-flight, all error cases, Done behavior, and unmount-safety.
DefaultMenu integration
apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx, apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx
Wires CopyToCollectionMenuItem into DefaultMenu gated by teamTemplateCollection flag and template-mode. Tests wrap DefaultMenu with FlagsProvider/ThemeProvider and assert presence/absence of the menu item across flag/mode combinations.
Feature plan & docs
docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md, docs/solutions/best-practices/subscription-bridged-dialog-orchestration-nes1637.md, docs/solutions/best-practices/template-gallery-page-collections-patterns-nes1539.md
Planning doc and two best-practices pages describing the feature plan, subscription-bridged orchestration pattern (mountedRef gating, safe loading sync), lifecycle/unmount cleanup for cross-component locks, and updates to template-gallery patterns.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • mikeallisonJS
  • edmonday
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a 'Copy to collection' action to template cards in Collections, which is the primary feature introduced across multiple new components and files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch siyangcao/nes-1637-add-copy-to-collection-action-to-template-cards-in

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Warnings
⚠️ ❗ Big PR (2462 changes)

(change count - 2462): Pull Request size seems relatively large. If Pull Request contains multiple changes, split each into separate PR will helps faster, easier review.

Generated by 🚫 dangerJS against 59ba63f

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 20, 2026

View your CI Pipeline Execution ↗ for commit 4419323

Command Status Duration Result
nx run journeys-admin-e2e:e2e ✅ Succeeded 27s View ↗
nx run-many --target=vercel-alias --projects=jo... ✅ Succeeded 1s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 13s View ↗
nx run-many --target=deploy --projects=journeys... ✅ Succeeded 29s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-21 03:30:49 UTC

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 20, 2026

View your CI Pipeline Execution ↗ for commit 02214be

Command Status Duration Result
nx run journeys-admin-e2e:e2e ✅ Succeeded 25s View ↗
nx run-many --target=vercel-alias --projects=jo... ✅ Succeeded 1s View ↗
nx run-many --target=upload-sourcemaps --projec... ✅ Succeeded 13s View ↗
nx run-many --target=deploy --projects=journeys... ✅ Succeeded 1m 15s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-20 23:42:50 UTC

@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 20, 2026 23:37 Inactive
@csiyang csiyang self-assigned this May 20, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

The latest updates on your projects.

Name Status Preview Updated (UTC)
journeys-admin ✅ Ready journeys-admin preview Thu May 21 15:27:44 NZST 2026

@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 00:39 Inactive
…item

Removing the gate in 2f6b4dd was out of scope and regressed PR #8510's
deliberate local-template design (Copy-to-team is hidden on the active
team's own templates by intent). Restore the gate; keep the Copy-to-collection
visibility change since that flow is same-team only and doesn't conflict.
@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 01:53 Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx`:
- Around line 1094-1194: Extend the existing test suite around
renderWithFlagAndTemplate/DefaultMenu to cover the collection-context gate by
adding test cases for both "in collection" and "not in collection" states (in
addition to the current flag × template combinations); simulate a journey with
and without a team (or explicitly set journey.team to the active team id vs
undefined) so the isLocalTemplate/collection context branch is exercised, and
assert presence/absence of the JourneysAdminMenuItemCopyToCollection test id and
the 'Copy to ...' menuitem accordingly to ensure visibility is tested for all
three dimensions: flag, template, and collection context.

In
`@apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx`:
- Around line 306-314: The condition rendering CopyToCollectionMenuItem is
missing the collection-context predicate: currently it only checks
teamTemplateCollection and template, allowing the menu to appear outside
Collections; update the JSX conditional that renders CopyToCollectionMenuItem to
include the collection-context boolean (the same prop or state used elsewhere to
indicate "inside collection" — e.g., collectionContext, isInCollectionView, or
the existing collection prop) so the component only renders when
teamTemplateCollection === true && template === true && <collection-context> ===
true, leaving the props (id, journey, handleCloseMenu, handleKeepMounted,
setHasOpenDialog) unchanged.

In
`@apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.spec.tsx`:
- Around line 317-319: The test helper declares resolveDuplicate as an empty
arrow function which violates the no-empty-function rule; replace its
initializer with a non-empty stub (for example change the default from () => {}
to () => undefined or to jest.fn()) and do the same for the other similar test
helpers referenced in this file (the other empty arrow initializers around the
CopyToCollectionMenuItem.spec.tsx tests). Keep the type signature ({ data?: {
journeyDuplicate?: { id: string } | null } }) => void and only change the
initializer to a benign no-op or jest.fn() so ESLint no-longer flags it.

In `@docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md`:
- Around line 258-290: The fenced code block containing the pseudocode for
handleSubmit, runAssign and the onComplete/onError flows is missing a language
tag (MD040); update the opening fence to include a language identifier (for
example "text" or "txt") so the block becomes ```text and leave the closing
fence as is; this will satisfy markdownlint without changing any of the
pseudocode references such as handleSubmit, runAssign,
useJourneyAiTranslateSubscription, onComplete, onError or guardedClose.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 47996ec1-ea5c-4cf1-a2ce-c63019cb256e

📥 Commits

Reviewing files that changed from the base of the PR and between 3149b14 and b8942bb.

📒 Files selected for processing (9)
  • apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.spec.tsx
  • apps/journeys-admin/src/components/JourneyList/JourneyCard/JourneyCardMenu/DefaultMenu/DefaultMenu.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.spec.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/CopyToCollectionDialog.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionDialog/index.ts
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.spec.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/CopyToCollectionMenuItem.tsx
  • apps/journeys-admin/src/components/TemplateGalleryPageList/CopyToCollectionMenuItem/index.ts
  • docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md

Comment thread docs/plans/2026-05-21-001-feat-nes-1637-copy-to-collection-action-plan.md Outdated
@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 02:16 Inactive
@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 02:23 Inactive
Production fixes:
- Narrow Formik error type on languageSelect helperText via resolveLanguageError helper (was: unsafe `as { id?: string }` cast that swallowed top-level Yup .required() strings)
- Memoise Yup schema (was reconstructed every render, forcing Formik re-validation)
- Drop .nullable() so inferred Yup type matches FormValues
- Remove unused translationProgress prop (dialog declared it, menu item never wired it)
- Export JourneyLanguage from dialog and import in menu item (dedupe)
- Inline handleDoneClick single-use wrapper
- Fallback success copy to "Copied to collection." when title is missing
- Drop redundant `as string` cast on collectionSelect helperText

Lifecycle safety:
- Gate runAssign on mountedRef BEFORE the assign mutation fires (was guarding setState only — mutation could fire on unmounted component)
- Release DnD lock and null pipeline refs on unmount (was leaking the parent's GalleryDialogLockContext lock if user navigated away mid-dialog)

Tests:
- Add ThemeProvider to render helpers in 3 spec files (per AGENTS.md)
- Add unmount-releases-DnD-lock test

Plan doc:
- Tag fenced code block with `text` lang
@github-actions github-actions Bot requested a deployment to Preview - journeys-admin May 21, 2026 02:48 Pending
@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 02:51 Inactive
@csiyang csiyang requested a review from edmonday May 21, 2026 03:03
Captures the three interlocking design decisions from NES-1637's
CopyToCollectionMenuItem implementation as a reusable pattern under
docs/solutions/best-practices/:

- Callback-driven split: useSubscription is not awaitable; orchestrate
  via two handlers (handleSubmit + runAssign) bridged by onComplete
- Gate the network call, not just setState: mountedRef pre-check before
  mutation await prevents server-side orphans on unmount
- Cross-component lock cleanup: lifecycle useEffect releases the DnD
  signal idempotently on unmount, distinct from guardedClose's
  normal-close path

Also adds a forward reference from NES-1539 Pattern 3 (the foundational
mountedRef + guardedClose pattern) to this extended variant.
@github-actions github-actions Bot requested a deployment to Preview - journeys-admin May 21, 2026 03:22 Pending
@github-actions github-actions Bot temporarily deployed to Preview - journeys-admin May 21, 2026 03:26 Inactive
Copy link
Copy Markdown
Contributor

@edmonday edmonday left a comment

Choose a reason for hiding this comment

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

Review summary

Concern: 1 — translation-failure copy doesn't tell the user the duplicate already exists in All Templates (the duplicate has been created and refetched by this point; only the translation/assign chain failed). Inconsistent with the assign-failure copy right next to it. User may retry and create a second duplicate.

Nit: 1 — minor cleanup hygiene on mount.

Otherwise solid: refs pattern handles subscription/unmount races cleanly, StrictMode-aware mountedRef, single-flight guard, aria-live status region, and 29 specs covering the full error matrix. CI green.

@edmonday edmonday self-requested a review May 21, 2026 22:06
Copy link
Copy Markdown
Contributor

@edmonday edmonday left a comment

Choose a reason for hiding this comment

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

LGTM

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