[Bulk workspace edits] Add Copy Policy Settings select-workspaces page (step 1 of 3)#90079
Conversation
Adds title, selectWorkspaces, whichWorkspaces, searchPlaceholder, and selectAll keys for the new "Copy settings" RHP flow's first step, across all ten language files.
Adds a new "Copy settings" item next to "Duplicate Workspace" in the admin three-dot menu on WorkspacesListPage. Uses the Gear icon and navigates to ROUTES.POLICY_COPY_SETTINGS, the first step of the new RHP flow. Visible only for admins (existing isAdmin guard).
First RHP step of the bulk Copy Policy Settings flow. Renders a SelectionList with MultiSelectListItem of policies the current user is admin of, excluding: - the source policy - personal policies - policies where the user is not admin - when the source is Corporate, non-Corporate targets (Release 1 restriction; Issue 7 lifts this with an upgrade step) Layout follows the design doc: - select-all Checkbox above the list (indeterminate when partial) - SearchBar appears only when there are more than 12 eligible workspaces - "Next" persists targetPolicyIDs through setCopyPolicySettingsData and routes to POLICY_COPY_SETTINGS_SELECT_FEATURES; disabled until at least one workspace is selected Adds CONST.SENTRY_LABEL.WORKSPACE.COPY_SETTINGS_SELECT_WORKSPACES_SELECT_ALL for the select-all pressable.
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx ts-node ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
Per review on PR Expensify#90079: use the Plus icon for "Duplicate workspace" and the Copy (duplicate) icon for "Copy settings", to better match each item's intent.
Per review on PR Expensify#90079: MultiSelectListItem renders its checkbox on the right side, so move the select-all Checkbox to the right of the header row (label on the left, checkbox on the right) so they line up with the per-row checkboxes below.
Drops the separate SearchBar component above the list in favour of SelectionList's built-in textInputOptions, which matches the search field used in the New Chat / start chat flow at /new/chat. The built-in input owns its own margin/padding so the design is consistent across the app. The "more than 12 eligible workspaces" gate is preserved by toggling shouldShowTextInput.
The built-in select-all header always rendered its checkbox on the left, which misaligned with lists whose per-row checkboxes use selectionButtonPosition=RIGHT (e.g. MultiSelectListItem). Adds a selectionButtonPosition prop to ListHeader (default LEFT for existing callers) and threads it from BaseSelectionList. When set to RIGHT, the label/checkbox order is swapped and the row uses justifyContentBetween so the checkbox sits flush right, matching the list rows below.
Three review fixes on the Select Workspaces page: - Use SelectionList's built-in onSelectAll with selectionButtonPosition=RIGHT, so the search input sits above the select-all header and the select-all checkbox lines up with the per-row checkboxes (both on the right). Drops the custom select-all header that used to sit above the SelectionList. - Render the workspace avatar at AVATAR_SIZE.DEFAULT via a leftElement on each list item. The previous icons-based path inside MultiSelectListItem hardcodes AVATAR_SIZE.SMALLER, which looked too small. Falls back to getDefaultWorkspaceAvatar when avatarURL is missing. - Track selectedTargetIDs in local useState instead of writing to COPY_POLICY_SETTINGS on every toggle. The Onyx key is now updated only when the user confirms "Next", so closing the RHP mid-flow naturally drops the in-progress selection without needing a manual clear-on-mount.
The page now uses SelectionList's built-in select-all header, which already reuses workspace.people.selectAll for its label and SENTRY_LABEL.SELECTION_LIST.LIST_HEADER_SELECT_ALL for its pressable. The custom workspace.copySettings.selectAll key (10 language files) and SENTRY_LABEL.WORKSPACE.COPY_SETTINGS_SELECT_WORKSPACES_SELECT_ALL are no longer referenced — remove them.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f2d85f6fb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Consolidates the two translation namespaces for the bulk Copy Policy Settings flow into a single workspace.copyPolicySettings block (next to the existing error key). Renames whichWorkspaces to description and updates the copy to: "Choose the workspaces you want to copy settings to, then select the settings you'd like to copy." Localizes the new description across all ten language files.
Updates the three-dot menu entry and the Select Workspaces page to read from workspace.copyPolicySettings.* now that the translation block was merged. Also swaps the page subheader to the renamed description key.
When search was active, toggleAll decided whether to clear or select against eligiblePolicies (the full unfiltered set) while SelectionList's header checkbox state was derived from filteredPolicies (the visible rows). That mismatch let the header appear checked yet pressing it would select additional hidden workspaces — copying settings to unintended targets. Scope select-all to the visible filtered rows: when every visible row is already selected, deselect just those; otherwise add the visible rows to the existing selection, preserving selections on rows hidden by the active search. Resolves Expensify#90079 (comment)
Could you add the link to the Slack message that approved the translation? |
|
Typecheck and Jest seem to be repeatedly failing |
@dmkt9 Please check https://expensify.slack.com/archives/C01GTK53T8Q/p1778753210690539 |
Ah, thank you. |
|
@dmkt9 any chance you can review this today you think? |
Yes. I will review this today. |
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppandroid.hybrid.mp4Android: mWeb Chromeandroid.chrome.mp4iOS: HybridAppios.hybrid.mp4iOS: mWeb Safariios.safari.mp4MacOS: Chrome / Safarimac.safari.mp4 |
| threeDotsMenuItems.push({ | ||
| icon: icons.Copy, | ||
| text: translate('workspace.copyPolicySettings.title'), | ||
| onSelected: () => (item.policyID ? Navigation.navigate(ROUTES.POLICY_COPY_SETTINGS.getRoute(item.policyID)) : undefined), | ||
| }); |
There was a problem hiding this comment.
This item will be rendered when isAdmin == true. If there is no other eligible workspace, this item will still display and open an empty list modal.
I am not sure whether this is a bug, since in the "Explanation of changes", we only refer to "isAdmin" as a guard:
- Three-dot menu entry in
src/pages/workspace/WorkspacesListPage.tsx— a new "Copy settings" item with the Gear icon next to "Duplicate Workspace", visible only for admins (existingisAdminguard). Navigates toROUTES.POLICY_COPY_SETTINGS.
There was a problem hiding this comment.
This makes sense; I think we should hide the item in that case. Will push a fix for it. cc @francoisl for visibility.
Precomputes eligible target policy IDs via useMemo so the per-item menu render does a constant-time check instead of iterating all policies. Hides the menu entry entirely when there are no valid targets for the source workspace.
|
🚧 @francoisl has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚀 Deployed to staging by https://github.com/francoisl in version: 9.3.79-1 🚀
Bundle Size Analysis (Sentry): |
|
Deploy Blocker #91381 was identified to be related to this PR. |
|
Deploy Blocker #91389 was identified to be related to this PR. |
|
🚀 Deployed to production by https://github.com/roryabraham in version: 9.3.79-4 🚀
|
Explanation of Change
Adds the entry point and the first RHP step of the new "Copy settings" flow that's part of the Bulk workspace edits project. This is task 3 of the project — task 1 (scaffold #89959) is merged; task 2 (action layer #89963) is still open and this PR is stacked on top of #89963. Please land #89963 first.
What this PR adds
Three-dot menu entry in
src/pages/workspace/WorkspacesListPage.tsx— a new "Copy settings" item with the Gear icon next to "Duplicate Workspace", visible only for admins (existingisAdminguard). Navigates toROUTES.POLICY_COPY_SETTINGS.Select Workspaces page in
src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectWorkspacesPage.tsx— first step of the RHP flow. Renders aSelectionListwithMultiSelectListItemof policies the current user is admin of, with the eligibility rules from the design doc:CORPORATE, onlyCORPORATEtargets are shown (Release 1 restriction; Issue 7 lifts this in R2 with an upgrade step)Layout:
<Checkbox>above the list (indeterminate when partial)<SearchBar>appears only when there are more than 12 eligible workspacestargetPolicyIDsviasetCopyPolicySettingsData(introduced in [No QA] [Bulk workspace edits] Add CopyPolicySettings action file and tests #89963) and navigates toPOLICY_COPY_SETTINGS_SELECT_FEATURES; disabled until at least one workspace is selectedTranslations — new
workspace.copySettings.{title, selectWorkspaces, whichWorkspaces, searchPlaceholder, selectAll}block in all 10 language files.Sentry label —
CONST.SENTRY_LABEL.WORKSPACE.COPY_SETTINGS_SELECT_WORKSPACES_SELECT_ALLfor the select-all pressable.The page reads its persisted state (
targetPolicyIDs) directly fromONYXKEYS.COPY_POLICY_SETTINGSso re-entry restores the user's previous selection.Fixed Issues
$ #88669
PROPOSAL: N/A
Tests
Setup
Sign in as an admin on at least two non-personal workspaces (a mix of Team/Collect and Corporate/Control if possible).
Test 1 — Three-dot menu entry
Test 2 — Eligibility filtering
Test 3 — Workspace avatars
avatarURL, verify the default workspace avatar (initials/colored placeholder) is rendered.Test 4 — Select-all alignment and behavior
Test 5 — Search
Test 6 — Next / commit selection
copyPolicySettings.targetPolicyIDsnow contains the selected IDs andcopyPolicySettings.sourcePolicyIDis set.Test 7 — Selection cleared on RHP close
Test 8 — Console
Offline tests
The page is a pure read of
ONYXKEYS.COLLECTION.POLICYplus auseStateselection; the only Onyx write happens at "Next" viasetCopyPolicySettingsData. Both should work fully offline:copyPolicySettings.targetPolicyIDsis written to Onyx even while offline.QA Steps
Same as Tests.
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari