Skip to content

[CP Staging] Fix imported merchant rule tag corruption for X: Y (multi-level) tag format#96407

Merged
NicolasBonet merged 3 commits into
mainfrom
claude-importMerchantRuleTagColonFormat
Jul 17, 2026
Merged

[CP Staging] Fix imported merchant rule tag corruption for X: Y (multi-level) tag format#96407
NicolasBonet merged 3 commits into
mainfrom
claude-importMerchantRuleTagColonFormat

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

When importing merchant/expense rules via spreadsheet, a Tag cell typed in X: Y format (a multi-level/dependent tag) was stored verbatim, including the space after the colon. Multi-level tags are canonically encoded as levels joined by a bare colon with no surrounding spaces (X:Y) — the same encoding the manual "Add tag" flow produces via trimTag(newTags.join(':')). Because the import skipped this normalization, the stored value X: Y was later split on : into ["X", " Y"] on display, so the rule row rendered X, Y and the tag field only resolved the first level X.

Conversely, on a policy with a single-level tag list, a colon in the Tag cell is part of the literal tag name, not a level separator. Storing it unescaped meant the value was later split on : and the tag wasn't persisted as one name (e.g. ab:cd resolved as two levels).

This adds a normalizeImportedTag helper in ImportedMerchantRulesPage that branches on policy.hasMultipleTagLists:

  • Multi-level policy: splits the imported cell on : (respecting escaped colons via getTagArrayFromName), trims each level, and re-joins with : (via trimTag), so X: Y is stored as X:Y — the canonical encoding the manual "Add tag" flow produces — and displays correctly in both the rule row and the tag field.
  • Single-level policy: escapes the colons via escapeTagName, so ab:cd is stored as ab\:cd — matching how manually created tag names are stored on the policy — and the full value persists as one literal tag name.

Unit tests cover both paths.

Fixed Issues

$ #96391
PROPOSAL: #96391 (comment)

Tests

Unit test:

  1. Run npx jest tests/unit/ImportedMerchantRulesPageTest.ts and verify all tests pass.

Manual:

The import interprets the Tag cell according to the workspace's tag setup at the time of import (multi-level tags vs. a single-level tag list), so both scenarios below must be tested — set up the tags before importing the rules.

Scenario A: Workspace with multi-level tags (colon = level separator)

  1. As a workspace admin on a Control workspace with Rules and Tags enabled, set up multi-level tags first: go to Tags > Import > Multi-level tags and upload a CSV like:
Level1,Level2
Parent,Child
North America,New York
  1. Create a CSV file with the following content (note the space after the colon in the Tag cells):
Merchant,Tag
Starbucks,Parent: Child
Uber,North America: New York
  1. Go to Workspaces > [workspace] > Rules, click More > Import merchant rules and upload the CSV from step 2.
  2. Map the Merchant column to Merchant is and the Tag column to Updated tag, then click Import.
  3. Verify the confirmation modal says 2 merchant rules have been added.
  4. In the Merchant section of the Rules page, verify the rule rows display Update tag to "Parent, Child" and Update tag to "North America, New York" (no space-prefixed or missing second level).
  5. Open the Starbucks rule and verify BOTH tag level fields are populated (Level1: Parent, Level2: Child) instead of only the first level.

Scenario B: Workspace with a single-level tag list (colon is part of the tag name)

  1. As a workspace admin on a Control workspace with Rules and Tags enabled (and NO multi-level tags), go to Tags > Add tag and create a tag named ab:cd.
  2. Create a CSV file with the following content:
Merchant,Tag
Starbucks,ab:cd
  1. Go to Workspaces > [workspace] > Rules, click More > Import merchant rules and upload the CSV from step 2.
  2. Map the Merchant column to Merchant is and the Tag column to Updated tag, then click Import.
  3. Verify the confirmation modal says 1 merchant rule has been added.
  4. In the Merchant section of the Rules page, verify the rule row displays Update tag to "ab:cd" (the full value persists as one tag name, not split or truncated to ab).
  5. Open the Starbucks rule and verify the tag field shows the ab:cd tag selected.
  • Verify that no errors appear in the JS console

Offline tests

N/A

QA Steps

Same as tests

  • 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

MacOS: Chrome / Safari pr96407-1-import-mapping pr96407-2-rule-rows pr96407-3-edit-rule-single-level-taglist pr96407-4-edit-rule-both-levels

MelvinBot and others added 2 commits July 17, 2026 15:02
Co-authored-by: Nicolás Bonet <NicolasBonet@users.noreply.github.com>
Co-authored-by: Nicolás Bonet <NicolasBonet@users.noreply.github.com>
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
.../rules/MerchantRules/ImportedMerchantRulesPage.tsx 6.45% <87.50%> (+6.45%) ⬆️
... and 82 files with indirect coverage changes

@NicolasBonet
NicolasBonet marked this pull request as ready for review July 17, 2026 16:11
@NicolasBonet
NicolasBonet requested a review from a team as a code owner July 17, 2026 16:11
@melvin-bot
melvin-bot Bot requested a review from Pujan92 July 17, 2026 16:11
@melvin-bot

melvin-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

@Pujan92 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and removed request for a team July 17, 2026 16:11
@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@NicolasBonet do I need to review this?

@NicolasBonet

Copy link
Copy Markdown
Contributor

@NicolasBonet do I need to review this?

please

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • 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 verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari
Screen.Recording.2026-07-18.at.01.01.51.mov

@truph01

truph01 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@Pujan92 Have you started reviewing this? If not, I can take over since it comes from the original PR where I am reviewer

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

TBH, I have started it.

@truph01

truph01 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@NicolasBonet It looks like the Melvin solution doesn't fix the issue:

Screen.Recording.2026-07-18.at.00.20.18.mov

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Looks QA issue expectation is incorrect, I just mentioned here #96391 (comment)

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot for a single-level tag, I think we are allowing a colon in the value. We can do a conditional change like below

const {policy, tags: policyTags} = usePolicy(policyID);
const tag = isMultiLevelTags(policyTags) ? normalizeImportedTag(getCellValue(tagColumn, rowIndex)) : escapeTagName(getCellValue(tagColumn, rowIndex));

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot instead of isMultiLevelTags, we can use policy.hasMultipleTagLists too.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Good catch — the concern is valid. normalizeImportedTag unconditionally splits on : (via getTagArrayFromName) and re-joins without spaces. On a single-level tag policy a colon is a legitimate character in the value, so North America: New York would be corrupted into North America:New York — collapsing the space and mis-encoding it as a two-level tag. Gating the normalization on isMultiLevelTags and falling back to escapeTagName for the single-level case is the right approach.

One adjustment to the snippet: usePolicy(policyID) in this file returns the policy object only, not {policy, tags}. isMultiLevelTags needs the PolicyTagLists from the policyTags_ Onyx collection, so the tags have to be read separately:

// usePolicy already returns `policy`; add the tag lists from Onyx
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
// import {isMultiLevelTags, escapeTagName} from '@libs/PolicyUtils';

const tag = isMultiLevelTags(policyTags)
    ? normalizeImportedTag(getCellValue(tagColumn, rowIndex))
    : escapeTagName(getCellValue(tagColumn, rowIndex));

This would also want the unit test extended to cover the single-level path (a colon in a single-level value stays escaped/verbatim rather than being split).


I can only make code changes when directed by the PR author, an assignee, or an existing co-author. To have me implement this, the PR assignee or author should reply with a request tagging me — NicolasBonet, want me to apply this?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Agreed — policy.hasMultipleTagLists is the better choice here. It's the canonical "this workspace uses multi-level tags" flag (set in Tag.ts), and since policy is already read via usePolicy in this file, it avoids the extra policyTags_ Onyx read that isMultiLevelTags would require. So the snippet simplifies to:

const tag = policy?.hasMultipleTagLists
    ? normalizeImportedTag(getCellValue(tagColumn, rowIndex))
    : escapeTagName(getCellValue(tagColumn, rowIndex));

No new imports beyond escapeTagName from @libs/PolicyUtils. (The single-level unit-test case still applies.)


I can only make code changes when directed by the PR author, an assignee, or an existing co-author. To have me implement this, the PR assignee or author should reply with a request tagging me — NicolasBonet, want me to apply this?

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@NicolasBonet can you check #96407 (comment) and ask Melvin to apply if that makes sense to you? @truph01 in case you can give a look too

Policy with Single Level Tag

Screen.Recording.2026-07-17.at.23.57.14.mov

Policy with Multi-Level Tag

Screen.Recording.2026-07-17.at.23.56.23.mov

@truph01

truph01 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@Pujan92 What about the case where the policy doesn't have any tags and users try importing a rule with tags?
Do you think we need the fix from BE first? Like what we plan to do with category #96408 (comment)

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

I am not sure what the requirement is for it, but I believe it is fine if that BE changes of adding the non-existent tag later as at the moment the user won't be blocked by adding the rule.

@truph01

truph01 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@Pujan92 What about the case where the policy doesn't have any tags and users try importing a rule with tags?
Do you think we need the fix from BE first? Like what we plan to do with category #96408 (comment)

I am not sure what the requirement is for it, but I believe it is fine if that BE changes of adding the non-existent tag later as at the moment the user won't be blocked by adding the rule.

@NicolasBonet What do you think about it?

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot trimming the space is required for both cases #96407 (comment). So consider assigning the value of normalizeImportedTag to one variable and using that for both ternary expressions.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Agreed — since the space-trimming should apply to both branches, computing normalizeImportedTag once and escaping it for the single-level case is cleaner and avoids the duplicated getCellValue call:

const normalizedTag = normalizeImportedTag(getCellValue(tagColumn, rowIndex));
const tag = policy?.hasMultipleTagLists ? normalizedTag : escapeTagName(normalizedTag);

normalizeImportedTag trims the spaces around each level for both cases; for a single-level policy we then escape the colons so the value is stored as one literal tag name (e.g. North America: New YorkNorth America:New YorkNorth America\:New York).

One edge case to keep in mind: escapeTagName escapes every colon, so if the imported single-level cell already contains an escaped colon (Time\: Tracking), normalizeImportedTag preserves the \: and escapeTagName would then double-escape it to \\:, which getTagArrayFromName reads as a literal backslash + a split point. Worth a unit-test case alongside the plain single-level one, and if it matters for the flow, escape only the unescaped colons.


I can only make code changes when directed by the PR author, an assignee, or an existing co-author. NicolasBonet — if this looks right to you, reply tagging me and I'll apply it.

@melvin-bot
melvin-bot Bot requested review from NicolasBonet and gijoe0295 and removed request for a team July 17, 2026 19:24
@melvin-bot

melvin-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

@gijoe0295 @NicolasBonet One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@NicolasBonet

Copy link
Copy Markdown
Contributor

Yes, we are solving two issues.

  1. What QA has raised, for the policy with a single-level tag, the colon value isn't persisted. Let ab:cd to be as it is by escaping the colon.
  2. Which Melvin fixed, do the trimming for multi-level tags and not escaping colon. with that, ab:cd will be considered 2-level tags.

Do you want me to share the recording for both cases, though I have added here

I pushed the changes to handle both cases, care to double check, please?

@truph01

truph01 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

@NicolasBonet I tested both cases, and everything looks good except that we don't automatically create a new tag when importing a rule with a tag that doesn't already exist.

@Pujan92 Pujan92 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@Pujan92

Pujan92 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

We need to update the test steps to clarify what needs to be tested according to the policy tags option(whether it has multiple tag lists or not)

@NicolasBonet

Copy link
Copy Markdown
Contributor

@truph01 That's expected and intentionally out of scope here — we don't auto-create tags anywhere in the app when a flow references a nonexistent one (tags are only created via the explicit Tags > Add tag and Tags > Import routes; personal expense rules and the manual merchant rule flow are selection-only over existing tags).

We also closed the category equivalent (PR 96408 and its BE fix) because (a) the creation call goes on the sequential write queue while the import fires immediately as a side-effect request, so the client can't guarantee the tag exists server-side before the rules land, and (b) auto-creating from a rules import is new product behavior — everywhere else a rule referencing a nonexistent tag/category is simply skipped at apply time. Tags are even trickier (multi-level tag lists, integration-synced tags).

We'll settle the desired behavior in #96389 first; this PR only fixes the colon encoding.

@NicolasBonet
NicolasBonet removed the request for review from gijoe0295 July 17, 2026 19:51
@NicolasBonet
NicolasBonet merged commit 8f7cef0 into main Jul 17, 2026
44 checks passed
@NicolasBonet
NicolasBonet deleted the claude-importMerchantRuleTagColonFormat branch July 17, 2026 20:00
@NicolasBonet NicolasBonet changed the title Fix imported merchant rule tag corruption for X: Y (multi-level) tag format [CP Staging] Fix imported merchant rule tag corruption for X: Y (multi-level) tag format Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚧 NicolasBonet has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

OSBotify pushed a commit that referenced this pull request Jul 17, 2026
…ColonFormat

Fix imported merchant rule tag corruption for X: Y (multi-level) tag format

(cherry picked from commit 8f7cef0)

(cherry-picked to staging by roryabraham)
@OSBotify OSBotify added the CP Staging marks PRs that have been CP'd to staging label Jul 17, 2026
@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.4.36-2 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 cancelled 🔪
🍎 iOS 🍎 cancelled 🔪

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I reviewed the changes in this PR against App/docs/articles and no help site changes are required.

Why: This PR is a behind-the-scenes bug fix — it normalizes how a X: Y tag cell is encoded when importing merchant rules via spreadsheet (ImportedMerchantRulesPage.tsx) so imported tags match the canonical encoding used everywhere else. It doesn't add or rename any feature, tab, setting, or button, and it doesn't change any documented behavior — it just makes an existing flow work correctly.

The only relevant article, Merchant Rules, documents the manual Add merchant rule flow and doesn't cover the spreadsheet-import path this fix touches, so there's nothing in it to update. No other article references this behavior.

Since no changes are required, I did not create a draft docs PR. NicolasBonet, if you believe the spreadsheet-import flow (or its tag-format expectations) should be documented, let me know and I'll draft an article update.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.4.38-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 cancelled 🔪
🍎 iOS 🍎 cancelled 🔪

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Help site review: no changes required.

This PR is an internal bug fix, not a user-facing behavior or feature change, so no updates to App/docs/articles are needed.

Why no docs update is needed
  • What the PR does: Adds a normalizeImportedTag helper in ImportedMerchantRulesPage.tsx that normalizes the Tag cell when importing merchant rules via spreadsheet — canonicalizing X: Y to X:Y on multi-level policies and escaping literal colons (ab:cdab\:cd) on single-level tag lists. It corrects a data-corruption bug so imported tags are stored and displayed correctly.
  • No documented behavior changes: The only merchant-rules article, Merchant Rules, documents creating rules manually via Add merchant rule and which fields rules can update. It does not describe the spreadsheet-import flow, CSV column mapping (Merchant is / Updated tag), or the internal tag-encoding format that this PR fixes.
  • Nature of the change: This restores expected behavior for an existing (undocumented) import path. There's no new feature, no new UI copy, and no change to any workflow described on the help site — so there's nothing to document or correct.

I searched App/docs/articles for the merchant-rules import terms (Import merchant rules, Updated tag, Merchant is) and found no article covering this flow.

Since no help site changes are required, I did not create a draft PR. @NicolasBonet, please confirm this assessment — if you believe the spreadsheet-import behavior should be documented, let me know and I'll draft the help site PR.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.4.40-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Help site review — no changes required

I reviewed the changes in this PR against the help site files under docs/articles, and no help site updates are needed.

Why:

  • This is a bug fix, not a feature or documented-behavior change. It corrects how a Tag cell in X: Y (multi-level) or ab:cd (single-level) format is normalized when importing merchant rules via spreadsheet, so imported tags resolve/display correctly.
  • The only relevant article, Workspace-Merchant-Rules.md, documents creating Merchant Rules through the UI (Add merchant rule). It does not document the spreadsheet import merchant rules flow at all, and it doesn't describe tag-cell colon formatting — so there's nothing in the docs that this fix contradicts or renders stale.
  • The tags-import article, Create-and-manage-expense-tags.md, covers importing tags, not importing merchant rules, and is unaffected by this change.

Since the fix restores expected behavior without introducing any new user-facing feature or workflow, there is no draft help site PR to create.

@NicolasBonet, please confirm you agree no help site changes are needed here. If you'd like me to document the "Import merchant rules" flow as a separate improvement (independent of this bug fix), let me know and I'll draft it.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/yuwenmemon in version: 9.4.40-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Cherry-picked to staging by https://github.com/roryabraham in version: 9.4.41-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Help site review — docs PR created

On re-review, I found a genuine documentation gap this PR relates to: the Import merchant rules spreadsheet flow (and how Tag cells are interpreted) is not documented anywhere. The existing Merchant Rules article only covers creating rules manually, so the behavior this PR fixes (X: Y → multi-level tag levels; ab:cd → one literal tag on single-level lists) was never captured on the help site.

I've created a draft help site PR adding a new article for it:

#96544 — Docs updates for E/A#96407 Document importing merchant rules via spreadsheet

It documents:

  • Who can import Merchant Rules (Workspace Admin, Rules enabled / Control plan).
  • The exact flow: Workspaces > [workspace] > Rules > More > Import merchant rules, column mapping, then Import.
  • How Tag values are interpreted for multi-level vs. single-level tag workspaces — the behavior corrected in this PR.

All feature names, menu items, and buttons were verified against the current UI strings and follow HELPSITE_NAMING_CONVENTIONS.md, HELP_AUTHORING_GUIDELINES.md, and TEMPLATE.md. The HelpDot label is applied and it's assigned to you.

@NicolasBonet, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CP Staging marks PRs that have been CP'd to staging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants