Skip to content

Group bulk export options by integration for multi-workspace selections - #96728

Draft
MelvinBot wants to merge 6 commits into
mainfrom
claude-bulkExportMultiWorkspaceIntegration
Draft

Group bulk export options by integration for multi-workspace selections#96728
MelvinBot wants to merge 6 commits into
mainfrom
claude-bulkExportMultiWorkspaceIntegration

Conversation

@MelvinBot

Copy link
Copy Markdown
Contributor

Explanation of Change

Bulk integration export options (export to accounting + "Mark as exported") only appeared when every selected report belonged to a single workspace, because getExportOptions() in useSearchBulkActions derived a single connectedIntegration from selectedPolicyIDs.length === 1. When a selection spanned multiple workspaces (e.g. one QBO report + one Sage Intacct report), policy was undefined, so no integration options were shown at all — even though each report was individually exportable. This matched neither Classic Expensify nor user expectations.

This PR groups the selected reports by their connected accounting integration and builds one "Export to " option and one "Mark as exported" option per integration, each scoped to just the reports for that integration. Export options are listed first, followed by the mark-as-exported options. A single-workspace selection collapses to one group, so the previous behavior is unchanged.

🤖 This is an automated draft PR created by MelvinBot at the request of ahmedGaber93, implementing the plan in this comment.

AI checks run locally (by MelvinBot):

  • npm test tests/unit/hooks/useSearchBulkActionsExportTest.ts — ✅ pass (3 tests, includes a new multi-workspace case)
  • npm run typecheck-tsgo — ✅ pass
  • npm run lint-changed — ✅ pass
  • npm run fmt (oxfmt) — ✅ applied, only the two changed files reformatted
  • npm run react-compiler-compliance-check check src/hooks/useSearchBulkActions.ts — no new divergence: the reported ref-access errors are pre-existing (identical error set on origin/main; the file already carries a react-hooks/refs eslint-disable)

Fixed Issues

$ #95820
PROPOSAL: #95820 (comment)

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review".
Suggested steps:

  1. Connect two workspaces to two different accounting integrations (e.g. one to QuickBooks Online, one to Sage Intacct).
  2. Have exportable reports on each workspace.
  3. Go to Reports, select reports spanning both workspaces.
  4. Open the bulk Export dropdown.
  5. Verify it shows one export option per integration (e.g. "QuickBooks Online", "Sage Intacct") followed by a "Mark as exported" option per integration (each with its integration icon).
  6. Verify choosing an integration's export/mark-as-exported action only affects that integration's reports.
  7. Verify a single-workspace selection still behaves as before.
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

// TODO: The human co-author must fill out the QA tests before marking this PR as "ready for review". These must be filled out, or the issue title must include "[No QA]."

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team July 22, 2026 11:03
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from an upstream branch since I don't have push access to your fork.

To take ownership of this branch and be able to push updates, run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-bulkExportMultiWorkspaceIntegration
git checkout -b claude-bulkExportMultiWorkspaceIntegration upstream/claude-bulkExportMultiWorkspaceIntegration
git push -u origin claude-bulkExportMultiWorkspaceIntegration

Then you can close this PR and open a new one from your fork.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 I checked all the checks on this PR. No code fix is needed — every real CI check passes.

The only failing check is PR Author Checklist / checklist, and it fails for a non-code reason:

You completed 0 out of 38 checklist items with 38 unfinished items
##[error]PR Author Checklist is not completely filled out.

This is expected for a draft PR: the Tests/QA sections still have // TODO: The human co-author must fill out... placeholders, and none of the 38 author-checklist boxes are ticked. That check only turns green once the human co-author fills out the testing steps and checks the boxes before marking the PR Ready for review — it's not something I should (or can) resolve by pushing code.

Everything else is green: typecheck, all 8 test jobs, ESLint, Bun tests, Storybook tests, React Compiler Compliance, Oxfmt, spellcheck, all build jobs, and the perf tests (baseline-perf-tests, branch-perf-tests, validate-perf-tests) all pass.

Next step: ahmedGaber93 (co-author), please fill in the Tests and QA sections and complete the author checklist, then mark the PR ready for review — the checklist check will pass at that point.

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/components/ConfirmModal.tsx 100.00% <ø> (ø)
src/components/ConfirmContent.tsx 88.70% <66.66%> (-1.13%) ⬇️
src/hooks/useSearchBulkActions.ts 57.59% <98.18%> (+3.47%) ⬆️
... and 25 files with indirect coverage changes

@ahmedGaber93

Copy link
Copy Markdown
Contributor

@MelvinBot Update the bulk action sorting to group each integration's actions together:

  • Export to Integration1
  • Mark as exported (Integration1)
  • Export to Integration2
  • Mark as exported (Integration2)

instead of:

  • Export to Integration1
  • Export to Integration2
  • Mark as exported (Integration1)
  • Mark as exported (Integration2)
Screenshot 2026-07-23 at 11 16 39 AM

…ether

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — pushed cdbe299.

The two loops in getExportOptions() (all "Export to X" first, then all "Mark as exported") are now merged into a single pass over reportsByIntegration, so each integration's actions are grouped:

  • Export to Integration1
  • Mark as exported (Integration1)
  • Export to Integration2
  • Mark as exported (Integration2)

See src/hooks/useSearchBulkActions.ts:1572-1606. The per-integration canExport/canMarkAsExported checks and report scoping are unchanged, and a single-workspace selection still collapses to one group.

Also extended the multi-workspace unit test to assert the new grouped ordering.

AI checks run locally:

  • npm test tests/unit/hooks/useSearchBulkActionsExportTest.ts — ✅ pass (3 tests, including the updated ordering assertion)
  • typecheck / lint-changed / oxfmt⚠️ could not run in this sandbox (the local tsc/tsgo and formatter binaries aren't resolvable here). This change is type/lint/format-neutral: no new imports, types, or signatures — it only reorders two existing loops into one using the same object literals — so CI's checks on this push will be the source of truth for those gates.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I analyzed the failing checks — there's no code failure to fix here.

The only failing check is PR Author Checklist / checklist, and it's not a code/CI test. It fails because the manual checklist hasn't been filled out:

You completed 0 out of 38 checklist items with 38 unfinished items
##[error]PR Author Checklist is not completely filled out.

Since this is an automated draft PR, the checklist boxes and the // TODO: Tests / QA Steps sections still need to be completed by the human co-author (ahmedGaber93) before this check will pass. That's expected for a draft and can't be resolved with a commit.

Every functional and AI check is green: typecheck, ESLint, Oxfmt, all 8 Jest test shards, Bun tests, Storybook tests, React Compiler Compliance, validate, and all builds. The two *-perf-tests are still running — they're informational performance benchmarks, not merge-gating.

I didn't push any change because there's nothing broken in the code.

@ahmedGaber93

ahmedGaber93 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Please update with the following changes.

1) Add a partial-export confirmation modal

When a selection spans multiple integrations, clicking "Export to " only exports the subset of reports connected to that integration. Before exporting, if it's a partial export (the integration's report count is less than the total number of selected reports), show a confirmation modal:

  • Title: "Export {exportableCount}/{selectedCount} reports to {Integration}?"
  • Description: "Only reports connected to {Integration} will be exported. The rest of your selection belongs to other integrations and will be skipped."
  • Confirm button: "Export {exportableCount} report(s)"
  • Cancel button: "Cancel"

2) Show BOTH confirmations in sequence when applicable

If the chosen integration's subset ALSO contains reports that were already exported, show two confirmations in sequence (never combined):

  1. First the partial-export modal above.
  2. Then the existing "export again" modal (workspace.exportAgainModal).

Confirming both proceeds with the export; cancelling either one aborts. Present them sequentially (present the second only after the first resolves) so they don't overlap.

3) Apply the SAME flow to "Mark as exported"

Route mark-as-exported through the same shared confirmation flow used by export so the behavior stays identical.

4) Tests

Update tests/unit/hooks/useSearchBulkActionsExportTest.ts to cover:

  • Per-integration options for multi-workspace selections.
  • Partial export shows the partial modal, then exports the subset.
  • Partial + already-exported shows the partial modal first, then the export-again modal, then exports.
  • Single-integration already-exported shows the export-again modal.
  • Already-exported detection works from report actions even when isExportedToIntegration is stale/false.
  • No partial modal when every selected report belongs to the chosen integration.
  • Equivalent coverage for the "Mark as exported" partial + export-again flow.

Please run npm run fmt, npm run lint-changed, npm run typecheck, and the export unit tests before pushing.

- Show a partial-export modal when Export/Mark-as-exported to an integration
  only covers a subset of the multi-integration selection
- Present the partial modal and the existing export-again modal sequentially
- Detect already-exported reports from report actions (live Onyx first, then
  the search snapshot) instead of the stale isExportedToIntegration field
- Route Mark as exported through the same shared confirmation flow
- Cover the new flows in useSearchBulkActionsExportTest

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — pushed to claude-bulkExportMultiWorkspaceIntegration (ea3fcb9). All five changes are in, routed through one shared confirmation flow so Export and Mark as exported behave identically.

What changed

1 & 2 — Partial-export modal, shown in sequence with export-again
Both bulk actions now run through buildIntegrationHandleExportAction. A partial export is detected when the chosen integration's report count is less than the total selected reports. When partial, the partial-export modal is shown first; only after it resolves does the existing workspace.exportAgainModal appear (if the subset was already exported). They're presented one after another via the modal promise chain, never combined — cancelling either aborts.

New copy lives under workspace.exportPartialModal (title / description / plural confirmText / cancelText) with params ExportPartialModalTitleParams and ExportPartialModalDescriptionParams added to params.ts, translated across all 10 locale files.

3 — Already-exported detection from report actions
The detection loop now treats a report as exported when any of: liveReport/snapshotReport pendingFields.export, liveReport/snapshotReport isExportedToIntegration === true, or isExported(reportActions) passed only the report actions (so it evaluates the actions instead of short-circuiting on the stale field). Report actions are read from live Onyx first, falling back to the search snapshot (reportActions_<reportID>).

4 — Mark as exported uses the exact same buildIntegrationHandleExportAction, so it inherits the partial + export-again flow.

5 — TestsuseSearchBulkActionsExportTest.ts now covers per-integration options, partial-only, partial + export-again, single-integration export-again, stale-isExportedToIntegration detection via report actions, the no-modal case, cancel-aborts, and the Mark-as-exported partial + export-again flow.

Checks (run locally)

  • npm test tests/unit/hooks/useSearchBulkActionsExportTest.ts — ✅ 10/10 pass
  • npm run typecheck (tsc, CI gate) — ✅ 0 errors
  • npm run lint on changed files — ✅ clean
  • npm run fmt — ✅ applied
  • React Compiler compliance — no new divergence (the pre-existing refs errors are unchanged from origin/main: 14 → 14)

One note for the human co-author: the plural confirmText uses one/other forms in every locale; the non-English translations are my best-effort and worth a native-speaker glance before merge.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only failing check is PR Author Checklist / checklist, and it's not a code problem — there's nothing for me to fix or push.

It failed with:

You completed 0 out of 38 checklist items with 38 unfinished items
PR Author Checklist is not completely filled out. Please check every box to verify you've thought about the item.

This is a human-only step: the PR Author Checklist must be filled out (and the Tests/QA Steps sections completed) by the human co-author before the PR is marked "Ready for review". I intentionally don't tick those boxes on your behalf, since each item is an attestation that a person reviewed/tested it.

ahmedGaber93, to clear this check: go through the PR Author Checklist in the PR body, check each box you've verified, and fill in the Tests and QA Steps sections (the // TODO placeholders). The check re-runs automatically on the next push/edit.

Everything else is green or still building — no failing code checks:

  • CLA, validate, build (developmentDebug, false), security/snyk — ✅ pass
  • build (Debug, true), build (Expensify Dev, …), build (New Expensify Dev, …) — ⏳ still running (not failed)

@ahmedGaber93

Copy link
Copy Markdown
Contributor

Hi @MelvinBot — please add these changes. This builds on the existing partial-export confirmation work in that PR.

Goal

Restructure the bulk export confirmation modals so each one shows a fixed subtitle on top and a scrollable list of report names below it. To support this, add a reusable subtitle prop to our confirm modal component.

What to do

  1. Add a subtitle prop to the confirm modal component (ConfirmContent + ConfirmModal). It should render between the title and the prompt, and stay fixed above the prompt when the prompt is scrollable. Include an optional subtitleStyles prop too.

  2. Partial-export modal (in useSearchBulkActions.ts): move the descriptive text into the new subtitle, and use the prompt to show the scrollable list of the report names that will actually be exported for the chosen integration. Enable prompt scrolling.

  3. Export-again modal (confirmExportAgainThenRun in the same file): do the same split — the "these reports were already exported, export again?" text goes in the subtitle, and the list of already-exported report names goes in the scrollable prompt. Please reuse the existing exportAgainModal.description translation for the subtitle text (without the inline list) rather than adding a new key, so the other places that use it aren't affected.

Resulting modal (example)

  • Title: Export 7/24 reports to Sage Intacct?
  • Subtitle: Only reports connected to Sage Intacct will be exported. The rest of your selection belongs to other integrations and will be skipped. followed by The following reports will be exported:
  • Prompt (scrollable): the list of report names

Please run our formatter, typecheck, lint, and the useSearchBulkActionsExportTest suite before pushing. Thanks!

…ed subtitle + scrollable report list

- Add subtitle/subtitleStyles props to ConfirmContent + ConfirmModal; subtitle
  renders between title and prompt and stays fixed above a scrollable prompt
- Partial-export modal: descriptive text moves to the subtitle, the scrollable
  prompt lists the report names that will be exported
- Export-again modal: reuse exportAgainModal.description (without the inline
  list) as the subtitle, list already-exported report names in the prompt
- Update useSearchBulkActionsExportTest to assert the subtitle/prompt split

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — pushed to claude-bulkExportMultiWorkspaceIntegration (a5a54e7). Both modals now show a fixed subtitle on top and a scrollable report-name list below.

What changed

1 — subtitle / subtitleStyles props on the confirm modal
Added to both ConfirmContent and ConfirmModal. The subtitle renders between the title and the prompt and, crucially, sits outside the ScrollView, so it stays fixed above the prompt when shouldEnablePromptScroll is on. A string subtitle is wrapped in a Text; a ReactNode is passed through untouched. ConfirmModalWrapper already spreads props, so it forwards the new props automatically.

2 — Partial-export modal (useSearchBulkActions.ts)
The descriptive text is now the subtitle; the prompt is the scrollable list of the report names that will actually be exported for the chosen integration, with shouldEnablePromptScroll: true. I appended The following reports will be exported: to the existing workspace.exportPartialModal.description (across all 10 locales) so the subtitle matches your example — no new translation key, and that key is only used here.

3 — Export-again modal (confirmExportAgainThenRun, same file)
Reuses the existing workspace.exportAgainModal.description for the subtitle, called with an empty reportName and .trim()ed so only the "already exported, export again?" text remains — other callers of that key are untouched. The already-exported report names go in the scrollable prompt.

Checks (run locally)

  • npm test tests/unit/hooks/useSearchBulkActionsExportTest.ts — ✅ 10/10 pass (updated to assert the subtitle/prompt split)
  • npm run typecheck (tsc, CI gate) — ✅ 0 errors
  • npm run lint on changed files — ✅ clean
  • npm run fmt — ✅ applied
  • React Compiler compliance — the two components pass; the hook's pre-existing refs errors are unchanged (14 → 14, no new divergence)

Same note as before: the non-English translations for the new The following reports will be exported: line are best-effort and worth a native-speaker glance.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Still the same, and there's nothing for me to fix in code.

The only failing check is PR Author Checklist / checklist, failing with:

You completed 0 out of 38 checklist items with 38 unfinished items
PR Author Checklist is not completely filled out. Please check every box to verify you've thought about the item.

This is a human-only attestation — I don't tick those boxes on your behalf. ahmedGaber93, to clear it, go through the PR Author Checklist in the PR body and check each box, and fill in the Tests / QA Steps sections (the // TODO placeholders). The check re-runs automatically on the next edit/push.

The remaining build (...) checks are still running, not failed. No code checks are failing.

@ahmedGaber93

Copy link
Copy Markdown
Contributor

@MelvinBot Merge main branch and fix the conflict

…iWorkspaceIntegration

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>

# Conflicts:
#	src/hooks/useSearchBulkActions.ts
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged main and resolved the conflict. The PR is now mergeable (no more conflicts).

Conflict: one overlapping change in the @src/types/onyx type import in src/hooks/useSearchBulkActions.ts. This branch had added ReportActions while main had added ExportTemplate. I kept the union of both:

import type {BillingGraceEndPeriod, ExportTemplate, Policy, Report, ReportAction, ReportActions, ReportNameValuePairs, SearchResults, Transaction, TransactionViolations} from '@src/types/onyx';

Both types are used in the merged file — ReportActions as OnyxEntry<ReportActions> and ExportTemplate in the export-option builder — so there's no unused-import risk.

⚠️ Note: I wasn't able to run typecheck/lint locally in this environment, so please run the checks and re-verify the test steps after the merge before marking ready for review.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR.

Next steps: A human should review the recent commits and resolve the conflicting CI errors manually.

@ahmedGaber93

Copy link
Copy Markdown
Contributor

Hi @MelvinBot — please add the following changes.

1. Show the export option when only some selected reports are eligible

In src/hooks/useSearchBulkActions.ts, when grouping reports per integration, we currently only show Export to <integration> / Mark as exported if every report in the group is eligible. Change this so the option appears when at least one report is eligible, and only the eligible subset is acted on:

  • Replace the .every(...) eligibility gate with a .filter(canReportBeExported(...)) that produces an exportableReportIDs (and markableReportIDs) array, and show the option when that array has length > 0.
  • Pass the eligible subset (not the whole group) to exportToIntegrationOnSearch / markAsManuallyExported.
  • This naturally triggers the existing partial-export confirmation for the eligible subset (e.g. select an Approved + a Submitted report → the option still shows → confirm "Export 1/2 reports" → only the approved one exports).

2. Align already-exported detection with the backend response

Revert wasExported detection back to main branch simpler check (this matches how the BE response).

if (!report?.pendingFields?.export && !report?.isExportedToIntegration) {
    continue;
}

3. Split the partial-export subtitle into two case-based reasons

description: ({integration, hasReportsOnOtherIntegrations, hasIneligibleReports}: ExportPartialModalDescriptionParams) => {
  const reasons: string[] = [];
  if (hasReportsOnOtherIntegrations) {
      reasons.push(`Only reports connected to ${CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[integration]} will be exported.`);
  }
  if (hasIneligibleReports) {
      reasons.push(`Only reports that are eligible to export will be exported.`);
  }
  return `${reasons.join('\n\n')}\n\nThe following reports will be exported:`;
},

Tests

  • Add a test that a mixed approved + submitted selection on the same workspace still shows the export option and only exports the approved report (with the partial-export modal shown).
  • Update wasExported tests to match the current implementation.

Please run the formatter, npm run typecheck, npm run lint, and the useSearchBulkActionsExportTest suite before pushing.

- Show Export/Mark-as-exported when at least one report in a group is eligible; act only on the eligible subset
- Revert already-exported detection to the backend-aligned pendingFields.export/isExportedToIntegration check
- Split the partial-export subtitle into per-case reasons (other integrations and/or ineligible reports)
- Update and add useSearchBulkActionsExport tests

Co-authored-by: ahmedGaber93 <ahmedGaber93@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Done — pushed to claude-bulkExportMultiWorkspaceIntegration in b29fe7e.

1. Show the export option when only some selected reports are eligible — replaced the .every(...) gate with a .filter(canReportBeExported(...)) that builds exportableReportIDs / markableReportIDs; the option shows when the subset is non-empty and only that subset is passed to exportToIntegrationOnSearch / markAsManuallyExported, so a mixed selection naturally hits the partial-export confirmation. See src/hooks/useSearchBulkActions.ts:1644-1682.

2. Align already-exported detection with the backend response — reverted wasExported to the simpler !report?.pendingFields?.export && !report?.isExportedToIntegration check and dropped the report-actions path (isExported import removed). See src/hooks/useSearchBulkActions.ts:1566-1577.

3. Split the partial-export subtitle into two case-based reasons — the description now takes hasReportsOnOtherIntegrations / hasIneligibleReports and joins the applicable reasons (src/languages/en.ts:7385, type updated in src/languages/params.ts:101). The two flags are computed at the call site from the eligible subset vs. the integration group size vs. the full selection.

Tests — added a mixed approved + submitted (same workspace) case that asserts the option still shows, the partial-export modal appears, and only the approved report is exported; updated the wasExported tests to drive already-exported state via isExportedToIntegration / pendingFields.export instead of report actions.

Checks run: npm run fmt, npm run typecheck (tsc), npm run lint, and useSearchBulkActionsExportTest (11/11 passing). React Compiler compliance is unchanged from the pre-existing baseline for this hook (the react-hooks/refs opt-out predates this change).

Two notes:

  • One small naming tweak: I inlined the two booleans into the translate call instead of using a local hasReportsOnOtherIntegrations variable, because the no-negated-variables lint rule trips on the ...OnOther... substring (it contains "not"). The translation param keeps the exact hasReportsOnOtherIntegrations name you specified.
  • I only updated the English copy for reason Add Expensify eslint #3. The other locales still compile (they destructure just {integration}) but will render their existing single-reason text — let me know if you want me to add translated strings for the two split reasons.

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