[Bulk workspace edits] Add Copy Policy Settings select-features page (step 2 of 3)#90220
Conversation
Ports Auth's Policy::getConnectionCompanyID logic (QBO → config.realmId, NetSuite → top-level accountID, Xero/Intacct/QBD/Certinia → config.credentials.companyID) to TypeScript. Exposes: - getConnectionCompanyID(policy, connectionName) - getAccountingConnectionIdentity(policy) — first valid connection - arePoliciesAccountingCompatible(source, target) — true only when both policies have no accounting connection, or share the same connection AND a matching non-empty companyID - areAllTargetsAccountingCompatible(source, targets) — every-target compatibility check used by the Select Features step Page 2 will use this to disable Categories/Tags/Reports/Taxes whenever any selected target has a mismatched (or missing) connection.
Covers the design-doc scenarios for accounting compatibility: - source connected to nothing, target connected to NetSuite - source connected to QBO, target connected to nothing - source NetSuite Acme vs target NetSuite ExpensivePie - different connection names entirely - both unconnected - same connection + same companyID - missing companyID on either side - QBO realmId equality Plus per-integration getConnectionCompanyID extraction (QBO realmId, NetSuite top-level accountID, credentials.companyID for Sage Intacct, Xero, and QBD).
Adds the strings used by the Select Features step (Page 2 of the Copy Policy Settings flow) to workspace.copyPolicySettings across all ten language files: - selectFeatures — page H2 - whichFeatures — page subheader - accountingDisabledTooltip — tooltip for the Categories/Tags/Reports/Taxes rows when targets are not connected to the same accounting account - workflowsWithoutMembersTitle / workflowsWithoutMembersPrompt — confirm modal shown when the admin selects Workflows but not Members
Second RHP step of the bulk Copy Policy Settings flow. Renders the 13 part rows (overview, members, reports, accounting, categories, tags, taxes, workflows, rules, distanceRates, perDiem, invoices, travel) in a SelectionList with MultiSelectListItem. Behaviors required by the design doc: - Categories/Tags/Reports/Taxes are disabled when any selected target workspace has a mismatched (or missing) accounting connection relative to the source, via areAllTargetsAccountingCompatible. - Selecting "Accounting" force-selects + force-disables those same four parts (syncing a connection implies its coding must come along). - On "Next", if Workflows is selected but Members is not, opens a confirm modal warning that Submission and Payment settings will be copied without members. On confirm (or when there's no conflict) persists `parts` to COPY_POLICY_SETTINGS via setCopyPolicySettingsData and routes to POLICY_COPY_SETTINGS_CONFIRM. - Selection lives in useState so closing the RHP discards it (matches Page 1).
|
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!
|
Replaces the verbose "Which configurations do you want to copy to the other workspaces? Only these settings will be overwritten on the target workspaces." with the shorter, more direct: "Select the settings to overwrite on your existing workspaces." Localizes the new copy across all ten language files.
cspell doesn't recognize "togglable" as a dictionary word. Rename the local variables (togglableParts / togglableSet / togglable comment) to the more conventional "selectable" — the meaning is identical in this context and it passes cspell without dictionary additions.
Refactors the page to inline the computed values (effectiveSelectedFeatures, listItems, isFeatureDisabled, selectableFeatures, toggleAll, onConfirm) that were previously wrapped in useMemo / useCallback. The values are already cheap to compute and the wrappers added noise without measurable benefit. Renames selectedParts -> selectedFeatures / togglePart -> toggleFeature for consistency with the page's UI vocabulary.
When the select-all checkbox sits on the right, the label is followed by the checkbox immediately, so the trailing horizontal padding from `styles.ph3` collides with the checkbox. Switch to `styles.pr3` on the right-positioned variant so only leading padding is applied to the label, keeping spacing symmetric with the left-positioned layout.
Mirrors the WorkspaceDuplicateSelectFeaturesForm pattern: each row now
includes a short summary derived from the source workspace ("12
categories", "3 members", "QuickBooks Online", "$, Berlin, …" for
overview, etc.) so the admin can see at a glance what will be copied.
Reuses existing helpers (getMemberAccountIDsForWorkspace,
getReportFieldsByPolicyID, getAllValidConnectedIntegration,
getDistanceRateCustomUnit / getPerDiemCustomUnit, getWorkflowRules /
getWorkspaceRules from the duplicate flow, formatAddressToString).
Filters out pending-delete entries when counting so the numbers reflect
what would actually be copied. Bumps alternateNumberOfSupportedLines
to 2 since some rows (rules, invoices) can produce longer captions.
|
@yuwenmemon All yours! |
yuwenmemon
left a comment
There was a problem hiding this comment.
Francois is on parental leave so I'll be reviewing for this project in his stead
|
🚧 @yuwenmemon 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/yuwenmemon in version: 9.3.83-0 🚀
Bundle Size Analysis (Sentry): |
|
Deploy Blocker #91791 was identified to be related to this PR. |
|
Deploy Blocker #91792 was identified to be related to this PR. |
|
Deploy Blocker #91793 was identified to be related to this PR. |
|
Deploy Blocker #91805 was identified to be related to this PR. |
|
Deploy Blocker #91821 was identified to be related to this PR. |
|
Deploy Blocker #91825 was identified to be related to this PR. |
|
Deploy Blocker #91827 was identified to be related to this PR. |
|
Deploy Blocker #91830 was identified to be related to this PR. |
|
Deploy Blocker #91833 was identified to be related to this PR. |
|
🚀 Deployed to production by https://github.com/mountiny in version: 9.3.83-3 🚀
|
Explanation of Change
Implements the second RHP step of the new Copy Settings flow that's part of the Bulk workspace edits project — task 4 of the project. Earlier tasks: scaffold #89959 (merged), action layer #89963, page 1 #90079. This PR is stacked on top of #90079 and depends on tasks 1 & 2 — please land the upstream PRs first.
What this PR adds
Accounting-compatibility utility in
src/libs/CopyPolicySettingsUtils.ts. PortsPolicy::getConnectionCompanyIDfrom Auth/auth/lib/Policy.cpp to TypeScript so the App can compare external accounts across policies:config.realmIdaccountIDconfig.credentials.companyIDExposes
arePoliciesAccountingCompatible(source, target)— two policies are compatible only when both have no accounting connection, or they share the same connection name AND a matching non-empty companyID.areAllTargetsAccountingCompatible(source, targets)runs that across every target.Unit tests in
tests/unit/CopyPolicySettingsUtilsTest.ts— 15 cases covering the design-doc scenarios:Acme Corpvs target NetSuiteExpensivePie→ INCOMPATIBLEgetConnectionCompanyIDextractionSelect Features page in
src/pages/workspace/copyPolicySettings/CopyPolicySettingsSelectFeaturesPage.tsx. Renders the 13 part rows (overview,members,reports,accounting,categories,tags,taxes,workflows,rules,distanceRates,perDiem,invoices,travel) in aSelectionListwithMultiSelectListItem. Behaviors:categories,tags,reports,taxesare greyed out + uncheckable whenever any selected target has a mismatched (or missing) connection vs. the source.accountingflips those same four parts toisSelected: true, isDisabled: trueso they can't be unchecked (syncing a connection implies its coding must come along).workflowsis selected butmembersis not, opens a confirm modal explaining that Submission and Payment settings will be copied without members. On confirm (or when there's no conflict) persistspartstoONYXKEYS.COPY_POLICY_SETTINGSviasetCopyPolicySettingsDataand routes toPOLICY_COPY_SETTINGS_CONFIRM.Translations — new
workspace.copyPolicySettings.{selectFeatures, whichFeatures, accountingDisabledTooltip, workflowsWithoutMembersTitle, workflowsWithoutMembersPrompt}across all 10 language files.Fixed Issues
$ #88671
PROPOSAL: N/A
Tests
Setup
Sign in as an admin on at least three Corporate (Control) workspaces. Use a workspace with multiple members, categories, tags, workflows, and optionally an accounting connection as the source — this ensures all applicable feature rows appear. Have at least one pair of workspaces sharing the same accounting connection (e.g. both connected to the same NetSuite account) and at least one workspace connected to a different accounting account (or none).
Test 1 — Page renders only applicable feature rows
Test 2 — Accounting-compatibility disables Categories/Tags/Reports/Taxes/Accounting
A, pick targets that all share the same NetSuite accountA. Reach Select Features.Categories,Tags,Reports,Taxes,Accountingare toggleable (not greyed out).Acme Corpvs target NetSuiteExpensivePie→ disabledTest 3 — Force-enable on Accounting
Accountingon.Categories,Tags,Reports,Taxesbecome checked and locked (cannot be toggled off).Accountingoff.Test 4 — Workflows-without-members warning
Workflowsbut leaveMembersunchecked.copyPolicySettings.partsin Onyx contains["workflows", ...].WorkflowsandMembersselected → verify no modal appears and the flow advances immediately.Membersrow won't be visible. SelectWorkflowsand click Next → verify the modal does NOT appear (there are no members to copy, so the warning is irrelevant).Test 5 — Select-all + Next
copyPolicySettings.partsis written to Onyx and the flow routes to the Confirm step.Test 6 — Selection cleared on RHP close
Test 7 — Console
Offline tests
The page only reads from Onyx (
COLLECTION.POLICY+COPY_POLICY_SETTINGS) and writes viasetCopyPolicySettingsDataon Next. Both should work offline:partsis written to Onyx.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