Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3858085
Refactor tag creation and import functions to use policy data object
Tony-MK Jan 12, 2026
3cb35ea
lint
Tony-MK Jan 12, 2026
b826cff
prettier
Tony-MK Jan 12, 2026
1df66d0
Refactor ImportedTagsPage to streamline policy data retrieval and imp…
Tony-MK Jan 12, 2026
29d7bba
refactor: tag-related functions and tests for improved consistency an…
Tony-MK Jan 12, 2026
ae6e08e
refactor: remove unused imports from ImportedTagsPage and WorkspaceCr…
Tony-MK Jan 13, 2026
ee75418
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Jan 13, 2026
4ea22d5
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Jan 19, 2026
951a3e8
refactor: resolved the failing tests from PolicyTagTest.ts.
Tony-MK Jan 19, 2026
968e697
refactor: update importPolicyTags function to accept policyID directly
Tony-MK Jan 25, 2026
2098e48
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Jan 25, 2026
120fec9
refactoring
Tony-MK Jan 26, 2026
097c66f
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Jan 26, 2026
c9b5f52
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Jan 26, 2026
5b7668d
typecheck
Tony-MK Jan 26, 2026
42fdbb6
Merge branch 'Expensify:main' into Tags-Violations
Tony-MK Feb 4, 2026
2234982
Merge branch 'Expensify:main' into Tags-Violations
Tony-MK Feb 5, 2026
fb0e1d4
Merge branch 'main' of https://github.com/Expensify/App into Tags-Vio…
Tony-MK Feb 9, 2026
1d62dea
Merge branch 'Tags-Violations' of https://github.com/Tony-MK/Expensif…
Tony-MK Feb 9, 2026
3046d50
Merge branch 'Expensify:main' into Tags-Violations
Tony-MK Feb 12, 2026
0d2656a
Merge branch 'Expensify:main' into Tags-Violations
Tony-MK Feb 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions src/libs/actions/Policy/Tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,33 +121,35 @@ function updateImportSpreadsheetData(tagsLength: number): OnyxData<typeof ONYXKE
}

function createPolicyTag(
policyID: string,
policyData: PolicyData,
tagName: string,
policyTags: PolicyTagLists = {},
setupTagsTaskReport?: OnyxEntry<Report>,
setupCategoriesAndTagsTaskReport?: OnyxEntry<Report>,
policyHasCustomCategories?: boolean,
) {
const {policy, tags: policyTags} = policyData;
const policyID = policy?.id;
const policyTag = PolicyUtils.getTagLists(policyTags)?.at(0) ?? ({} as PolicyTagList);
Comment thread
Tony-MK marked this conversation as resolved.
const newTagName = PolicyUtils.escapeTagName(tagName);
const tagListsOptimisticData = {
[policyTag.name]: {
tags: {
[newTagName]: {
name: newTagName,
enabled: true,
errors: null,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
},
};

const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY_TAGS> = {
optimisticData: [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
value: {
[policyTag.name]: {
tags: {
[newTagName]: {
name: newTagName,
enabled: true,
errors: null,
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
},
},
},
},
value: tagListsOptimisticData,
},
],
successData: [
Expand Down Expand Up @@ -183,6 +185,8 @@ function createPolicyTag(
],
};

pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, tagListsOptimisticData);

const parameters = {
policyID,
tags: JSON.stringify([{name: newTagName}]),
Expand All @@ -202,10 +206,12 @@ function createPolicyTag(
function importPolicyTags(policyID: string, tags: PolicyTag[]) {
const onyxData = updateImportSpreadsheetData(tags.length);

// eslint-disable-next-line @typescript-eslint/naming-convention
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.

❌ CONSISTENCY-5 (docs)

ESLint rule disables must be accompanied by a clear comment explaining why the rule needs to be disabled. This helps team members understand the exception and promotes better maintainability.

Suggested fix:

// The backend API expects 'GL Code' as a property name, which violates TypeScript naming conventions
// eslint-disable-next-line @typescript-eslint/naming-convention
const optimisticTags = tags.map((tag) => ({name: tag.name, enabled: tag.enabled, 'GL Code': tag['GL Code']}));

Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

const optimisticTags = tags.map((tag) => ({name: tag.name, enabled: tag.enabled, 'GL Code': tag['GL Code']}));

const parameters = {
policyID,
// eslint-disable-next-line @typescript-eslint/naming-convention
tags: JSON.stringify(tags.map((tag) => ({name: tag.name, enabled: tag.enabled, 'GL Code': tag['GL Code']}))),
tags: JSON.stringify(optimisticTags),
Comment thread
Tony-MK marked this conversation as resolved.
};

API.write(WRITE_COMMANDS.IMPORT_TAGS_SPREADSHEET, parameters, onyxData);
Expand Down
13 changes: 7 additions & 6 deletions src/pages/workspace/tags/WorkspaceCreateTagPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput';
import useLocalize from '@hooks/useLocalize';
import useOnboardingTaskInformation from '@hooks/useOnboardingTaskInformation';
import useOnyx from '@hooks/useOnyx';
import usePolicyData from '@hooks/usePolicyData';
import useThemeStyles from '@hooks/useThemeStyles';
import {addErrorMessage} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
Expand All @@ -31,8 +32,8 @@ type WorkspaceCreateTagPageProps =

function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) {
const policyID = route.params.policyID;
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true});
const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true});
const policyData = usePolicyData(policyID);
const {tags: policyTagLists, categories: policyCategories} = policyData;
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
const setupTagsTaskReportID = introSelected?.setupTags;
const [setupTagsTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${setupTagsTaskReportID}`, {canBeMissing: true});
Expand All @@ -49,7 +50,7 @@ function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) {
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_TAG_FORM>) => {
const errors: FormInputErrors<typeof ONYXKEYS.FORMS.WORKSPACE_TAG_FORM> = {};
const tagName = escapeTagName(values.tagName.trim());
const {tags} = getTagList(policyTags, 0);
const {tags} = getTagList(policyTagLists, 0);

if (!isRequiredFulfilled(tagName)) {
errors.tagName = translate('workspace.tags.tagRequiredError');
Expand All @@ -64,16 +65,16 @@ function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) {

return errors;
},
[policyTags, translate],
[policyTagLists, translate],
);

const createTag = useCallback(
(values: FormOnyxValues<typeof ONYXKEYS.FORMS.WORKSPACE_TAG_FORM>) => {
createPolicyTag(policyID, values.tagName.trim(), policyTags, setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories);
createPolicyTag(policyData, values.tagName.trim(), setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories);
Keyboard.dismiss();
Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined);
},
[policyID, policyTags, isQuickSettingsFlow, backTo, setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories],
[policyID, policyData, isQuickSettingsFlow, backTo, setupTagsTaskReport, setupCategoriesAndTagsTaskReport, policyHasCustomCategories],
);

return (
Expand Down
37 changes: 30 additions & 7 deletions tests/actions/PolicyTagTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,16 @@ describe('actions/Policy', () => {
const newTagName = 'new tag';
const fakePolicyTags = createRandomPolicyTags(tagListName);

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);

mockFetch.pause();
await waitForBatchedUpdates();

const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

// When creating a new tag
createPolicyTag(fakePolicy.id, newTagName, fakePolicyTags);
createPolicyTag(policyData.current, newTagName);
await waitForBatchedUpdates();

// Then the tag should appear optimistically with pending state so the user sees immediate feedback
Expand Down Expand Up @@ -322,12 +327,17 @@ describe('actions/Policy', () => {
const newTagName = 'new tag';
const fakePolicyTags = createRandomPolicyTags(tagListName);

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);

mockFetch.pause();
await waitForBatchedUpdates();
mockFetch.fail();

const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

// When the API fails
createPolicyTag(fakePolicy.id, newTagName, fakePolicyTags);
createPolicyTag(policyData.current, newTagName);
await waitForBatchedUpdates();
mockFetch.resume();
await waitForBatchedUpdates();
Expand All @@ -345,11 +355,15 @@ describe('actions/Policy', () => {

const newTagName = 'new tag';

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);

mockFetch.pause();
await waitForBatchedUpdates();

const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

// When adding the first tag
createPolicyTag(fakePolicy.id, newTagName, {});
createPolicyTag(policyData.current, newTagName);
await waitForBatchedUpdates();

// Then the tag should be created in a new list with pending state so the user sees immediate feedback
Expand Down Expand Up @@ -389,6 +403,7 @@ describe('actions/Policy', () => {

mockFetch.pause();

await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy);
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags);
await waitForBatchedUpdates();

Expand All @@ -398,8 +413,10 @@ describe('actions/Policy', () => {
expect(result.current[0]).toBeDefined();
});

const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

// When using data from useOnyx hook
createPolicyTag(fakePolicy.id, newTagName, result.current[0] ?? {});
createPolicyTag(policyData.current, newTagName);
await waitForBatchedUpdates();

// Then the tag should appear optimistically with pending state so the user sees immediate feedback
Expand Down Expand Up @@ -2243,7 +2260,9 @@ describe('actions/Policy', () => {
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport);

createPolicyTag(fakePolicy.id, newTagName, fakeTags, fakeTaskReport, undefined, false);
const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

createPolicyTag(policyData.current, newTagName, fakeTaskReport);

await waitForBatchedUpdates();

Expand Down Expand Up @@ -2276,7 +2295,9 @@ describe('actions/Policy', () => {
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport);

createPolicyTag(fakePolicy.id, newTagName, fakeTags, undefined, fakeTaskReport, true);
const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

createPolicyTag(policyData.current, newTagName, undefined, fakeTaskReport, true);

await waitForBatchedUpdates();

Expand Down Expand Up @@ -2309,7 +2330,9 @@ describe('actions/Policy', () => {
await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakeTags);
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${fakeTaskReportID}`, fakeTaskReport);

createPolicyTag(fakePolicy.id, newTagName, fakeTags, undefined, fakeTaskReport, false);
const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider});

createPolicyTag(policyData.current, newTagName, undefined, fakeTaskReport, false);

await waitForBatchedUpdates();

Expand Down
Loading