Skip to content

Commit eaf8138

Browse files
mountinyOSBotify
authored andcommitted
Merge pull request #82555 from Expensify/vit-revert82527
[CP Staging] Revert "feat: rule - support multilevel tags" (cherry picked from commit ddf9e7d) (cherry-picked to staging by mountiny)
1 parent 58ed437 commit eaf8138

File tree

16 files changed

+128
-367
lines changed

16 files changed

+128
-367
lines changed

src/ROUTES.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -471,15 +471,15 @@ const ROUTES = {
471471
},
472472
SETTINGS_RULES: 'settings/rules',
473473
SETTINGS_RULES_ADD: {
474-
route: 'settings/rules/new/:field?/:index?',
475-
getRoute: (field?: ValueOf<typeof CONST.EXPENSE_RULES.FIELDS>, index?: number) => {
476-
return `settings/rules/new/${field ? StringUtils.camelToHyphenCase(field) : ''}${index !== undefined ? `/${index === -1 ? ':index' : index}` : ''}` as const;
474+
route: 'settings/rules/new/:field?',
475+
getRoute: (field?: ValueOf<typeof CONST.EXPENSE_RULES.FIELDS>) => {
476+
return `settings/rules/new/${field ? StringUtils.camelToHyphenCase(field) : ''}` as const;
477477
},
478478
},
479479
SETTINGS_RULES_EDIT: {
480-
route: 'settings/rules/edit/:hash/:field?/:index?',
481-
getRoute: (hash?: string, field?: ValueOf<typeof CONST.EXPENSE_RULES.FIELDS>, index?: number) => {
482-
return `settings/rules/edit/${hash ?? ':hash'}/${field ? StringUtils.camelToHyphenCase(field) : ''}${index !== undefined ? `/${index === -1 ? ':index' : index}` : ''}` as const;
480+
route: 'settings/rules/edit/:hash/:field?',
481+
getRoute: (hash?: string, field?: ValueOf<typeof CONST.EXPENSE_RULES.FIELDS>) => {
482+
return `settings/rules/edit/${hash ?? ':hash'}/${field ? StringUtils.camelToHyphenCase(field) : ''}` as const;
483483
},
484484
},
485485
SETTINGS_LEGAL_NAME: 'settings/profile/legal-name',
@@ -2809,8 +2809,8 @@ const ROUTES = {
28092809
getRoute: (policyID: string, ruleID?: string) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/category` as const,
28102810
},
28112811
RULES_MERCHANT_TAG: {
2812-
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/tag/:orderWeight',
2813-
getRoute: (policyID: string, ruleID?: string, orderWeight?: number) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/tag/${orderWeight}` as const,
2812+
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/tag',
2813+
getRoute: (policyID: string, ruleID?: string) => `workspaces/${policyID}/rules/merchant-rules/${ruleID ?? 'new'}/tag` as const,
28142814
},
28152815
RULES_MERCHANT_TAX: {
28162816
route: 'workspaces/:policyID/rules/merchant-rules/:ruleID/tax',

src/components/Rule/RuleSelectionBase.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ type RuleSelectionBaseProps = {
1818
/** The translation key for the page title */
1919
titleKey: TranslationPaths;
2020

21-
/** The translated page title */
22-
title?: string;
23-
2421
/** Test ID for the screen wrapper */
2522
testID: string;
2623

@@ -43,7 +40,7 @@ type RuleSelectionBaseProps = {
4340
hash?: string;
4441
};
4542

46-
function RuleSelectionBase({titleKey, title, testID, selectedItem, items, onSave, onBack, backToRoute, hash}: RuleSelectionBaseProps) {
43+
function RuleSelectionBase({titleKey, testID, selectedItem, items, onSave, onBack, backToRoute, hash}: RuleSelectionBaseProps) {
4744
const styles = useThemeStyles();
4845
const {translate} = useLocalize();
4946

@@ -55,7 +52,7 @@ function RuleSelectionBase({titleKey, title, testID, selectedItem, items, onSave
5552
shouldEnableMaxHeight
5653
>
5754
<HeaderWithBackButton
58-
title={title ?? translate(titleKey)}
55+
title={translate(titleKey)}
5956
onBackButtonPress={onBack}
6057
/>
6158
<View style={[styles.flex1]}>

src/libs/ExpenseRuleUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {ExpenseRuleForm} from '@src/types/form';
44
import type {ExpenseRule, TaxRate} from '@src/types/onyx';
55
import {getDecodedCategoryName} from './CategoryUtils';
66
import Parser from './Parser';
7-
import {getCommaSeparatedTagNameWithSanitizedColons} from './PolicyUtils';
7+
import {getCleanedTagName} from './PolicyUtils';
88
import StringUtils from './StringUtils';
99

1010
type ChangeKey = 'billable' | 'category' | 'comment' | 'merchant' | 'reimbursable' | 'tag' | 'tax';
@@ -54,7 +54,7 @@ function formatExpenseRuleChanges(rule: ExpenseRule, translate: LocaleContextPro
5454
addChange('reimbursable', rule.reimbursable === 'true');
5555
}
5656
if (rule.tag) {
57-
addChange('tag', getCommaSeparatedTagNameWithSanitizedColons(rule.tag));
57+
addChange('tag', getCleanedTagName(rule.tag));
5858
}
5959
if (rule.tax?.field_id_TAX?.value) {
6060
addChange('tax', rule.tax.field_id_TAX.value);

src/libs/Navigation/linkingConfig/config.ts

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
356356
[SCREENS.SETTINGS.RULES.ADD_MERCHANT]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.MERCHANT),
357357
[SCREENS.SETTINGS.RULES.ADD_RENAME_MERCHANT]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.RENAME_MERCHANT),
358358
[SCREENS.SETTINGS.RULES.ADD_CATEGORY]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.CATEGORY),
359-
[SCREENS.SETTINGS.RULES.ADD_TAG]: {
360-
path: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.TAG, -1),
361-
parse: {
362-
index: Number,
363-
},
364-
},
359+
[SCREENS.SETTINGS.RULES.ADD_TAG]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.TAG),
365360
[SCREENS.SETTINGS.RULES.ADD_TAX]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.TAX),
366361
[SCREENS.SETTINGS.RULES.ADD_DESCRIPTION]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.DESCRIPTION),
367362
[SCREENS.SETTINGS.RULES.ADD_REIMBURSABLE]: ROUTES.SETTINGS_RULES_ADD.getRoute(CONST.EXPENSE_RULES.FIELDS.REIMBURSABLE),
@@ -374,12 +369,7 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
374369
[SCREENS.SETTINGS.RULES.EDIT_MERCHANT]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.MERCHANT),
375370
[SCREENS.SETTINGS.RULES.EDIT_RENAME_MERCHANT]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.RENAME_MERCHANT),
376371
[SCREENS.SETTINGS.RULES.EDIT_CATEGORY]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.CATEGORY),
377-
[SCREENS.SETTINGS.RULES.EDIT_TAG]: {
378-
path: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.TAG, -1),
379-
parse: {
380-
index: Number,
381-
},
382-
},
372+
[SCREENS.SETTINGS.RULES.EDIT_TAG]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.TAG),
383373
[SCREENS.SETTINGS.RULES.EDIT_TAX]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.TAX),
384374
[SCREENS.SETTINGS.RULES.EDIT_DESCRIPTION]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.DESCRIPTION),
385375
[SCREENS.SETTINGS.RULES.EDIT_REIMBURSABLE]: ROUTES.SETTINGS_RULES_EDIT.getRoute(undefined, CONST.EXPENSE_RULES.FIELDS.REIMBURSABLE),
@@ -1220,9 +1210,6 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
12201210
},
12211211
[SCREENS.WORKSPACE.RULES_MERCHANT_TAG]: {
12221212
path: ROUTES.RULES_MERCHANT_TAG.route,
1223-
parse: {
1224-
orderWeight: Number,
1225-
},
12261213
},
12271214
[SCREENS.WORKSPACE.RULES_MERCHANT_TAX]: {
12281215
path: ROUTES.RULES_MERCHANT_TAX.route,

src/libs/Navigation/types.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,7 @@ type SettingsNavigatorParamList = {
246246
[SCREENS.SETTINGS.RULES.ADD_MERCHANT]: undefined;
247247
[SCREENS.SETTINGS.RULES.ADD_RENAME_MERCHANT]: undefined;
248248
[SCREENS.SETTINGS.RULES.ADD_CATEGORY]: undefined;
249-
[SCREENS.SETTINGS.RULES.ADD_TAG]: {
250-
index: number;
251-
};
249+
[SCREENS.SETTINGS.RULES.ADD_TAG]: undefined;
252250
[SCREENS.SETTINGS.RULES.ADD_TAX]: undefined;
253251
[SCREENS.SETTINGS.RULES.ADD_DESCRIPTION]: undefined;
254252
[SCREENS.SETTINGS.RULES.ADD_REIMBURSABLE]: undefined;
@@ -269,7 +267,6 @@ type SettingsNavigatorParamList = {
269267
};
270268
[SCREENS.SETTINGS.RULES.EDIT_TAG]: {
271269
hash: string;
272-
index: number;
273270
};
274271
[SCREENS.SETTINGS.RULES.EDIT_TAX]: {
275272
hash: string;
@@ -1450,7 +1447,6 @@ type SettingsNavigatorParamList = {
14501447
[SCREENS.WORKSPACE.RULES_MERCHANT_TAG]: {
14511448
policyID: string;
14521449
ruleID: string;
1453-
orderWeight: number;
14541450
};
14551451
[SCREENS.WORKSPACE.RULES_MERCHANT_TAX]: {
14561452
policyID: string;

src/libs/ReportLayoutUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {GroupedTransactions} from '@src/types/onyx';
44
import type Report from '@src/types/onyx/Report';
55
import type Transaction from '@src/types/onyx/Transaction';
66
import {getDecodedCategoryName, isCategoryMissing} from './CategoryUtils';
7-
import {isTagMissing} from './TagUtils';
7+
import isTagMissing from './TagUtils';
88
import {getAmount, getCategory, getCurrency, getTag, isTransactionPendingDelete} from './TransactionUtils';
99

1010
/**

src/libs/TagUtils.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,4 @@ function isTagMissing(tag: string | undefined): boolean {
1111
return tag === CONST.SEARCH.TAG_EMPTY_VALUE;
1212
}
1313

14-
/**
15-
* Removes ":" from the end of a tag string, which is used as a delimiter for multilevel tags in a rule
16-
*/
17-
function trimTag(tag: string): string {
18-
const tagWithoutEscapedColons = tag.replaceAll('\\:', '☢');
19-
return tagWithoutEscapedColons.replace(/:*$/, '').replaceAll('☢', '\\:');
20-
}
21-
22-
export {isTagMissing, trimTag};
14+
export default isTagMissing;

src/libs/TagsOptionsListUtils.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -250,29 +250,5 @@ function hasMatchingTag(policyTagLists: OnyxEntry<PolicyTagLists>, transactionTa
250250
});
251251
}
252252

253-
/**
254-
* Gets enabled tags filtered by parent tag at a specific index level.
255-
*
256-
* Filters the policy tags to return only enabled tags whose parent tag filter
257-
* matches the provided parent tag value at the given index level.
258-
*
259-
* @param tags - The policy tags object containing all available tags
260-
* @param tag - The tag string (potentially multi-level, e.g., "California:North")
261-
* @param index - The index level to truncate the tag to for parent filtering
262-
* @returns Array of enabled policy tags that match the parent tag filter
263-
*/
264-
function getEnabledTags(tags: PolicyTags, tag: string, index: number) {
265-
// Truncate tag to the current level (e.g., "California:North")
266-
const parentTag = getTagArrayFromName(tag).slice(0, index).join(':');
267-
268-
return Object.values(tags).filter((policyTag) => {
269-
if (!policyTag.enabled) {
270-
return false;
271-
}
272-
const filterRegex = policyTag.rules?.parentTagsFilter ?? policyTag.parentTagsFilter;
273-
return !filterRegex || new RegExp(filterRegex).test(parentTag);
274-
});
275-
}
276-
277-
export {getTagsOptions, getTagListSections, hasEnabledTags, sortTags, getTagVisibility, hasMatchingTag, getEnabledTags};
253+
export {getTagsOptions, getTagListSections, hasEnabledTags, sortTags, getTagVisibility, hasMatchingTag};
278254
export type {SelectedTagOption, TagVisibility, TagOption};

src/pages/settings/Rules/Fields/AddTagPage.tsx

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,52 @@
1-
import React, {useMemo} from 'react';
2-
import type {ValueOf} from 'type-fest';
1+
import React from 'react';
2+
import type {OnyxCollection} from 'react-native-onyx';
33
import RuleSelectionBase from '@components/Rule/RuleSelectionBase';
44
import useOnyx from '@hooks/useOnyx';
55
import {updateDraftRule} from '@libs/actions/User';
66
import Navigation from '@libs/Navigation/Navigation';
77
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
88
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
9-
import {getCleanedTagName, getTagLists} from '@libs/PolicyUtils';
10-
import {trimTag} from '@libs/TagUtils';
11-
import {getTagArrayFromName} from '@libs/TransactionUtils';
9+
import {getCleanedTagName, getTagNamesFromTagsLists} from '@libs/PolicyUtils';
1210
import ONYXKEYS from '@src/ONYXKEYS';
1311
import ROUTES from '@src/ROUTES';
1412
import type SCREENS from '@src/SCREENS';
1513
import type {PolicyTagLists} from '@src/types/onyx';
16-
import getEmptyArray from '@src/types/utils/getEmptyArray';
14+
import {getEmptyObject} from '@src/types/utils/EmptyObject';
1715

1816
type AddTagPageProps = PlatformStackScreenProps<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.RULES.EDIT_TAG>;
1917

2018
function AddTagPage({route}: AddTagPageProps) {
21-
const {hash, index: orderWeight} = route.params ?? {};
22-
2319
const [form] = useOnyx(ONYXKEYS.FORMS.EXPENSE_RULE_FORM, {canBeMissing: true});
24-
const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true});
25-
const [policyTags = getEmptyArray<ValueOf<PolicyTagLists>>()] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${activePolicyID}`, {canBeMissing: true, selector: getTagLists});
26-
const hasDependentTags = policyTags.some((tagList) => Object.values(tagList.tags).some((tag) => !!tag.rules?.parentTagsFilter || !!tag.parentTagsFilter));
27-
const tagList = policyTags.find((item) => item.orderWeight === orderWeight);
28-
const formTags = getTagArrayFromName(form?.tag ?? '');
29-
const formTag = formTags.at(orderWeight);
20+
const [allPolicyTagLists = getEmptyObject<NonNullable<OnyxCollection<PolicyTagLists>>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {
21+
canBeMissing: true,
22+
});
3023

31-
const tagItems = useMemo(() => {
32-
const tags: Array<{name: string; value: string}> = [];
24+
const selectedTagItem = form?.tag ? {name: getCleanedTagName(form.tag), value: form.tag} : undefined;
3325

34-
for (const tag of Object.values(tagList?.tags ?? {})) {
35-
if (tag.name !== formTag && !tag.enabled) {
36-
continue;
37-
}
38-
tags.push({name: getCleanedTagName(tag.name), value: tag.name});
39-
}
26+
const tagItems = () => {
27+
const uniqueTagNames = new Set<string>();
4028

41-
return tags;
42-
}, [tagList?.tags, formTag]);
29+
const tagListsUnpacked = Object.values(allPolicyTagLists ?? {}).filter((item) => !!item);
30+
for (const tag of tagListsUnpacked.map(getTagNamesFromTagsLists).flat()) {
31+
uniqueTagNames.add(tag);
32+
}
4333

44-
const selectedTagItem = tagItems.find(({value}) => value === formTag);
34+
return Array.from(uniqueTagNames).map((tagName) => ({name: getCleanedTagName(tagName), value: tagName}));
35+
};
4536

37+
const hash = route.params?.hash;
4638
const backToRoute = hash ? ROUTES.SETTINGS_RULES_EDIT.getRoute(hash) : ROUTES.SETTINGS_RULES_ADD.getRoute();
4739

4840
const onSave = (value?: string) => {
49-
const newTags = [...formTags];
50-
if (hasDependentTags) {
51-
newTags.splice(orderWeight, newTags.length - orderWeight, value ?? '');
52-
} else {
53-
newTags[orderWeight] = value ?? '';
54-
}
55-
updateDraftRule({tag: trimTag(newTags.join(':'))});
41+
updateDraftRule({tag: value});
5642
};
5743

5844
return (
5945
<RuleSelectionBase
6046
titleKey="common.tag"
61-
title={tagList?.name}
6247
testID="AddTagPage"
6348
selectedItem={selectedTagItem}
64-
items={tagItems}
49+
items={tagItems()}
6550
onSave={onSave}
6651
onBack={() => Navigation.goBack(backToRoute)}
6752
backToRoute={backToRoute}

0 commit comments

Comments
 (0)