From 9f57e200b87294a39867472146753acea9b77f2f Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 1 Aug 2024 18:10:10 +0530 Subject: [PATCH 01/94] Add tests for group chat name --- __mocks__/@react-native-reanimated/index.ts | 10 + src/pages/InviteReportParticipantsPage.tsx | 17 +- tests/ui/GroupChatNameTests.tsx | 393 ++++++++++++++++++++ tests/ui/UnreadIndicatorsTest.tsx | 8 +- tests/unit/ReportUtilsTest.ts | 94 +++++ tests/utils/LHNTestUtils.tsx | 20 +- tests/utils/TestHelper.ts | 59 +++ 7 files changed, 584 insertions(+), 17 deletions(-) create mode 100644 __mocks__/@react-native-reanimated/index.ts create mode 100644 tests/ui/GroupChatNameTests.tsx diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts new file mode 100644 index 000000000000..28efba1dde69 --- /dev/null +++ b/__mocks__/@react-native-reanimated/index.ts @@ -0,0 +1,10 @@ +// __mocks__/react-native-reanimated/index.js +const actualAnimated = jest.requireActual('react-native-reanimated/mock'); + +const mock = { + ...actualAnimated, + createAnimatedPropAdapter: jest.fn(), + useReducedMotion: jest.fn(), +}; + +export default mock; diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index a4d5c5518ba2..4db57f5f2f01 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -11,6 +11,7 @@ import InviteMemberListItem from '@components/SelectionList/InviteMemberListItem import type {Section} from '@components/SelectionList/types'; import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd'; import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd'; +import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; @@ -44,7 +45,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen const styles = useThemeStyles(); const {translate} = useLocalize(); - const [searchTerm, setSearchTerm] = useState(''); + const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [selectedOptions, setSelectedOptions] = useState([]); const [invitePersonalDetails, setInvitePersonalDetails] = useState([]); const [recentReports, setRecentReports] = useState([]); @@ -57,7 +58,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen ); useEffect(() => { - const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], searchTerm, excludedUsers, false, options.reports, true); + const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], debouncedSearchTerm, excludedUsers, false, options.reports, true); // Update selectedOptions with the latest personalDetails information const detailsMap: Record = {}; @@ -77,7 +78,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen setRecentReports(inviteOptions.recentReports); setSelectedOptions(newSelectedOptions); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change - }, [personalDetails, betas, searchTerm, excludedUsers, options]); + }, [personalDetails, betas, debouncedSearchTerm, excludedUsers, options]); const sections = useMemo(() => { const sectionsArr: Sections = []; @@ -88,11 +89,11 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen // Filter all options that is a part of the search term or in the personal details let filterSelectedOptions = selectedOptions; - if (searchTerm !== '') { + if (debouncedSearchTerm !== '') { filterSelectedOptions = selectedOptions.filter((option) => { const accountID = option?.accountID; const isOptionInPersonalDetails = invitePersonalDetails.some((personalDetail) => accountID && personalDetail?.accountID === accountID); - const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(searchTerm); + const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm); const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue); return isPartOfSearchTerm || isOptionInPersonalDetails; }); @@ -130,7 +131,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen } return sectionsArr; - }, [invitePersonalDetails, searchTerm, selectedOptions, translate, userToInvite, areOptionsInitialized, recentReports]); + }, [invitePersonalDetails, debouncedSearchTerm, selectedOptions, translate, userToInvite, areOptionsInitialized, recentReports]); const toggleOption = useCallback( (option: OptionsListUtils.MemberForList) => { @@ -171,7 +172,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen }, [selectedOptions, backRoute, reportID, validate]); const headerMessage = useMemo(() => { - const searchValue = searchTerm.trim().toLowerCase(); + const searchValue = debouncedSearchTerm.trim().toLowerCase(); const expensifyEmails = CONST.EXPENSIFY_EMAILS as string[]; if (!userToInvite && expensifyEmails.includes(searchValue)) { return translate('messages.errorMessageInvalidEmail'); @@ -187,7 +188,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen return translate('messages.userIsAlreadyMember', {login: searchValue, name: reportName ?? ''}); } return OptionsListUtils.getHeaderMessage(invitePersonalDetails.length !== 0, !!userToInvite, searchValue); - }, [searchTerm, userToInvite, excludedUsers, invitePersonalDetails, translate, reportName]); + }, [debouncedSearchTerm, userToInvite, excludedUsers, invitePersonalDetails, translate, reportName]); const footerContent = useMemo( () => ( diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx new file mode 100644 index 000000000000..e0ac71120768 --- /dev/null +++ b/tests/ui/GroupChatNameTests.tsx @@ -0,0 +1,393 @@ +/* eslint-disable testing-library/no-node-access */ +import type * as NativeNavigation from '@react-navigation/native'; +import {act, render, screen, waitFor} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import * as Localize from '@libs/Localize'; +import * as AppActions from '@userActions/App'; +import * as User from '@userActions/User'; +import App from '@src/App'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Participant} from '@src/types/onyx/Report'; +import PusherHelper from '../utils/PusherHelper'; +import * as TestHelper from '../utils/TestHelper'; +import {navigateToSidebarOption} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +// We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App +jest.setTimeout(50000); + +jest.mock('../../src/components/ConfirmedRoute.tsx'); + +// Needed for: https://stackoverflow.com/questions/76903168/mocking-libraries-in-jest +jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ + /* eslint-disable-next-line @typescript-eslint/naming-convention */ + __esModule: true, + default: { + ignoreLogs: jest.fn(), + ignoreAllLogs: jest.fn(), + }, +})); + +/** + * We need to keep track of the transitionEnd callback so we can trigger it in our tests + */ +let transitionEndCB: () => void; + +type ListenerMock = { + triggerTransitionEnd: () => void; + addListener: jest.Mock; +}; + +/** + * This is a helper function to create a mock for the addListener function of the react-navigation library. + * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate + * the transitionEnd event that is triggered when the screen transition animation is completed. + * + * This can't be moved to a utils file because Jest wants any external function to stay in the scope. + * Details: https://github.com/jestjs/jest/issues/2567 + */ +const createAddListenerMock = (): ListenerMock => { + const transitionEndListeners: Array<() => void> = []; + const triggerTransitionEnd = () => { + transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); + }; + + const addListener: jest.Mock = jest.fn().mockImplementation((listener, callback: () => void) => { + if (listener === 'transitionEnd') { + transitionEndListeners.push(callback); + } + return () => { + transitionEndListeners.filter((cb) => cb !== callback); + }; + }); + + return {triggerTransitionEnd, addListener}; +}; + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + const {triggerTransitionEnd, addListener} = createAddListenerMock(); + transitionEndCB = triggerTransitionEnd; + + const useNavigation = () => + ({ + navigate: jest.fn(), + ...actualNav.useNavigation, + getState: () => ({ + routes: [], + }), + addListener, + } as typeof NativeNavigation.useNavigation); + + return { + ...actualNav, + useNavigation, + getState: () => ({ + routes: [], + }), + } as typeof NativeNavigation; +}); + +beforeAll(() => { + TestHelper.beforeAllSetupUITests(); +}); + +const REPORT_ID = '1'; +const USER_A_ACCOUNT_ID = 1; +const USER_A_EMAIL = 'user_a@test.com'; +const USER_B_ACCOUNT_ID = 2; +const USER_B_EMAIL = 'user_b@test.com'; +const USER_C_ACCOUNT_ID = 3; +const USER_C_EMAIL = 'user_c@test.com'; +const USER_D_ACCOUNT_ID = 4; +const USER_D_EMAIL = 'user_d@test.com'; +const USER_E_ACCOUNT_ID = 5; +const USER_E_EMAIL = 'user_e@test.com'; +const USER_F_ACCOUNT_ID = 6; +const USER_F_EMAIL = 'user_f@test.com'; +const USER_G_ACCOUNT_ID = 7; +const USER_G_EMAIL = 'user_g@test.com'; +const USER_H_ACCOUNT_ID = 8; +const USER_H_EMAIL = 'user_h@test.com'; + +/** + * Sets up a test with a logged in user. Returns the test instance. + */ +function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Promise { + // Render the App and sign in as a test user. + render(); + + const participants: Record = {}; + participantAccountIDs?.forEach((id) => { + participants[id] = { + hidden: false, + role: id === 1 ? CONST.REPORT.ROLE.ADMIN : CONST.REPORT.ROLE.MEMBER, + } as Participant; + }); + + return waitForBatchedUpdatesWithAct() + .then(async () => { + await waitForBatchedUpdatesWithAct(); + const hintText = Localize.translateLocal('loginForm.loginForm'); + const loginForm = screen.queryAllByLabelText(hintText); + expect(loginForm).toHaveLength(1); + + await act(async () => { + await TestHelper.signInWithTestUser(USER_A_ACCOUNT_ID, USER_A_EMAIL, undefined, undefined, 'A'); + }); + return waitForBatchedUpdatesWithAct(); + }) + .then(() => { + User.subscribeToUserEvents(); + return waitForBatchedUpdates(); + }) + .then(async () => { + // Simulate setting an unread report and personal details + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { + reportID: REPORT_ID, + reportName, + lastMessageText: 'Test', + participants, + lastActorAccountID: USER_B_ACCOUNT_ID, + type: CONST.REPORT.TYPE.CHAT, + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + }); + + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [USER_A_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_A_EMAIL, USER_A_ACCOUNT_ID, 'A'), + [USER_B_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'), + [USER_C_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_C_EMAIL, USER_C_ACCOUNT_ID, 'C'), + [USER_D_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_D_EMAIL, USER_D_ACCOUNT_ID, 'D'), + [USER_E_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_E_EMAIL, USER_E_ACCOUNT_ID, 'E'), + [USER_F_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_F_EMAIL, USER_F_ACCOUNT_ID, 'F'), + [USER_G_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_G_EMAIL, USER_G_ACCOUNT_ID, 'G'), + [USER_H_ACCOUNT_ID]: TestHelper.buildPersonalDetails(USER_H_EMAIL, USER_H_ACCOUNT_ID, 'H'), + }); + + // We manually setting the sidebar as loaded since the onLayout event does not fire in tests + AppActions.setSidebarLoaded(); + return waitForBatchedUpdatesWithAct(); + }); +} + +/** + * Tests for checking the group chat names at places like LHN, chat header, details page etc. + * Note that limit of 5 names is only for the header. + */ +describe('Tests for group chat name', () => { + beforeEach(() => { + jest.clearAllMocks(); + Onyx.clear(); + + // Unsubscribe to pusher channels + PusherHelper.teardown(); + }); + + const participantAccountIDs4 = [USER_A_ACCOUNT_ID, USER_B_ACCOUNT_ID, USER_C_ACCOUNT_ID, USER_D_ACCOUNT_ID]; + const participantAccountIDs8 = [...participantAccountIDs4, USER_E_ACCOUNT_ID, USER_F_ACCOUNT_ID, USER_G_ACCOUNT_ID, USER_H_ACCOUNT_ID]; + + it('Should show correctly in LHN', () => + signInAndGetApp('A, B, C, D', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); + })); + + it('Should show correctly in LHN when report name is not present', () => + signInAndGetApp('', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); + })); + + it('Should show all 8 names in LHN when 8 participants are present', () => + signInAndGetApp('', participantAccountIDs8).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H')); + })); + + it('Check if group name shows fine for report header', () => + signInAndGetApp('', participantAccountIDs4) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'A, B, C, D'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show only 5 names when there are 8 participants in the report header', () => + signInAndGetApp('', participantAccountIDs8) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'A, B, C, D, E'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show exact name in header when report name is available with 4 participants', () => + signInAndGetApp('Test chat', participantAccountIDs4) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe('Test chat'); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = 'Test chat'; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show exact name in header when report name is available with 8 participants', () => + signInAndGetApp("Let's talk", participantAccountIDs8) + .then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + expect(displayNameText?.props?.children?.[0]).toBe("Let's talk"); + + return navigateToSidebarOption(0); + }) + .then(waitForBatchedUpdates) + .then(async () => { + await act(() => transitionEndCB?.()); + const name = "Let's talk"; + const displayNameTexts = screen.queryAllByLabelText(name); + return waitFor(() => expect(displayNameTexts).toHaveLength(1)); + })); + + it('Should show last message preview in LHN', () => + signInAndGetApp('A, B, C, D', participantAccountIDs4).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const lastChatHintText = Localize.translateLocal('accessibilityHints.lastChatMessagePreview'); + const lastChatText = screen.queryByLabelText(lastChatHintText); + + return waitFor(() => expect(lastChatText?.props?.children).toBe('B: Test')); + })); + + it('Should sort the names before displaying', () => + signInAndGetApp('', [USER_E_ACCOUNT_ID, ...participantAccountIDs4]).then(() => { + // Verify the sidebar links are rendered + const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); + const sidebarLinks = screen.queryAllByLabelText(sidebarLinksHintText); + expect(sidebarLinks).toHaveLength(1); + + // Verify there is only one option in the sidebar + const optionRowsHintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(optionRowsHintText); + expect(optionRows).toHaveLength(1); + + const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); + const displayNameText = screen.queryByLabelText(displayNameHintText); + + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E')); + })); +}); diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 1d31a707d81d..1a0bd3aeb279 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -25,6 +25,7 @@ import type {ReportAction, ReportActions} from '@src/types/onyx'; import type {NativeNavigationMock} from '../../__mocks__/@react-navigation/native'; import PusherHelper from '../utils/PusherHelper'; import * as TestHelper from '../utils/TestHelper'; +import {navigateToSidebarOption} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -82,13 +83,6 @@ function navigateToSidebar(): Promise { return waitForBatchedUpdates(); } -async function navigateToSidebarOption(index: number): Promise { - const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - const optionRows = screen.queryAllByAccessibilityHint(hintText); - fireEvent(optionRows[index], 'press'); - await waitForBatchedUpdatesWithAct(); -} - function areYouOnChatListScreen(): boolean { const hintText = Localize.translateLocal('sidebarScreen.listOfChats'); const sidebarLinks = screen.queryAllByLabelText(hintText); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 598c0e3bcbd6..29502ba6177e 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -10,6 +10,7 @@ import type {PersonalDetailsList, Policy, Report, ReportAction} from '@src/types import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import * as NumberUtils from '../../src/libs/NumberUtils'; import * as LHNTestUtils from '../utils/LHNTestUtils'; +import {fakePersonalDetails} from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // Be sure to include the mocked permissions library or else the beta tests won't work @@ -991,4 +992,97 @@ describe('ReportUtils', () => { expect(report).toEqual(undefined); }); }); + + describe('getGroupChatName tests', () => { + afterEach(() => Onyx.clear()); + + describe('When participantAccountIDs is passed to getGroupChatName', () => { + it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4])).toEqual('Four, One, Three, Two'); + }); + + it('Should show all participants name if count <= 5 and shouldApplyLimit is true', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('Four, One, Three, Two'); + }); + + it('Should show 5 participants name if count > 5 and shouldApplyLimit is true', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], true)).toEqual('Five, Four, One, Three, Two'); + }); + + it('Should show all participants name if count > 5 and shouldApplyLimit is false', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + }); + + it('Should use correct display name for participants', async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); + expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); + }); + }); + + describe('When participantAccountIDs is not passed to getGroupChatName and report ID is passed', () => { + it('Should show report name if count <= 5 and shouldApplyLimit is false', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4], 0, false, [1]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count <= 5 and shouldApplyLimit is true', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4], 0, false, [1]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, true, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count > 5 and shouldApplyLimit is true', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, true, report)).toEqual("Let's talk"); + }); + + it('Should show report name if count > 5 and shouldApplyLimit is false', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: "Let's talk", + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual("Let's talk"); + }); + + it('Should show participant names if report name is not available', async () => { + const report = { + ...LHNTestUtils.getFakeReport([1, 2, 3, 4, 5, 6, 7, 8], 0, false, [1, 2]), + chatType: CONST.REPORT.CHAT_TYPE.GROUP, + reportID: `1`, + reportName: '', + }; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}1`, report); + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); + expect(ReportUtils.getGroupChatName(undefined, false, report)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + }); + }); + }); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 7197529cd43c..1d8a13ead7d6 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -112,6 +112,13 @@ const fakePersonalDetails: PersonalDetailsList = { avatar: 'none', firstName: 'Nine', }, + 10: { + accountID: 10, + login: 'email10@test.com', + displayName: 'Email Ten', + avatar: 'none', + firstName: 'Ten', + }, }; let lastFakeReportID = 0; @@ -120,16 +127,25 @@ let lastFakeReportActionID = 0; /** * @param millisecondsInThePast the number of milliseconds in the past for the last message timestamp (to order reports by most recent messages) */ -function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0, isUnread = false): Report { +function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0, isUnread = false, adminIDs: number[] = []): Report { const lastVisibleActionCreated = DateUtils.getDBTime(Date.now() - millisecondsInThePast); + const participants = ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs); + + adminIDs.forEach((id) => { + participants[id] = { + hidden: false, + role: CONST.REPORT.ROLE.ADMIN, + }; + }); + return { type: CONST.REPORT.TYPE.CHAT, reportID: `${++lastFakeReportID}`, reportName: 'Report', lastVisibleActionCreated, lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastVisibleActionCreated, 1) : lastVisibleActionCreated, - participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), + participants, }; } diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 84ea2b2aafbe..6f97c23a5b29 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,3 +1,4 @@ +import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; @@ -13,6 +14,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import appSetup from '@src/setup'; import type {Response as OnyxResponse, PersonalDetails, Report} from '@src/types/onyx'; import waitForBatchedUpdates from './waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from './waitForBatchedUpdatesWithAct'; +import * as Localize from '@libs/Localize'; type MockFetch = jest.MockedFn & { pause: () => void; @@ -305,6 +308,59 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { ).toEqual(expect.objectContaining(obj)); } +/** + * This is a helper function to create a mock for the addListener function of the react-navigation library. + * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate + * the transitionEnd event that is triggered when the screen transition animation is completed. + * + * @returns An object with two functions: triggerTransitionEnd and addListener + */ +const createAddListenerMock = () => { + const transitionEndListeners: Listener[] = []; + const triggerTransitionEnd = () => { + transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); + }; + + const addListener = jest.fn().mockImplementation((listener, callback: Listener) => { + if (listener === 'transitionEnd') { + transitionEndListeners.push(callback); + } + return () => { + transitionEndListeners.filter((cb) => cb !== callback); + }; + }); + + return {triggerTransitionEnd, addListener}; +}; + +async function navigateToSidebarOption(index: number): Promise { + const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); + const optionRows = screen.queryAllByAccessibilityHint(hintText); + fireEvent(optionRows[index], 'press'); + await waitForBatchedUpdatesWithAct(); +} + +function beforeAllSetupUITests(shouldConnectToPusher = false) { + // In this test, we are generically mocking the responses of all API requests by mocking fetch() and having it + // return 200. In other tests, we might mock HttpUtils.xhr() with a more specific mock data response (which means + // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling + // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to + // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. + global.fetch = getGlobalFetchMock(); + + Linking.setInitialURL('https://new.expensify.com/'); + appSetup(); + + if (shouldConnectToPusher) { + PusherConnectionManager.init(); + Pusher.init({ + appKey: CONFIG.PUSHER.APP_KEY, + cluster: CONFIG.PUSHER.CLUSTER, + authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, + }); + } +} + export type {MockFetch, FormData}; export { assertFormDataMatchesObject, @@ -318,4 +374,7 @@ export { expectAPICommandToHaveBeenCalled, expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, + createAddListenerMock, + navigateToSidebarOption, + beforeAllSetupUITests, }; From 7b5298a9e61ceeefdb87d3398902a6038602bffa Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 2 Aug 2024 02:34:04 +0530 Subject: [PATCH 02/94] Update --- __mocks__/@react-native-reanimated/index.ts | 3 ++- tests/ui/GroupChatNameTests.tsx | 4 ++++ tests/utils/TestHelper.ts | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts index 28efba1dde69..df9cc4ecef8d 100644 --- a/__mocks__/@react-native-reanimated/index.ts +++ b/__mocks__/@react-native-reanimated/index.ts @@ -1,4 +1,5 @@ -// __mocks__/react-native-reanimated/index.js +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + const actualAnimated = jest.requireActual('react-native-reanimated/mock'); const mock = { diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index e0ac71120768..7eb70b412f70 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -1,4 +1,8 @@ /* eslint-disable testing-library/no-node-access */ + +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ import type * as NativeNavigation from '@react-navigation/native'; import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 6f97c23a5b29..b10f165a96be 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,8 +1,10 @@ import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; +import type {Listener} from 'onfido-sdk-ui/types/shared/EventEmitter'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import type {ApiCommand, ApiRequestCommandParameters} from '@libs/API/types'; +import * as Localize from '@libs/Localize'; import * as Pusher from '@libs/Pusher/pusher'; import PusherConnectionManager from '@libs/PusherConnectionManager'; import CONFIG from '@src/CONFIG'; @@ -15,7 +17,6 @@ import appSetup from '@src/setup'; import type {Response as OnyxResponse, PersonalDetails, Report} from '@src/types/onyx'; import waitForBatchedUpdates from './waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from './waitForBatchedUpdatesWithAct'; -import * as Localize from '@libs/Localize'; type MockFetch = jest.MockedFn & { pause: () => void; @@ -318,6 +319,7 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { const createAddListenerMock = () => { const transitionEndListeners: Listener[] = []; const triggerTransitionEnd = () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); }; From a428070ae660713f0a7bfab57631b20a4c9b00fe Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:38:39 +0530 Subject: [PATCH 03/94] Update --- tests/ui/GroupChatNameTests.tsx | 50 +-------------------------------- tests/utils/TestHelper.ts | 1 - 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 7eb70b412f70..fa84ee1e12d5 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -45,55 +45,7 @@ type ListenerMock = { addListener: jest.Mock; }; -/** - * This is a helper function to create a mock for the addListener function of the react-navigation library. - * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate - * the transitionEnd event that is triggered when the screen transition animation is completed. - * - * This can't be moved to a utils file because Jest wants any external function to stay in the scope. - * Details: https://github.com/jestjs/jest/issues/2567 - */ -const createAddListenerMock = (): ListenerMock => { - const transitionEndListeners: Array<() => void> = []; - const triggerTransitionEnd = () => { - transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); - }; - - const addListener: jest.Mock = jest.fn().mockImplementation((listener, callback: () => void) => { - if (listener === 'transitionEnd') { - transitionEndListeners.push(callback); - } - return () => { - transitionEndListeners.filter((cb) => cb !== callback); - }; - }); - - return {triggerTransitionEnd, addListener}; -}; - -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - const {triggerTransitionEnd, addListener} = createAddListenerMock(); - transitionEndCB = triggerTransitionEnd; - - const useNavigation = () => - ({ - navigate: jest.fn(), - ...actualNav.useNavigation, - getState: () => ({ - routes: [], - }), - addListener, - } as typeof NativeNavigation.useNavigation); - - return { - ...actualNav, - useNavigation, - getState: () => ({ - routes: [], - }), - } as typeof NativeNavigation; -}); +jest.mock('@react-navigation/native'); beforeAll(() => { TestHelper.beforeAllSetupUITests(); diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index b10f165a96be..f80ce747837c 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -1,6 +1,5 @@ import {fireEvent, screen} from '@testing-library/react-native'; import {Str} from 'expensify-common'; -import type {Listener} from 'onfido-sdk-ui/types/shared/EventEmitter'; import {Linking} from 'react-native'; import Onyx from 'react-native-onyx'; import type {ApiCommand, ApiRequestCommandParameters} from '@libs/API/types'; From e8956bcc2b2c5e77054675f39dc8a740a27f3742 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:44:44 +0530 Subject: [PATCH 04/94] Update TestHelper.ts --- tests/utils/TestHelper.ts | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index f80ce747837c..72dbfaafe61f 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -308,32 +308,6 @@ function assertFormDataMatchesObject(formData: FormData, obj: Report) { ).toEqual(expect.objectContaining(obj)); } -/** - * This is a helper function to create a mock for the addListener function of the react-navigation library. - * The reason we need this is because we need to trigger the transitionEnd event in our tests to simulate - * the transitionEnd event that is triggered when the screen transition animation is completed. - * - * @returns An object with two functions: triggerTransitionEnd and addListener - */ -const createAddListenerMock = () => { - const transitionEndListeners: Listener[] = []; - const triggerTransitionEnd = () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return - transitionEndListeners.forEach((transitionEndListener) => transitionEndListener()); - }; - - const addListener = jest.fn().mockImplementation((listener, callback: Listener) => { - if (listener === 'transitionEnd') { - transitionEndListeners.push(callback); - } - return () => { - transitionEndListeners.filter((cb) => cb !== callback); - }; - }); - - return {triggerTransitionEnd, addListener}; -}; - async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); const optionRows = screen.queryAllByAccessibilityHint(hintText); @@ -375,7 +349,6 @@ export { expectAPICommandToHaveBeenCalled, expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, - createAddListenerMock, navigateToSidebarOption, beforeAllSetupUITests, }; From 3e2f30e6eacb00fe7d154a859d49574a3f726b09 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:11:44 +0530 Subject: [PATCH 05/94] Update --- tests/unit/ReportUtilsTest.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 334244ec066e..9543e02c3c73 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -996,30 +996,48 @@ describe('ReportUtils', () => { describe('getGroupChatName tests', () => { afterEach(() => Onyx.clear()); + const fourParticipants = [ + {accountID: 1, login: "email1@test.com"}, + {accountID: 2, login: "email2@test.com"}, + {accountID: 3, login: "email3@test.com"}, + {accountID: 4, login: "email4@test.com"}, + ] + + const eightParticipants = [ + {accountID: 1, login: "email1@test.com"}, + {accountID: 2, login: "email2@test.com"}, + {accountID: 3, login: "email3@test.com"}, + {accountID: 4, login: "email4@test.com"}, + {accountID: 5, login: "email5@test.com"}, + {accountID: 6, login: "email6@test.com"}, + {accountID: 7, login: "email7@test.com"}, + {accountID: 8, login: "email8@test.com"}, + ] + describe('When participantAccountIDs is passed to getGroupChatName', () => { it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4])).toEqual('Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(fourParticipants)).toEqual('Four, One, Three, Two'); }); it('Should show all participants name if count <= 5 and shouldApplyLimit is true', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(fourParticipants)).toEqual('Four, One, Three, Two'); }); it('Should show 5 participants name if count > 5 and shouldApplyLimit is true', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], true)).toEqual('Five, Four, One, Three, Two'); + expect(ReportUtils.getGroupChatName(eightParticipants, true)).toEqual('Five, Four, One, Three, Two'); }); it('Should show all participants name if count > 5 and shouldApplyLimit is false', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, fakePersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4, 5, 6, 7, 8], false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); + expect(ReportUtils.getGroupChatName(eightParticipants, false)).toEqual('Eight, Five, Four, One, Seven, Six, Three, Two'); }); it('Should use correct display name for participants', async () => { await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, participantsPersonalDetails); - expect(ReportUtils.getGroupChatName([1, 2, 3, 4], true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); + expect(ReportUtils.getGroupChatName(fourParticipants, true)).toEqual('(833) 240-3627, floki@vikings.net, Lagertha, Ragnar'); }); }); From 84deb67dd9c5e3003286a160f994e0b7ae40e2c6 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:16:01 +0530 Subject: [PATCH 06/94] Update --- tests/ui/GroupChatNameTests.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index fa84ee1e12d5..25defa2ff283 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -3,7 +3,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import type * as NativeNavigation from '@react-navigation/native'; import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; @@ -40,11 +39,6 @@ jest.mock('react-native/Libraries/LogBox/LogBox', () => ({ */ let transitionEndCB: () => void; -type ListenerMock = { - triggerTransitionEnd: () => void; - addListener: jest.Mock; -}; - jest.mock('@react-navigation/native'); beforeAll(() => { From 221b3ff1399fb6120b2fa8d7fb8016626b3bf8c3 Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:31:49 +0530 Subject: [PATCH 07/94] Lint fixes --- tests/unit/ReportUtilsTest.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 9543e02c3c73..bf8f48e988ab 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -997,22 +997,22 @@ describe('ReportUtils', () => { afterEach(() => Onyx.clear()); const fourParticipants = [ - {accountID: 1, login: "email1@test.com"}, - {accountID: 2, login: "email2@test.com"}, - {accountID: 3, login: "email3@test.com"}, - {accountID: 4, login: "email4@test.com"}, - ] + {accountID: 1, login: 'email1@test.com'}, + {accountID: 2, login: 'email2@test.com'}, + {accountID: 3, login: 'email3@test.com'}, + {accountID: 4, login: 'email4@test.com'}, + ]; const eightParticipants = [ - {accountID: 1, login: "email1@test.com"}, - {accountID: 2, login: "email2@test.com"}, - {accountID: 3, login: "email3@test.com"}, - {accountID: 4, login: "email4@test.com"}, - {accountID: 5, login: "email5@test.com"}, - {accountID: 6, login: "email6@test.com"}, - {accountID: 7, login: "email7@test.com"}, - {accountID: 8, login: "email8@test.com"}, - ] + {accountID: 1, login: 'email1@test.com'}, + {accountID: 2, login: 'email2@test.com'}, + {accountID: 3, login: 'email3@test.com'}, + {accountID: 4, login: 'email4@test.com'}, + {accountID: 5, login: 'email5@test.com'}, + {accountID: 6, login: 'email6@test.com'}, + {accountID: 7, login: 'email7@test.com'}, + {accountID: 8, login: 'email8@test.com'}, + ]; describe('When participantAccountIDs is passed to getGroupChatName', () => { it('Should show all participants name if count <= 5 and shouldApplyLimit is false', async () => { From beb006f9e68dfc560e39183a219a337937f7e7d8 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 16:35:53 +0530 Subject: [PATCH 08/94] fix: Room - Create room whisper reappears when interacting with it after workspace is deleted. Signed-off-by: krishna2323 --- .../AttachmentCarousel/extractAttachments.ts | 5 +++-- .../AttachmentCarousel/index.native.tsx | 4 ++-- .../Attachments/AttachmentCarousel/index.tsx | 18 +++++++++++++++--- src/components/ParentNavigationSubtitle.tsx | 2 +- src/libs/OptionsListUtils.ts | 4 ++-- src/libs/ReportActionsUtils.ts | 19 ++++++++++++++----- src/libs/SidebarUtils.ts | 2 +- src/pages/home/ReportScreen.tsx | 5 ++++- src/pages/home/report/ReportActionsList.tsx | 4 ++-- 9 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 81ee6d08934b..69b0b8229f4a 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -19,7 +19,8 @@ function extractAttachments( accountID, parentReportAction, reportActions, - }: {privateNotes?: Record; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry}, + reportID, + }: {privateNotes?: Record; accountID?: number; parentReportAction?: OnyxEntry; reportActions?: OnyxEntry; reportID: string}, ) { const targetNote = privateNotes?.[Number(accountID)]?.note ?? ''; const attachments: Attachment[] = []; @@ -95,7 +96,7 @@ function extractAttachments( const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; actions.forEach((action, key) => { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key) || ReportActionsUtils.isMoneyRequestAction(action)) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID) || ReportActionsUtils.isMoneyRequestAction(action)) { return; } diff --git a/src/components/Attachments/AttachmentCarousel/index.native.tsx b/src/components/Attachments/AttachmentCarousel/index.native.tsx index a8eb614202a7..9aa619eb1cda 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.native.tsx @@ -34,9 +34,9 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; let newAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID}); + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID, reportID: report.reportID}); } else { - newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions}); + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions, reportID: report.reportID}); } let newIndex = newAttachments.findIndex(compareImage); diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index a1408aaf400e..ac4975d85665 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -76,9 +76,9 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi const parentReportAction = report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : undefined; let newAttachments: Attachment[] = []; if (type === CONST.ATTACHMENT_TYPE.NOTE && accountID) { - newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID}); + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.NOTE, {privateNotes: report.privateNotes, accountID, reportID: report.reportID}); } else { - newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined}); + newAttachments = extractAttachments(CONST.ATTACHMENT_TYPE.REPORT, {parentReportAction, reportActions: reportActions ?? undefined, reportID: report.reportID}); } if (isEqual(attachments, newAttachments)) { @@ -117,7 +117,19 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi onNavigate(attachment); } } - }, [report.privateNotes, reportActions, parentReportActions, compareImage, report.parentReportActionID, attachments, setDownloadButtonVisibility, onNavigate, accountID, type]); + }, [ + report.privateNotes, + reportActions, + parentReportActions, + compareImage, + report.parentReportActionID, + attachments, + setDownloadButtonVisibility, + onNavigate, + accountID, + type, + report.reportID, + ]); // Scroll position is affected when window width is resized, so we readjust it on width changes useEffect(() => { diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 997106f3e649..ef0f981a8c77 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -39,7 +39,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct { const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1'); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', parentReportID); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID)); if (isVisibleAction && !isOffline) { diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index fbf2f3b94c7c..2e19d5a9538f 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -327,7 +327,7 @@ Onyx.connect({ // does not match a closed or created state. const reportActionsForDisplay = sortedReportActions.filter( (reportAction, actionKey) => - ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey, reportID) && !ReportActionUtils.isWhisperAction(reportAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && @@ -677,7 +677,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails const iouReport = ReportUtils.getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find( (reportAction, key): reportAction is ReportAction => - ReportActionUtils.shouldReportActionBeVisible(reportAction, key) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, key, reportID) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && ReportActionUtils.isMoneyRequestAction(reportAction), ); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 3b5e0a8eeaa3..105fbee48c31 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -27,6 +27,7 @@ import Parser from './Parser'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; +import * as ReportUtils from './ReportUtils'; import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; import StringUtils from './StringUtils'; // eslint-disable-next-line import/no-cycle @@ -630,7 +631,15 @@ const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTy * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. */ -function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number): boolean { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, reportID: string): boolean { + const report = ReportUtils.getReport(reportID); + if ( + (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPending(report?.reportID ?? '-1') || isActionableMentionWhisper(reportAction)) && + !ReportUtils.canUserPerformWriteAction(report) + ) { + return false; + } + if (!reportAction) { return false; } @@ -706,7 +715,7 @@ function isResolvedActionTrackExpense(reportAction: OnyxEntry): bo * Checks if a reportAction is fit for display as report last action, meaning that * it satisfies shouldReportActionBeVisible, it's not whisper action and not deleted. */ -function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry): boolean { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry, reportID: string): boolean { if (!reportAction) { return false; } @@ -718,7 +727,7 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry< // If a whisper action is the REPORT_PREVIEW action, we are displaying it. // If the action's message text is empty and it is not a deleted parent with visible child actions, hide it. Else, consider the action to be displayable. return ( - shouldReportActionBeVisible(reportAction, reportAction.reportActionID) && + shouldReportActionBeVisible(reportAction, reportAction.reportActionID, reportID) && !(isWhisperAction(reportAction) && !isReportPreviewAction(reportAction) && !isMoneyRequestAction(reportAction)) && !(isDeletedAction(reportAction) && !isDeletedParentAction(reportAction)) && !isResolvedActionTrackExpense(reportAction) @@ -760,7 +769,7 @@ function getLastVisibleAction(reportID: string, actionsToMerge: Record shouldReportActionBeVisibleAsLastAction(action)); + const visibleReportActions = reportActions.filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action, reportID)); const sortedReportActions = getSortedReportActions(visibleReportActions, true); if (sortedReportActions.length === 0) { return undefined; @@ -1087,7 +1096,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn */ function doesReportHaveVisibleActions(reportID: string, actionsToMerge: ReportActions = {}): boolean { const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge, true)); - const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action)); + const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, reportID)); // Exclude the task system message and the created message const visibleReportActionsWithoutTaskSystemMessage = visibleReportActions.filter((action) => !isTaskAction(action) && !isCreatedAction(action)); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index eb5b3c58cdef..4c2a245af9dc 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -46,7 +46,7 @@ Onyx.connect({ // The report is only visible if it is the last action not deleted that // does not match a closed or created state. const reportActionsForDisplay = actionsArray.filter( - (reportAction) => ReportActionsUtils.shouldReportActionBeVisibleAsLastAction(reportAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, + (reportAction) => ReportActionsUtils.shouldReportActionBeVisibleAsLastAction(reportAction, reportID) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, ); const reportAction = reportActionsForDisplay.at(-1); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index 8afeb0cf2307..56545e80aeae 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -335,7 +335,10 @@ function ReportScreen({route, currentReportID = '', navigation}: ReportScreenPro ? reportActions.length > 0 : reportActions.length >= CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); - const isLinkedActionDeleted = useMemo(() => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID), [linkedAction]); + const isLinkedActionDeleted = useMemo( + () => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, report?.reportID ?? '-1'), + [linkedAction, report?.reportID], + ); const prevIsLinkedActionDeleted = usePrevious(linkedAction ? isLinkedActionDeleted : undefined); const isLinkedActionInaccessibleWhisper = useMemo( () => !!linkedAction && ReportActionsUtils.isWhisperAction(linkedAction) && !(linkedAction?.whisperedToAccountIDs ?? []).includes(currentUserAccountID), diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index ce925d4375af..6025fe64ffb2 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -193,9 +193,9 @@ function ReportActionsList({ ReportActionsUtils.isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors) && - ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID), + ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID, report.reportID), ), - [sortedReportActions, isOffline], + [sortedReportActions, isOffline, report.reportID], ); /** From 921f47429960cd6b5f1110e50f5bb7139a6736e2 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 16:43:39 +0530 Subject: [PATCH 09/94] fix lint issues. Signed-off-by: krishna2323 --- src/libs/ReportActionsUtils.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 105fbee48c31..8681980163e6 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -28,7 +28,6 @@ import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; import * as ReportUtils from './ReportUtils'; -import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; import StringUtils from './StringUtils'; // eslint-disable-next-line import/no-cycle import * as TransactionUtils from './TransactionUtils'; @@ -123,7 +122,7 @@ function isCreatedAction(reportAction: OnyxInputOrEntry): boolean return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; } -function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { +function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { const message = reportAction?.message ?? []; if (!Array.isArray(message)) { @@ -136,7 +135,7 @@ function isDeletedAction(reportAction: OnyxInputOrEntry): bo return (getReportActionMessage(reportAction)?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } -function isReversedTransaction(reportAction: OnyxInputOrEntry) { +function isReversedTransaction(reportAction: OnyxInputOrEntry) { return (getReportActionMessage(reportAction)?.isReversedTransaction ?? false) && ((reportAction as ReportAction)?.childVisibleActionCount ?? 0) > 0; } @@ -360,7 +359,7 @@ function getParentReportAction(report: OnyxInputOrEntry): OnyxEntry): boolean { +function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { return ( isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && @@ -990,11 +989,11 @@ function isSplitBillAction(reportAction: OnyxInputOrEntry): report return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { +function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } -function isPayAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { +function isPayAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY; } @@ -1204,7 +1203,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): ]; } -function getReportActionHtml(reportAction: PartialReportAction): string { +function getReportActionHtml(reportAction: ReportUtils.PartialReportAction): string { return getReportActionMessage(reportAction)?.html ?? ''; } @@ -1220,7 +1219,7 @@ function getTextFromHtml(html?: string): string { return html ? Parser.htmlToText(html) : ''; } -function isOldDotLegacyAction(action: OldDotReportAction | PartialReportAction): action is PartialReportAction { +function isOldDotLegacyAction(action: OldDotReportAction | ReportUtils.PartialReportAction): action is ReportUtils.PartialReportAction { return [ CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT, CONST.REPORT.ACTIONS.TYPE.DONATION, @@ -1260,7 +1259,7 @@ function isOldDotReportAction(action: ReportAction | OldDotReportAction) { ].some((oldDotActionName) => oldDotActionName === action.actionName); } -function getMessageOfOldDotLegacyAction(legacyAction: PartialReportAction) { +function getMessageOfOldDotLegacyAction(legacyAction: ReportUtils.PartialReportAction) { if (!Array.isArray(legacyAction?.message)) { return getReportActionText(legacyAction); } @@ -1275,7 +1274,7 @@ function getMessageOfOldDotLegacyAction(legacyAction: PartialReportAction) { /** * Helper method to format message of OldDot Actions. */ -function getMessageOfOldDotReportAction(oldDotAction: PartialReportAction | OldDotReportAction, withMarkdown = true): string { +function getMessageOfOldDotReportAction(oldDotAction: ReportUtils.PartialReportAction | OldDotReportAction, withMarkdown = true): string { if (isOldDotLegacyAction(oldDotAction)) { return getMessageOfOldDotLegacyAction(oldDotAction); } From 597e24318607e9d089961b35b9d008ceef0c9894 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 17:58:44 +0530 Subject: [PATCH 10/94] minor update. Signed-off-by: krishna2323 --- .../LHNOptionsList/LHNOptionsList.tsx | 2 +- src/hooks/usePaginatedReportActions.ts | 2 +- src/libs/Middleware/Pagination.ts | 4 +-- src/libs/ReportActionsUtils.ts | 31 ++++++++++--------- src/libs/actions/Report.ts | 2 +- src/pages/Debug/Report/DebugReportActions.tsx | 2 +- .../report/ReportActionItemParentAction.tsx | 6 +++- src/pages/home/report/ReportActionsView.tsx | 2 +- src/pages/home/report/ThreadDivider.tsx | 2 +- .../perf-test/ReportActionsUtils.perf-test.ts | 4 +-- tests/unit/ReportActionsUtilsTest.ts | 6 ++-- 11 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 08240a211804..b317d3020e2a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -139,7 +139,7 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio : '-1'; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions, reportID); const lastReportAction = sortedReportActions.at(0); // Get the transaction for the last report action diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts index a32d4f7d3dd0..e3525dcf91f5 100644 --- a/src/hooks/usePaginatedReportActions.ts +++ b/src/hooks/usePaginatedReportActions.ts @@ -14,7 +14,7 @@ function usePaginatedReportActions(reportID?: string, reportActionID?: string) { const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, true), }); const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${reportIDWithDefault}`); diff --git a/src/libs/Middleware/Pagination.ts b/src/libs/Middleware/Pagination.ts index bfa8183ac03b..7b3abdee849c 100644 --- a/src/libs/Middleware/Pagination.ts +++ b/src/libs/Middleware/Pagination.ts @@ -15,7 +15,7 @@ type PagedResource = OnyxValues[TResourc type PaginationCommonConfig = { resourceCollectionKey: TResourceKey; pageCollectionKey: TPageKey; - sortItems: (items: OnyxValues[TResourceKey]) => Array>; + sortItems: (items: OnyxValues[TResourceKey], reportID: string) => Array>; getItemID: (item: PagedResource) => string; }; @@ -96,7 +96,7 @@ const Pagination: Middleware = (requestResponse, request) => { // Create a new page based on the response const pageItems = (response.onyxData.find((data) => data.key === resourceKey)?.value ?? {}) as OnyxValues[typeof resourceCollectionKey]; - const sortedPageItems = sortItems(pageItems); + const sortedPageItems = sortItems(pageItems, resourceID); if (sortedPageItems.length === 0) { // Must have at least 1 action to create a page. Log.hmmm(`[Pagination] Did not receive any items in the response to ${request.command}`); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8681980163e6..8a1ff9431e28 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -27,7 +27,8 @@ import Parser from './Parser'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; -import * as ReportUtils from './ReportUtils'; +import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; +import {canUserPerformWriteAction, getReport} from './ReportUtils.ts'; import StringUtils from './StringUtils'; // eslint-disable-next-line import/no-cycle import * as TransactionUtils from './TransactionUtils'; @@ -122,7 +123,7 @@ function isCreatedAction(reportAction: OnyxInputOrEntry): boolean return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED; } -function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { +function isDeletedAction(reportAction: OnyxInputOrEntry): boolean { const message = reportAction?.message ?? []; if (!Array.isArray(message)) { @@ -135,7 +136,7 @@ function isDeletedAction(reportAction: OnyxInputOrEntry): bo return (getReportActionMessage(reportAction)?.isDeletedParentAction ?? false) && (reportAction?.childVisibleActionCount ?? 0) > 0; } -function isReversedTransaction(reportAction: OnyxInputOrEntry) { +function isReversedTransaction(reportAction: OnyxInputOrEntry) { return (getReportActionMessage(reportAction)?.isReversedTransaction ?? false) && ((reportAction as ReportAction)?.childVisibleActionCount ?? 0) > 0; } @@ -359,7 +360,7 @@ function getParentReportAction(report: OnyxInputOrEntry): OnyxEntry): boolean { +function isSentMoneyReportAction(reportAction: OnyxEntry): boolean { return ( isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY && @@ -631,10 +632,10 @@ const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTy * and supported type, it's not deleted and also not closed. */ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, reportID: string): boolean { - const report = ReportUtils.getReport(reportID); + const report = getReport(reportID); if ( (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPending(report?.reportID ?? '-1') || isActionableMentionWhisper(reportAction)) && - !ReportUtils.canUserPerformWriteAction(report) + !canUserPerformWriteAction(report) ) { return false; } @@ -834,7 +835,7 @@ function filterOutDeprecatedReportActions(reportActions: OnyxEntry | ReportAction[], shouldIncludeInvisibleActions = false): ReportAction[] { +function getSortedReportActionsForDisplay(reportActions: OnyxEntry | ReportAction[], reportID: string, shouldIncludeInvisibleActions = false): ReportAction[] { let filteredReportActions: ReportAction[] = []; if (!reportActions) { return []; @@ -844,7 +845,7 @@ function getSortedReportActionsForDisplay(reportActions: OnyxEntry shouldReportActionBeVisible(reportAction, key)) + .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key, reportID)) .map(([, reportAction]) => reportAction); } @@ -989,11 +990,11 @@ function isSplitBillAction(reportAction: OnyxInputOrEntry): report return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; } -function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { +function isTrackExpenseAction(reportAction: OnyxEntry): reportAction is ReportAction { return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.TRACK; } -function isPayAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { +function isPayAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction { return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY; } @@ -1203,7 +1204,7 @@ function getMemberChangeMessageElements(reportAction: OnyxEntry): ]; } -function getReportActionHtml(reportAction: ReportUtils.PartialReportAction): string { +function getReportActionHtml(reportAction: PartialReportAction): string { return getReportActionMessage(reportAction)?.html ?? ''; } @@ -1219,7 +1220,7 @@ function getTextFromHtml(html?: string): string { return html ? Parser.htmlToText(html) : ''; } -function isOldDotLegacyAction(action: OldDotReportAction | ReportUtils.PartialReportAction): action is ReportUtils.PartialReportAction { +function isOldDotLegacyAction(action: OldDotReportAction | PartialReportAction): action is PartialReportAction { return [ CONST.REPORT.ACTIONS.TYPE.DELETED_ACCOUNT, CONST.REPORT.ACTIONS.TYPE.DONATION, @@ -1259,7 +1260,7 @@ function isOldDotReportAction(action: ReportAction | OldDotReportAction) { ].some((oldDotActionName) => oldDotActionName === action.actionName); } -function getMessageOfOldDotLegacyAction(legacyAction: ReportUtils.PartialReportAction) { +function getMessageOfOldDotLegacyAction(legacyAction: PartialReportAction) { if (!Array.isArray(legacyAction?.message)) { return getReportActionText(legacyAction); } @@ -1274,7 +1275,7 @@ function getMessageOfOldDotLegacyAction(legacyAction: ReportUtils.PartialReportA /** * Helper method to format message of OldDot Actions. */ -function getMessageOfOldDotReportAction(oldDotAction: ReportUtils.PartialReportAction | OldDotReportAction, withMarkdown = true): string { +function getMessageOfOldDotReportAction(oldDotAction: PartialReportAction | OldDotReportAction, withMarkdown = true): string { if (isOldDotLegacyAction(oldDotAction)) { return getMessageOfOldDotLegacyAction(oldDotAction); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 13b14d380758..7ac929b9fad0 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -274,7 +274,7 @@ registerPaginationConfig({ nextCommand: READ_COMMANDS.GET_NEWER_ACTIONS, resourceCollectionKey: ONYXKEYS.COLLECTION.REPORT_ACTIONS, pageCollectionKey: ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES, - sortItems: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), + sortItems: (reportActions, reportID) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportID, true), getItemID: (reportAction) => reportAction.reportActionID, }); diff --git a/src/pages/Debug/Report/DebugReportActions.tsx b/src/pages/Debug/Report/DebugReportActions.tsx index e7c4059fffe7..d25c9175a4b3 100644 --- a/src/pages/Debug/Report/DebugReportActions.tsx +++ b/src/pages/Debug/Report/DebugReportActions.tsx @@ -23,7 +23,7 @@ function DebugReportActions({reportID}: DebugReportActionsProps) { const styles = useThemeStyles(); const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, true), }); const renderItem = ({item}: ListRenderItemInfo) => ( { - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1'); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( + ancestor.reportAction, + ancestor.reportAction.reportActionID ?? '-1', + ancestor.report.reportID, + ); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1')); if (isVisibleAction && !isOffline) { diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index 8896611905ca..b58de22bc520 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -90,7 +90,7 @@ function ReportActionsView({ const route = useRoute>(); const [session] = useOnyx(ONYXKEYS.SESSION); const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? -1}`, { - selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true), + selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, report.reportID, true), }); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? -1}`); const prevTransactionThreadReport = usePrevious(transactionThreadReport); diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index d2ffa97f58b2..6fec617e4c37 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -47,7 +47,7 @@ function ThreadDivider({ancestor, isLinkDisabled = false}: ThreadDividerProps) { ) : ( { - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1'); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1', ancestor.report.reportID); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1')); if (isVisibleAction && !isOffline) { diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index a33a448cfee7..5e258436edc7 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -93,7 +93,7 @@ describe('ReportActionsUtils', () => { }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId); await waitForBatchedUpdates(); await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); @@ -132,7 +132,7 @@ describe('ReportActionsUtils', () => { test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions)); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index d753069265f8..b6e29e89d025 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -302,7 +302,7 @@ describe('ReportActionsUtils', () => { }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1'); expect(result).toStrictEqual(input); }); @@ -392,7 +392,7 @@ describe('ReportActionsUtils', () => { ], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1'); input.pop(); expect(result).toStrictEqual(input); }); @@ -437,7 +437,7 @@ describe('ReportActionsUtils', () => { message: [{html: '', type: 'Action type', text: 'Action text'}], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1'); input.pop(); expect(result).toStrictEqual(input); }); From 352abf82c0e2c577133871a18813af3189dbe22b Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 17:59:42 +0530 Subject: [PATCH 11/94] minor update. Signed-off-by: krishna2323 --- src/libs/Middleware/Pagination.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Middleware/Pagination.ts b/src/libs/Middleware/Pagination.ts index 7b3abdee849c..251609d1254c 100644 --- a/src/libs/Middleware/Pagination.ts +++ b/src/libs/Middleware/Pagination.ts @@ -115,7 +115,7 @@ const Pagination: Middleware = (requestResponse, request) => { const resourceCollections = resources.get(resourceCollectionKey) ?? {}; const existingItems = resourceCollections[resourceKey] ?? {}; const allItems = fastMerge(existingItems, pageItems, true); - const sortedAllItems = sortItems(allItems); + const sortedAllItems = sortItems(allItems, resourceID); const pagesCollections = pages.get(pageCollectionKey) ?? {}; const existingPages = pagesCollections[pageKey] ?? []; From 534c19e7e5fbd9c934bd725ef5570c9525012530 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 13 Oct 2024 21:44:24 +0530 Subject: [PATCH 12/94] fix lint issue. Signed-off-by: krishna2323 --- src/hooks/usePaginatedReportActions.ts | 2 +- src/libs/ReportActionsUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts index e3525dcf91f5..342d73b08bd8 100644 --- a/src/hooks/usePaginatedReportActions.ts +++ b/src/hooks/usePaginatedReportActions.ts @@ -14,7 +14,7 @@ function usePaginatedReportActions(reportID?: string, reportActionID?: string) { const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID ?? '-1', true), }); const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${reportIDWithDefault}`); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 8a1ff9431e28..0807f8f95ed7 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -28,7 +28,7 @@ import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; -import {canUserPerformWriteAction, getReport} from './ReportUtils.ts'; +import {canUserPerformWriteAction, getReport} from './ReportUtils'; import StringUtils from './StringUtils'; // eslint-disable-next-line import/no-cycle import * as TransactionUtils from './TransactionUtils'; From 33d1ba31a3d0f1ec31e749eba325ed03c66918b2 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:59:08 +0530 Subject: [PATCH 13/94] Update --- tests/ui/GroupChatNameTests.tsx | 4 ---- tests/utils/TestHelper.ts | 29 +++++------------------------ 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 25defa2ff283..393cf2c6dad2 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -41,10 +41,6 @@ let transitionEndCB: () => void; jest.mock('@react-navigation/native'); -beforeAll(() => { - TestHelper.beforeAllSetupUITests(); -}); - const REPORT_ID = '1'; const USER_A_ACCOUNT_ID = 1; const USER_A_EMAIL = 'user_a@test.com'; diff --git a/tests/utils/TestHelper.ts b/tests/utils/TestHelper.ts index 084edc1d1567..394c05fe8b7c 100644 --- a/tests/utils/TestHelper.ts +++ b/tests/utils/TestHelper.ts @@ -313,30 +313,12 @@ function assertFormDataMatchesObject(obj: Report, formData?: FormData) { async function navigateToSidebarOption(index: number): Promise { const hintText = Localize.translateLocal('accessibilityHints.navigatesToChat'); - const optionRows = screen.queryAllByAccessibilityHint(hintText); - fireEvent(optionRows[index], 'press'); - await waitForBatchedUpdatesWithAct(); -} - -function beforeAllSetupUITests(shouldConnectToPusher = false) { - // In this test, we are generically mocking the responses of all API requests by mocking fetch() and having it - // return 200. In other tests, we might mock HttpUtils.xhr() with a more specific mock data response (which means - // fetch() never gets called so it does not need mocking) or we might have fetch throw an error to test error handling - // behavior. But here we just want to treat all API requests as a generic "success" and in the cases where we need to - // simulate data arriving we will just set it into Onyx directly with Onyx.merge() or Onyx.set() etc. - global.fetch = getGlobalFetchMock(); - - Linking.setInitialURL('https://new.expensify.com/'); - appSetup(); - - if (shouldConnectToPusher) { - PusherConnectionManager.init(); - Pusher.init({ - appKey: CONFIG.PUSHER.APP_KEY, - cluster: CONFIG.PUSHER.CLUSTER, - authEndpoint: `${CONFIG.EXPENSIFY.DEFAULT_API_ROOT}api/AuthenticatePusher?`, - }); + const optionRow = screen.queryAllByAccessibilityHint(hintText).at(index); + if (!optionRow) { + return; } + fireEvent(optionRow, 'press'); + await waitForBatchedUpdatesWithAct(); } export type {MockFetch, FormData}; @@ -353,5 +335,4 @@ export { expectAPICommandToHaveBeenCalledWith, setupGlobalFetchMock, navigateToSidebarOption, - beforeAllSetupUITests, }; From 304f0d2fc357e24e88006c0a35f821dc1f1639c1 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:05:35 +0530 Subject: [PATCH 14/94] Update --- tests/ui/GroupChatNameTests.tsx | 3 +++ tests/utils/LHNTestUtils.tsx | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 393cf2c6dad2..3c13605925c9 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -41,6 +41,8 @@ let transitionEndCB: () => void; jest.mock('@react-navigation/native'); +TestHelper.setupApp(); + const REPORT_ID = '1'; const USER_A_ACCOUNT_ID = 1; const USER_A_EMAIL = 'user_a@test.com'; @@ -69,6 +71,7 @@ function signInAndGetApp(reportName = '', participantAccountIDs?: number[]): Pro const participants: Record = {}; participantAccountIDs?.forEach((id) => { participants[id] = { + notificationPreference: 'always', hidden: false, role: id === 1 ? CONST.REPORT.ROLE.ADMIN : CONST.REPORT.ROLE.MEMBER, } as Participant; diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index 7a9873483069..02223d36f3c6 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -134,7 +134,7 @@ function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0 adminIDs.forEach((id) => { participants[id] = { - hidden: false, + notificationPreference: 'always', role: CONST.REPORT.ROLE.ADMIN, }; }); From e24e1ccd2c10cc2fff1f3addc8240f429d4f8d51 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:50:05 +0530 Subject: [PATCH 15/94] Update --- __mocks__/@react-native-reanimated/index.ts | 11 ----------- tests/ui/GroupChatNameTests.tsx | 4 ++-- 2 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 __mocks__/@react-native-reanimated/index.ts diff --git a/__mocks__/@react-native-reanimated/index.ts b/__mocks__/@react-native-reanimated/index.ts deleted file mode 100644 index df9cc4ecef8d..000000000000 --- a/__mocks__/@react-native-reanimated/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ - -const actualAnimated = jest.requireActual('react-native-reanimated/mock'); - -const mock = { - ...actualAnimated, - createAnimatedPropAdapter: jest.fn(), - useReducedMotion: jest.fn(), -}; - -export default mock; diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 3c13605925c9..919279ad00e3 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -174,7 +174,7 @@ describe('Tests for group chat name', () => { return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D')); })); - it('Should show all 8 names in LHN when 8 participants are present', () => + it('Should show limited names in LHN when 8 participants are present', () => signInAndGetApp('', participantAccountIDs8).then(() => { // Verify the sidebar links are rendered const sidebarLinksHintText = Localize.translateLocal('sidebarScreen.listOfChats'); @@ -189,7 +189,7 @@ describe('Tests for group chat name', () => { const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H')); + return waitFor(() => expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E')); })); it('Check if group name shows fine for report header', () => From a8b9955c47368b50588b7bbbb27026d71b855e32 Mon Sep 17 00:00:00 2001 From: ShridharGoel <35566748+ShridharGoel@users.noreply.github.com> Date: Wed, 30 Oct 2024 17:57:33 +0530 Subject: [PATCH 16/94] Update --- tests/ui/GroupChatNameTests.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/GroupChatNameTests.tsx b/tests/ui/GroupChatNameTests.tsx index 919279ad00e3..fc383efe4e28 100644 --- a/tests/ui/GroupChatNameTests.tsx +++ b/tests/ui/GroupChatNameTests.tsx @@ -236,7 +236,7 @@ describe('Tests for group chat name', () => { const displayNameHintText = Localize.translateLocal('accessibilityHints.chatUserDisplayNames'); const displayNameText = screen.queryByLabelText(displayNameHintText); - expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E, F, G, H'); + expect(displayNameText?.props?.children?.[0]).toBe('A, B, C, D, E'); return navigateToSidebarOption(0); }) From 0705ceed3a258cda1fb33e819b662b56539fd78a Mon Sep 17 00:00:00 2001 From: Ugo Giordano <144606237+ugogiordano@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:02:41 +0100 Subject: [PATCH 17/94] Fixing duplicate Concierge chat Addressing the issue of duplicate Concierge chats appearing in the Left Hand Navigation (LHN) when deep linking to Concierge. --- src/pages/ConciergePage.tsx | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index 46f17e76c083..7ab8deef1bef 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -1,9 +1,8 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect, useRef} from 'react'; +import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, useOnyx,withOnyx} from 'react-native-onyx'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -33,21 +32,23 @@ function ConciergePage({session}: ConciergePageProps) { const styles = useThemeStyles(); const isUnmounted = useRef(false); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); - useFocusEffect(() => { - if (session && 'authToken' in session) { - App.confirmReadyToOpenApp(); - // Pop the concierge loading page before opening the concierge report. - Navigation.isNavigationReady().then(() => { - if (isUnmounted.current) { - return; - } - Report.navigateToConciergeChat(true, () => !isUnmounted.current); - }); - } else { - Navigation.navigate(); - } - }); + useFocusEffect( + useCallback(() => { + if (session && 'authToken' in session) { + App.confirmReadyToOpenApp(); + Navigation.isNavigationReady().then(() => { + if (isUnmounted.current || isLoadingReportData === undefined || !!isLoadingReportData) { + return; + } + Report.navigateToConciergeChat(true, () => !isUnmounted.current); + }); + } else { + Navigation.navigate(); + } + }, [session, isLoadingReportData]), + ); useEffect(() => { isUnmounted.current = false; From b64f63db80eb53a9e6dab3c735f7293557daa53b Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:41:41 +0530 Subject: [PATCH 18/94] Update timeout of UnreadIndicatorsTest.tsx --- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index bada60720e36..4cb2b5463fb9 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -30,7 +30,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; // We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App -jest.setTimeout(30000); +jest.setTimeout(40000); jest.mock('@react-navigation/native'); jest.mock('../../src/libs/Notification/LocalNotification'); From bd9f6e8077ce434b1b034c45e195928dc94c81ab Mon Sep 17 00:00:00 2001 From: Shridhar Goel <35566748+ShridharGoel@users.noreply.github.com> Date: Thu, 31 Oct 2024 01:42:35 +0530 Subject: [PATCH 19/94] Update timeout of UnreadIndicatorsTest.tsx --- tests/ui/UnreadIndicatorsTest.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 4cb2b5463fb9..c201d8cabd74 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -30,7 +30,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; // We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App -jest.setTimeout(40000); +jest.setTimeout(45000); jest.mock('@react-navigation/native'); jest.mock('../../src/libs/Notification/LocalNotification'); From e602e696743a5f2554fc5cf4f86872776ac47e93 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 04:56:08 +0530 Subject: [PATCH 20/94] pass 'canUserPerformWriteAction' to shouldReportActionBeVisible. Signed-off-by: krishna2323 --- .../AttachmentCarousel/extractAttachments.ts | 5 +- .../LHNOptionsList/LHNOptionsList.tsx | 4 +- src/components/ParentNavigationSubtitle.tsx | 5 +- src/hooks/usePaginatedReportActions.ts | 5 +- src/libs/OptionsListUtils.ts | 7 +- src/libs/ReportActionsUtils.ts | 33 ++--- src/libs/actions/Report.ts | 21 +++- src/pages/Debug/Report/DebugReportActions.tsx | 5 +- src/pages/home/ReportScreen.tsx | 6 +- .../report/ReportActionItemParentAction.tsx | 113 +++++++++--------- src/pages/home/report/ReportActionsList.tsx | 4 +- src/pages/home/report/ReportActionsView.tsx | 3 +- src/pages/home/report/ThreadDivider.tsx | 8 +- .../perf-test/ReportActionsUtils.perf-test.ts | 8 +- tests/unit/ReportActionsUtilsTest.ts | 6 +- 15 files changed, 137 insertions(+), 96 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 69b0b8229f4a..5ebaf8af673e 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -4,6 +4,7 @@ import type {ValueOf} from 'type-fest'; import type {Attachment} from '@components/Attachments/types'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import CONST from '@src/CONST'; import type {ReportAction, ReportActions} from '@src/types/onyx'; @@ -24,6 +25,8 @@ function extractAttachments( ) { const targetNote = privateNotes?.[Number(accountID)]?.note ?? ''; const attachments: Attachment[] = []; + const report = ReportUtils.getReport(reportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); // We handle duplicate image sources by considering the first instance as original. Selecting any duplicate // and navigating back (<) shows the image preceding the first instance, not the selected duplicate's position. @@ -96,7 +99,7 @@ function extractAttachments( const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; actions.forEach((action, key) => { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID) || ReportActionsUtils.isMoneyRequestAction(action)) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action),) { return; } diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index b317d3020e2a..c03d7822f9e3 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -19,6 +19,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as DraftCommentUtils from '@libs/DraftCommentUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -139,7 +140,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio : '-1'; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions, reportID); + + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions, reportID, ReportUtils.canUserPerformWriteAction(itemFullReport)); const lastReportAction = sortedReportActions.at(0); // Get the transaction for the last report action diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index ef0f981a8c77..248eaec9c9b1 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -5,6 +5,7 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; import ROUTES from '@src/ROUTES'; @@ -29,6 +30,8 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct const {workspaceName, reportName} = parentNavigationSubtitleData; const {isOffline} = useNetwork(); const {translate} = useLocalize(); + const report = ReportUtils.getReport(parentReportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); // We should not display the parent navigation subtitle if the user does not have access to the parent chat (the reportName is empty in this case) if (!reportName) { @@ -39,7 +42,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct { const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', parentReportID); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', parentReportID, canUserPerformWriteAction); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID)); if (isVisibleAction && !isOffline) { diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts index 342d73b08bd8..6b476db46d7d 100644 --- a/src/hooks/usePaginatedReportActions.ts +++ b/src/hooks/usePaginatedReportActions.ts @@ -2,6 +2,7 @@ import {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import PaginationUtils from '@libs/PaginationUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import ONYXKEYS from '@src/ONYXKEYS'; /** @@ -11,10 +12,12 @@ function usePaginatedReportActions(reportID?: string, reportActionID?: string) { // Use `||` instead of `??` to handle empty string. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const reportIDWithDefault = reportID || '-1'; + const report = ReportUtils.getReport(reportIDWithDefault); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID ?? '-1', true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID ?? '-1', canUserPerformWriteAction, true), }); const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${reportIDWithDefault}`); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index df4b75893336..9fd121fdfb5b 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -328,11 +328,14 @@ Onyx.connect({ lastReportActions[reportID] = firstReportAction; } + const report = ReportUtils.getReport(reportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + // The report is only visible if it is the last action not deleted that // does not match a closed or created state. const reportActionsForDisplay = sortedReportActions.filter( (reportAction, actionKey) => - ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey, reportID) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey, reportID, canUserPerformWriteAction) && !ReportActionUtils.isWhisperAction(reportAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && @@ -590,7 +593,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails const iouReport = ReportUtils.getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find( (reportAction, key): reportAction is ReportAction => - ReportActionUtils.shouldReportActionBeVisible(reportAction, key, reportID) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, key, reportID, ReportUtils.canUserPerformWriteAction(report)) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && ReportActionUtils.isMoneyRequestAction(reportAction), ); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index be0efc67ce5b..7778cb29d552 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -28,7 +28,6 @@ import * as PersonalDetailsUtils from './PersonalDetailsUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; import type {OptimisticIOUReportAction, PartialReportAction} from './ReportUtils'; -import {canUserPerformWriteAction, getReport} from './ReportUtils'; import StringUtils from './StringUtils'; // eslint-disable-next-line import/no-cycle import * as TransactionUtils from './TransactionUtils'; @@ -636,12 +635,8 @@ const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTy * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. */ -function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, reportID: string): boolean { - const report = getReport(reportID); - if ( - (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPending(report?.reportID ?? '-1') || isActionableMentionWhisper(reportAction)) && - !canUserPerformWriteAction(report) - ) { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, reportID: string, canUserPerformWriteAction?: boolean): boolean { + if ((isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPending(reportID ?? '-1') || isActionableMentionWhisper(reportAction)) && !canUserPerformWriteAction) { return false; } @@ -720,7 +715,7 @@ function isResolvedActionTrackExpense(reportAction: OnyxEntry): bo * Checks if a reportAction is fit for display as report last action, meaning that * it satisfies shouldReportActionBeVisible, it's not whisper action and not deleted. */ -function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry, reportID: string): boolean { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry, reportID: string, canUserPerformWriteAction?: boolean): boolean { if (!reportAction) { return false; } @@ -732,7 +727,7 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry< // If a whisper action is the REPORT_PREVIEW action, we are displaying it. // If the action's message text is empty and it is not a deleted parent with visible child actions, hide it. Else, consider the action to be displayable. return ( - shouldReportActionBeVisible(reportAction, reportAction.reportActionID, reportID) && + shouldReportActionBeVisible(reportAction, reportAction.reportActionID, reportID, canUserPerformWriteAction) && !(isWhisperAction(reportAction) && !isReportPreviewAction(reportAction) && !isMoneyRequestAction(reportAction)) && !(isDeletedAction(reportAction) && !isDeletedParentAction(reportAction)) && !isResolvedActionTrackExpense(reportAction) @@ -765,7 +760,7 @@ function replaceBaseURLInPolicyChangeLogAction(reportAction: ReportAction): Repo return updatedReportAction; } -function getLastVisibleAction(reportID: string, actionsToMerge: Record | null> = {}): OnyxEntry { +function getLastVisibleAction(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge: Record | null> = {}): OnyxEntry { let reportActions: Array = []; if (!isEmpty(actionsToMerge)) { reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge ?? {}, true)) as Array< @@ -774,7 +769,7 @@ function getLastVisibleAction(reportID: string, actionsToMerge: Record shouldReportActionBeVisibleAsLastAction(action, reportID)); + const visibleReportActions = reportActions.filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action, reportID, canUserPerformWriteAction)); const sortedReportActions = getSortedReportActions(visibleReportActions, true); if (sortedReportActions.length === 0) { return undefined; @@ -796,10 +791,11 @@ function formatLastMessageText(lastMessageText: string) { function getLastVisibleMessage( reportID: string, + canUserPerformWriteAction?: boolean, actionsToMerge: Record | null> = {}, reportAction: OnyxInputOrEntry | undefined = undefined, ): LastVisibleMessage { - const lastVisibleAction = reportAction ?? getLastVisibleAction(reportID, actionsToMerge); + const lastVisibleAction = reportAction ?? getLastVisibleAction(reportID, canUserPerformWriteAction, actionsToMerge); const message = getReportActionMessage(lastVisibleAction); if (message && isReportMessageAttachment(message)) { @@ -840,7 +836,12 @@ function filterOutDeprecatedReportActions(reportActions: OnyxEntry | ReportAction[], reportID: string, shouldIncludeInvisibleActions = false): ReportAction[] { +function getSortedReportActionsForDisplay( + reportActions: OnyxEntry | ReportAction[], + reportID: string, + canUserPerformWriteAction?: boolean, + shouldIncludeInvisibleActions = false, +): ReportAction[] { let filteredReportActions: ReportAction[] = []; if (!reportActions) { return []; @@ -850,7 +851,7 @@ function getSortedReportActionsForDisplay(reportActions: OnyxEntry shouldReportActionBeVisible(reportAction, key, reportID)) + .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key, reportID, canUserPerformWriteAction)) .map(([, reportAction]) => reportAction); } @@ -1099,9 +1100,9 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn * When we delete certain reports, we want to check whether there are any visible actions left to display. * If there are no visible actions left (including system messages), we can hide the report from view entirely */ -function doesReportHaveVisibleActions(reportID: string, actionsToMerge: ReportActions = {}): boolean { +function doesReportHaveVisibleActions(reportID: string, canUserPerformWriteAction: boolean, actionsToMerge: ReportActions = {}): boolean { const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge, true)); - const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, reportID)); + const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, reportID, canUserPerformWriteAction)); // Exclude the task system message and the created message const visibleReportActionsWithoutTaskSystemMessage = visibleReportActions.filter((action) => !isTaskAction(action) && !isCreatedAction(action)); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index c219137e210c..03ad1de54b5d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -279,7 +279,11 @@ registerPaginationConfig({ nextCommand: READ_COMMANDS.GET_NEWER_ACTIONS, resourceCollectionKey: ONYXKEYS.COLLECTION.REPORT_ACTIONS, pageCollectionKey: ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES, - sortItems: (reportActions, reportID) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportID, true), + sortItems: (reportActions, reportID) => { + const report = ReportUtils.getReport(reportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + return ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportID, canUserPerformWriteAction, true); + }, getItemID: (reportAction) => reportAction.reportActionID, }); @@ -1450,8 +1454,10 @@ function deleteReportComment(reportID: string, reportAction: ReportAction) { lastVisibleActionCreated: '', }; const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportUtils.getLastVisibleMessage(originalReportID, optimisticReportActions as ReportActions); + const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); if (lastMessageText || lastMessageTranslationKey) { - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(originalReportID, optimisticReportActions as ReportActions); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(originalReportID, canUserPerformWriteAction, optimisticReportActions as ReportActions); const lastVisibleActionCreated = lastVisibleAction?.created; const lastActorAccountID = lastVisibleAction?.actorAccountID; optimisticReport = { @@ -1461,7 +1467,6 @@ function deleteReportComment(reportID: string, reportAction: ReportAction) { lastActorAccountID, }; } - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const didCommentMentionCurrentUser = ReportActionsUtils.didMessageMentionCurrentUser(reportAction); if (didCommentMentionCurrentUser && reportAction.created === report?.lastMentionedTime) { const reportActionsForReport = allReportActions?.[reportID]; @@ -1592,6 +1597,8 @@ function handleUserDeletedLinksInHtml(newCommentText: string, originalCommentMar /** Saves a new message for a comment. Marks the comment as edited, which will be reflected in the UI. */ function editReportComment(reportID: string, originalReportAction: OnyxEntry, textForNewComment: string, videoAttributeCache?: Record) { const originalReportID = ReportUtils.getOriginalReportID(reportID, originalReportAction); + const report = ReportUtils.getReport(originalReportID ?? '-1'); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); if (!originalReportID || !originalReportAction) { return; @@ -1657,7 +1664,7 @@ function editReportComment(reportID: string, originalReportAction: OnyxEntry ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, canUserPerformWriteAction, true), }); const renderItem = ({item}: ListRenderItemInfo) => ( = CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); const isLinkedActionDeleted = useMemo( - () => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, report?.reportID ?? '-1'), - [linkedAction, report?.reportID], + () => + !!linkedAction && + !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, report?.reportID ?? '-1', ReportUtils.canUserPerformWriteAction(report)), + [linkedAction, report], ); const prevIsLinkedActionDeleted = usePrevious(linkedAction ? isLinkedActionDeleted : undefined); const isLinkedActionInaccessibleWhisper = useMemo( diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index 98cd0448e047..67e16e5b068c 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -106,61 +106,66 @@ function ReportActionItemParentAction({ {/* eslint-disable-next-line react-compiler/react-compiler */} - {allAncestors.map((ancestor) => ( - Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)} - > - - {ReportActionsUtils.isTripPreview(ancestor?.reportAction) ? ( - - - - ) : ( - { - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( - ancestor.reportAction, - ancestor.reportAction.reportActionID ?? '-1', - ancestor.report.reportID, - ); - // Pop the thread report screen before navigating to the chat report. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1')); - if (isVisibleAction && !isOffline) { - // Pop the chat report screen before navigating to the linked report action. - Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1', ancestor.reportAction.reportActionID)); - } - } - : undefined - } - parentReportAction={parentReportAction} - report={ancestor.report} - reportActions={reportActions} - transactionThreadReport={transactionThreadReport} - action={ancestor.reportAction} - displayAsGroup={false} - isMostRecentIOUReportAction={false} - shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker} - index={index} - isFirstVisibleReportAction={isFirstVisibleReportAction} - shouldUseThreadDividerLine={shouldUseThreadDividerLine} - hideThreadReplies + {allAncestors.map((ancestor) => { + const ancestorReport = ReportUtils.getReport(ancestor.report.reportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(ancestorReport); + return ( + Report.navigateToConciergeChatAndDeleteReport(ancestor.report.reportID)} + > + - )} - - ))} + {ReportActionsUtils.isTripPreview(ancestor?.reportAction) ? ( + + + + ) : ( + { + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( + ancestor.reportAction, + ancestor.reportAction.reportActionID ?? '-1', + ancestor.report.reportID, + canUserPerformWriteAction, + ); + // Pop the thread report screen before navigating to the chat report. + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1')); + if (isVisibleAction && !isOffline) { + // Pop the chat report screen before navigating to the linked report action. + Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1', ancestor.reportAction.reportActionID)); + } + } + : undefined + } + parentReportAction={parentReportAction} + report={ancestor.report} + reportActions={reportActions} + transactionThreadReport={transactionThreadReport} + action={ancestor.reportAction} + displayAsGroup={false} + isMostRecentIOUReportAction={false} + shouldDisplayNewMarker={ancestor.shouldDisplayNewMarker} + index={index} + isFirstVisibleReportAction={isFirstVisibleReportAction} + shouldUseThreadDividerLine={shouldUseThreadDividerLine} + hideThreadReplies + /> + )} + + ); + })} {shouldDisplayReplyDivider && } ); diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index 22eda76df1de..95ddd3f561f5 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -195,9 +195,9 @@ function ReportActionsList({ ReportActionsUtils.isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors) && - ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID, report.reportID), + ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID, report.reportID, ReportUtils.canUserPerformWriteAction(report)), ), - [sortedReportActions, isOffline, report.reportID], + [sortedReportActions, isOffline, report], ); const lastAction = sortedVisibleReportActions.at(0); const sortedVisibleReportActionsObjects: OnyxTypes.ReportActions = useMemo( diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index b58de22bc520..fb1f848b677c 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -90,7 +90,8 @@ function ReportActionsView({ const route = useRoute>(); const [session] = useOnyx(ONYXKEYS.SESSION); const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? -1}`, { - selector: (reportActions: OnyxEntry) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, report.reportID, true), + selector: (reportActions: OnyxEntry) => + ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, report.reportID, ReportUtils.canUserPerformWriteAction(report), true), }); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? -1}`); const prevTransactionThreadReport = usePrevious(transactionThreadReport); diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 6fec617e4c37..290d173d9a5c 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -11,6 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import type {Ancestor} from '@libs/ReportUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; @@ -47,7 +48,12 @@ function ThreadDivider({ancestor, isLinkDisabled = false}: ThreadDividerProps) { ) : ( { - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1', ancestor.report.reportID); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( + ancestor.reportAction, + ancestor.reportAction.reportActionID ?? '-1', + ancestor.report.reportID, + ReportUtils.canUserPerformWriteAction(ancestor.report), + ); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(ancestor.report.reportID ?? '-1')); if (isVisibleAction && !isOffline) { diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index 5e258436edc7..d6c573e846e8 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -89,11 +89,11 @@ describe('ReportActionsUtils', () => { } as unknown as ReportActions; await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge)); + await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, true, actionsToMerge)); }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId); + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId, true); await waitForBatchedUpdates(); await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); @@ -127,12 +127,12 @@ describe('ReportActionsUtils', () => { } as unknown as ReportActions; await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge)); + await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, true, actionsToMerge)); }); test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId)); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId, true)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 5f3f14971d8e..ef74f34f6413 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -306,7 +306,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, "1"); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); expect(result).toStrictEqual(expectedOutput); }); @@ -401,7 +401,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, -1), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1'); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); expect(result).toStrictEqual(expectedOutput); }); @@ -445,7 +445,7 @@ describe('ReportActionsUtils', () => { message: [{html: '', type: 'Action type', text: 'Action text'}], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1'); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); input.pop(); expect(result).toStrictEqual(input); }); From 7b32415551e04a96e2b0d7b75378c5350bc4cfbc Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 05:00:04 +0530 Subject: [PATCH 21/94] fix tests. Signed-off-by: krishna2323 --- .../Attachments/AttachmentCarousel/extractAttachments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 5ebaf8af673e..620b7ce4c01d 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -99,7 +99,7 @@ function extractAttachments( const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; actions.forEach((action, key) => { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action),) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action)) { return; } From 16ca72b963baaf7deda1e8d37a23294b13eb8a16 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 05:10:41 +0530 Subject: [PATCH 22/94] fix: Unchanged files with check annotations. Signed-off-by: krishna2323 --- src/libs/ReportUtils.ts | 4 ++-- src/libs/SidebarUtils.ts | 2 +- src/libs/actions/Report.ts | 10 ++++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7ad1b72dfebd..7b2d592057d1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8233,7 +8233,7 @@ function findPolicyExpenseChatByPolicyID(policyID: string): OnyxEntry { * @param actionsToMerge * @returns containing the calculated message preview data of the report */ -function getReportLastMessage(reportID: string, actionsToMerge?: ReportActions) { +function getReportLastMessage(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge?: ReportActions) { let result: Partial = { lastMessageTranslationKey: '', lastMessageText: '', @@ -8243,7 +8243,7 @@ function getReportLastMessage(reportID: string, actionsToMerge?: ReportActions) const {lastMessageText = '', lastMessageTranslationKey = ''} = getLastVisibleMessage(reportID, actionsToMerge); if (lastMessageText || lastMessageTranslationKey) { - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, actionsToMerge); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, canUserPerformWriteAction, actionsToMerge); const lastVisibleActionCreated = lastVisibleAction?.created; const lastActorAccountID = lastVisibleAction?.actorAccountID; result = { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 862750920bbc..d88698e7647a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -485,7 +485,7 @@ function getOptionData({ result.alternateText = lastMessageTextFromReport.length > 0 ? ReportUtils.formatReportLastMessageText(Parser.htmlToText(lastMessageText)) - : ReportActionsUtils.getLastVisibleMessage(report.reportID, {}, lastAction)?.lastMessageText; + : ReportActionsUtils.getLastVisibleMessage(report.reportID, result.isAllowedToComment, {}, lastAction)?.lastMessageText; if (!result.alternateText) { result.alternateText = ReportUtils.formatReportLastMessageText(getWelcomeMessage(report, policy).messageText ?? Localize.translateLocal('report.noActivityYet')); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 03ad1de54b5d..20bbaf404190 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3913,9 +3913,10 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt }, }; - const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, optimisticReportActions as ReportActions); - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportId}`]; + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, canUserPerformWriteAction, optimisticReportActions as ReportActions); + const reportUpdateDataWithCurrentLastMessage = { lastMessageTranslationKey: report?.lastMessageTranslationKey, lastMessageText: report?.lastMessageText, @@ -3988,9 +3989,10 @@ function resolveActionableReportMentionWhisper( }, }; - const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, optimisticReportActions as ReportActions); - const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportId}`]; + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); + const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, canUserPerformWriteAction, optimisticReportActions as ReportActions); + const reportUpdateDataWithCurrentLastMessage = { lastMessageTranslationKey: report?.lastMessageTranslationKey, lastMessageText: report?.lastMessageText, From 95bec1fc21766cb15edd356762c84936782092c4 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 05:16:54 +0530 Subject: [PATCH 23/94] minor fixes. Signed-off-by: krishna2323 --- src/libs/ReportUtils.ts | 10 ++++++---- src/libs/actions/Report.ts | 6 ++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7b2d592057d1..a466561e2d83 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2609,7 +2609,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amoun */ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: ReportActions = {}): LastVisibleMessage { const report = getReportOrDraftReport(reportID); - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID ?? '-1', actionsToMerge); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID ?? '-1', canUserPerformWriteAction(report), actionsToMerge); // For Chat Report with deleted parent actions, let us fetch the correct message if (ReportActionsUtils.isDeletedParentAction(lastVisibleAction) && !isEmptyObject(report) && isChatReport(report)) { @@ -2620,7 +2620,7 @@ function getLastVisibleMessage(reportID: string | undefined, actionsToMerge: Rep } // Fetch the last visible message for report represented by reportID and based on actions to merge. - return ReportActionsUtils.getLastVisibleMessage(reportID ?? '-1', actionsToMerge); + return ReportActionsUtils.getLastVisibleMessage(reportID ?? '-1', canUserPerformWriteAction(report), actionsToMerge); } /** @@ -8231,9 +8231,10 @@ function findPolicyExpenseChatByPolicyID(policyID: string): OnyxEntry { * A function to get the report last message. This is usually used to restore the report message preview in LHN after report actions change. * @param reportID * @param actionsToMerge + * @param canUserPerformWriteActionInReport * @returns containing the calculated message preview data of the report */ -function getReportLastMessage(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge?: ReportActions) { +function getReportLastMessage(reportID: string, actionsToMerge?: ReportActions) { let result: Partial = { lastMessageTranslationKey: '', lastMessageText: '', @@ -8243,7 +8244,8 @@ function getReportLastMessage(reportID: string, canUserPerformWriteAction?: bool const {lastMessageText = '', lastMessageTranslationKey = ''} = getLastVisibleMessage(reportID, actionsToMerge); if (lastMessageText || lastMessageTranslationKey) { - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, canUserPerformWriteAction, actionsToMerge); + const report = getReportOrDraftReport(reportID); + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, canUserPerformWriteAction(report), actionsToMerge); const lastVisibleActionCreated = lastVisibleAction?.created; const lastActorAccountID = lastVisibleAction?.actorAccountID; result = { diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 20bbaf404190..58b4be53e2f0 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3914,8 +3914,7 @@ function resolveActionableMentionWhisper(reportId: string, reportAction: OnyxEnt }; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportId}`]; - const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); - const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, canUserPerformWriteAction, optimisticReportActions as ReportActions); + const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, optimisticReportActions as ReportActions); const reportUpdateDataWithCurrentLastMessage = { lastMessageTranslationKey: report?.lastMessageTranslationKey, @@ -3990,8 +3989,7 @@ function resolveActionableReportMentionWhisper( }; const report = ReportConnection.getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${reportId}`]; - const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); - const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, canUserPerformWriteAction, optimisticReportActions as ReportActions); + const reportUpdateDataWithPreviousLastMessage = ReportUtils.getReportLastMessage(reportId, optimisticReportActions as ReportActions); const reportUpdateDataWithCurrentLastMessage = { lastMessageTranslationKey: report?.lastMessageTranslationKey, From 0ac89334bd30ba159603543da120676bbf158781 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 05:27:05 +0530 Subject: [PATCH 24/94] fix: TypeScript Checks. Signed-off-by: krishna2323 --- src/libs/actions/IOU.ts | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7ce9b9dfb272..fc16084f91fb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1734,8 +1734,12 @@ function getDeleteTrackExpenseInformation( }, ...(actionableWhisperReportActionID && {[actionableWhisperReportActionID]: {originalMessage: {resolution}}}), } as OnyxTypes.ReportActions; - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(chatReport?.reportID ?? '-1', updatedReportAction); - const {lastMessageText = '', lastMessageHtml = ''} = ReportActionsUtils.getLastVisibleMessage(chatReport?.reportID ?? '-1', updatedReportAction); + let canUserPerformWriteAction = true; + if (chatReport) { + canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(chatReport); + } + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(chatReport?.reportID ?? '-1', canUserPerformWriteAction, updatedReportAction); + const {lastMessageText = '', lastMessageHtml = ''} = ReportActionsUtils.getLastVisibleMessage(chatReport?.reportID ?? '-1', canUserPerformWriteAction, updatedReportAction); // STEP 4: Build Onyx data const optimisticData: OnyxUpdate[] = []; @@ -5705,8 +5709,12 @@ function prepareToCleanUpMoneyRequest(transactionID: string, reportAction: OnyxT }, } as Record>; - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1', updatedReportAction); - const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1', updatedReportAction).lastMessageText; + let canUserPerformWriteAction = true; + if (chatReport) { + canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(chatReport); + } + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(iouReport?.reportID ?? '-1', canUserPerformWriteAction, updatedReportAction); + const iouReportLastMessageText = ReportActionsUtils.getLastVisibleMessage(iouReport?.reportID ?? '-1', canUserPerformWriteAction, updatedReportAction).lastMessageText; const shouldDeleteIOUReport = iouReportLastMessageText.length === 0 && !ReportActionsUtils.isDeletedParentAction(lastVisibleAction) && (!transactionThreadID || shouldDeleteTransactionThread); @@ -5899,6 +5907,10 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo } if (shouldDeleteIOUReport) { + let canUserPerformWriteAction = true; + if (chatReport) { + canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(chatReport); + } onyxUpdates.push( { onyxMethod: Onyx.METHOD.MERGE, @@ -5906,8 +5918,12 @@ function cleanUpMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repo value: { hasOutstandingChildRequest: false, iouReportID: null, - lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport?.chatReportID ?? '-1', {[reportPreviewAction?.reportActionID ?? '-1']: null})?.lastMessageText, - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport?.chatReportID ?? '-1', {[reportPreviewAction?.reportActionID ?? '-1']: null})?.created, + lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport?.chatReportID ?? '-1', canUserPerformWriteAction, { + [reportPreviewAction?.reportActionID ?? '-1']: null, + })?.lastMessageText, + lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport?.chatReportID ?? '-1', canUserPerformWriteAction, { + [reportPreviewAction?.reportActionID ?? '-1']: null, + })?.created, }, }, { @@ -6015,14 +6031,21 @@ function deleteMoneyRequest(transactionID: string, reportAction: OnyxTypes.Repor } if (shouldDeleteIOUReport) { + let canUserPerformWriteAction = true; + if (chatReport) { + canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(chatReport); + } optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: { hasOutstandingChildRequest: false, iouReportID: null, - lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport?.chatReportID ?? '-1', {[reportPreviewAction?.reportActionID ?? '-1']: null})?.lastMessageText, - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport?.chatReportID ?? '-1', {[reportPreviewAction?.reportActionID ?? '-1']: null})?.created, + lastMessageText: ReportActionsUtils.getLastVisibleMessage(iouReport?.chatReportID ?? '-1', canUserPerformWriteAction, {[reportPreviewAction?.reportActionID ?? '-1']: null}) + ?.lastMessageText, + lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(iouReport?.chatReportID ?? '-1', canUserPerformWriteAction, { + [reportPreviewAction?.reportActionID ?? '-1']: null, + })?.created, }, }); optimisticData.push({ From 5fb19370ad8426a34892bf2d8a0d60b8d3b032fa Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 31 Oct 2024 05:36:29 +0530 Subject: [PATCH 25/94] fix: TypeScript Checks. Signed-off-by: krishna2323 --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/actions/Task.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 7778cb29d552..bd04628ea264 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1100,7 +1100,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn * When we delete certain reports, we want to check whether there are any visible actions left to display. * If there are no visible actions left (including system messages), we can hide the report from view entirely */ -function doesReportHaveVisibleActions(reportID: string, canUserPerformWriteAction: boolean, actionsToMerge: ReportActions = {}): boolean { +function doesReportHaveVisibleActions(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge: ReportActions = {}): boolean { const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge, true)); const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, reportID, canUserPerformWriteAction)); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index c5a2442048fc..130023be885b 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -967,9 +967,10 @@ function deleteTask(report: OnyxEntry) { const optimisticReportActionID = optimisticCancelReportAction.reportActionID; const parentReportAction = getParentReportAction(report); const parentReport = getParentReport(report); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); // If the task report is the last visible action in the parent report, we should navigate back to the parent report - const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID ?? '-1'); + const shouldDeleteTaskReport = !ReportActionsUtils.doesReportHaveVisibleActions(report.reportID ?? '-1', canUserPerformWriteAction); const optimisticReportAction: Partial = { pendingAction: shouldDeleteTaskReport ? CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE : CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, previousMessage: parentReportAction?.message, @@ -1006,8 +1007,14 @@ function deleteTask(report: OnyxEntry) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, value: { - lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '-1', optimisticReportActions as OnyxTypes.ReportActions).lastMessageText ?? '', - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '-1', optimisticReportActions as OnyxTypes.ReportActions)?.created, + lastMessageText: + ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '-1', canUserPerformWriteAction, optimisticReportActions as OnyxTypes.ReportActions).lastMessageText ?? + '', + lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction( + parentReport?.reportID ?? '-1', + canUserPerformWriteAction, + optimisticReportActions as OnyxTypes.ReportActions, + )?.created, hasOutstandingChildTask, }, }, From c57030110d6b8c9ba0a9a453352c4b417f856935 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 6 Nov 2024 09:00:19 +0700 Subject: [PATCH 26/94] Fix list not scrolled up when search query empty --- src/components/Search/SearchRouter/SearchRouter.tsx | 7 ++++++- src/components/SelectionList/BaseSelectionList.tsx | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/Search/SearchRouter/SearchRouter.tsx b/src/components/Search/SearchRouter/SearchRouter.tsx index 83d7d5d89b20..6f62a08db00f 100644 --- a/src/components/Search/SearchRouter/SearchRouter.tsx +++ b/src/components/Search/SearchRouter/SearchRouter.tsx @@ -37,6 +37,7 @@ import ROUTES from '@src/ROUTES'; import SearchRouterInput from './SearchRouterInput'; import SearchRouterList from './SearchRouterList'; import type {ItemWithQuery} from './SearchRouterList'; +import isEmpty from 'lodash/isEmpty'; type SearchRouterProps = { onRouterClose: () => void; @@ -293,6 +294,7 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { ], ); + const prevUserQueryRef = useRef(null); const onSearchChange = useCallback( (userQuery: string) => { let newUserQuery = userQuery; @@ -302,11 +304,14 @@ function SearchRouter({onRouterClose}: SearchRouterProps) { setTextInputValue(newUserQuery); const autocompleteParsedQuery = parseForAutocomplete(newUserQuery); updateAutocomplete(autocompleteParsedQuery?.autocomplete?.value ?? '', autocompleteParsedQuery?.ranges ?? [], autocompleteParsedQuery?.autocomplete?.key); - if (newUserQuery) { + if (newUserQuery || !isEmpty(prevUserQueryRef.current)) { listRef.current?.updateAndScrollToFocusedIndex(0); } else { listRef.current?.updateAndScrollToFocusedIndex(-1); } + + // Store the previous newUserQuery + prevUserQueryRef.current = newUserQuery; }, [autocompleteSuggestions, setTextInputValue, updateAutocomplete], ); diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 3e1b3a3c2d70..ffb6c64a0fc5 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -572,8 +572,9 @@ function BaseSelectionList( } // Remove the focus if the search input is empty or selected options length is changed (and allOptions length remains the same) // else focus on the first non disabled item + const newSelectedIndex = - textInputValue === '' || (flattenedSections.selectedOptions.length !== prevSelectedOptionsLength && prevAllOptionsLength === flattenedSections.allOptions.length) ? -1 : 0; + (isEmpty(prevTextInputValue) && textInputValue === '') || (flattenedSections.selectedOptions.length !== prevSelectedOptionsLength && prevAllOptionsLength === flattenedSections.allOptions.length) ? -1 : 0; // reseting the currrent page to 1 when the user types something setCurrentPage(1); From edf7c52fb9464d18297e71e69cb55f4dadc01f1f Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 6 Nov 2024 17:42:07 +0530 Subject: [PATCH 27/94] remove reportID parameter from shouldReportActionBeVisible. Signed-off-by: krishna2323 --- .../AttachmentCarousel/extractAttachments.ts | 2 +- .../LHNOptionsList/LHNOptionsList.tsx | 3 ++- src/components/ParentNavigationSubtitle.tsx | 6 +++-- src/hooks/usePaginatedReportActions.ts | 2 +- src/libs/OptionsListUtils.ts | 4 +-- src/libs/Permissions.ts | 1 + src/libs/ReportActionsUtils.ts | 27 ++++++++++--------- src/libs/SidebarUtils.ts | 6 +++-- src/libs/SubscriptionUtils.ts | 1 + src/libs/actions/Report.ts | 2 +- src/pages/Debug/Report/DebugReportActions.tsx | 4 +-- src/pages/home/ReportScreen.tsx | 4 +-- .../report/ReportActionItemParentAction.tsx | 1 - src/pages/home/report/ReportActionsList.tsx | 2 +- src/pages/home/report/ReportActionsView.tsx | 2 +- src/pages/home/report/ThreadDivider.tsx | 1 - .../perf-test/ReportActionsUtils.perf-test.ts | 4 +-- tests/unit/ReportActionsUtilsTest.ts | 6 ++--- 18 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts index 620b7ce4c01d..51d5cfb1d981 100644 --- a/src/components/Attachments/AttachmentCarousel/extractAttachments.ts +++ b/src/components/Attachments/AttachmentCarousel/extractAttachments.ts @@ -99,7 +99,7 @@ function extractAttachments( const actions = [...(parentReportAction ? [parentReportAction] : []), ...ReportActionsUtils.getSortedReportActions(Object.values(reportActions ?? {}))]; actions.forEach((action, key) => { - if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, reportID, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action)) { + if (!ReportActionsUtils.shouldReportActionBeVisible(action, key, canUserPerformWriteAction) || ReportActionsUtils.isMoneyRequestAction(action)) { return; } diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c03d7822f9e3..9594e6ede24a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -141,7 +141,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const hasDraftComment = DraftCommentUtils.isValidDraftComment(draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`]); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions, reportID, ReportUtils.canUserPerformWriteAction(itemFullReport)); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(itemFullReport); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(itemReportActions, canUserPerformWriteAction); const lastReportAction = sortedReportActions.at(0); // Get the transaction for the last report action diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 248eaec9c9b1..2b808432c6ab 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -8,6 +9,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; @@ -30,7 +32,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct const {workspaceName, reportName} = parentNavigationSubtitleData; const {isOffline} = useNetwork(); const {translate} = useLocalize(); - const report = ReportUtils.getReport(parentReportID); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${parentReportID}`); const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); // We should not display the parent navigation subtitle if the user does not have access to the parent chat (the reportName is empty in this case) @@ -42,7 +44,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct { const parentAction = ReportActionsUtils.getReportAction(parentReportID, parentReportActionID ?? '-1'); - const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', parentReportID, canUserPerformWriteAction); + const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible(parentAction, parentAction?.reportActionID ?? '-1', canUserPerformWriteAction); // Pop the thread report screen before navigating to the chat report. Navigation.goBack(ROUTES.REPORT_WITH_ID.getRoute(parentReportID)); if (isVisibleAction && !isOffline) { diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts index 6b476db46d7d..8bcc7c9ce539 100644 --- a/src/hooks/usePaginatedReportActions.ts +++ b/src/hooks/usePaginatedReportActions.ts @@ -17,7 +17,7 @@ function usePaginatedReportActions(reportID?: string, reportActionID?: string) { const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID ?? '-1', canUserPerformWriteAction, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, canUserPerformWriteAction, true), }); const [reportActionPages] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_PAGES}${reportIDWithDefault}`); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 0a92e482df7a..069db8e3419f 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -335,7 +335,7 @@ Onyx.connect({ // does not match a closed or created state. const reportActionsForDisplay = sortedReportActions.filter( (reportAction, actionKey) => - ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey, reportID, canUserPerformWriteAction) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, actionKey, canUserPerformWriteAction) && !ReportActionUtils.isWhisperAction(reportAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && @@ -593,7 +593,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails const iouReport = ReportUtils.getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find( (reportAction, key): reportAction is ReportAction => - ReportActionUtils.shouldReportActionBeVisible(reportAction, key, reportID, ReportUtils.canUserPerformWriteAction(report)) && + ReportActionUtils.shouldReportActionBeVisible(reportAction, key, ReportUtils.canUserPerformWriteAction(report)) && reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && ReportActionUtils.isMoneyRequestAction(reportAction), ); diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 0853bd9c18ce..7083f04fab93 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,6 +4,7 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { + return true; return !!betas?.includes(CONST.BETAS.ALL); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index fd45d9843178..7118bf8445e9 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -635,8 +635,11 @@ const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTy * Checks if a reportAction is fit for display, meaning that it's not deprecated, is of a valid * and supported type, it's not deleted and also not closed. */ -function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, reportID: string, canUserPerformWriteAction?: boolean): boolean { - if ((isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPending(reportID ?? '-1') || isActionableMentionWhisper(reportAction)) && !canUserPerformWriteAction) { +function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, canUserPerformWriteAction?: boolean): boolean { + if ( + (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPendingReportAction(reportAction) || isActionableMentionWhisper(reportAction)) && + !canUserPerformWriteAction + ) { return false; } @@ -715,7 +718,7 @@ function isResolvedActionTrackExpense(reportAction: OnyxEntry): bo * Checks if a reportAction is fit for display as report last action, meaning that * it satisfies shouldReportActionBeVisible, it's not whisper action and not deleted. */ -function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry, reportID: string, canUserPerformWriteAction?: boolean): boolean { +function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry, canUserPerformWriteAction?: boolean): boolean { if (!reportAction) { return false; } @@ -727,7 +730,7 @@ function shouldReportActionBeVisibleAsLastAction(reportAction: OnyxInputOrEntry< // If a whisper action is the REPORT_PREVIEW action, we are displaying it. // If the action's message text is empty and it is not a deleted parent with visible child actions, hide it. Else, consider the action to be displayable. return ( - shouldReportActionBeVisible(reportAction, reportAction.reportActionID, reportID, canUserPerformWriteAction) && + shouldReportActionBeVisible(reportAction, reportAction.reportActionID, canUserPerformWriteAction) && !(isWhisperAction(reportAction) && !isReportPreviewAction(reportAction) && !isMoneyRequestAction(reportAction)) && !(isDeletedAction(reportAction) && !isDeletedParentAction(reportAction)) && !isResolvedActionTrackExpense(reportAction) @@ -769,7 +772,7 @@ function getLastVisibleAction(reportID: string, canUserPerformWriteAction?: bool } else { reportActions = Object.values(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}); } - const visibleReportActions = reportActions.filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action, reportID, canUserPerformWriteAction)); + const visibleReportActions = reportActions.filter((action): action is ReportAction => shouldReportActionBeVisibleAsLastAction(action, canUserPerformWriteAction)); const sortedReportActions = getSortedReportActions(visibleReportActions, true); if (sortedReportActions.length === 0) { return undefined; @@ -838,7 +841,6 @@ function filterOutDeprecatedReportActions(reportActions: OnyxEntry | ReportAction[], - reportID: string, canUserPerformWriteAction?: boolean, shouldIncludeInvisibleActions = false, ): ReportAction[] { @@ -851,7 +853,7 @@ function getSortedReportActionsForDisplay( filteredReportActions = Object.values(reportActions).filter(Boolean); } else { filteredReportActions = Object.entries(reportActions) - .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key, reportID, canUserPerformWriteAction)) + .filter(([key, reportAction]) => shouldReportActionBeVisible(reportAction, key, canUserPerformWriteAction)) .map(([, reportAction]) => reportAction); } @@ -1102,7 +1104,7 @@ function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEn */ function doesReportHaveVisibleActions(reportID: string, canUserPerformWriteAction?: boolean, actionsToMerge: ReportActions = {}): boolean { const reportActions = Object.values(fastMerge(allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? {}, actionsToMerge, true)); - const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, reportID, canUserPerformWriteAction)); + const visibleReportActions = Object.values(reportActions ?? {}).filter((action) => shouldReportActionBeVisibleAsLastAction(action, canUserPerformWriteAction)); // Exclude the task system message and the created message const visibleReportActionsWithoutTaskSystemMessage = visibleReportActions.filter((action) => !isTaskAction(action) && !isCreatedAction(action)); @@ -1495,11 +1497,12 @@ function isActionableJoinRequest(reportAction: OnyxEntry): reportA return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST); } -function getActionableJoinRequestPendingReportAction(reportID: string): OnyxEntry { - const findPendingRequest = Object.values(getAllReportActions(reportID)).find( - (reportActionItem) => isActionableJoinRequest(reportActionItem) && getOriginalMessage(reportActionItem)?.choice === ('' as JoinWorkspaceResolution), - ); +function isActionableJoinRequestPendingReportAction(reportAction: OnyxEntry): boolean { + return isActionableJoinRequest(reportAction) && getOriginalMessage(reportAction)?.choice === ('' as JoinWorkspaceResolution); +} +function getActionableJoinRequestPendingReportAction(reportID: string): OnyxEntry { + const findPendingRequest = Object.values(getAllReportActions(reportID)).find((reportActionItem) => isActionableJoinRequestPendingReportAction(reportActionItem)); return findPendingRequest; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 49ed7392cec9..cbe7027e5939 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -41,13 +41,15 @@ Onyx.connect({ return; } const reportID = CollectionUtils.extractCollectionItemID(key); - + const report = ReportUtils.getReport(reportID); + const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); const actionsArray: ReportAction[] = ReportActionsUtils.getSortedReportActions(Object.values(actions)); // The report is only visible if it is the last action not deleted that // does not match a closed or created state. const reportActionsForDisplay = actionsArray.filter( - (reportAction) => ReportActionsUtils.shouldReportActionBeVisibleAsLastAction(reportAction, reportID) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, + (reportAction) => + ReportActionsUtils.shouldReportActionBeVisibleAsLastAction(reportAction, canUserPerformWriteAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED, ); const reportAction = reportActionsForDisplay.at(-1); diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index f2ceef9069fa..6cbf6d5b0d9e 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -435,6 +435,7 @@ function doesUserHavePaymentCardAdded(): boolean { * Whether the user's billable actions should be restricted. */ function shouldRestrictUserBillableActions(policyID: string): boolean { + return false; const currentDate = new Date(); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 63e578a6c513..1ec8375415d4 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -298,7 +298,7 @@ registerPaginationConfig({ sortItems: (reportActions, reportID) => { const report = ReportUtils.getReport(reportID); const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); - return ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportID, canUserPerformWriteAction, true); + return ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, canUserPerformWriteAction, true); }, getItemID: (reportAction) => reportAction.reportActionID, }); diff --git a/src/pages/Debug/Report/DebugReportActions.tsx b/src/pages/Debug/Report/DebugReportActions.tsx index f9f0a6e9a89c..9368ca5116bd 100644 --- a/src/pages/Debug/Report/DebugReportActions.tsx +++ b/src/pages/Debug/Report/DebugReportActions.tsx @@ -22,11 +22,11 @@ type DebugReportActionsProps = { function DebugReportActions({reportID}: DebugReportActionsProps) { const {translate, datetimeToCalendarTime} = useLocalize(); const styles = useThemeStyles(); - const report = ReportUtils.getReport(reportID); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { canEvict: false, - selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, reportID, canUserPerformWriteAction, true), + selector: (allReportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(allReportActions, canUserPerformWriteAction, true), }); const renderItem = ({item}: ListRenderItemInfo) => ( = CONST.REPORT.MIN_INITIAL_REPORT_ACTION_COUNT || isPendingActionExist || (doesCreatedActionExists() && reportActions.length > 0); const isLinkedActionDeleted = useMemo( - () => - !!linkedAction && - !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, report?.reportID ?? '-1', ReportUtils.canUserPerformWriteAction(report)), + () => !!linkedAction && !ReportActionsUtils.shouldReportActionBeVisible(linkedAction, linkedAction.reportActionID, ReportUtils.canUserPerformWriteAction(report)), [linkedAction, report], ); const prevIsLinkedActionDeleted = usePrevious(linkedAction ? isLinkedActionDeleted : undefined); diff --git a/src/pages/home/report/ReportActionItemParentAction.tsx b/src/pages/home/report/ReportActionItemParentAction.tsx index 67e16e5b068c..d66b91436b79 100644 --- a/src/pages/home/report/ReportActionItemParentAction.tsx +++ b/src/pages/home/report/ReportActionItemParentAction.tsx @@ -137,7 +137,6 @@ function ReportActionItemParentAction({ const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1', - ancestor.report.reportID, canUserPerformWriteAction, ); // Pop the thread report screen before navigating to the chat report. diff --git a/src/pages/home/report/ReportActionsList.tsx b/src/pages/home/report/ReportActionsList.tsx index b8c4a92a185e..1090e09d0a6a 100644 --- a/src/pages/home/report/ReportActionsList.tsx +++ b/src/pages/home/report/ReportActionsList.tsx @@ -188,7 +188,7 @@ function ReportActionsList({ ReportActionsUtils.isDeletedParentAction(reportAction) || reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || reportAction.errors) && - ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID, report.reportID, ReportUtils.canUserPerformWriteAction(report)), + ReportActionsUtils.shouldReportActionBeVisible(reportAction, reportAction.reportActionID, ReportUtils.canUserPerformWriteAction(report)), ), [sortedReportActions, isOffline, report], ); diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx index fb1f848b677c..5319b7dc72bd 100755 --- a/src/pages/home/report/ReportActionsView.tsx +++ b/src/pages/home/report/ReportActionsView.tsx @@ -91,7 +91,7 @@ function ReportActionsView({ const [session] = useOnyx(ONYXKEYS.SESSION); const [transactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID ?? -1}`, { selector: (reportActions: OnyxEntry) => - ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, report.reportID, ReportUtils.canUserPerformWriteAction(report), true), + ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, ReportUtils.canUserPerformWriteAction(report), true), }); const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID ?? -1}`); const prevTransactionThreadReport = usePrevious(transactionThreadReport); diff --git a/src/pages/home/report/ThreadDivider.tsx b/src/pages/home/report/ThreadDivider.tsx index 290d173d9a5c..0cf34d6f40e6 100644 --- a/src/pages/home/report/ThreadDivider.tsx +++ b/src/pages/home/report/ThreadDivider.tsx @@ -51,7 +51,6 @@ function ThreadDivider({ancestor, isLinkDisabled = false}: ThreadDividerProps) { const isVisibleAction = ReportActionsUtils.shouldReportActionBeVisible( ancestor.reportAction, ancestor.reportAction.reportActionID ?? '-1', - ancestor.report.reportID, ReportUtils.canUserPerformWriteAction(ancestor.report), ); // Pop the thread report screen before navigating to the chat report. diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index d6c573e846e8..b0ebd7c0c10a 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -93,7 +93,7 @@ describe('ReportActionsUtils', () => { }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId, true); + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true); await waitForBatchedUpdates(); await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); @@ -132,7 +132,7 @@ describe('ReportActionsUtils', () => { test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, reportId, true)); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index ef74f34f6413..b0d65517d12f 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -306,7 +306,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); expect(result).toStrictEqual(expectedOutput); }); @@ -401,7 +401,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, -1), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); expect(result).toStrictEqual(expectedOutput); }); @@ -445,7 +445,7 @@ describe('ReportActionsUtils', () => { message: [{html: '', type: 'Action type', text: 'Action text'}], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, '1', true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); input.pop(); expect(result).toStrictEqual(input); }); From b5c3e3a206e22f927ae65c24acced9e2c68770dd Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 7 Nov 2024 07:23:58 +0530 Subject: [PATCH 28/94] fix: mWeb - Chat - Part of the #Admins welcome message is still in english when changing language. Signed-off-by: krishna2323 --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index ab404143d030..14541f785bda 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -660,7 +660,7 @@ const translations = { beginningOfChatHistoryDomainRoomPartTwo: ' Úsalo para chatear con colegas, compartir consejos y hacer preguntas.', beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAdminRoomPartOneParams) => `Este chat es con los administradores del espacio de trabajo ${workspaceName}.`, - beginningOfChatHistoryAdminRoomPartTwo: ' Use it to chat about workspace setup and more.', + beginningOfChatHistoryAdminRoomPartTwo: ' Úsalo para hablar sobre la configuración del espacio de trabajo y más.', beginningOfChatHistoryAnnounceRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartOneParams) => `Este chat es con todos en ${workspaceName}.`, beginningOfChatHistoryAnnounceRoomPartTwo: ` Úsalo para hablar sobre la configuración del espacio de trabajo y más.`, beginningOfChatHistoryUserRoomPartOne: 'ste chat es para todo lo relacionado con ', From ea27cc36a326419c2246eb869392cb2567ad8a07 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 7 Nov 2024 13:15:12 +0530 Subject: [PATCH 29/94] remove redundant code. Signed-off-by: krishna2323 --- src/libs/Permissions.ts | 1 - src/libs/SubscriptionUtils.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 7083f04fab93..0853bd9c18ce 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -4,7 +4,6 @@ import type {IOUType} from '@src/CONST'; import type Beta from '@src/types/onyx/Beta'; function canUseAllBetas(betas: OnyxEntry): boolean { - return true; return !!betas?.includes(CONST.BETAS.ALL); } diff --git a/src/libs/SubscriptionUtils.ts b/src/libs/SubscriptionUtils.ts index 6cbf6d5b0d9e..f2ceef9069fa 100644 --- a/src/libs/SubscriptionUtils.ts +++ b/src/libs/SubscriptionUtils.ts @@ -435,7 +435,6 @@ function doesUserHavePaymentCardAdded(): boolean { * Whether the user's billable actions should be restricted. */ function shouldRestrictUserBillableActions(policyID: string): boolean { - return false; const currentDate = new Date(); const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; From 40b7435863a3eb15ba2f96128930ed0415ea6f9c Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 7 Nov 2024 13:30:54 +0530 Subject: [PATCH 30/94] minor update. Signed-off-by: krishna2323 --- src/libs/ReportActionsUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 7118bf8445e9..ccee59f64db7 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -636,6 +636,10 @@ const supportedActionTypes: ReportActionName[] = [...Object.values(otherActionTy * and supported type, it's not deleted and also not closed. */ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: string | number, canUserPerformWriteAction?: boolean): boolean { + if (!reportAction) { + return false; + } + if ( (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPendingReportAction(reportAction) || isActionableMentionWhisper(reportAction)) && !canUserPerformWriteAction @@ -643,10 +647,6 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: return false; } - if (!reportAction) { - return false; - } - if (isReportActionDeprecated(reportAction, key)) { return false; } From d1727aee7ea2e869e0b9a578a369b0612a9e79a9 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Thu, 7 Nov 2024 15:28:05 +0100 Subject: [PATCH 31/94] fix: first batch of console errors/warnings --- desktop/main.ts | 8 +++++++ package-lock.json | 22 +++++++++++++++---- package.json | 2 +- ...ntroller+1.14.4+001+disable-android.patch} | 4 ++-- src/CONFIG.ts | 1 + .../index.native.tsx | 7 ++++-- 6 files changed, 35 insertions(+), 9 deletions(-) rename patches/{react-native-keyboard-controller+1.14.1+001+disable-android.patch => react-native-keyboard-controller+1.14.4+001+disable-android.patch} (96%) diff --git a/desktop/main.ts b/desktop/main.ts index 1221b05a8388..fcdd5b913038 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -25,6 +25,14 @@ const {DESKTOP_SHORTCUT_ACCELERATOR, LOCALES} = CONST; // geolocation api (window.navigator.geolocation.getCurrentPosition) to work on desktop. // Source: https://github.com/electron/electron/blob/98cd16d336f512406eee3565be1cead86514db7b/docs/api/environment-variables.md#google_api_key process.env.GOOGLE_API_KEY = CONFIG.GCP_GEOLOCATION_API_KEY; +/** + * Suppresses Content Security Policy (CSP) console warnings related to 'unsafe-eval'. + * This is required because: + * 1. Webpack utilizes eval() for module bundling + * 2. The application requires 'unsafe-eval' in CSP to function properly + * Note: CSP warnings are expected and unavoidable in this context + */ +process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = CONFIG.ELECTRON_DISABLE_SECURITY_WARNINGS; app.setName('New Expensify'); diff --git a/package-lock.json b/package-lock.json index 0497c2e625d1..62b599507242 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,7 +91,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "1.14.1", + "react-native-keyboard-controller": "1.14.4", "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", @@ -34696,6 +34696,16 @@ "resolved": "git+ssh://git@github.com/Expensify/react-native-image-size.git#cb392140db4953a283590d7cf93b4d0461baa2a9", "integrity": "sha512-kF/8fGsKoOnjPZceipRUaM9Xg9a/aKXU2Vm5eHYEKHrRt8FP39oCbaELPTb/vUKRTu1HmEGffDFzRT02BcdzYQ==" }, + "node_modules/react-native-is-edge-to-edge": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.6.tgz", + "integrity": "sha512-1pHnFTlBahins6UAajXUqeCOHew9l9C2C8tErnpGC3IyLJzvxD+TpYAixnCbrVS52f7+NvMttbiSI290XfwN0w==", + "license": "MIT", + "peerDependencies": { + "react": ">=18.2.0", + "react-native": ">=0.73.0" + } + }, "node_modules/react-native-key-command": { "version": "1.0.8", "license": "MIT", @@ -34715,9 +34725,13 @@ "license": "MIT" }, "node_modules/react-native-keyboard-controller": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.14.1.tgz", - "integrity": "sha512-HUrZTaaDPxm94EVXlguwJB2gm6mc+VRTTzR66luFGZJZnL2SJoxN+dwsNW3twkwUVDrCPPA3U21q9YWUKVmwvg==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/react-native-keyboard-controller/-/react-native-keyboard-controller-1.14.4.tgz", + "integrity": "sha512-hVt9KhK2dxBNtk4xHTnKLeO9Jv7v5h2TZlIeCQkbBLMd5NIJa4ll0GxIpbuutjP1ctPdhXUVpCfQzgXXJOYlzw==", + "license": "MIT", + "dependencies": { + "react-native-is-edge-to-edge": "^1.1.6" + }, "peerDependencies": { "react": "*", "react-native": "*", diff --git a/package.json b/package.json index 1b12bab2d1d8..d49ee9116aa0 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "react-native-image-picker": "^7.0.3", "react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#cb392140db4953a283590d7cf93b4d0461baa2a9", "react-native-key-command": "^1.0.8", - "react-native-keyboard-controller": "1.14.1", + "react-native-keyboard-controller": "1.14.4", "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", diff --git a/patches/react-native-keyboard-controller+1.14.1+001+disable-android.patch b/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch similarity index 96% rename from patches/react-native-keyboard-controller+1.14.1+001+disable-android.patch rename to patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch index 6bb62155a98c..8d2d81aab40a 100644 --- a/patches/react-native-keyboard-controller+1.14.1+001+disable-android.patch +++ b/patches/react-native-keyboard-controller+1.14.4+001+disable-android.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt -index 7ef8b36..f4d44ff 100644 +index 93c20d3..df1e846 100644 --- a/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt +++ b/node_modules/react-native-keyboard-controller/android/src/main/java/com/reactnativekeyboardcontroller/views/EdgeToEdgeReactViewGroup.kt @@ -74,7 +74,7 @@ class EdgeToEdgeReactViewGroup( @@ -51,7 +51,7 @@ index 7ef8b36..f4d44ff 100644 } // endregion -@@ -219,7 +219,7 @@ class EdgeToEdgeReactViewGroup( +@@ -223,7 +223,7 @@ class EdgeToEdgeReactViewGroup( fun forceStatusBarTranslucent(isStatusBarTranslucent: Boolean) { if (active && this.isStatusBarTranslucent != isStatusBarTranslucent) { this.isStatusBarTranslucent = isStatusBarTranslucent diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 8a30c8bf57c2..e5e9a9d1540a 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -103,4 +103,5 @@ export default { }, // to read more about StrictMode see: contributingGuides/STRICT_MODE.md USE_REACT_STRICT_MODE_IN_DEV: false, + ELECTRON_DISABLE_SECURITY_WARNINGS: 'true', } as const; diff --git a/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx b/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx index ff050f673951..2450ce800e0e 100644 --- a/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx +++ b/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx @@ -1,11 +1,14 @@ import {CellContainer} from '@shopify/flash-list'; import type {CellContainerProps} from '@shopify/flash-list/dist/native/cell-container/CellContainer'; +import type {ForwardedRef} from 'react'; +import {forwardRef} from 'react'; -function OptionRowRendererComponent(props: CellContainerProps) { +function OptionRowRendererComponent(props: CellContainerProps, ref: ForwardedRef) { return ( ); @@ -13,4 +16,4 @@ function OptionRowRendererComponent(props: CellContainerProps) { OptionRowRendererComponent.displayName = 'OptionRowRendererComponent'; -export default OptionRowRendererComponent; +export default forwardRef(OptionRowRendererComponent); From c5f38477a95b1b48e155e7639320f4f42d068f89 Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Fri, 8 Nov 2024 14:31:02 +0100 Subject: [PATCH 32/94] fix: eslint --- desktop/main.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index fcdd5b913038..ba8eb32b8ea6 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -115,7 +115,7 @@ process.argv.forEach((arg) => { return; } - expectedUpdateVersion = arg.substr(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); + expectedUpdateVersion = arg.substring(`${EXPECTED_UPDATE_VERSION_FLAG}=`.length); }); // Add the listeners and variables required to ensure that auto-updating @@ -513,9 +513,8 @@ const mainWindow = (): Promise => { // open the default browser instead of a new electron window browserWindow.webContents.setWindowOpenHandler(({url}) => { const denial = {action: 'deny'} as const; - // Make sure local urls stay in electron perimeter - if (url.substr(0, 'file://'.length).toLowerCase() === 'file://') { + if (url.substring(0, 'file://'.length).toLowerCase() === 'file://') { return denial; } From c870f7534bac9855eac39e7eaf2642c0306cb06f Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Mon, 11 Nov 2024 10:56:28 +0530 Subject: [PATCH 33/94] minor update. Signed-off-by: krishna2323 --- src/libs/ReportActionsUtils.ts | 14 +++++++------- src/libs/ReportUtils.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index ccee59f64db7..8d828f457ece 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -640,13 +640,6 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: return false; } - if ( - (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPendingReportAction(reportAction) || isActionableMentionWhisper(reportAction)) && - !canUserPerformWriteAction - ) { - return false; - } - if (isReportActionDeprecated(reportAction, key)) { return false; } @@ -675,6 +668,13 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: return false; } + if ( + (isActionableReportMentionWhisper(reportAction) || isActionableJoinRequestPendingReportAction(reportAction) || isActionableMentionWhisper(reportAction)) && + !canUserPerformWriteAction + ) { + return false; + } + if (isTripPreview(reportAction)) { return true; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 52a1937b580e..316131993f1a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -8321,7 +8321,7 @@ function getReportLastMessage(reportID: string, actionsToMerge?: ReportActions) const {lastMessageText = '', lastMessageTranslationKey = ''} = getLastVisibleMessage(reportID, actionsToMerge); if (lastMessageText || lastMessageTranslationKey) { - const report = getReportOrDraftReport(reportID); + const report = getReport(reportID); const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(reportID, canUserPerformWriteAction(report), actionsToMerge); const lastVisibleActionCreated = lastVisibleAction?.created; const lastActorAccountID = lastVisibleAction?.actorAccountID; From 631925a7e2e5f7e75ea6bd661ee506f7d2d8bf15 Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Mon, 11 Nov 2024 20:40:01 +1300 Subject: [PATCH 34/94] Fix fullscreen video controls difficult to click --- .../Attachments/AttachmentCarousel/index.tsx | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/components/Attachments/AttachmentCarousel/index.tsx b/src/components/Attachments/AttachmentCarousel/index.tsx index d9c4f7e93fbe..bf1daece733e 100644 --- a/src/components/Attachments/AttachmentCarousel/index.tsx +++ b/src/components/Attachments/AttachmentCarousel/index.tsx @@ -3,7 +3,7 @@ import type {MutableRefObject} from 'react'; import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {ListRenderItemInfo} from 'react-native'; import {Keyboard, PixelRatio, View} from 'react-native'; -import type {GestureType} from 'react-native-gesture-handler'; +import type {ComposedGesture, GestureType} from 'react-native-gesture-handler'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import {useOnyx} from 'react-native-onyx'; import Animated, {scrollTo, useAnimatedRef, useSharedValue} from 'react-native-reanimated'; @@ -38,6 +38,19 @@ const viewabilityConfig = { const MIN_FLING_VELOCITY = 500; +type DeviceAwareGestureDetectorProps = { + canUseTouchScreen: boolean; + gesture: ComposedGesture | GestureType; + children: React.ReactNode; +}; + +function DeviceAwareGestureDetector({canUseTouchScreen, gesture, children}: DeviceAwareGestureDetectorProps) { + // Don't render GestureDetector on non-touchable devices to prevent unexpected pointer event capture. + // This issue is left out on touchable devices since finger touch works fine. + // See: https://github.com/Expensify/App/issues/51246 + return canUseTouchScreen ? {children} : children; +} + function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibility, type, accountID, onClose}: AttachmentCarouselProps) { const theme = useTheme(); const {translate} = useLocalize(); @@ -286,7 +299,10 @@ function AttachmentCarousel({report, source, onNavigate, setDownloadButtonVisibi cancelAutoHideArrow={cancelAutoHideArrows} /> - + - + From 2c145f09eac8953cb750f6134a162755164e07ea Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 13 Nov 2024 09:40:53 +0100 Subject: [PATCH 35/94] fix: eslint --- desktop/main.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index f55d7e3c045a..e7b7e177fb0a 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -5,16 +5,16 @@ import log from 'electron-log'; import type {ElectronLog} from 'electron-log'; import {autoUpdater} from 'electron-updater'; import {machineId} from 'node-machine-id'; +import checkForUpdates from '@libs/checkForUpdates'; +import * as Localize from '@libs/Localize'; +import CONFIG from '@src/CONFIG'; +import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; +import type PlatformSpecificUpdater from '@src/setup/platformSetup/types'; +import type {Locale} from '@src/types/onyx'; import type {CreateDownloadQueueModule, DownloadItem} from './createDownloadQueue'; import serve from './electron-serve'; import ELECTRON_EVENTS from './ELECTRON_EVENTS'; -import CONFIG from './src/CONFIG'; -import CONST from './src/CONST'; -import type {TranslationPaths} from './src/languages/types'; -import checkForUpdates from './src/libs/checkForUpdates'; -import * as Localize from './src/libs/Localize'; -import type PlatformSpecificUpdater from './src/setup/platformSetup/types'; -import type {Locale} from './src/types/onyx'; const createDownloadQueue = require('./createDownloadQueue').default; From dfdfb191a56bced20df82737a1525bd681458f0b Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 13 Nov 2024 22:21:55 +0530 Subject: [PATCH 36/94] minor fix. Signed-off-by: krishna2323 --- src/hooks/usePaginatedReportActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/usePaginatedReportActions.ts b/src/hooks/usePaginatedReportActions.ts index 8bcc7c9ce539..791b6c47d1a0 100644 --- a/src/hooks/usePaginatedReportActions.ts +++ b/src/hooks/usePaginatedReportActions.ts @@ -12,7 +12,7 @@ function usePaginatedReportActions(reportID?: string, reportActionID?: string) { // Use `||` instead of `??` to handle empty string. // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const reportIDWithDefault = reportID || '-1'; - const report = ReportUtils.getReport(reportIDWithDefault); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const canUserPerformWriteAction = ReportUtils.canUserPerformWriteAction(report); const [sortedAllReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDWithDefault}`, { From 78282c3183d18147d15e051a8195a774efc741a1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 13:52:34 +0700 Subject: [PATCH 37/94] prettier --- src/components/SelectionList/BaseSelectionList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 2f79c34d3f8a..1186ca9acc6f 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -649,7 +649,10 @@ function BaseSelectionList( // else focus on the first non disabled item const newSelectedIndex = - (isEmpty(prevTextInputValue) && textInputValue === '') || (flattenedSections.selectedOptions.length !== prevSelectedOptionsLength && prevAllOptionsLength === flattenedSections.allOptions.length) ? -1 : 0; + (isEmpty(prevTextInputValue) && textInputValue === '') || + (flattenedSections.selectedOptions.length !== prevSelectedOptionsLength && prevAllOptionsLength === flattenedSections.allOptions.length) + ? -1 + : 0; // reseting the currrent page to 1 when the user types something setCurrentPage(1); From 885a7b3312cd68e625289ba3edf7f86602fa14a7 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 15 Nov 2024 13:55:47 +0700 Subject: [PATCH 38/94] extend comment --- src/components/SelectionList/BaseSelectionList.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index 1186ca9acc6f..66d648f1b472 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -645,9 +645,8 @@ function BaseSelectionList( ) { return; } - // Remove the focus if the search input is empty or selected options length is changed (and allOptions length remains the same) + // Remove the focus if the search input is empty and prev search input not empty or selected options length is changed (and allOptions length remains the same) // else focus on the first non disabled item - const newSelectedIndex = (isEmpty(prevTextInputValue) && textInputValue === '') || (flattenedSections.selectedOptions.length !== prevSelectedOptionsLength && prevAllOptionsLength === flattenedSections.allOptions.length) From 9a622df4f3f13d1dcb808351eeceec4625f17208 Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Fri, 15 Nov 2024 00:10:14 -0800 Subject: [PATCH 39/94] fix category menu stll presents while the list is empty --- src/pages/Search/AdvancedSearchFilters.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index 4398f89e3ecc..ba7b71c7fecc 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -28,6 +28,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList, Policy, PolicyTagLists, Report} from '@src/types/onyx'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const baseFilterConfig = { date: { @@ -266,7 +267,9 @@ function AdvancedSearchFilters() { const personalDetails = usePersonalDetails(); const [policies = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY); - const [allPolicyCategories = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); + const [allPolicyCategories = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, { + selector: (policyCategories) => Object.fromEntries(Object.entries(policyCategories ?? {}).filter(([_, categories]) => !isEmptyObject(categories))), + }); const singlePolicyCategories = allPolicyCategories[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const [allPolicyTagLists = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); const singlePolicyTagLists = allPolicyTagLists[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`]; From 63a2a8c653a348bf494255c055b6739f3562ff51 Mon Sep 17 00:00:00 2001 From: NJ-2020 Date: Fri, 15 Nov 2024 00:26:42 -0800 Subject: [PATCH 40/94] fix eslint error --- src/pages/Search/AdvancedSearchFilters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index ba7b71c7fecc..74b089ddbaad 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -268,7 +268,7 @@ function AdvancedSearchFilters() { const [policies = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [allPolicyCategories = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, { - selector: (policyCategories) => Object.fromEntries(Object.entries(policyCategories ?? {}).filter(([_, categories]) => !isEmptyObject(categories))), + selector: (policyCategories) => Object.fromEntries(Object.entries(policyCategories ?? {}).filter(([, categories]) => !isEmptyObject(categories))), }); const singlePolicyCategories = allPolicyCategories[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const [allPolicyTagLists = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); From 94ef46b065965fff6cb0479223933250a4b260c6 Mon Sep 17 00:00:00 2001 From: c3024 Date: Fri, 15 Nov 2024 18:46:08 +0530 Subject: [PATCH 41/94] remove links to the tour after it has been viewed from anywhere --- src/pages/Search/EmptySearchView.tsx | 26 ++++++++++++++++--- .../FloatingActionButtonAndPopover.tsx | 4 +++ src/types/onyx/IntroSelected.ts | 3 +++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 19d00a06771e..f9a4350f730c 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -16,12 +16,15 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; +import {hasSeenTourSelector} from '@libs/onboardingSelectors'; import * as ReportUtils from '@libs/ReportUtils'; import {getNavatticURL} from '@libs/TourUtils'; import * as TripsResevationUtils from '@libs/TripReservationUtils'; import variables from '@styles/variables'; import * as IOU from '@userActions/IOU'; import * as Link from '@userActions/Link'; +import * as Task from '@userActions/Task'; +import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; @@ -94,9 +97,15 @@ function EmptySearchView({type}: EmptySearchViewProps) { ); }, [styles, translate, ctaErrorMessage]); - const [onboardingPurpose] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {selector: (introSelected) => introSelected?.choice}); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); + const onboardingPurpose = introSelected?.choice; const {environment} = useEnvironment(); const navatticURL = getNavatticURL(environment, onboardingPurpose); + const [hasSeenTour = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + selector: hasSeenTourSelector, + }); + const viewTourTaskReportID = introSelected?.['viewTour']; + const [viewTourTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${viewTourTaskReportID}`); const content = useMemo(() => { switch (type) { @@ -123,7 +132,18 @@ function EmptySearchView({type}: EmptySearchViewProps) { title: translate('search.searchResults.emptyExpenseResults.title'), subtitle: translate('search.searchResults.emptyExpenseResults.subtitle'), buttons: [ - {buttonText: translate('emptySearchView.takeATour'), buttonAction: () => Link.openExternalLink(navatticURL)}, + ...(!hasSeenTour + ? [ + { + buttonText: translate('emptySearchView.takeATour'), + buttonAction: () => { + Link.openExternalLink(navatticURL); + Welcome.setSelfTourViewed(); + Task.completeTask(viewTourTaskReport); + }, + }, + ] + : []), { buttonText: translate('iou.createExpense'), buttonAction: () => interceptAnonymousUser(() => IOU.startMoneyRequest(CONST.IOU.TYPE.CREATE, ReportUtils.generateReportID())), @@ -143,7 +163,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { headerContentStyles: styles.emptyStateFolderWebStyles, }; } - }, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage, navatticURL]); + }, [type, StyleUtils, translate, theme, styles, subtitleComponent, ctaErrorMessage, navatticURL, hasSeenTour, viewTourTaskReport]); return ( { Welcome.setSelfTourViewed(); Link.openExternalLink(navatticURL); + Task.completeTask(viewTourTaskReport); }, }, ] diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index edeb79d34113..d8077a4a8a4a 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -11,6 +11,9 @@ type IntroSelected = { /** Whether the onboarding is complete */ isInviteOnboardingComplete?: boolean; + + /** Task reportID for 'viewTour' type */ + viewTour?: string; }; export default IntroSelected; From 7e0b7d20fac8048b9fa04ea5be5256f9100be95a Mon Sep 17 00:00:00 2001 From: c3024 Date: Fri, 15 Nov 2024 21:20:18 +0530 Subject: [PATCH 42/94] fix lint --- src/pages/Search/EmptySearchView.tsx | 2 +- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index f9a4350f730c..44e2a93316fe 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -104,7 +104,7 @@ function EmptySearchView({type}: EmptySearchViewProps) { const [hasSeenTour = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasSeenTourSelector, }); - const viewTourTaskReportID = introSelected?.['viewTour']; + const viewTourTaskReportID = introSelected?.viewTour; const [viewTourTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${viewTourTaskReportID}`); const content = useMemo(() => { diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx index 871c13ecce17..39233e4fa1c2 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.tsx @@ -506,7 +506,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu}: Fl reportNameValuePairs, ]); - const viewTourTaskReportID = introSelected?.['viewTour']; + const viewTourTaskReportID = introSelected?.viewTour; const [viewTourTaskReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${viewTourTaskReportID}`); return ( From 0e1da049d3dc09298742804f7fc1b398ca199d28 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 17 Nov 2024 00:02:10 +0530 Subject: [PATCH 43/94] add tests for ReportActionsUtils.getSortedReportActionsForDisplay. Signed-off-by: krishna2323 --- .../perf-test/ReportActionsUtils.perf-test.ts | 4 +- tests/unit/ReportActionsUtilsTest.ts | 111 +++++++++++++++++- 2 files changed, 110 insertions(+), 5 deletions(-) diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index b0ebd7c0c10a..4190d7c1c089 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -93,7 +93,7 @@ describe('ReportActionsUtils', () => { }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true); + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true, true); await waitForBatchedUpdates(); await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); @@ -132,7 +132,7 @@ describe('ReportActionsUtils', () => { test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true)); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true, true)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index b0d65517d12f..b129900a14e4 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -306,7 +306,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); expect(result).toStrictEqual(expectedOutput); }); @@ -401,7 +401,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, -1), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); expect(result).toStrictEqual(expectedOutput); }); @@ -445,10 +445,115 @@ describe('ReportActionsUtils', () => { message: [{html: '', type: 'Action type', text: 'Action text'}], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); input.pop(); expect(result).toStrictEqual(input); }); + + it('should filter whisper action in a archieved report', () => { + const input: ReportAction[] = [ + { + created: '2022-11-13 22:27:01.825', + reportActionID: '8401445780099176', + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + }, + { + created: '2022-11-12 22:27:01.825', + reportActionID: '6401435781022176', + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + }, + { + created: '2022-11-11 22:27:01.825', + reportActionID: '2962390724708756', + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + originalMessage: { + amount: 0, + currency: 'USD', + type: 'split', // change to const + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + }, + { + created: '2022-11-10 22:27:01.825', + reportActionID: '1609646094152486', + actionName: CONST.REPORT.ACTIONS.TYPE.RENAMED, + originalMessage: { + html: 'Hello world', + lastModified: '2022-11-10 22:27:01.825', + oldName: 'old name', + newName: 'new name', + }, + message: [ + { + html: 'Hello world', + type: 'Action type', + text: 'Action text', + }, + ], + }, + { + created: '2022-11-09 22:27:01.825', + reportActionID: '8049485084562457', + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER, + originalMessage: {}, + message: [{html: 'updated the Approval Mode from "Submit and Approve" to "Submit and Close"', type: 'Action type', text: 'Action text'}], + }, + { + created: '2022-11-08 22:27:06.825', + reportActionID: '1661970171066216', + actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED, + originalMessage: { + paymentType: 'ACH', + }, + message: [{html: 'Waiting for the bank account', type: 'Action type', text: 'Action text'}], + }, + { + created: '2022-11-06 22:27:08.825', + reportActionID: '1661970171066220', + actionName: CONST.REPORT.ACTIONS.TYPE.TASK_EDITED, + originalMessage: { + html: 'Hello world', + whisperedTo: [], + }, + message: [{html: 'I have changed the task', type: 'Action type', text: 'Action text'}], + }, + ]; + + // Expected output should have the `CREATED` action at last + // eslint-disable-next-line rulesdir/prefer-at + const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, 4), ...input.slice(5)]; + + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, false); + expect(result).toStrictEqual(expectedOutput); + }); }); describe('getLastVisibleAction', () => { From 676c82247bcee7c46f1ec201e692d942ffecdc36 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 17 Nov 2024 14:55:59 +0530 Subject: [PATCH 44/94] fix unit tests. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index b129900a14e4..9f4610fd94c1 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -547,11 +547,10 @@ describe('ReportActionsUtils', () => { }, ]; - // Expected output should have the `CREATED` action at last - // eslint-disable-next-line rulesdir/prefer-at + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); + // Expected output should filter out "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER" & "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER" action type const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, 4), ...input.slice(5)]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, false); expect(result).toStrictEqual(expectedOutput); }); }); From af332d716691ccd47e012229a90a0a4ec655d3df Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 17 Nov 2024 14:58:54 +0530 Subject: [PATCH 45/94] minor update. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 9f4610fd94c1..b2d310644de1 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -450,7 +450,7 @@ describe('ReportActionsUtils', () => { expect(result).toStrictEqual(input); }); - it('should filter whisper action in a archieved report', () => { + it('should filter whisper action in a archived report', () => { const input: ReportAction[] = [ { created: '2022-11-13 22:27:01.825', @@ -548,7 +548,8 @@ describe('ReportActionsUtils', () => { ]; const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); - // Expected output should filter out "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER" & "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER" action type + // Expected output should filter out "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER" & "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER" + // action type because "canUserPerformWriteAction" is false (report is archived) const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, 4), ...input.slice(5)]; expect(result).toStrictEqual(expectedOutput); From ed6c4ff37b5970b18b6a1e4fd3f253caf01aa775 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Sun, 17 Nov 2024 15:15:15 +0530 Subject: [PATCH 46/94] fix tests. Signed-off-by: krishna2323 --- tests/perf-test/ReportActionsUtils.perf-test.ts | 4 ++-- tests/unit/ReportActionsUtilsTest.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index 4190d7c1c089..b0ebd7c0c10a 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -93,7 +93,7 @@ describe('ReportActionsUtils', () => { }); test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true, true); + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true); await waitForBatchedUpdates(); await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray)); @@ -132,7 +132,7 @@ describe('ReportActionsUtils', () => { test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true, true)); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true)); }); test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index b2d310644de1..7528c29177cc 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -306,7 +306,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); expect(result).toStrictEqual(expectedOutput); }); @@ -401,7 +401,7 @@ describe('ReportActionsUtils', () => { // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, -1), input[1]]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); expect(result).toStrictEqual(expectedOutput); }); @@ -445,7 +445,7 @@ describe('ReportActionsUtils', () => { message: [{html: '', type: 'Action type', text: 'Action text'}], }, ]; - const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true, true); + const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, true); input.pop(); expect(result).toStrictEqual(input); }); From a1ef32e8e580dfa81b41540de98f7473ff78c9a6 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 18 Nov 2024 01:35:42 +0700 Subject: [PATCH 47/94] fix text link style is not applied in code block --- .../HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx index 122db1e7877b..64f3e2e87771 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx +++ b/src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx @@ -80,7 +80,7 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) { if (props.childTnode.tagName === 'br') { return {'\n'}; } - if (props.childTnode.type === 'text') { + if (props.childTnode.type === 'text' && props.childTnode.tagName !== 'code') { return ( Date: Sun, 17 Nov 2024 22:13:50 -0800 Subject: [PATCH 48/94] Replace "policy" with "workspace" in QuickBooks-Time.md --- docs/articles/expensify-classic/connections/QuickBooks-Time.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/connections/QuickBooks-Time.md b/docs/articles/expensify-classic/connections/QuickBooks-Time.md index 5bbd2c4b583c..bcc06e171d4f 100644 --- a/docs/articles/expensify-classic/connections/QuickBooks-Time.md +++ b/docs/articles/expensify-classic/connections/QuickBooks-Time.md @@ -1,6 +1,6 @@ --- title: Expensify and TSheets/QuickBooks Time Integration Guide -description: This help document explains how to connect TSheets/QuickBooks Time to your Expensify policy +description: This help document explains how to connect TSheets/QuickBooks Time to your Expensify workspace --- # Overview From a9d46821f66af440947ee75c99ac1c8e41eaddb1 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:19:07 -0800 Subject: [PATCH 49/94] Update Set-up-your-individual-workspace.md to reflect "workspace" in lieu of "policy" --- .../workspaces/Set-up-your-individual-workspace.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/workspaces/Set-up-your-individual-workspace.md b/docs/articles/expensify-classic/workspaces/Set-up-your-individual-workspace.md index c8be9a2728d5..04f2688eee90 100644 --- a/docs/articles/expensify-classic/workspaces/Set-up-your-individual-workspace.md +++ b/docs/articles/expensify-classic/workspaces/Set-up-your-individual-workspace.md @@ -10,7 +10,7 @@ To set up your individual workspace, 1. Hover over Settings, then click **Workspaces**. 2. Click the **Individual** tab on the left. -3. Select the policy type that best fits your needs. +3. Select the workspace type that best fits your needs. 4. Set up your workspace details including the workspace name, expense rules, categories, and more. {% include info.html %} From b6b5bcc0e6ba71e274e7586ef4e331d763525f74 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:20:56 -0800 Subject: [PATCH 50/94] Update Company-Card-Settings.md to reflect "workspace" in lieu of "policy" --- .../company-cards/Company-Card-Settings.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md index 0fde76c8fa92..553171d73dde 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md +++ b/docs/articles/expensify-classic/connect-credit-cards/company-cards/Company-Card-Settings.md @@ -29,9 +29,9 @@ Personal Liability: Users are allowed to delete company card expenses. If you update the settings on an existing company card feed, the changes will apply to expenses imported after the date the setting is saved. The update will not affect previously imported expenses. -## Preferred policy +## Preferred workspace -Setting a preferred policy for a company card feed will ensure that the imported transactions are added to a report on the policy you set. This setting is useful when members are on multiple policies and need to ensure their company card expenses are reported to a particular policy. +Setting a preferred workspace for a company card feed will ensure that the imported transactions are added to a report on the workspace you set. This setting is useful when members are on multiple workspaces and need to ensure their company card expenses are reported to a particular workspace. # How to use Scheduled Submit with company cards All expenses must be placed on a report if they need to be approved; with Scheduled Submit, you no longer need to worry about the arduous task of employees creating their expenses, adding them to a report, and submitting them manually. All they need to do is SmartScan their receipts and Concierge will take care of the rest, on a variety of schedules that you can set according to your preferences! @@ -41,11 +41,11 @@ Concierge won't automatically submit expenses on reports that have Expense Viola An employee can add comments in the Expense Comment field or at the bottom of the report to clarify any details. ## Enable Scheduled Submit -Scheduled Submit is enabled in the Group Policy by navigating to Settings > Policies > Group > Policy Name > Reports > Scheduled Submit +Scheduled Submit is enabled in the Group Workspace by navigating to Settings > Workspaces > Group > Workspace Name > Reports > Scheduled Submit Use the toggle to enable Scheduled Submit Choose your desired frequency -If Scheduled Submit is disabled on the group policy level (or set to a manual frequency), and you have noticed expense reports are still automatically submitted to the group policy, it's likely Scheduled Submit is enabled on the user’s Individual Policy settings. +If Scheduled Submit is disabled on the group workspace level (or set to a manual frequency), and you have noticed expense reports are still automatically submitted to the group workspace, it's likely Scheduled Submit is enabled on the user’s Individual Workspace settings. # How to connect company cards to an accounting integration @@ -59,7 +59,7 @@ You're all done. After the account is set, exported expenses will be mapped to t ## Pooled GL account To export credit card expenses to a pooled GL account: -Go to Settings > Policies > Group > Policy Name > Connections > Accounting Integrations > Configure +Go to Settings > Workspaces > Group > Workspace Name > Connections > Accounting Integrations > Configure Select Credit Card / Charge Card / Bank Transaction as your Non-reimbursable export option. Please review the Export Settings page for exporting Expense Reports to NetSuite Select the Vendor/liability account you want to export all non-reimbursable expenses to. @@ -86,7 +86,7 @@ It's important to note that eReceipts are not generated for lodging expenses. Mo {% include faq-begin.md %} ## What plan/subscription is required in order to manage corporate cards? -Group Policy (Collect or Control plan only) +Group Workspace (Collect or Control plan only) ## When do my company card transactions import to Expensify? Credit card transactions are imported to Expensify once they’re posted to the bank account. This usually takes 1-3 business days between the point of purchase and when the transactions populate in your account. From c76318c22d9fe09707b89205c26a3eef713d49d0 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:21:49 -0800 Subject: [PATCH 51/94] Update SAML-SSO.md to reflect "workspace" in lieu of "policy" --- docs/articles/expensify-classic/domains/SAML-SSO.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/domains/SAML-SSO.md b/docs/articles/expensify-classic/domains/SAML-SSO.md index a6032afe8d24..da4bd5639120 100644 --- a/docs/articles/expensify-classic/domains/SAML-SSO.md +++ b/docs/articles/expensify-classic/domains/SAML-SSO.md @@ -88,7 +88,7 @@ Before getting started, you will need a verified domain and Control plan to set 6. The new trust is now created. Highlight the trust, then click *Edit claim rules* on the right. 7. Click *Add a Rule*. 8. The default option should be *Send LDAP Attributes as Claims*. Click Next. -9. Depending upon how your Active Directory is set up, you may or may not have a useful email address associated with each user, or you may have a policy to use the UPN as the user attribute for authentication. If so, using the UPN user attribute may be appropriate for you. If not, you can use the email address attribute. +9. Depending upon how your Active Directory is set up, you may or may not have a useful email address associated with each user, or you may have a workspace to use the UPN as the user attribute for authentication. If so, using the UPN user attribute may be appropriate for you. If not, you can use the email address attribute. 10. Give the rule a name like *Get email address from AD*. Choose Active Directory as the attribute store from the dropdown list. Choose your source user attribute to pass to Expensify that has users’ email address info in it, usually either *E-Mail-Address* or *User-Principal-Name*. Select the outgoing claim type as “E-Mail Address”. Click OK. 11. Add another rule; this time, we want to *Transform an Incoming Claim*. Click Next. 12. Name the rule *Send email address*. The Incoming claim type should be *E-Mail Address*. The outgoing claim type should be *Name ID*, and the outgoing name ID format should be *Email*. Click OK. From 7c9db1e97bf258dcc295842b117b43c965438ac8 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:22:54 -0800 Subject: [PATCH 52/94] Update Change-Plan-Or-Subscription.md to reflect "workspace" in lieu of "policy" --- .../expensify-billing/Change-Plan-Or-Subscription.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md b/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md index b245a26d10a0..0c0153522af3 100644 --- a/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md +++ b/docs/articles/expensify-classic/expensify-billing/Change-Plan-Or-Subscription.md @@ -80,7 +80,7 @@ Once you’ve successfully downgraded to a free Expensify account, your Workspac ## Will I be charged for a monthly subscription even if I don't use SmartScans? Yes, the Monthly Subscription is prepaid and not based on activity, so you'll be charged regardless of usage. -## I'm on a group policy; do I need the monthly subscription too? -Probably not. Group policy members already have unlimited SmartScans, so there's usually no need to buy the subscription. However, you can use it for personal use if you leave your company's Workspace. +## I'm on a group workspace; do I need the monthly subscription too? +Probably not. Group workspace members already have unlimited SmartScans, so there's usually no need to buy the subscription. However, you can use it for personal use if you leave your company's Workspace. {% include faq-end.md %} From 3144de591da282010b6b31baa862841082d16e37 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:23:46 -0800 Subject: [PATCH 53/94] Update Personal-Credit-Cards.md to reflect "workspace" in lieu of "policy" --- .../connect-credit-cards/Personal-Credit-Cards.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md b/docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md index 05149ebf868e..36717a421c67 100644 --- a/docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md +++ b/docs/articles/expensify-classic/connect-credit-cards/Personal-Credit-Cards.md @@ -8,7 +8,7 @@ Welcome to the world of effortless expense tracking! Connecting your personal cr ## How to connect your personal card to import expenses Importing your card or bank via Account Settings will: Automatically sync your bank/card transactions with your Expensify account. These will merge seamlessly with any SmartScanned expenses in your account. -Generate IRS-compliant eReceipts, provided your Policy Admin has enabled this feature. +Generate IRS-compliant eReceipts, provided your Workspace Admin has enabled this feature. Discover below the numerous ways to easily bring your personal card expenses into Expensify below. ### *Important terms to know:* @@ -45,7 +45,7 @@ _Please note: an OFX file type will require no editing but not all banks' OFX fi 6. Set the date format to match your CSV and adjust the currency to match your bank account currency. 7. If you've previously imported expenses for the same card, choose the default layout of a previously uploaded spreadsheet. 8. Scroll down and select which columns map to the merchant, date and amount (as a number without a currency symbol) – these are required presets which must be assigned. -9. If applicable, you can also map specific Categories and Tags as long as you don't have an integration connection to your default group policy. If you have an integration connected, you'll want to add the Categories and Tags to the expense after the expense is uploaded. +9. If applicable, you can also map specific Categories and Tags as long as you don't have an integration connection to your default group workspace. If you have an integration connected, you'll want to add the Categories and Tags to the expense after the expense is uploaded. 10. Check the preview of your selection under *Output Preview*. If everything looks good, you can then select *Add Expenses*. 11. For checking accounts, you may need to "Flip Amount Sign" as transactions are often exported as negative amounts. From 2888230032e9c945f7a58893bc15fca1ee5406d5 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:25:59 -0800 Subject: [PATCH 54/94] Update Configure-Netsuite.md to reflect "workspace" in lieu of "policy" --- .../connections/netsuite/Configure-Netsuite.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md index aecf21acfc3f..068e4dd5bca9 100644 --- a/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md +++ b/docs/articles/expensify-classic/connections/netsuite/Configure-Netsuite.md @@ -40,18 +40,18 @@ The three options for the date your report will export with are: **Expense Reports:** Expensify transactions will export reimbursable expenses as expense reports by default, which will be posted to the payables account designated in NetSuite. -**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and will be mapped to the subsidiary associated with the corresponding policy. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. +**Vendor Bills:** Expensify transactions export as vendor bills in NetSuite and will be mapped to the subsidiary associated with the corresponding workspace. Each report will be posted as payable to the vendor associated with the employee who submitted the report. You can also set an approval level in NetSuite for vendor bills. -**Journal Entries:** Expensify transactions that are set to export as journal entries in NetSuite will be mapped to the subsidiary associated with this policy. All the transactions will be posted to the payable account specified in the policy. You can also set an approval level in NetSuite for the journal entries. +**Journal Entries:** Expensify transactions that are set to export as journal entries in NetSuite will be mapped to the subsidiary associated with this workspace. All the transactions will be posted to the payable account specified in the workspace. You can also set an approval level in NetSuite for the journal entries. - Journal entry forms by default do not contain a customer column, so it is not possible to export customers or projects with this export option - The credit line and header level classifications are pulled from the employee record ## Export Settings for Non-Reimbursable Expenses -**Vendor Bills:** Non-reimbursable expenses will be posted as a vendor bill payable to the default vendor specified in your policy's connection settings. If you centrally manage your company cards through Domains, you can export expenses from each card to a specific vendor in NetSuite. You can also set an approval level in NetSuite for the bills. +**Vendor Bills:** Non-reimbursable expenses will be posted as a vendor bill payable to the default vendor specified in your workspace's connection settings. If you centrally manage your company cards through Domains, you can export expenses from each card to a specific vendor in NetSuite. You can also set an approval level in NetSuite for the bills. -**Journal Entries:** Non-reimbursable expenses will be posted to the Journal Entries posting account selected in your policy's connection settings. If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in NetSuite. +**Journal Entries:** Non-reimbursable expenses will be posted to the Journal Entries posting account selected in your workspace's connection settings. If you centrally manage your company cards through Domains, you can export expenses from each card to a specific account in NetSuite. - Expensify Card expenses will always export as Journal Entries, even if you have Expense Reports or Vendor Bills configured for non-reimbursable expenses on the Export tab - Journal entry forms do not contain a customer column, so it is not possible to export customers or projects with this export option From ac12ff6d13e4bd61d0251340af468203f8cd886a Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:27:34 -0800 Subject: [PATCH 55/94] Update Create-and-Pay-Bills.md to reflect "workspace" in lieu of "policy" --- .../bank-accounts-and-payments/payments/Create-and-Pay-Bills.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md index aff11c059d81..b231984f61e2 100644 --- a/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md +++ b/docs/articles/expensify-classic/bank-accounts-and-payments/payments/Create-and-Pay-Bills.md @@ -11,7 +11,7 @@ You can receive bills in three ways: - Manual Upload: For physical bills, create a Bill in Expensify from the Reports page. # Bill Pay Workflow -1. When a vendor or supplier sends a bill to Expensify, the document is automatically SmartScanned, and a Bill is created. This Bill is managed by the primary domain contact, who can view it on the Reports page within their default group policy. +1. When a vendor or supplier sends a bill to Expensify, the document is automatically SmartScanned, and a Bill is created. This Bill is managed by the primary domain contact, who can view it on the Reports page within their default group workspace. 2. Once the Bill is ready for processing, it follows the established approval workflow. As each person approves it, the Bill appears in the next approver’s Inbox. The final approver will pay the Bill using one of the available payment methods. From eb3db9b2499dce334cabd5d8aac21072e3949d67 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:28:00 -0800 Subject: [PATCH 56/94] Update Enable-and-set-up-expense-violations.md to reflect "workspace" in lieu of "policy" --- .../workspaces/Enable-and-set-up-expense-violations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md index 7c3d8077c14d..1d5814138f6e 100644 --- a/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md +++ b/docs/articles/expensify-classic/workspaces/Enable-and-set-up-expense-violations.md @@ -29,7 +29,7 @@ If your workspace has automations set to automatically submit reports for approv - **Receipt required amount**: How much a single expense can cost before a receipt is required {% include info.html %} -Expensify includes certain system mandatory violations that can't be disabled, even if your policy has violations turned off. +Expensify includes certain system mandatory violations that can't be disabled, even if your workspace has violations turned off. {% include end-info.html %} # Set category rules From 714960442aeb35aa8562bdf29ba9ff115f9ec483 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:28:33 -0800 Subject: [PATCH 57/94] Update Netsuite-Troubleshooting.md to reflect "workspace" in lieu of "policy" --- .../connections/netsuite/Netsuite-Troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md b/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md index 01aa21a28b80..6e3e9beef144 100644 --- a/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md +++ b/docs/articles/expensify-classic/connections/netsuite/Netsuite-Troubleshooting.md @@ -97,8 +97,8 @@ This can happen if the employee’s subsidiary in NetSuite doesn’t match what - Ensure the email on the employee record in NetSuite matches the email address of the report submitter in Expensify. - In NetSuite, make sure the employee's hire date is in the past and/or the termination date is in the future. 4. **Currency Match for Journal Entries:** - - If exporting as Journal Entries, ensure the currency for the NetSuite employee record, NetSuite subsidiary, and Expensify policy all match. - - In NetSuite, go to the **Human Resources** tab > **Expense Report Currencies**, and add the subsidiary/policy currency if necessary. + - If exporting as Journal Entries, ensure the currency for the NetSuite employee record, NetSuite subsidiary, and Expensify workspace all match. + - In NetSuite, go to the **Human Resources** tab > **Expense Report Currencies**, and add the subsidiary/workspace currency if necessary. # ExpensiError NS0024: Invalid Customer or Project Tag From 57e60a8b197a44131d19c281f137d7702a9a0ce0 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:28:54 -0800 Subject: [PATCH 58/94] Update Create-a-workspace-for-yourself.md to reflect "workspace" in lieu of "policy" --- .../getting-started/Create-a-workspace-for-yourself.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself.md b/docs/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself.md index 69dea87ad8ea..5d64a9de3df5 100644 --- a/docs/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself.md +++ b/docs/articles/expensify-classic/getting-started/Create-a-workspace-for-yourself.md @@ -39,7 +39,7 @@ Here’s how to determine whether a personal or group workspace might be best fo
  1. Hover over Settings, then click Workspaces.
  2. Click the Individual tab on the left.
  3. -
  4. Select the policy type that best fits your needs.
  5. +
  6. Select the workspace type that best fits your needs.
  7. Set up your workspace details including the workspace name, expense rules, categories, and more.
From f3a2e762792d18b77acea83d63aa613ddb0322d6 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:30:20 -0800 Subject: [PATCH 59/94] Update Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md to reflect "workspace" in lieu of "policy" --- .../Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md index bded231d1daa..66466b57c854 100644 --- a/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md +++ b/docs/articles/expensify-classic/getting-started/playbooks/Expensify-Playbook-For-US-Based-Bootstrapped-Startups.md @@ -90,4 +90,4 @@ To view and pay bills: When you have bills to pay you can click *View all bills* under the *Manage your bills* box and we’ll keep a neatly organized list of all of the bills you can pay via ACH directly from your Expensify account. # You’re all set! -Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Concierge directly in *[new.expensify.com](https://new.expensify.com/concierge)*, or email concierge@expensify.com. Create a Collect or Control Policy, and we’ll automatically assign a dedicated Setup Specialist to you. +Congrats, you are all set up! If you need any assistance with anything mentioned above, reach out to either your Concierge directly in *[new.expensify.com](https://new.expensify.com/concierge)*, or email concierge@expensify.com. Create a Collect or Control Workspace, and we’ll automatically assign a dedicated Setup Specialist to you. From 7e3625fac2465fc962604db789dd5bed9643d526 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:31:55 -0800 Subject: [PATCH 60/94] Update Deel.md to reflect "workspace" in lieu of "policy" --- docs/articles/expensify-classic/connections/Deel.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/articles/expensify-classic/connections/Deel.md b/docs/articles/expensify-classic/connections/Deel.md index 12e616d9657f..bdc4b89206ca 100644 --- a/docs/articles/expensify-classic/connections/Deel.md +++ b/docs/articles/expensify-classic/connections/Deel.md @@ -5,7 +5,7 @@ description: Automatically sync expenses from Expensify to Deel # Overview -This guide is for business clients who want to set up policies and synchronize expenses from Expensify to Deel. This one-way synchronization ensures that Expensify becomes the definitive source for all employee expenses. +This guide is for business clients who want to set up workspaces and synchronize expenses from Expensify to Deel. This one-way synchronization ensures that Expensify becomes the definitive source for all employee expenses. If you are a contractor or employee working for a company using Expensify, please refer to: @@ -16,7 +16,7 @@ If you are a contractor or employee working for a company using Expensify, pleas By integrating Expensify with Deel, you can utilize Expensify’s approval workflows to ensure timely payment through Deel for your team. -This process involves aligning user profiles and expense policies between Expensify and Deel. Once connected, Deel will scan for approved expenses from matched users included in selected workspaces for integration, allowing Deel to import these expenses for reimbursement. +This process involves aligning user profiles and expense workspaces between Expensify and Deel. Once connected, Deel will scan for approved expenses from matched users included in selected workspaces for integration, allowing Deel to import these expenses for reimbursement. This synchronization is one-way. Expenses and receipts logged and approved in Expensify will sync to Deel. Expenses logged in Deel will not sync to Expensify. @@ -27,7 +27,7 @@ This synchronization is one-way. Expenses and receipts logged and approved in Ex To establish a connection, make sure you have the following: - Deel Organization Manager permissions -- Expensify Admin permissions for policies you wish to integrate with Deel +- Expensify Admin permissions for workspaces you wish to integrate with Deel - A paid Expensify subscription to approve expenses and sync them to Deel Expensify Admin permissions can be intricate. Refer to [Expensify’s Introduction to Integration]([https://example.com](https://integrations.expensify.com/Integration-Server/doc/#introduction)) for more details. From 3f9c311ef0dbe594f3d41bc2f9a338a83479caad Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:32:46 -0800 Subject: [PATCH 61/94] Update Tax-Tracking.md to reflect "workspace" in lieu of "policy" --- docs/articles/expensify-classic/workspaces/Tax-Tracking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/articles/expensify-classic/workspaces/Tax-Tracking.md b/docs/articles/expensify-classic/workspaces/Tax-Tracking.md index 7b859c5101b1..c47e5ed51f32 100644 --- a/docs/articles/expensify-classic/workspaces/Tax-Tracking.md +++ b/docs/articles/expensify-classic/workspaces/Tax-Tracking.md @@ -11,9 +11,9 @@ Expensify’s tax tracking feature allows you to: # How to Enable Tax Tracking Tax tracking can be enabled in the Tax section of the Workspace settings of any Workspace, whether group or individual. ## If Connected to an Accounting Integration -If your group Workspace is connected to Xero, QuickBooks Online, Sage Intacct, or NetSuite, make sure to first enable tax via the connection configuration page (Settings > Policies > Group > [Workspace Name] > Connections > Configure) and then sync the connection. Your tax rates will be imported from the accounting system and indicated by its logo. +If your group Workspace is connected to Xero, QuickBooks Online, Sage Intacct, or NetSuite, make sure to first enable tax via the connection configuration page (Settings > Workspaces > Group > [Workspace Name] > Connections > Configure) and then sync the connection. Your tax rates will be imported from the accounting system and indicated by its logo. ## Not Connected to an Accounting Integration -If your Workspace is not connected to an accounting system, go to Settings > Policies > Group > [Workspace Name] > Tax to enable tax. +If your Workspace is not connected to an accounting system, go to Settings > Workspaces > Group > [Workspace Name] > Tax to enable tax. # Tracking Tax by Expense Category To set a different tax rate for a specific expense type in the Workspace currency, go to Settings > Workspaces > Group > [Workspace Name] > Categories page. Click "Edit Rules" next to the desired category and set the "Category default tax". This will be applied to new expenses, overriding the default Workspace currency tax rate. From 3114a2e6d047490bee353186977a4d5968cf7ba9 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:33:18 -0800 Subject: [PATCH 62/94] Update Automatic-Receipt-Audit.md to reflect "workspace" in lieu of "policy" --- .../expensify-classic/reports/Automatic-Receipt-Audit.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/reports/Automatic-Receipt-Audit.md b/docs/articles/expensify-classic/reports/Automatic-Receipt-Audit.md index f0d112b86e9f..61640ce69b77 100644 --- a/docs/articles/expensify-classic/reports/Automatic-Receipt-Audit.md +++ b/docs/articles/expensify-classic/reports/Automatic-Receipt-Audit.md @@ -17,5 +17,5 @@ All Expensify Control plans automatically come with Concierge Receipt Audit. If **Can I disable Concierge Receipt Audit?** -All Control plan policies automatically include Concierge Receipt Audit. At this time, it cannot be disabled. +All Control plan workspaces automatically include Concierge Receipt Audit. At this time, it cannot be disabled. {% include faq-end.md %} From c4d2cadb22a17161efa015e208f2aa6d19f8d773 Mon Sep 17 00:00:00 2001 From: greg-schroeder Date: Sun, 17 Nov 2024 22:33:54 -0800 Subject: [PATCH 63/94] Update Greenhouse.md to reflect "workspace" in lieu of "policy" --- docs/articles/expensify-classic/connections/Greenhouse.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/articles/expensify-classic/connections/Greenhouse.md b/docs/articles/expensify-classic/connections/Greenhouse.md index b44e5a090d17..282ba33fd607 100644 --- a/docs/articles/expensify-classic/connections/Greenhouse.md +++ b/docs/articles/expensify-classic/connections/Greenhouse.md @@ -38,6 +38,6 @@ Expensify's direct integration with Greenhouse allows you to automatically send ## In Expensify: -1. Navigate to **Settings > Policies > Group > _[Workspace Name]_ > Members** +1. Navigate to **Settings > Workspaces > Group > _[Workspace Name]_ > Members** 2. The candidate you just sent to Expensify should be listed in the workspace members list 3. If the Recruiter (or Recruiting Coordinator) field was filled in in Greenhouse, the candidate will already be configured to submit reports to that recruiter for approval. If no Recruiter was selected, then the candidate will submit based on the Expensify workspace approval settings. From 789bc6a6702d52656b1d7255deb8f2b1f9c07524 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 18 Nov 2024 18:07:42 +0300 Subject: [PATCH 64/94] ellipsis color style --- src/components/ParentNavigationSubtitle.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 997106f3e649..11b361642c6e 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -52,7 +52,7 @@ function ParentNavigationSubtitle({parentNavigationSubtitleData, parentReportAct style={pressableStyles} > {!!reportName && ( From c6ca14f5dcf201468518a440b0ba27c054c30dde Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:57:40 +1300 Subject: [PATCH 65/94] Add patch to enable allowRecursiveCommitsWithSynchronousMountOnAndroid feature flag --- ...ts-with-synchronous-mount-on-android.patch | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch diff --git a/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch b/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch new file mode 100644 index 000000000000..e3bf56f2e458 --- /dev/null +++ b/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch @@ -0,0 +1,20 @@ +diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp +index 280ae46..2f29b9b 100644 +--- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp ++++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp +@@ -483,8 +483,13 @@ void Binding::schedulerShouldRenderTransactions( + return; + } + +- if (ReactNativeFeatureFlags:: +- allowRecursiveCommitsWithSynchronousMountOnAndroid()) { ++ // if (ReactNativeFeatureFlags:: ++ // allowRecursiveCommitsWithSynchronousMountOnAndroid()) { ++ ++ // Avoid freeze and crash caused by deadlock. ++ // Remove this patch after the feature flag is enabled by default. ++ // See: https://github.com/Expensify/App/issues/52496 ++ if (true) { + std::vector pendingTransactions; + + { From 14002f325322650532011c5e5f970dfdf4f77854 Mon Sep 17 00:00:00 2001 From: Ugo Giordano <144606237+ugogiordano@users.noreply.github.com> Date: Tue, 19 Nov 2024 01:40:33 +0100 Subject: [PATCH 66/94] Update ConciergePage.tsx fixing linter checks --- src/pages/ConciergePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index 7ab8deef1bef..514e43638a0e 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -2,7 +2,8 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import {OnyxEntry, useOnyx,withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; From 98335d171a19b33f81e3a81e655eed3eeec27025 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Mon, 18 Nov 2024 17:08:31 -0800 Subject: [PATCH 67/94] FIX: Android - Caret resets to the beginning of the input --- ...eact-native+0.75.2+021+ReactEditText.patch | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 patches/react-native+0.75.2+021+ReactEditText.patch diff --git a/patches/react-native+0.75.2+021+ReactEditText.patch b/patches/react-native+0.75.2+021+ReactEditText.patch new file mode 100644 index 000000000000..2f9e3e3284a4 --- /dev/null +++ b/patches/react-native+0.75.2+021+ReactEditText.patch @@ -0,0 +1,42 @@ +diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java ++++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +@@ -418,6 +418,10 @@ public class ReactEditText extends AppCompatEditText { + return; + } + ++ maybeSetSelection(start, end); ++ } ++ ++ private void maybeSetSelection(int start, int end) { + if (start != ReactConstants.UNSET && end != ReactConstants.UNSET) { + // clamp selection values for safety + start = clampToTextLength(start); +@@ -544,7 +548,8 @@ public class ReactEditText extends AppCompatEditText { + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); + setInputType(mStagedInputType); +- setSelection(selectionStart, selectionEnd); ++ // Restore the selection ++ maybeSetSelection(selectionStart, selectionEnd); + } + } + +@@ -1063,11 +1068,17 @@ public class ReactEditText extends AppCompatEditText { + public void onAttachedToWindow() { + super.onAttachedToWindow(); + ++ int selectionStart = getSelectionStart(); ++ int selectionEnd = getSelectionEnd(); ++ + // Used to ensure that text is selectable inside of removeClippedSubviews + // See https://github.com/facebook/react-native/issues/6805 for original + // fix that was ported to here. + + super.setTextIsSelectable(true); ++ ++ // Restore the selection since `setTextIsSelectable` changed it. ++ maybeSetSelection(selectionStart, selectionEnd); + + if (mContainsImages) { + Spanned text = getText(); \ No newline at end of file From 71d75b9c6a71002f77418e67470548ff7c9b42b1 Mon Sep 17 00:00:00 2001 From: Novell <76243292+NJ-2020@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:50:23 -0800 Subject: [PATCH 68/94] fix category menu still presents in offline mode --- src/pages/Search/AdvancedSearchFilters.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index e2fa6a5d92b4..ae35aeb5dab5 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -29,7 +29,6 @@ import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList, Policy, PolicyTagLists, Report} from '@src/types/onyx'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; const baseFilterConfig = { date: { @@ -279,7 +278,13 @@ function AdvancedSearchFilters() { const [policies = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [allPolicyCategories = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES, { - selector: (policyCategories) => Object.fromEntries(Object.entries(policyCategories ?? {}).filter(([, categories]) => !isEmptyObject(categories))), + selector: (policyCategories) => + Object.fromEntries( + Object.entries(policyCategories ?? {}).filter(([, categories]) => { + const availableCategories = Object.values(categories ?? {}).filter((category) => category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + return availableCategories.length > 0; + }), + ), }); const singlePolicyCategories = allPolicyCategories[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`]; const [allPolicyTagLists = {}] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); From 1baea20c071cd21bd6086f1256ff0bcaa86b23ea Mon Sep 17 00:00:00 2001 From: Novell <76243292+NJ-2020@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:51:21 -0800 Subject: [PATCH 69/94] revert isEmptyObject import --- src/pages/Search/AdvancedSearchFilters.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index ae35aeb5dab5..60c01e6f75f0 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -29,6 +29,7 @@ import ROUTES from '@src/ROUTES'; import type {SearchAdvancedFiltersForm} from '@src/types/form'; import type {CardList, PersonalDetailsList, Policy, PolicyTagLists, Report} from '@src/types/onyx'; import type {PolicyFeatureName} from '@src/types/onyx/Policy'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; const baseFilterConfig = { date: { From 10e3e96fd2c4ae4a510ca4ccc85ec122c82b04d7 Mon Sep 17 00:00:00 2001 From: c3024 Date: Tue, 19 Nov 2024 12:36:54 +0530 Subject: [PATCH 70/94] prettier --- src/pages/Search/EmptySearchView.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index 136b65ee8ad2..d541b13c63c2 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -18,8 +18,8 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; -import * as PolicyUtils from '@libs/PolicyUtils'; import {hasSeenTourSelector} from '@libs/onboardingSelectors'; +import * as PolicyUtils from '@libs/PolicyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {getNavatticURL} from '@libs/TourUtils'; import * as TripsResevationUtils from '@libs/TripReservationUtils'; @@ -191,7 +191,10 @@ function EmptySearchView({type}: EmptySearchViewProps) { ctaErrorMessage, navatticURL, shouldRedirectToExpensifyClassic, - , hasSeenTour, viewTourTaskReport]); + , + hasSeenTour, + viewTourTaskReport, + ]); return ( <> From a3604223949e0d79ea53a1782f20b7dd499eef83 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Tue, 19 Nov 2024 14:42:42 +0530 Subject: [PATCH 71/94] update test input data and comments. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 138 +++++++++++++++------------ 1 file changed, 78 insertions(+), 60 deletions(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 7528c29177cc..12e6c7ba2f92 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -450,72 +450,59 @@ describe('ReportActionsUtils', () => { expect(result).toStrictEqual(input); }); - it('should filter whisper action in a archived report', () => { + it('should filter actionable whisper actions e.g. "join", "create room" when room is archived', () => { const input: ReportAction[] = [ { - created: '2022-11-13 22:27:01.825', - reportActionID: '8401445780099176', - actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, - originalMessage: { - html: 'Hello world', - whisperedTo: [], - }, + created: '2024-11-19 07:59:27.352', + reportActionID: '2143762315092102133', + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, message: [ { - html: 'Hello world', - type: 'Action type', - text: 'Action text', + type: 'TEXT', + style: 'strong', + text: 'You', }, - ], - }, - { - created: '2022-11-12 22:27:01.825', - reportActionID: '6401435781022176', - actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, - originalMessage: { - html: 'Hello world', - whisperedTo: [], - }, - message: [ { - html: 'Hello world', - type: 'Action type', - text: 'Action text', + type: 'TEXT', + style: 'normal', + text: ' created this report', }, ], }, { - created: '2022-11-11 22:27:01.825', - reportActionID: '2962390724708756', - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + created: '2024-11-19 08:04:13.728', + reportActionID: '1607371725956675966', + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, originalMessage: { - amount: 0, - currency: 'USD', - type: 'split', // change to const + html: '', + whisperedTo: [], + lastModified: '2024-11-19 08:04:13.728', + mentionedAccountIDs: [18301266], }, message: [ { - html: 'Hello world', - type: 'Action type', - text: 'Action text', + html: '', + text: '@as', + type: 'COMMENT', + whisperedTo: [], }, ], }, { - created: '2022-11-10 22:27:01.825', - reportActionID: '1609646094152486', - actionName: CONST.REPORT.ACTIONS.TYPE.RENAMED, + created: '2024-11-19 08:00:14.352', + reportActionID: '4655978522337302598', + actionName: CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, originalMessage: { - html: 'Hello world', - lastModified: '2022-11-10 22:27:01.825', - oldName: 'old name', - newName: 'new name', + html: '#join', + whisperedTo: [], + lastModified: '2024-11-19 08:00:14.352', }, message: [ { - html: 'Hello world', - type: 'Action type', - text: 'Action text', + html: '#join', + text: '#join', + type: 'COMMENT', + whisperedTo: [], }, ], }, @@ -523,34 +510,65 @@ describe('ReportActionsUtils', () => { created: '2022-11-09 22:27:01.825', reportActionID: '8049485084562457', actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER, - originalMessage: {}, - message: [{html: 'updated the Approval Mode from "Submit and Approve" to "Submit and Close"', type: 'Action type', text: 'Action text'}], + originalMessage: { + lastModified: '2024-11-19 08:00:14.353', + mentionedAccountIDs: [], + whisperedTo: [18301266], + }, + message: { + html: "Heads up, #join doesn't exist yet. Do you want to create it?", + text: "Heads up, #join doesn't exist yet. Do you want to create it?", + type: 'COMMENT', + whisperedTo: [18301266], + }, }, + { - created: '2022-11-08 22:27:06.825', - reportActionID: '1661970171066216', - actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENT_QUEUED, + created: '2022-11-12 22:27:01.825', + reportActionID: '6401435781022176', + actionName: CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, originalMessage: { - paymentType: 'ACH', + inviteeAccountIDs: [18414674], + lastModified: '2024-11-19 08:04:25.813', + whisperedTo: [18301266], }, - message: [{html: 'Waiting for the bank account', type: 'Action type', text: 'Action text'}], + message: [ + { + html: "Heads up, isn't a member of this room.", + text: "Heads up, isn't a member of this room.", + type: 'COMMENT', + }, + ], }, { - created: '2022-11-06 22:27:08.825', - reportActionID: '1661970171066220', - actionName: CONST.REPORT.ACTIONS.TYPE.TASK_EDITED, + created: '2024-11-19 08:13:30.653', + reportActionID: '2700998753002050048', + actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, originalMessage: { - html: 'Hello world', - whisperedTo: [], + lastModified: '2024-11-19 08:13:30.653', + policyName: "As's Workspace 65", + reason: 'policyDeleted', }, - message: [{html: 'I have changed the task', type: 'Action type', text: 'Action text'}], + message: [ + { + type: 'TEXT', + style: 'strong', + text: 'You', + }, + { + type: 'TEXT', + style: 'normal', + text: ' closed this report', + }, + ], }, ]; const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); - // Expected output should filter out "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER" & "CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER" - // action type because "canUserPerformWriteAction" is false (report is archived) - const expectedOutput: ReportAction[] = [...input.slice(0, 1), ...input.slice(2, 4), ...input.slice(5)]; + // Expected output should filter out actionable whisper actions type e.g. "join", "create room" + // because "canUserPerformWriteAction" is false (report is archived) + // eslint-disable-next-line rulesdir/prefer-at + const expectedOutput: ReportAction[] = [...input.slice(1, 3), input[0]]; expect(result).toStrictEqual(expectedOutput); }); From 49c9ad48610756da0c7c09896f81a02b9c184c91 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 19 Nov 2024 10:42:07 +0100 Subject: [PATCH 72/94] remove cachedTotal key --- src/libs/ReportUtils.ts | 2 -- src/libs/actions/IOU.ts | 4 ---- src/types/onyx/Report.ts | 3 --- src/types/utils/whitelistedReportKeys.ts | 1 - 4 files changed, 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 373a861f7c2e..a6fa2ee45181 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -441,7 +441,6 @@ type TransactionDetails = { type OptimisticIOUReport = Pick< Report, - | 'cachedTotal' | 'type' | 'chatReportID' | 'currency' @@ -4553,7 +4552,6 @@ function buildOptimisticIOUReport( return { type: CONST.REPORT.TYPE.IOU, - cachedTotal: formattedTotal, chatReportID, currency, managerID: payerAccountID, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 5b3592407f89..a25eda71666a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2613,10 +2613,6 @@ function getUpdateMoneyRequestParams( updatedMoneyRequestReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, updatedReportAction.actorAccountID ?? -1, diff, TransactionUtils.getCurrency(transaction), false, true); } - if (updatedMoneyRequestReport) { - updatedMoneyRequestReport.cachedTotal = CurrencyUtils.convertToDisplayString(updatedMoneyRequestReport.total, transactionDetails?.currency); - } - optimisticData.push( { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 7eae6affd254..4df489930e23 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -151,9 +151,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< /** The report visibility */ visibility?: RoomVisibility; - /** Report cached total */ - cachedTotal?: string; - /** Invoice room receiver data */ invoiceReceiver?: InvoiceReceiver; diff --git a/src/types/utils/whitelistedReportKeys.ts b/src/types/utils/whitelistedReportKeys.ts index 71510e498854..b3126f07827b 100644 --- a/src/types/utils/whitelistedReportKeys.ts +++ b/src/types/utils/whitelistedReportKeys.ts @@ -34,7 +34,6 @@ type WhitelistedReport = OnyxCommon.OnyxValueWithOfflineFeedback< writeCapability: unknown; type: unknown; visibility: unknown; - cachedTotal: unknown; invoiceReceiver: unknown; lastMessageTranslationKey: unknown; parentReportID: unknown; From fa8281f23f59dd56ed4c7384bd4c3015c668d71d Mon Sep 17 00:00:00 2001 From: QichenZhu <57348009+QichenZhu@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:14:06 +1300 Subject: [PATCH 73/94] Remove comments --- ...cursive-commits-with-synchronous-mount-on-android.patch | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch b/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch index e3bf56f2e458..95c3b08dc433 100644 --- a/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch +++ b/patches/react-native+0.75.2+021+allow-recursive-commits-with-synchronous-mount-on-android.patch @@ -1,16 +1,13 @@ diff --git a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp -index 280ae46..2f29b9b 100644 +index 280ae46..572fb3d 100644 --- a/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp +++ b/node_modules/react-native/ReactAndroid/src/main/jni/react/fabric/Binding.cpp -@@ -483,8 +483,13 @@ void Binding::schedulerShouldRenderTransactions( +@@ -483,8 +483,10 @@ void Binding::schedulerShouldRenderTransactions( return; } - if (ReactNativeFeatureFlags:: - allowRecursiveCommitsWithSynchronousMountOnAndroid()) { -+ // if (ReactNativeFeatureFlags:: -+ // allowRecursiveCommitsWithSynchronousMountOnAndroid()) { -+ + // Avoid freeze and crash caused by deadlock. + // Remove this patch after the feature flag is enabled by default. + // See: https://github.com/Expensify/App/issues/52496 From 5868f72668a3fdd7277082d5feb8a72dab582374 Mon Sep 17 00:00:00 2001 From: Povilas Zirgulis Date: Tue, 19 Nov 2024 15:17:46 +0200 Subject: [PATCH 74/94] bump onyx version to 2.0.81 --- package-lock.json | 36 ++++++++++++++++++++++++++++++++---- package.json | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 22a03bc5e464..2f7848db4572 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,7 +95,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.79", + "react-native-onyx": "2.0.81", "react-native-pager-view": "6.5.0", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", @@ -30774,6 +30774,18 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "node_modules/lodash.bindall": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.bindall/-/lodash.bindall-4.4.0.tgz", + "integrity": "sha512-NQ+QvFohS2gPbWpyLfyuiF0ZQA3TTaJ+n0XDID5jwtMZBKE32gN5vSyy7xBVsqvJkvT/UY9dvHXIk9tZmBVF3g==", + "license": "MIT" + }, + "node_modules/lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==", + "license": "MIT" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "dev": true, @@ -30822,10 +30834,22 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "license": "MIT" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "license": "MIT" }, + "node_modules/lodash.transform": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", + "integrity": "sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==", + "license": "MIT" + }, "node_modules/lodash.union": { "version": "4.6.0", "dev": true, @@ -35727,13 +35751,17 @@ } }, "node_modules/react-native-onyx": { - "version": "2.0.79", - "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.79.tgz", - "integrity": "sha512-1rbhDdufp2vXmw3ttCtEXPK3p6F94nqKgqqvcRIqo6xLzgTI74rdm3Kqiyx4r6tYCTjN/TfmI/KLV+2EUShJZQ==", + "version": "2.0.81", + "resolved": "https://registry.npmjs.org/react-native-onyx/-/react-native-onyx-2.0.81.tgz", + "integrity": "sha512-EwBqruX4lLnlk3KyZp4bst/voekLJFus7UhtvKmDuqR2Iz/FremwE04JW6YxGyc7C6KpbQrCFdWg/oF9ptRAtg==", "license": "MIT", "dependencies": { "ascii-table": "0.0.9", "fast-equals": "^4.0.3", + "lodash.bindall": "^4.4.0", + "lodash.clone": "^4.5.0", + "lodash.pick": "^4.4.0", + "lodash.transform": "^4.6.0", "underscore": "^1.13.6" }, "engines": { diff --git a/package.json b/package.json index d984af3f9431..de759131c0b8 100644 --- a/package.json +++ b/package.json @@ -152,7 +152,7 @@ "react-native-launch-arguments": "^4.0.2", "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", - "react-native-onyx": "2.0.79", + "react-native-onyx": "2.0.81", "react-native-pager-view": "6.5.0", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", From 68d8fd61447bee19cd101f6b1be57d79cb52ba93 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 20 Nov 2024 02:50:12 +0530 Subject: [PATCH 75/94] update comments and input data. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 56 +++++++--------------------- 1 file changed, 14 insertions(+), 42 deletions(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 12e6c7ba2f92..4e0f59cce032 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -451,24 +451,11 @@ describe('ReportActionsUtils', () => { }); it('should filter actionable whisper actions e.g. "join", "create room" when room is archived', () => { + // Given several different action types, including actionable whispers for creating, inviting and joining rooms, as well as non-actionable whispers + // - ADD_COMMENT + // - ACTIONABLE_REPORT_MENTION_WHISPER + // - ACTIONABLE_MENTION_WHISPER const input: ReportAction[] = [ - { - created: '2024-11-19 07:59:27.352', - reportActionID: '2143762315092102133', - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'You', - }, - { - type: 'TEXT', - style: 'normal', - text: ' created this report', - }, - ], - }, { created: '2024-11-19 08:04:13.728', reportActionID: '1607371725956675966', @@ -540,35 +527,20 @@ describe('ReportActionsUtils', () => { }, ], }, - { - created: '2024-11-19 08:13:30.653', - reportActionID: '2700998753002050048', - actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, - originalMessage: { - lastModified: '2024-11-19 08:13:30.653', - policyName: "As's Workspace 65", - reason: 'policyDeleted', - }, - message: [ - { - type: 'TEXT', - style: 'strong', - text: 'You', - }, - { - type: 'TEXT', - style: 'normal', - text: ' closed this report', - }, - ], - }, ]; + // When the report actions are sorted for display with the second parameter (canUserPerformWriteAction) set to false (to simulate a report that has been archived) const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); - // Expected output should filter out actionable whisper actions type e.g. "join", "create room" - // because "canUserPerformWriteAction" is false (report is archived) + // Then the output should correctly filter out the actionable whisper types for "join", "inivite" and "create room" + // because the report is archived and making those actions not only doesn't make sense from a UX standpoint, + // but leads to server errors since those actions aren't possible. // eslint-disable-next-line rulesdir/prefer-at - const expectedOutput: ReportAction[] = [...input.slice(1, 3), input[0]]; + const expectedOutput: ReportAction[] = input.filter( + (action) => + action.actionName !== CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER && + action.actionName !== CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_JOIN_REQUEST && + action.actionName !== CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_MENTION_WHISPER, + ); expect(result).toStrictEqual(expectedOutput); }); From 7d32b416cdc49d55285ccffe5c9e82ec76507398 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 20 Nov 2024 02:58:05 +0530 Subject: [PATCH 76/94] minor update. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 4e0f59cce032..b444617bfd6f 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -531,9 +531,8 @@ describe('ReportActionsUtils', () => { // When the report actions are sorted for display with the second parameter (canUserPerformWriteAction) set to false (to simulate a report that has been archived) const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); - // Then the output should correctly filter out the actionable whisper types for "join", "inivite" and "create room" - // because the report is archived and making those actions not only doesn't make sense from a UX standpoint, - // but leads to server errors since those actions aren't possible. + // The output should correctly filter out the actionable whisper types for "join," "invite," and "create room" because the report is archived. + // Taking these actions not only doesn't make sense from a UX standpoint, but also leads to server errors since such actions are not possible. // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = input.filter( (action) => From 5954d3e19703cddaaafc577c07ebcd32fe2f3775 Mon Sep 17 00:00:00 2001 From: Andrew Gable Date: Tue, 19 Nov 2024 15:08:39 -0700 Subject: [PATCH 77/94] Change path to iOS and Android fastlane keys to fix production builds --- fastlane/Fastfile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index dcf7c9f238a6..54084367040c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -244,12 +244,12 @@ platform :android do ENV["SUPPLY_UPLOAD_MAX_RETRIES"]="5" google_play_track_version_codes( package_name: "org.me.mobiexpensifyg", - json_key: './android/app/android-fastlane-json-key.json', + json_key: './android-fastlane-json-key.json', track: 'internal' ) upload_to_play_store( package_name: "org.me.mobiexpensifyg", - json_key: './android/app/android-fastlane-json-key.json', + json_key: './android-fastlane-json-key.json', version_code: ENV["VERSION"].to_i, track: 'internal', track_promote_to: 'production', @@ -268,11 +268,11 @@ platform :android do productionVersionCodes = google_play_track_version_codes( track: 'production', package_name: "org.me.mobiexpensifyg", - json_key: './android/app/android-fastlane-json-key.json', + json_key: './android-fastlane-json-key.json', ) upload_to_play_store( package_name: "org.me.mobiexpensifyg", - json_key: './android/app/android-fastlane-json-key.json', + json_key: './android-fastlane-json-key.json', version_code: productionVersionCodes.sort.last, # Get the latest version code track: 'production', rollout: '1', @@ -592,7 +592,7 @@ platform :ios do desc "Submit HybridApp to 100% rollout on App Store" lane :complete_hybrid_rollout do - api_token = Spaceship::ConnectAPI::Token.from_json_file("./ios/ios-fastlane-json-key.json") + api_token = Spaceship::ConnectAPI::Token.from_json_file("./ios-fastlane-json-key.json") Spaceship::ConnectAPI.token = api_token app = Spaceship::ConnectAPI::App.find("com.expensify.expensifylite") @@ -604,7 +604,7 @@ platform :ios do lane :submit_hybrid_for_rollout do deliver( app_identifier: "com.expensify.expensifylite", - api_key_path: "./ios/ios-fastlane-json-key.json", + api_key_path: "./ios-fastlane-json-key.json", # Skip HTML report verification force: true, From b6f73fe2eb2cc9a8e18822826de2b95b3c55c3f3 Mon Sep 17 00:00:00 2001 From: Ugo Giordano <144606237+ugogiordano@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:52:20 +0100 Subject: [PATCH 78/94] Update ConciergePage.tsx Migrating from `withOnyx` to `useOnyx`. --- src/pages/ConciergePage.tsx | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index 514e43638a0e..40e2a6094ac7 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -1,38 +1,27 @@ import {useFocusEffect} from '@react-navigation/native'; -import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import ReportActionsSkeletonView from '@components/ReportActionsSkeletonView'; import ReportHeaderSkeletonView from '@components/ReportHeaderSkeletonView'; import ScreenWrapper from '@components/ScreenWrapper'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as App from '@userActions/App'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; -import type SCREENS from '@src/SCREENS'; -import type {Session} from '@src/types/onyx'; - -type ConciergePageOnyxProps = { - /** Session info for the currently logged in user. */ - session: OnyxEntry; -}; - -type ConciergePageProps = ConciergePageOnyxProps & StackScreenProps; /* * This is a "utility page", that does this: * - If the user is authenticated, find their concierge chat and re-route to it * - Else re-route to the login page */ -function ConciergePage({session}: ConciergePageProps) { +function ConciergePage() { const styles = useThemeStyles(); const isUnmounted = useRef(false); const {shouldUseNarrowLayout} = useResponsiveLayout(); + const [session] = useOnyx(ONYXKEYS.SESSION); const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA, {initialValue: true}); useFocusEffect( @@ -70,8 +59,4 @@ function ConciergePage({session}: ConciergePageProps) { ConciergePage.displayName = 'ConciergePage'; -export default withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, -})(ConciergePage); +export default ConciergePage; From 716c953b1d108f88b8a25a1e49b783421c1134a6 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 20 Nov 2024 15:02:21 +0530 Subject: [PATCH 79/94] remove 'eslint-disable-next-line' comment. Signed-off-by: krishna2323 --- tests/unit/ReportActionsUtilsTest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index b444617bfd6f..b94740375261 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -533,7 +533,6 @@ describe('ReportActionsUtils', () => { const result = ReportActionsUtils.getSortedReportActionsForDisplay(input, false); // The output should correctly filter out the actionable whisper types for "join," "invite," and "create room" because the report is archived. // Taking these actions not only doesn't make sense from a UX standpoint, but also leads to server errors since such actions are not possible. - // eslint-disable-next-line rulesdir/prefer-at const expectedOutput: ReportAction[] = input.filter( (action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.ACTIONABLE_REPORT_MENTION_WHISPER && From 47433dab737b6abbbef099efe87e092362ea8eb5 Mon Sep 17 00:00:00 2001 From: Tom Rhys Jones Date: Wed, 20 Nov 2024 10:31:23 +0000 Subject: [PATCH 80/94] Accounting task instruction update --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index ef5eaf4e403b..06feb863a80a 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5034,7 +5034,7 @@ const CONST = { '\n' + `Here’s how to connect to ${integrationName}:\n` + '\n' + - '1. Click your profile photo.\n' + + '1. Click the settings tab.\n' + '2. Go to Workspaces.\n' + '3. Select your workspace.\n' + '4. Click Accounting.\n' + From 0aa5aed214f3644f7ec10c68ebbd201ced3d9936 Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa <5201282+rlinoz@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:06:42 -0300 Subject: [PATCH 81/94] Revert "Fix distance amount changes after submitting track distance expense with description" --- .../parameters/CategorizeTrackedExpenseParams.ts | 2 -- .../API/parameters/ShareTrackedExpenseParams.ts | 2 -- src/libs/actions/IOU.ts | 16 +--------------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts index d999f96fb505..78eb0adecc5e 100644 --- a/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts +++ b/src/libs/API/parameters/CategorizeTrackedExpenseParams.ts @@ -20,8 +20,6 @@ type CategorizeTrackedExpenseParams = { taxCode: string; taxAmount: number; billable?: boolean; - waypoints?: string; - customUnitRateID?: string; }; export default CategorizeTrackedExpenseParams; diff --git a/src/libs/API/parameters/ShareTrackedExpenseParams.ts b/src/libs/API/parameters/ShareTrackedExpenseParams.ts index c89c0d400e72..cee4bc40d9ac 100644 --- a/src/libs/API/parameters/ShareTrackedExpenseParams.ts +++ b/src/libs/API/parameters/ShareTrackedExpenseParams.ts @@ -20,8 +20,6 @@ type ShareTrackedExpenseParams = { taxCode: string; taxAmount: number; billable?: boolean; - customUnitRateID?: string; - waypoints?: string; }; export default ShareTrackedExpenseParams; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e9eba8d57ef4..600cb8dbe328 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3389,8 +3389,6 @@ function categorizeTrackedExpense( billable?: boolean, receipt?: Receipt, createdWorkspaceParams?: CreateWorkspaceParams, - waypoints?: string, - customUnitRateID?: string, ) { const {optimisticData, successData, failureData} = onyxData ?? {}; @@ -3437,8 +3435,6 @@ function categorizeTrackedExpense( policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, - waypoints, - customUnitRateID, }; API.write(WRITE_COMMANDS.CATEGORIZE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); @@ -3474,8 +3470,6 @@ function shareTrackedExpense( billable?: boolean, receipt?: Receipt, createdWorkspaceParams?: CreateWorkspaceParams, - waypoints?: string, - customUnitRateID?: string, ) { const {optimisticData, successData, failureData} = onyxData ?? {}; @@ -3522,8 +3516,6 @@ function shareTrackedExpense( policyExpenseCreatedReportActionID: createdWorkspaceParams?.expenseCreatedReportActionID, adminsChatReportID: createdWorkspaceParams?.adminsChatReportID, adminsCreatedReportActionID: createdWorkspaceParams?.adminsCreatedReportActionID, - waypoints, - customUnitRateID, }; API.write(WRITE_COMMANDS.SHARE_TRACKED_EXPENSE, parameters, {optimisticData, successData, failureData}); @@ -3827,8 +3819,6 @@ function trackExpense( value: recentServerValidatedWaypoints, }); - const waypoints = validWaypoints ? JSON.stringify(sanitizeRecentWaypoints(validWaypoints)) : undefined; - switch (action) { case CONST.IOU.ACTION.CATEGORIZE: { if (!linkedTrackedExpenseReportAction || !actionableWhisperReportActionID || !linkedTrackedExpenseReportID) { @@ -3859,8 +3849,6 @@ function trackExpense( billable, trackedReceipt, createdWorkspaceParams, - waypoints, - customUnitRateID, ); break; } @@ -3892,8 +3880,6 @@ function trackExpense( billable, trackedReceipt, createdWorkspaceParams, - waypoints, - customUnitRateID, ); break; } @@ -3922,7 +3908,7 @@ function trackExpense( receiptGpsPoints: gpsPoints ? JSON.stringify(gpsPoints) : undefined, transactionThreadReportID: transactionThreadReportID ?? '-1', createdReportActionIDForThread: createdReportActionIDForThread ?? '-1', - waypoints, + waypoints: validWaypoints ? JSON.stringify(sanitizeRecentWaypoints(validWaypoints)) : undefined, customUnitRateID, }; if (actionableWhisperReportActionIDParam) { From 9f83a4118ff0b8080ac8c037b36a26252a12b06f Mon Sep 17 00:00:00 2001 From: OSBotify Date: Wed, 20 Nov 2024 12:53:13 +0000 Subject: [PATCH 82/94] Update version to 9.0.64-4 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2f7f2bd527db..b672c601e341 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006403 - versionName "9.0.64-3" + versionCode 1009006404 + versionName "9.0.64-4" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 5fb52516800a..08ab0c7aba57 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.64.3 + 9.0.64.4 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 09403adcc493..e3b4f59ce64d 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.64.3 + 9.0.64.4 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index d4ecfca41e38..7ecf35bd30d0 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.64 CFBundleVersion - 9.0.64.3 + 9.0.64.4 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 18d24d2d377b..445e4328e7ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.64-3", + "version": "9.0.64-4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.64-3", + "version": "9.0.64-4", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ce12f384ea45..4f66ca5d13dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.64-3", + "version": "9.0.64-4", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From c5835835e64190f5fd4b168ff04947b7240c64da Mon Sep 17 00:00:00 2001 From: c3024 Date: Wed, 20 Nov 2024 18:31:15 +0530 Subject: [PATCH 83/94] fix lint --- src/pages/Search/EmptySearchView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/EmptySearchView.tsx b/src/pages/Search/EmptySearchView.tsx index d541b13c63c2..507ab593eafb 100644 --- a/src/pages/Search/EmptySearchView.tsx +++ b/src/pages/Search/EmptySearchView.tsx @@ -191,7 +191,6 @@ function EmptySearchView({type}: EmptySearchViewProps) { ctaErrorMessage, navatticURL, shouldRedirectToExpensifyClassic, - , hasSeenTour, viewTourTaskReport, ]); From 58d3ffe1da15c4cbb03404181a8cccd62a42a80b Mon Sep 17 00:00:00 2001 From: kubabutkiewicz Date: Wed, 20 Nov 2024 17:20:09 +0100 Subject: [PATCH 84/94] fix: resolve comments --- desktop/main.ts | 1 + .../LHNOptionsList/OptionRowRendererComponent/index.native.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/desktop/main.ts b/desktop/main.ts index e7b7e177fb0a..4f642d90da51 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -515,6 +515,7 @@ const mainWindow = (): Promise => { // open the default browser instead of a new electron window browserWindow.webContents.setWindowOpenHandler(({url}) => { const denial = {action: 'deny'} as const; + // Make sure local urls stay in electron perimeter if (url.slice(0, 'file://'.length).toLowerCase() === 'file://') { return denial; diff --git a/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx b/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx index 2450ce800e0e..a0179145b77c 100644 --- a/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx +++ b/src/components/LHNOptionsList/OptionRowRendererComponent/index.native.tsx @@ -2,8 +2,9 @@ import {CellContainer} from '@shopify/flash-list'; import type {CellContainerProps} from '@shopify/flash-list/dist/native/cell-container/CellContainer'; import type {ForwardedRef} from 'react'; import {forwardRef} from 'react'; +import type {View} from 'react-native'; -function OptionRowRendererComponent(props: CellContainerProps, ref: ForwardedRef) { +function OptionRowRendererComponent(props: CellContainerProps, ref: ForwardedRef) { return ( Date: Wed, 20 Nov 2024 09:27:45 -0800 Subject: [PATCH 85/94] Update en.ts Copy clarification per https://expensify.slack.com/archives/C07NZ8B1VTQ/p1732118238630129 --- src/languages/en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 591f7eb0ed42..0c985d917eee 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1465,7 +1465,7 @@ const translations = { workflowTitle: 'Spend', workflowDescription: 'Configure a workflow from the moment spend occurs, including approval and payment.', delaySubmissionTitle: 'Delay submissions', - delaySubmissionDescription: 'Delay expense submissions based on a custom schedule, or keep this option disabled to maintain realtime spend visibility.', + delaySubmissionDescription: 'Choose a custom schedule for submitting expenses, or leave this off for realtime updates on spending.', submissionFrequency: 'Submission frequency', submissionFrequencyDateOfMonth: 'Date of month', addApprovalsTitle: 'Add approvals', From 1e8fb70616638594b00eacc2867786ca9db850dd Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 20 Nov 2024 10:33:16 -0800 Subject: [PATCH 86/94] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 91557c9defbf..c294b343d2cf 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1466,7 +1466,7 @@ const translations = { workflowTitle: 'Gasto', workflowDescription: 'Configure un flujo de trabajo desde el momento en que se produce el gasto, incluida la aprobación y el pago', delaySubmissionTitle: 'Retrasar envíos', - delaySubmissionDescription: 'Retrasa la presentación de gastos en base a un calendario personalizado, o mantén esta opción desactivada para seguir viendo los gastos en tiempo real.', + delaySubmissionDescription: 'Elige una frecuencia para enviar los gastos, o dejalo desactivado para recibir actualizaciones en tiempo real sobre los gastos.', submissionFrequency: 'Frecuencia de envíos', submissionFrequencyDateOfMonth: 'Fecha del mes', addApprovalsTitle: 'Aprobaciones', From 96d69a4fd332d11cad526c58add9e62422c08838 Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 20 Nov 2024 10:40:09 -0800 Subject: [PATCH 87/94] Update es.ts Updating per https://expensify.slack.com/archives/C21FRDWCV/p1732122644966439 --- src/languages/es.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 91557c9defbf..23e1eba907c5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1171,13 +1171,13 @@ const translations = { }, updateRequiredView: { updateRequired: 'Actualización requerida', - pleaseInstall: 'Por favor, actualiza a la última versión de Nuevo Expensify', + pleaseInstall: 'Por favor, actualiza a la última versión de New Expensify', toGetLatestChanges: 'Para móvil o escritorio, descarga e instala la última versión. Para la web, actualiza tu navegador.', }, initialSettingsPage: { about: 'Acerca de', aboutPage: { - description: 'La Nueva Expensify está creada por una comunidad de desarrolladores de código abierto de todo el mundo. Ayúdanos a construir el futuro de Expensify.', + description: 'New Expensify está creada por una comunidad de desarrolladores de código abierto de todo el mundo. Ayúdanos a construir el futuro de Expensify.', appDownloadLinks: 'Enlaces para descargar la App', viewKeyboardShortcuts: 'Ver atajos de teclado', viewTheCode: 'Ver código', @@ -1266,7 +1266,7 @@ const translations = { }, passwordPage: { changePassword: 'Cambiar contraseña', - changingYourPasswordPrompt: 'El cambio de contraseña va a afectar tanto a la cuenta de Expensify.com como la de Nuevo Expensify.', + changingYourPasswordPrompt: 'El cambio de contraseña va a afectar tanto a la cuenta de Expensify.com como la de New Expensify.', currentPassword: 'Contraseña actual', newPassword: 'Nueva contraseña', newPasswordPrompt: 'La nueva contraseña debe ser diferente de la antigua y contener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.', @@ -1861,7 +1861,7 @@ const translations = { enterPassword: 'Escribe una contraseña', setPassword: 'Configura tu contraseña', newPasswordPrompt: 'La contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.', - passwordFormTitle: '¡Bienvenido de vuelta al Nuevo Expensify! Por favor, elige una contraseña.', + passwordFormTitle: '¡Bienvenido de vuelta al New Expensify! Por favor, elige una contraseña.', passwordNotSet: 'No se pudo cambiar tu clave. Te hemos enviado un nuevo enlace para que intentes cambiar la clave nuevamente.', setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.', validateAccount: 'Verificar cuenta', @@ -2015,10 +2015,10 @@ const translations = { butFirst: 'Pero primero, lo aburrido. Lee la jerga legal en el siguiente paso y haz clic en "Aceptar" cuando estés listo.', genericError: 'Se ha producido un error al procesar este paso. Inténtalo de nuevo.', cameraPermissionsNotGranted: 'Permiso para acceder a la cámara', - cameraRequestMessage: 'Necesitamos acceso a tu cámara para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > Nuevo Expensify.', + cameraRequestMessage: 'Necesitamos acceso a tu cámara para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > New Expensify.', microphonePermissionsNotGranted: 'Permiso para acceder al micrófono', microphoneRequestMessage: - 'Necesitamos acceso a tu micrófono para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > Nuevo Expensify.', + 'Necesitamos acceso a tu micrófono para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > New Expensify.', originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', documentNeedsBetterQuality: 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', @@ -4610,17 +4610,17 @@ const translations = { }, }, desktopApplicationMenu: { - mainMenu: 'Nuevo Expensify', - about: 'Sobre Nuevo Expensify', - update: 'Actualizar Nuevo Expensify', + mainMenu: 'New Expensify', + about: 'Sobre New Expensify', + update: 'Actualizar New Expensify', checkForUpdates: 'Buscar actualizaciones', toggleDevTools: 'Ver herramientas de desarrollo', viewShortcuts: 'Ver atajos de teclado', services: 'Servicios', - hide: 'Ocultar Nuevo Expensify', + hide: 'Ocultar New Expensify', hideOthers: 'Ocultar otros', showAll: 'Mostrar todos', - quit: 'Salir de Nuevo Expensify', + quit: 'Salir de New Expensify', fileMenu: 'Archivo', closeWindow: 'Cerrar ventana', editMenu: 'Editar', From 1f2f0420704bb17b2a9b29d673a6bcf57f8b09b0 Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 20 Nov 2024 10:54:23 -0800 Subject: [PATCH 88/94] Update es.ts --- src/languages/es.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 23e1eba907c5..bd8b62255ff9 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1861,7 +1861,7 @@ const translations = { enterPassword: 'Escribe una contraseña', setPassword: 'Configura tu contraseña', newPasswordPrompt: 'La contraseña debe tener al menos 8 caracteres, 1 letra mayúscula, 1 letra minúscula y 1 número.', - passwordFormTitle: '¡Bienvenido de vuelta al New Expensify! Por favor, elige una contraseña.', + passwordFormTitle: '¡Bienvenido de vuelta a New Expensify! Por favor, elige una contraseña.', passwordNotSet: 'No se pudo cambiar tu clave. Te hemos enviado un nuevo enlace para que intentes cambiar la clave nuevamente.', setPasswordLinkInvalid: 'El enlace para configurar tu contraseña ha expirado. Te hemos enviado un nuevo enlace a tu correo.', validateAccount: 'Verificar cuenta', From 62b1aa0010ad1b38ddfab980f21f76a1b65f9499 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Thu, 21 Nov 2024 02:15:15 +0700 Subject: [PATCH 89/94] fix: new member shows empty avatar in report invite page --- src/pages/InviteReportParticipantsPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index 6a5bf6832fd6..3138f1c1fc88 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -57,6 +57,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen useEffect(() => { UserSearchPhraseActions.updateUserSearchPhrase(debouncedSearchTerm); + Report.searchInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); // Any existing participants and Expensify emails should not be eligible for invitation From 561dcb21766444712c3af4bdb75c7e895c0401fc Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Thu, 21 Nov 2024 02:52:10 +0700 Subject: [PATCH 90/94] migrate to useOnyx --- src/pages/InviteReportParticipantsPage.tsx | 25 ++++++---------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index 3138f1c1fc88..c15afc552810 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -2,8 +2,7 @@ import type {RouteProp} from '@react-navigation/native'; import {useRoute} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {useOptionsList} from '@components/OptionListContextProvider'; @@ -30,20 +29,15 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {InvitedEmailsToAccountIDs, PersonalDetailsList} from '@src/types/onyx'; +import type {InvitedEmailsToAccountIDs} from '@src/types/onyx'; import type {WithReportOrNotFoundProps} from './home/report/withReportOrNotFound'; import withReportOrNotFound from './home/report/withReportOrNotFound'; -type InviteReportParticipantsPageOnyxProps = { - /** All of the personal details for everyone */ - personalDetails: OnyxEntry; -}; - -type InviteReportParticipantsPageProps = InviteReportParticipantsPageOnyxProps & WithReportOrNotFoundProps & WithNavigationTransitionEndProps; +type InviteReportParticipantsPageProps = WithReportOrNotFoundProps & WithNavigationTransitionEndProps; type Sections = Array>>; -function InviteReportParticipantsPage({betas, personalDetails, report, didScreenTransitionEnd}: InviteReportParticipantsPageProps) { +function InviteReportParticipantsPage({betas, report, didScreenTransitionEnd}: InviteReportParticipantsPageProps) { const route = useRoute>(); const {options, areOptionsInitialized} = useOptionsList({ shouldInitialize: didScreenTransitionEnd, @@ -51,6 +45,7 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen const styles = useThemeStyles(); const {translate} = useLocalize(); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const [userSearchPhrase] = useOnyx(ONYXKEYS.ROOM_MEMBERS_USER_SEARCH_PHRASE); const [searchValue, debouncedSearchTerm, setSearchValue] = useDebouncedState(userSearchPhrase ?? ''); const [selectedOptions, setSelectedOptions] = useState([]); @@ -260,12 +255,4 @@ function InviteReportParticipantsPage({betas, personalDetails, report, didScreen InviteReportParticipantsPage.displayName = 'InviteReportParticipantsPage'; -export default withNavigationTransitionEnd( - withReportOrNotFound()( - withOnyx({ - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - }, - })(InviteReportParticipantsPage), - ), -); +export default withNavigationTransitionEnd(withReportOrNotFound()(InviteReportParticipantsPage)); From 16295a1228cf55f46269f0fcaf9082e63a45340b Mon Sep 17 00:00:00 2001 From: James Dean Date: Wed, 20 Nov 2024 12:06:14 -0800 Subject: [PATCH 91/94] Update es.ts fixing per https://github.com/Expensify/App/pull/52839 --- src/languages/es.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index bd8b62255ff9..ac071dd2a48f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2017,8 +2017,7 @@ const translations = { cameraPermissionsNotGranted: 'Permiso para acceder a la cámara', cameraRequestMessage: 'Necesitamos acceso a tu cámara para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > New Expensify.', microphonePermissionsNotGranted: 'Permiso para acceder al micrófono', - microphoneRequestMessage: - 'Necesitamos acceso a tu micrófono para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > New Expensify.', + microphoneRequestMessage: 'Necesitamos acceso a tu micrófono para completar la verificación de tu cuenta de banco. Por favor habilita los permisos en Configuración > New Expensify.', originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', documentNeedsBetterQuality: 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', From fcac1f9cc291cbd52dc06fdc90016c58d46182e9 Mon Sep 17 00:00:00 2001 From: Matt Allen Date: Wed, 20 Nov 2024 13:47:22 -0800 Subject: [PATCH 92/94] Update PULL_REQUEST_TEMPLATE.md Updated line 88 > If any non-english text was added/modified, I used [JaimeGPT](https://chatgpt.com/g/g-2dgOQl5VM-english-to-spanish-translator-aka-jaimegpt) to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message: --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 459a780ca8b4..f4f6a90ae6db 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -85,7 +85,7 @@ This is a checklist for PR authors. Please make sure to complete all tasks and c - [ ] 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 shown in the product is localized by adding it to `src/languages/*` files and using the [translation method](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60) - - [ ] If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message: + - [ ] If any non-english text was added/modified, I used [JaimeGPT](https://chatgpt.com/g/g-2dgOQl5VM-english-to-spanish-translator-aka-jaimegpt) to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message: - [ ] I verified all numbers, amounts, dates and phone numbers shown in the product are using the [localization methods](https://github.com/Expensify/App/blob/4bd99402cebdf4d7394e0d1f260879ea238197eb/src/components/withLocalize.js#L60-L68) - [ ] 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) - [ ] I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README. From 474743dc4d1d316b2a4912770c667b5dc935aa11 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 21 Nov 2024 01:01:59 +0000 Subject: [PATCH 93/94] Update version to 9.0.65-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- ios/NotificationServiceExtension/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index b672c601e341..2bd71fb3ef9d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006404 - versionName "9.0.64-4" + versionCode 1009006500 + versionName "9.0.65-0" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 08ab0c7aba57..da87f5931833 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.64 + 9.0.65 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.64.4 + 9.0.65.0 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index e3b4f59ce64d..693f910f9de7 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.64 + 9.0.65 CFBundleSignature ???? CFBundleVersion - 9.0.64.4 + 9.0.65.0 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 7ecf35bd30d0..c0e65dc46eee 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.64 + 9.0.65 CFBundleVersion - 9.0.64.4 + 9.0.65.0 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index b693ae1e74ac..1a0d8647b5c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.64-4", + "version": "9.0.65-0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.64-4", + "version": "9.0.65-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 120366e9f77a..f5f01be3d305 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.64-4", + "version": "9.0.65-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From c18812ae3cac59025da0f6c8657750dfcb779a65 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Thu, 21 Nov 2024 01:51:20 +0000 Subject: [PATCH 94/94] Update version to 9.0.65-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- ios/NotificationServiceExtension/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2bd71fb3ef9d..38e05df288fb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -110,8 +110,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009006500 - versionName "9.0.65-0" + versionCode 1009006501 + versionName "9.0.65-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index da87f5931833..5f361a3cc53b 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.65.0 + 9.0.65.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 693f910f9de7..3f0481c530d0 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 9.0.65.0 + 9.0.65.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index c0e65dc46eee..29c7703f56d8 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -13,7 +13,7 @@ CFBundleShortVersionString 9.0.65 CFBundleVersion - 9.0.65.0 + 9.0.65.1 NSExtension NSExtensionPointIdentifier diff --git a/package-lock.json b/package-lock.json index 1a0d8647b5c3..0975df3d5a7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "9.0.65-0", + "version": "9.0.65-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.65-0", + "version": "9.0.65-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index f5f01be3d305..66e729ed3875 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.65-0", + "version": "9.0.65-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",