From 6c17f2b7a293f8516cb1b66339d420958a5d8cb5 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Fri, 8 Sep 2023 14:02:04 +0100 Subject: [PATCH 01/19] feat: add tag support to getNewChatOptions --- src/CONST.ts | 1 + src/libs/OptionsListUtils.js | 153 +++++++- src/pages/tasks/TaskAssigneeSelectorModal.js | 3 + tests/unit/OptionsListUtilsTest.js | 367 +++++++++++++++++++ 4 files changed, 523 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 795d09d48322..71bc0a5558a8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2619,6 +2619,7 @@ const CONST = { INDENTS: ' ', PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, + TAG_LIST_THRESHOLD: 8, DEMO_PAGES: { SAASTR: 'SaaStrDemoSetup', SBE: 'SbeDemoSetup', diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index d26ad48430b0..5daf0ba6db7a 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -602,7 +602,7 @@ function isCurrentUser(userDetails) { /** * Build the options for the category tree hierarchy via indents * - * @param {Object[]} options - an initial strings array + * @param {Object[]} options - an initial object array * @param {Boolean} options[].enabled - a flag to enable/disable option in a list * @param {String} options[].name - a name of an option * @param {Boolean} [isOneLine] - a flag to determine if text should be one line @@ -736,6 +736,132 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt return categorySections; } +/** + * Transforms the provided tags into objects with a specific structure. + * + * @param {Object[]} tags - an initial tag array + * @param {Boolean} options[].enabled - a flag to enable/disable option in a list + * @param {String} options[].name - a name of an option + * @returns {Array} + */ +function getTagsOptions(tags) { + const options = []; + + _.each(tags, (tag) => + options.push({ + text: tag.name, + keyForList: tag.name, + searchText: tag.name, + tooltipText: tag.name, + isDisabled: !tag.enabled, + }), + ); + + return options; +} + +/** + * Build the section list for tags + * + * @param {Object[]} tags + * @param {String} tags[].name + * @param {Boolean} tags[].enabled + * @param {String[]} recentlyUsedTags + * @param {Object[]} selectedOptions + * @param {String} selectedOptions[].name + * @param {String} searchInputValue + * @param {Number} maxRecentReportsToShow + * @returns {Array} + */ +function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInputValue, maxRecentReportsToShow) { + const tagSections = []; + const numberOfTags = _.size(tags); + let indexOffset = 0; + + if (!_.isEmpty(searchInputValue)) { + const searchTags = _.filter(tags, (tag) => tag.name.toLowerCase().includes(searchInputValue.toLowerCase())); + + tagSections.push({ + // "Search" section + title: '', + shouldShow: false, + indexOffset, + data: getTagsOptions(searchTags), + }); + + return tagSections; + } + + if (numberOfTags < CONST.TAG_LIST_THRESHOLD) { + tagSections.push({ + // "All" section when items amount less than the threshold + title: '', + shouldShow: false, + indexOffset, + data: getTagsOptions(tags), + }); + + return tagSections; + } + + const selectedOptionNames = _.map(selectedOptions, (selectedOption) => selectedOption.name); + const filteredRecentlyUsedTags = _.map( + _.filter(recentlyUsedTags, (tag) => !_.includes(selectedOptionNames, tag)), + (tag) => { + const tagObject = _.find(tags, (t) => t.name === tag); + return { + name: tag, + enabled: tagObject && tagObject.enabled, + }; + }, + ); + const filteredTags = _.filter(tags, (tag) => !_.includes(selectedOptionNames, tag.name)); + + if (!_.isEmpty(selectedOptions)) { + const selectedTagOptions = _.map(selectedOptions, (option) => { + const tagObject = _.find(tags, (t) => t.name === option.name); + return { + name: option.name, + enabled: tagObject && tagObject.enabled, + }; + }); + + tagSections.push({ + // "Selected" section + title: '', + shouldShow: false, + indexOffset, + data: getTagsOptions(selectedTagOptions), + }); + + indexOffset += selectedOptions.length; + } + + if (!_.isEmpty(filteredRecentlyUsedTags)) { + const cutRecentlyUsedTags = filteredRecentlyUsedTags.slice(0, maxRecentReportsToShow); + + tagSections.push({ + // "Recent" section + title: Localize.translateLocal('common.recent'), + shouldShow: true, + indexOffset, + data: getTagsOptions(cutRecentlyUsedTags), + }); + + indexOffset += filteredRecentlyUsedTags.length; + } + + tagSections.push({ + // "All" section when items amount more than the threshold + title: Localize.translateLocal('common.all'), + shouldShow: true, + indexOffset, + data: getTagsOptions(filteredTags), + }); + + return tagSections; +} + /** * Build the options * @@ -772,6 +898,9 @@ function getOptions( includeCategories = false, categories = {}, recentlyUsedCategories = [], + includeTags = false, + tags = {}, + recentlyUsedTags = [], canInviteUser = true, }, ) { @@ -784,6 +913,20 @@ function getOptions( userToInvite: null, currentUserOption: null, categoryOptions, + tagOptions: [], + }; + } + + if (includeTags) { + const tagOptions = getTagListSections(_.values(tags), recentlyUsedTags, selectedOptions, searchInputValue, maxRecentReportsToShow); + + return { + recentReports: [], + personalDetails: [], + userToInvite: null, + currentUserOption: null, + categoryOptions: [], + tagOptions, }; } @@ -794,6 +937,7 @@ function getOptions( userToInvite: null, currentUserOption: null, categoryOptions: [], + tagOptions: [], }; } @@ -1044,6 +1188,7 @@ function getOptions( userToInvite: canInviteUser ? userToInvite : null, currentUserOption, categoryOptions: [], + tagOptions: [], }; } @@ -1143,6 +1288,9 @@ function getNewChatOptions( includeCategories = false, categories = {}, recentlyUsedCategories = [], + includeTags = false, + tags = {}, + recentlyUsedTags = [], canInviteUser = true, ) { return getOptions(reports, personalDetails, { @@ -1158,6 +1306,9 @@ function getNewChatOptions( includeCategories, categories, recentlyUsedCategories, + includeTags, + tags, + recentlyUsedTags, canInviteUser, }); } diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 10cb27196efd..374c9bab7aa3 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -92,6 +92,9 @@ function TaskAssigneeSelectorModal(props) { {}, [], false, + {}, + [], + false, ); setHeaderMessage(OptionsListUtils.getHeaderMessage(recentReports?.length + personalDetails?.length !== 0 || currentUserOption, Boolean(userToInvite), searchValue)); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 7dc47619ffed..70d5f8556277 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -1065,6 +1065,373 @@ describe('OptionsListUtils', () => { expect(largeWrongSearchResult.categoryOptions).toStrictEqual(largeWrongSearchResultList); }); + it('getNewChatOptions() for tags', () => { + const search = 'ing'; + const emptySearch = ''; + const wrongSearch = 'bla bla'; + const recentlyUsedTags = ['Engineering', 'HR']; + + const selectedOptions = [ + { + name: 'Medical', + }, + ]; + const smallTagsList = { + Engineering: { + enabled: false, + name: 'Engineering', + }, + Medical: { + enabled: true, + name: 'Medical', + }, + Accounting: { + enabled: true, + name: 'Accounting', + }, + HR: { + enabled: true, + name: 'HR', + }, + }; + const smallResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Engineering', + keyForList: 'Engineering', + searchText: 'Engineering', + tooltipText: 'Engineering', + isDisabled: true, + }, + { + text: 'Medical', + keyForList: 'Medical', + searchText: 'Medical', + tooltipText: 'Medical', + isDisabled: false, + }, + { + text: 'Accounting', + keyForList: 'Accounting', + searchText: 'Accounting', + tooltipText: 'Accounting', + isDisabled: false, + }, + { + text: 'HR', + keyForList: 'HR', + searchText: 'HR', + tooltipText: 'HR', + isDisabled: false, + }, + ], + }, + ]; + const smallSearchResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Engineering', + keyForList: 'Engineering', + searchText: 'Engineering', + tooltipText: 'Engineering', + isDisabled: true, + }, + { + text: 'Accounting', + keyForList: 'Accounting', + searchText: 'Accounting', + tooltipText: 'Accounting', + isDisabled: false, + }, + ], + }, + ]; + const smallWrongSearchResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [], + }, + ]; + const largeTagsList = { + Engineering: { + enabled: false, + name: 'Engineering', + }, + Medical: { + enabled: true, + name: 'Medical', + }, + Accounting: { + enabled: true, + name: 'Accounting', + }, + HR: { + enabled: true, + name: 'HR', + }, + Food: { + enabled: true, + name: 'Food', + }, + Traveling: { + enabled: false, + name: 'Traveling', + }, + Cleaning: { + enabled: true, + name: 'Cleaning', + }, + Software: { + enabled: true, + name: 'Software', + }, + OfficeSupplies: { + enabled: false, + name: 'Office Supplies', + }, + Taxes: { + enabled: true, + name: 'Taxes', + }, + Benefits: { + enabled: true, + name: 'Benefits', + }, + }; + const largeResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Medical', + keyForList: 'Medical', + searchText: 'Medical', + tooltipText: 'Medical', + isDisabled: false, + }, + ], + }, + { + title: 'Recent', + shouldShow: true, + indexOffset: 1, + data: [ + { + text: 'Engineering', + keyForList: 'Engineering', + searchText: 'Engineering', + tooltipText: 'Engineering', + isDisabled: true, + }, + { + text: 'HR', + keyForList: 'HR', + searchText: 'HR', + tooltipText: 'HR', + isDisabled: false, + }, + ], + }, + { + title: 'All', + shouldShow: true, + indexOffset: 3, + data: [ + { + text: 'Engineering', + keyForList: 'Engineering', + searchText: 'Engineering', + tooltipText: 'Engineering', + isDisabled: true, + }, + { + text: 'Accounting', + keyForList: 'Accounting', + searchText: 'Accounting', + tooltipText: 'Accounting', + isDisabled: false, + }, + { + text: 'HR', + keyForList: 'HR', + searchText: 'HR', + tooltipText: 'HR', + isDisabled: false, + }, + { + text: 'Food', + keyForList: 'Food', + searchText: 'Food', + tooltipText: 'Food', + isDisabled: false, + }, + { + text: 'Traveling', + keyForList: 'Traveling', + searchText: 'Traveling', + tooltipText: 'Traveling', + isDisabled: true, + }, + { + text: 'Cleaning', + keyForList: 'Cleaning', + searchText: 'Cleaning', + tooltipText: 'Cleaning', + isDisabled: false, + }, + { + text: 'Software', + keyForList: 'Software', + searchText: 'Software', + tooltipText: 'Software', + isDisabled: false, + }, + { + text: 'Office Supplies', + keyForList: 'Office Supplies', + searchText: 'Office Supplies', + tooltipText: 'Office Supplies', + isDisabled: true, + }, + { + text: 'Taxes', + keyForList: 'Taxes', + searchText: 'Taxes', + tooltipText: 'Taxes', + isDisabled: false, + }, + { + text: 'Benefits', + keyForList: 'Benefits', + searchText: 'Benefits', + tooltipText: 'Benefits', + isDisabled: false, + }, + ], + }, + ]; + const largeSearchResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [ + { + text: 'Engineering', + keyForList: 'Engineering', + searchText: 'Engineering', + tooltipText: 'Engineering', + isDisabled: true, + }, + { + text: 'Accounting', + keyForList: 'Accounting', + searchText: 'Accounting', + tooltipText: 'Accounting', + isDisabled: false, + }, + { + text: 'Traveling', + keyForList: 'Traveling', + searchText: 'Traveling', + tooltipText: 'Traveling', + isDisabled: true, + }, + { + text: 'Cleaning', + keyForList: 'Cleaning', + searchText: 'Cleaning', + tooltipText: 'Cleaning', + isDisabled: false, + }, + ], + }, + ]; + const largeWrongSearchResultList = [ + { + title: '', + shouldShow: false, + indexOffset: 0, + data: [], + }, + ]; + + const smallResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, false, {}, [], true, smallTagsList); + expect(smallResult.tagOptions).toStrictEqual(smallResultList); + + const smallSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], search, [], [], false, false, false, {}, [], true, smallTagsList); + expect(smallSearchResult.tagOptions).toStrictEqual(smallSearchResultList); + + const smallWrongSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], wrongSearch, [], [], false, false, false, {}, [], true, smallTagsList); + expect(smallWrongSearchResult.tagOptions).toStrictEqual(smallWrongSearchResultList); + + const largeResult = OptionsListUtils.getNewChatOptions( + REPORTS, + PERSONAL_DETAILS, + [], + emptySearch, + selectedOptions, + [], + false, + false, + false, + {}, + [], + true, + largeTagsList, + recentlyUsedTags, + ); + expect(largeResult.tagOptions).toStrictEqual(largeResultList); + + const largeSearchResult = OptionsListUtils.getNewChatOptions( + REPORTS, + PERSONAL_DETAILS, + [], + search, + selectedOptions, + [], + false, + false, + false, + {}, + [], + true, + largeTagsList, + recentlyUsedTags, + ); + expect(largeSearchResult.tagOptions).toStrictEqual(largeSearchResultList); + + const largeWrongSearchResult = OptionsListUtils.getNewChatOptions( + REPORTS, + PERSONAL_DETAILS, + [], + wrongSearch, + selectedOptions, + [], + false, + false, + false, + {}, + [], + true, + largeTagsList, + recentlyUsedTags, + ); + expect(largeWrongSearchResult.tagOptions).toStrictEqual(largeWrongSearchResultList); + }); + it('getCategoryOptionTree()', () => { const categories = { Taxi: { From a45d61a7c6cdcfa57f8495c9dc6cf0dc99394608 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Fri, 8 Sep 2023 14:05:37 +0100 Subject: [PATCH 02/19] fix: add missing jsdoc --- src/libs/OptionsListUtils.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 5daf0ba6db7a..c99b10c75c0e 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -740,8 +740,8 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt * Transforms the provided tags into objects with a specific structure. * * @param {Object[]} tags - an initial tag array - * @param {Boolean} options[].enabled - a flag to enable/disable option in a list - * @param {String} options[].name - a name of an option + * @param {Boolean} tags[].enabled - a flag to enable/disable option in a list + * @param {String} tags[].name - a name of an option * @returns {Array} */ function getTagsOptions(tags) { @@ -1273,6 +1273,9 @@ function getIOUConfirmationOptionsFromParticipants(participants, amountText) { * @param {boolean} [includeCategories] * @param {Object} [categories] * @param {Array} [recentlyUsedCategories] + * @param {boolean} [includeTags] + * @param {Object} [tags] + * @param {Array} [recentlyUsedTags] * @param {boolean} [canInviteUser] * @returns {Object} */ From 4fdefef2c8b2d5e3014c5cd4e5265f63a5f80c95 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Thu, 14 Sep 2023 10:42:10 +0100 Subject: [PATCH 03/19] feat: build sections and show search input --- src/components/TagPicker/index.js | 68 +++++++++++++++++++------------ 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 25021bd817d7..8ce8e3e4fa00 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -1,7 +1,8 @@ -import React, {useMemo} from 'react'; +import React, { useMemo, useState } from 'react'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import {withOnyx} from 'react-native-onyx'; +import { withOnyx } from 'react-native-onyx'; +import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; @@ -9,10 +10,17 @@ import ROUTES from '../../ROUTES'; import useLocalize from '../../hooks/useLocalize'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import OptionsSelector from '../OptionsSelector'; -import {propTypes, defaultProps} from './tagPickerPropTypes'; +import { propTypes, defaultProps } from './tagPickerPropTypes'; -function TagPicker({policyTags, reportID, tag, iouType, iou}) { - const {translate} = useLocalize(); +function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, iou }) { + const { translate } = useLocalize(); + const [searchValue, setSearchValue] = useState(''); + + const policyTagList = lodashGet(policyTags, [tag, 'tags'], []); + const policyTagsCount = _.size(policyTagList); + const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; + + const shouldShowTextInput = !isTagsCountBelowThreshold; const selectedOptions = useMemo(() => { if (!iou.tag) { @@ -23,29 +31,31 @@ function TagPicker({policyTags, reportID, tag, iouType, iou}) { { name: iou.tag, enabled: true, + accountID: null, }, ]; }, [iou.tag]); - // Only shows one section, which will be the default behavior if there are - // less than 8 policy tags - // TODO: support sections with search - const sections = useMemo(() => { - const tagList = _.chain(lodashGet(policyTags, [tag, 'tags'], {})) - .values() - .map((t) => ({ - text: t.name, - keyForList: t.name, - tooltipText: t.name, - })) - .value(); + const initialFocusedIndex = useMemo(() => { + if (isTagsCountBelowThreshold && selectedOptions.length > 0) { + return _.chain(policyTagList) + .values() + .findIndex((tag) => tag.name === selectedOptions[0].name, true) + .value(); + } - return [ - { - data: tagList, - }, - ]; - }, [policyTags, tag]); + return 0; + }, [policyTagList, selectedOptions, isTagsCountBelowThreshold]); + + const sections = useMemo( + () => OptionsListUtils.getNewChatOptions( + {}, + {}, + [], searchValue, selectedOptions, [], false, false, false, {}, [], + true, policyTagList, policyRecentlyUsedTags, false).tagOptions + , + [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTags] + ); const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, ''); @@ -66,9 +76,13 @@ function TagPicker({policyTags, reportID, tag, iouType, iou}) { headerMessage={headerMessage} textInputLabel={translate('common.search')} boldStyle - value="" + highlightSelectedOptions + isRowMultilineSupported + shouldShowTextInput={shouldShowTextInput} + value={searchValue} + initialFocusedIndex={initialFocusedIndex} + onChangeText={setSearchValue} onSelectRow={updateTag} - shouldShowTextInput={false} /> ); } @@ -79,10 +93,10 @@ TagPicker.defaultProps = defaultProps; export default withOnyx({ policyTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + key: ({ policyID }) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, policyRecentlyUsedTags: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + key: ({ policyID }) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, }, iou: { key: ONYXKEYS.IOU, From e2f1dc92027184af1cda9b33dd2421bcbd1a1e78 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Thu, 14 Sep 2023 14:27:06 +0100 Subject: [PATCH 04/19] fix: conditional check --- src/components/MoneyRequestConfirmationList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index 966f5f4340a7..0bfba40576bf 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -520,7 +520,7 @@ function MoneyRequestConfirmationList(props) { disabled={didConfirm || props.isReadOnly} /> )} - {canUseTags && !!tagList && ( + {canUseTags && !_.isEmpty(tagList) && ( Date: Thu, 14 Sep 2023 15:47:58 +0100 Subject: [PATCH 05/19] fix: adapt to policyRecentlyUsedTags --- src/components/TagPicker/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 8ce8e3e4fa00..e6a365b98661 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -16,6 +16,7 @@ function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, const { translate } = useLocalize(); const [searchValue, setSearchValue] = useState(''); + const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []); const policyTagList = lodashGet(policyTags, [tag, 'tags'], []); const policyTagsCount = _.size(policyTagList); const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; @@ -52,9 +53,9 @@ function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, {}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], - true, policyTagList, policyRecentlyUsedTags, false).tagOptions + true, policyTagList, policyRecentlyUsedTagsList, false).tagOptions , - [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTags] + [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTagsList] ); const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, ''); From 6a47b41dbfb39ed6cd46ba0493aaca7a00888ac6 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Mon, 18 Sep 2023 08:46:45 +0100 Subject: [PATCH 06/19] fix: use another approach for fetching report --- src/pages/iou/MoneyRequestTagPage.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index a1795d50df8a..3dfad9eaa35d 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -58,7 +58,7 @@ function MoneyRequestTagPage({route, report, policyTags}) { const tagListName = lodashGet(tagList, 'name', ''); const navigateBack = () => { - Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, lodashGet(report, 'reportID', ''))); + Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, report.reportID)); }; return ( @@ -86,15 +86,9 @@ MoneyRequestTagPage.propTypes = propTypes; MoneyRequestTagPage.defaultProps = defaultProps; export default compose( - withOnyx({ - iou: { - key: ONYXKEYS.IOU, - }, - }), withOnyx({ report: { - // Fetch report ID from IOU participants if no report ID is set in route - key: ({route, iou}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID', '') || lodashGet(iou, 'participants.0.reportID', '')}`, + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`, }, }), withOnyx({ From b4fff89e7d04d796178979eedf7d4b0610a687f4 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Mon, 18 Sep 2023 14:19:03 +0100 Subject: [PATCH 07/19] feat: save IOU tag and send it in API request --- src/components/TagPicker/index.js | 35 ++++++------ src/libs/TransactionUtils.js | 3 ++ src/libs/actions/IOU.js | 54 ++++++++++++++++++- .../iou/steps/MoneyRequestConfirmPage.js | 5 +- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index e6a365b98661..4920042849e3 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -1,19 +1,20 @@ -import React, { useMemo, useState } from 'react'; +import React, {useMemo, useState} from 'react'; import _ from 'underscore'; import lodashGet from 'lodash/get'; -import { withOnyx } from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; +import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; -import ROUTES from '../../ROUTES'; +import * as IOU from '../../libs/actions/IOU'; import useLocalize from '../../hooks/useLocalize'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import OptionsSelector from '../OptionsSelector'; -import { propTypes, defaultProps } from './tagPickerPropTypes'; +import {propTypes, defaultProps} from './tagPickerPropTypes'; -function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, iou }) { - const { translate } = useLocalize(); +function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, iou}) { + const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []); @@ -49,13 +50,9 @@ function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, }, [policyTagList, selectedOptions, isTagsCountBelowThreshold]); const sections = useMemo( - () => OptionsListUtils.getNewChatOptions( - {}, - {}, - [], searchValue, selectedOptions, [], false, false, false, {}, [], - true, policyTagList, policyRecentlyUsedTagsList, false).tagOptions - , - [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTagsList] + () => + OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, policyTagList, policyRecentlyUsedTagsList, false).tagOptions, + [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTagsList], ); const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, ''); @@ -64,8 +61,12 @@ function TagPicker({ reportID, tag, iouType, policyTags, policyRecentlyUsedTags, Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); }; - const updateTag = () => { - // TODO: add logic to save the selected tag + const updateTag = (tag) => { + if (tag.searchText === iou.tag) { + IOU.resetMoneyRequestTag(); + } else { + IOU.setMoneyRequestTag(tag.searchText); + } navigateBack(); }; @@ -94,10 +95,10 @@ TagPicker.defaultProps = defaultProps; export default withOnyx({ policyTags: { - key: ({ policyID }) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, }, policyRecentlyUsedTags: { - key: ({ policyID }) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, }, iou: { key: ONYXKEYS.IOU, diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index 4ca8b48d284e..baa657a7a5d6 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -34,6 +34,7 @@ Onyx.connect({ * @param {String} [filename] * @param {String} [existingTransactionID] When creating a distance request, an empty transaction has already been created with a transactionID. In that case, the transaction here needs to have it's transactionID match what was already generated. * @param {String} [category] + * @param {String} [tag] * @returns {Object} */ function buildOptimisticTransaction( @@ -49,6 +50,7 @@ function buildOptimisticTransaction( filename = '', existingTransactionID = null, category = '', + tag = '', ) { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) @@ -77,6 +79,7 @@ function buildOptimisticTransaction( receipt, filename, category, + tag, }; } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 3cefcd00ed60..0aab0a63c500 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -52,6 +52,13 @@ Onyx.connect({ callback: (val) => (allRecentlyUsedCategories = val), }); +let allRecentlyUsedTags = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, + waitForCollectionCallback: true, + callback: (val) => (allRecentlyUsedTags = val), +}); + let userAccountID = ''; let currentUserEmail = ''; Onyx.connect({ @@ -94,6 +101,7 @@ function resetMoneyRequestInfo(id = '') { participantAccountIDs: [], merchant: CONST.TRANSACTION.DEFAULT_MERCHANT, category: '', + tag: '', created, receiptPath: '', receiptSource: '', @@ -111,6 +119,7 @@ function buildOnyxDataForMoneyRequest( optimisticPersonalDetailListAction, reportPreviewAction, optimisticRecentlyUsedCategories, + optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, ) { @@ -168,6 +177,14 @@ function buildOnyxDataForMoneyRequest( }); } + if (!_.isEmpty(optimisticPolicyRecentlyUsedTags)) { + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`, + value: optimisticPolicyRecentlyUsedTags, + }); + } + if (!_.isEmpty(optimisticPersonalDetailListAction)) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -341,6 +358,7 @@ function buildOnyxDataForMoneyRequest( * @param {Object} [receipt] * @param {String} [existingTransactionID] * @param {String} [category] + * @param {String} [tag] * @returns {Object} data * @returns {String} data.payerEmail * @returns {Object} data.iouReport @@ -368,6 +386,7 @@ function getMoneyRequestInformation( receipt = undefined, existingTransactionID = undefined, category = undefined, + tag = undefined, ) { const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); const payerAccountID = Number(participant.accountID); @@ -433,6 +452,7 @@ function getMoneyRequestInformation( filename, existingTransactionID, category, + tag, ); const uniquePolicyRecentlyUsedCategories = allRecentlyUsedCategories @@ -443,6 +463,15 @@ function getMoneyRequestInformation( : []; const optimisticPolicyRecentlyUsedCategories = [category, ...uniquePolicyRecentlyUsedCategories]; + const optimisticPolicyRecentlyUsedTags = {}; + if (allRecentlyUsedTags) { + // For now it only uses the first tag of the policy, since multi-tags are not yet supported + const recentlyUsedPolicyTags = allRecentlyUsedTags[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`]; + const recentlyUsedTagListKey = _.first(_.keys(recentlyUsedPolicyTags)); + const uniquePolicyRecentlyUsedTags = _.filter(recentlyUsedPolicyTags[recentlyUsedTagListKey], (recentlyUsedPolicyTag) => recentlyUsedPolicyTag !== tag); + optimisticPolicyRecentlyUsedTags[recentlyUsedTagListKey] = [tag, ...uniquePolicyRecentlyUsedTags]; + } + // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction // needs to be manually merged into the optimistic transaction. This is because buildOnyxDataForMoneyRequest() uses `Onyx.set()` for the transaction // data. This is a big can of worms to change it to `Onyx.merge()` as explored in https://expensify.slack.com/archives/C05DWUDHVK7/p1692139468252109. @@ -512,6 +541,7 @@ function getMoneyRequestInformation( optimisticPersonalDetailListAction, reportPreviewAction, optimisticPolicyRecentlyUsedCategories, + optimisticPolicyRecentlyUsedTags, isNewChatReport, isNewIOUReport, ); @@ -542,11 +572,12 @@ function getMoneyRequestInformation( * @param {String} created * @param {String} [transactionID] * @param {String} [category] + * @param {String} [tag] * @param {Number} amount * @param {String} currency * @param {String} merchant */ -function createDistanceRequest(report, participant, comment, created, transactionID, category, amount, currency, merchant) { +function createDistanceRequest(report, participant, comment, created, transactionID, category, tag, amount, currency, merchant) { const optimisticReceipt = { source: ReceiptGeneric, state: CONST.IOU.RECEIPT_STATE.OPEN, @@ -564,6 +595,7 @@ function createDistanceRequest(report, participant, comment, created, transactio optimisticReceipt, transactionID, category, + tag, ); API.write( 'CreateDistanceRequest', @@ -579,6 +611,7 @@ function createDistanceRequest(report, participant, comment, created, transactio waypoints: JSON.stringify(TransactionUtils.getValidWaypoints(transaction.comment.waypoints, true)), created, category, + tag, }, onyxData, ); @@ -600,8 +633,9 @@ function createDistanceRequest(report, participant, comment, created, transactio * @param {String} comment * @param {Object} [receipt] * @param {String} [category] + * @param {String} [tag] */ -function requestMoney(report, amount, currency, created, merchant, payeeEmail, payeeAccountID, participant, comment, receipt = undefined, category = undefined) { +function requestMoney(report, amount, currency, created, merchant, payeeEmail, payeeAccountID, participant, comment, receipt = undefined, category = undefined, tag = undefined) { // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); const currentChatReport = isMoneyRequestReport ? ReportUtils.getReport(report.chatReportID) : report; @@ -618,6 +652,7 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p receipt, undefined, category, + tag, ); API.write( @@ -638,6 +673,7 @@ function requestMoney(report, amount, currency, created, merchant, payeeEmail, p reportPreviewReportActionID: reportPreviewAction.reportActionID, receipt, category, + tag, }, onyxData, ); @@ -921,6 +957,7 @@ function createSplitsAndOnyxData(participants, currentUserLogin, currentUserAcco oneOnOnePersonalDetailListAction, oneOnOneReportPreviewAction, [], + {}, isNewOneOnOneChatReport, shouldCreateNewOneOnOneIOUReport, ); @@ -1948,6 +1985,17 @@ function resetMoneyRequestCategory() { Onyx.merge(ONYXKEYS.IOU, {category: ''}); } +/** + * @param {String} tag + */ +function setMoneyRequestTag(tag) { + Onyx.merge(ONYXKEYS.IOU, {tag}); +} + +function resetMoneyRequestTag() { + Onyx.merge(ONYXKEYS.IOU, {tag: ''}); +} + /** * @param {Object[]} participants */ @@ -2032,6 +2080,8 @@ export { setMoneyRequestMerchant, setMoneyRequestCategory, resetMoneyRequestCategory, + setMoneyRequestTag, + resetMoneyRequestTag, setMoneyRequestParticipants, setMoneyRequestReceipt, createEmptyTransaction, diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index 38a5b9c82c07..897c3d34fc28 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -135,6 +135,7 @@ function MoneyRequestConfirmPage(props) { trimmedComment, receipt, props.iou.category, + props.iou.tag, ); }, [ @@ -146,6 +147,7 @@ function MoneyRequestConfirmPage(props) { props.currentUserPersonalDetails.login, props.currentUserPersonalDetails.accountID, props.iou.category, + props.iou.tag, ], ); @@ -162,12 +164,13 @@ function MoneyRequestConfirmPage(props) { props.iou.created, props.iou.transactionID, props.iou.category, + props.iou.tag, props.iou.amount, props.iou.currency, props.iou.merchant, ); }, - [props.report, props.iou.created, props.iou.transactionID, props.iou.category, props.iou.amount, props.iou.currency, props.iou.merchant], + [props.report, props.iou.created, props.iou.transactionID, props.iou.category, props.iou.tag, props.iou.amount, props.iou.currency, props.iou.merchant], ); const createTransaction = useCallback( From eee0d51ce4b48d20cb2c71e42f0494d2451c0126 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Mon, 18 Sep 2023 14:21:10 +0100 Subject: [PATCH 08/19] fix: lint errors --- src/components/TagPicker/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 4920042849e3..724b4b767363 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -42,7 +42,7 @@ function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, if (isTagsCountBelowThreshold && selectedOptions.length > 0) { return _.chain(policyTagList) .values() - .findIndex((tag) => tag.name === selectedOptions[0].name, true) + .findIndex((policyTag) => policyTag.name === selectedOptions[0].name, true) .value(); } @@ -61,11 +61,11 @@ function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); }; - const updateTag = (tag) => { - if (tag.searchText === iou.tag) { + const updateTag = (selectedTag) => { + if (selectedTag.searchText === iou.tag) { IOU.resetMoneyRequestTag(); } else { - IOU.setMoneyRequestTag(tag.searchText); + IOU.setMoneyRequestTag(selectedTag.searchText); } navigateBack(); }; From 68a757f1171fc6361b8750e1cc8d3a8c0e4c3192 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 10:57:37 +0100 Subject: [PATCH 09/19] feat: move update logic outside picker --- src/components/TagPicker/index.js | 29 ++++--------------- .../TagPicker/tagPickerPropTypes.js | 16 +++------- src/pages/iou/MoneyRequestTagPage.js | 24 +++++++++++++-- 3 files changed, 30 insertions(+), 39 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 724b4b767363..e83ef8a9997a 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -4,16 +4,13 @@ import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; -import ROUTES from '../../ROUTES'; import styles from '../../styles/styles'; -import Navigation from '../../libs/Navigation/Navigation'; -import * as IOU from '../../libs/actions/IOU'; import useLocalize from '../../hooks/useLocalize'; import * as OptionsListUtils from '../../libs/OptionsListUtils'; import OptionsSelector from '../OptionsSelector'; import {propTypes, defaultProps} from './tagPickerPropTypes'; -function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, iou}) { +function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, onSubmit}) { const {translate} = useLocalize(); const [searchValue, setSearchValue] = useState(''); @@ -25,18 +22,18 @@ function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, const shouldShowTextInput = !isTagsCountBelowThreshold; const selectedOptions = useMemo(() => { - if (!iou.tag) { + if (!selectedTag) { return []; } return [ { - name: iou.tag, + name: selectedTag, enabled: true, accountID: null, }, ]; - }, [iou.tag]); + }, [selectedTag]); const initialFocusedIndex = useMemo(() => { if (isTagsCountBelowThreshold && selectedOptions.length > 0) { @@ -57,19 +54,6 @@ function TagPicker({reportID, tag, iouType, policyTags, policyRecentlyUsedTags, const headerMessage = OptionsListUtils.getHeaderMessage(lodashGet(sections, '[0].data.length', 0) > 0, false, ''); - const navigateBack = () => { - Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, reportID)); - }; - - const updateTag = (selectedTag) => { - if (selectedTag.searchText === iou.tag) { - IOU.resetMoneyRequestTag(); - } else { - IOU.setMoneyRequestTag(selectedTag.searchText); - } - navigateBack(); - }; - return ( ); } @@ -100,7 +84,4 @@ export default withOnyx({ policyRecentlyUsedTags: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`, }, - iou: { - key: ONYXKEYS.IOU, - }, })(TagPicker); diff --git a/src/components/TagPicker/tagPickerPropTypes.js b/src/components/TagPicker/tagPickerPropTypes.js index ad57a0409f15..a5d94605a76a 100644 --- a/src/components/TagPicker/tagPickerPropTypes.js +++ b/src/components/TagPicker/tagPickerPropTypes.js @@ -1,22 +1,18 @@ import PropTypes from 'prop-types'; import tagPropTypes from '../tagPropTypes'; -import {iouPropTypes, iouDefaultProps} from '../../pages/iou/propTypes'; const propTypes = { - /** The report ID of the IOU */ - reportID: PropTypes.string.isRequired, - /** The policyID we are getting tags for */ policyID: PropTypes.string.isRequired, + /** The selected tag of the money request */ + selectedTag: PropTypes.string.isRequired, + /** The name of tag list we are getting tags for */ tag: PropTypes.string.isRequired, - /** The type of IOU report, i.e. bill, request, send */ - iouType: PropTypes.string.isRequired, - /** Callback to submit the selected tag */ - onSubmit: PropTypes.func, + onSubmit: PropTypes.func.isRequired, /* Onyx Props */ /** Collection of tags attached to a policy */ @@ -29,15 +25,11 @@ const propTypes = { /** List of recently used tags */ policyRecentlyUsedTags: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.string)), - - /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ - iou: iouPropTypes, }; const defaultProps = { policyTags: {}, policyRecentlyUsedTags: {}, - iou: iouDefaultProps, }; export {propTypes, defaultProps}; diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 3dfad9eaa35d..2f80cd0aace9 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -5,6 +5,7 @@ import lodashGet from 'lodash/get'; import {withOnyx} from 'react-native-onyx'; import compose from '../../libs/compose'; import ROUTES from '../../ROUTES'; +import * as IOU from '../../libs/actions/IOU'; import Navigation from '../../libs/Navigation/Navigation'; import useLocalize from '../../hooks/useLocalize'; import ScreenWrapper from '../../components/ScreenWrapper'; @@ -15,6 +16,7 @@ import tagPropTypes from '../../components/tagPropTypes'; import ONYXKEYS from '../../ONYXKEYS'; import reportPropTypes from '../reportPropTypes'; import styles from '../../styles/styles'; +import {iouPropTypes, iouDefaultProps} from './propTypes'; const propTypes = { /** Navigation route context info provided by react navigation */ @@ -40,14 +42,18 @@ const propTypes = { tags: PropTypes.objectOf(tagPropTypes), }), ), + + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ + iou: iouPropTypes, }; const defaultProps = { report: {}, policyTags: {}, + iou: iouDefaultProps, }; -function MoneyRequestTagPage({route, report, policyTags}) { +function MoneyRequestTagPage({route, report, policyTags, iou}) { const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); @@ -61,6 +67,15 @@ function MoneyRequestTagPage({route, report, policyTags}) { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, report.reportID)); }; + const updateTag = (selectedTag) => { + if (selectedTag.searchText === iou.tag) { + IOU.resetMoneyRequestTag(); + } else { + IOU.setMoneyRequestTag(selectedTag.searchText); + } + navigateBack(); + }; + return ( {translate('iou.tagSelection', {tagListName} || translate('common.tag'))} ); @@ -90,6 +105,9 @@ export default compose( report: { key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${lodashGet(route, 'params.reportID')}`, }, + iou: { + key: ONYXKEYS.IOU, + }, }), withOnyx({ policyTags: { From e98ca8001daba1b1a314b7a129a2cfe8d65f8306 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 14:06:51 +0100 Subject: [PATCH 10/19] fix: filter out disabled tags --- .../MoneyRequestConfirmationList.js | 3 +- src/components/TagPicker/index.js | 2 +- src/libs/OptionsListUtils.js | 22 ++++--- src/pages/iou/MoneyRequestTagPage.js | 2 +- tests/unit/OptionsListUtilsTest.js | 58 +------------------ 5 files changed, 15 insertions(+), 72 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index ca7f6d5521a8..80d16c226ae9 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -201,6 +201,7 @@ function MoneyRequestConfirmationList(props) { const tagList = lodashGet(props.policyTags, [tagListKey, 'tags'], []); const tagListName = lodashGet(props.policyTags, [tagListKey, 'name'], ''); const canUseTags = Permissions.canUseTags(props.betas); + const shouldShowTags = canUseTags && _.any(tagList, (tag) => tag.enabled); const hasRoute = TransactionUtils.hasRoute(transaction); const isDistanceRequestWithoutRoute = props.isDistanceRequest && !hasRoute; @@ -526,7 +527,7 @@ function MoneyRequestConfirmationList(props) { disabled={didConfirm || props.isReadOnly} /> )} - {canUseTags && !_.isEmpty(tagList) && ( + {shouldShowTags && ( policyTag.enabled)); const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; const shouldShowTextInput = !isTagsCountBelowThreshold; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 74d4df24b8ae..6e9d4ac185f0 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -775,11 +775,12 @@ function getTagsOptions(tags) { */ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInputValue, maxRecentReportsToShow) { const tagSections = []; - const numberOfTags = _.size(tags); + const enabledTags = _.filter(tags, (tag) => tag.enabled); + const numberOfTags = _.size(enabledTags); let indexOffset = 0; if (!_.isEmpty(searchInputValue)) { - const searchTags = _.filter(tags, (tag) => tag.name.toLowerCase().includes(searchInputValue.toLowerCase())); + const searchTags = _.filter(enabledTags, (tag) => tag.name.toLowerCase().includes(searchInputValue.toLowerCase())); tagSections.push({ // "Search" section @@ -798,7 +799,7 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput title: '', shouldShow: false, indexOffset, - data: getTagsOptions(tags), + data: getTagsOptions(enabledTags), }); return tagSections; @@ -806,23 +807,20 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput const selectedOptionNames = _.map(selectedOptions, (selectedOption) => selectedOption.name); const filteredRecentlyUsedTags = _.map( - _.filter(recentlyUsedTags, (tag) => !_.includes(selectedOptionNames, tag)), - (tag) => { + _.filter(recentlyUsedTags, (tag) => { const tagObject = _.find(tags, (t) => t.name === tag); - return { - name: tag, - enabled: tagObject && tagObject.enabled, - }; - }, + return Boolean(tagObject && tagObject.enabled) && !_.includes(selectedOptionNames, tag); + }), + (tag) => ({name: tag, enabled: true}), ); - const filteredTags = _.filter(tags, (tag) => !_.includes(selectedOptionNames, tag.name)); + const filteredTags = _.filter(enabledTags, (tag) => !_.includes(selectedOptionNames, tag.name)); if (!_.isEmpty(selectedOptions)) { const selectedTagOptions = _.map(selectedOptions, (option) => { const tagObject = _.find(tags, (t) => t.name === option.name); return { name: option.name, - enabled: tagObject && tagObject.enabled, + enabled: Boolean(tagObject && tagObject.enabled), }; }); diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 2f80cd0aace9..3866877a7794 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -85,7 +85,7 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { title={tagListName || translate('common.tag')} onBackButtonPress={navigateBack} /> - {translate('iou.tagSelection', {tagListName} || translate('common.tag'))} + {translate('iou.tagSelection', {tagName: tagListName || translate('common.tag')})} { shouldShow: false, indexOffset: 0, data: [ - { - text: 'Engineering', - keyForList: 'Engineering', - searchText: 'Engineering', - tooltipText: 'Engineering', - isDisabled: true, - }, { text: 'Medical', keyForList: 'Medical', @@ -1128,13 +1121,6 @@ describe('OptionsListUtils', () => { shouldShow: false, indexOffset: 0, data: [ - { - text: 'Engineering', - keyForList: 'Engineering', - searchText: 'Engineering', - tooltipText: 'Engineering', - isDisabled: true, - }, { text: 'Accounting', keyForList: 'Accounting', @@ -1219,13 +1205,6 @@ describe('OptionsListUtils', () => { shouldShow: true, indexOffset: 1, data: [ - { - text: 'Engineering', - keyForList: 'Engineering', - searchText: 'Engineering', - tooltipText: 'Engineering', - isDisabled: true, - }, { text: 'HR', keyForList: 'HR', @@ -1238,15 +1217,8 @@ describe('OptionsListUtils', () => { { title: 'All', shouldShow: true, - indexOffset: 3, + indexOffset: 2, data: [ - { - text: 'Engineering', - keyForList: 'Engineering', - searchText: 'Engineering', - tooltipText: 'Engineering', - isDisabled: true, - }, { text: 'Accounting', keyForList: 'Accounting', @@ -1268,13 +1240,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Food', isDisabled: false, }, - { - text: 'Traveling', - keyForList: 'Traveling', - searchText: 'Traveling', - tooltipText: 'Traveling', - isDisabled: true, - }, { text: 'Cleaning', keyForList: 'Cleaning', @@ -1289,13 +1254,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Software', isDisabled: false, }, - { - text: 'Office Supplies', - keyForList: 'Office Supplies', - searchText: 'Office Supplies', - tooltipText: 'Office Supplies', - isDisabled: true, - }, { text: 'Taxes', keyForList: 'Taxes', @@ -1319,13 +1277,6 @@ describe('OptionsListUtils', () => { shouldShow: false, indexOffset: 0, data: [ - { - text: 'Engineering', - keyForList: 'Engineering', - searchText: 'Engineering', - tooltipText: 'Engineering', - isDisabled: true, - }, { text: 'Accounting', keyForList: 'Accounting', @@ -1333,13 +1284,6 @@ describe('OptionsListUtils', () => { tooltipText: 'Accounting', isDisabled: false, }, - { - text: 'Traveling', - keyForList: 'Traveling', - searchText: 'Traveling', - tooltipText: 'Traveling', - isDisabled: true, - }, { text: 'Cleaning', keyForList: 'Cleaning', From df3ee738d0f75372a9a7db8cc3a874daee9ab64c Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 14:10:55 +0100 Subject: [PATCH 11/19] refactor: reorder --- src/pages/iou/MoneyRequestTagPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 3866877a7794..5c5fd0f939fc 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -56,13 +56,13 @@ const defaultProps = { function MoneyRequestTagPage({route, report, policyTags, iou}) { const {translate} = useLocalize(); - const iouType = lodashGet(route, 'params.iouType', ''); - // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(policyTags)); const tagList = lodashGet(policyTags, tagListKey, {}); const tagListName = lodashGet(tagList, 'name', ''); + const iouType = lodashGet(route, 'params.iouType', ''); + const navigateBack = () => { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, report.reportID)); }; From 8b4245f93645e59a348a1a57d6c52640c47ec4d1 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 14:19:45 +0100 Subject: [PATCH 12/19] revert: reorder --- src/pages/iou/MoneyRequestTagPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 5c5fd0f939fc..3866877a7794 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -56,13 +56,13 @@ const defaultProps = { function MoneyRequestTagPage({route, report, policyTags, iou}) { const {translate} = useLocalize(); + const iouType = lodashGet(route, 'params.iouType', ''); + // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(policyTags)); const tagList = lodashGet(policyTags, tagListKey, {}); const tagListName = lodashGet(tagList, 'name', ''); - const iouType = lodashGet(route, 'params.iouType', ''); - const navigateBack = () => { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, report.reportID)); }; From 1cd6b03b0f0498d9044c96d6dd384505508fcb96 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 17:02:31 +0100 Subject: [PATCH 13/19] refactor: simplify --- src/libs/OptionsListUtils.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 6e9d4ac185f0..a9217b99d6b9 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -745,19 +745,13 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt * @returns {Array} */ function getTagsOptions(tags) { - const options = []; - - _.each(tags, (tag) => - options.push({ - text: tag.name, - keyForList: tag.name, - searchText: tag.name, - tooltipText: tag.name, - isDisabled: !tag.enabled, - }), - ); - - return options; + return _.map(tags, tag => ({ + text: tag.name, + keyForList: tag.name, + searchText: tag.name, + tooltipText: tag.name, + isDisabled: !tag.enabled, + })) } /** From 96628220c6065aca06fe3b199258aa30791c83d5 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 17:02:57 +0100 Subject: [PATCH 14/19] fix: prettuer --- src/libs/OptionsListUtils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index a9217b99d6b9..20a9f01a08ae 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -745,13 +745,13 @@ function getCategoryListSections(categories, recentlyUsedCategories, selectedOpt * @returns {Array} */ function getTagsOptions(tags) { - return _.map(tags, tag => ({ + return _.map(tags, (tag) => ({ text: tag.name, keyForList: tag.name, searchText: tag.name, tooltipText: tag.name, isDisabled: !tag.enabled, - })) + })); } /** From 118a79107f071d2c6a50b5a2331edd42ece96381 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Tue, 19 Sep 2023 17:09:55 +0100 Subject: [PATCH 15/19] refactor: simplify --- src/pages/iou/MoneyRequestTagPage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/MoneyRequestTagPage.js b/src/pages/iou/MoneyRequestTagPage.js index 3866877a7794..c9b5eb4f8f6f 100644 --- a/src/pages/iou/MoneyRequestTagPage.js +++ b/src/pages/iou/MoneyRequestTagPage.js @@ -61,7 +61,7 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { // Fetches the first tag list of the policy const tagListKey = _.first(_.keys(policyTags)); const tagList = lodashGet(policyTags, tagListKey, {}); - const tagListName = lodashGet(tagList, 'name', ''); + const tagListName = lodashGet(tagList, 'name', translate('common.tag')); const navigateBack = () => { Navigation.goBack(ROUTES.getMoneyRequestConfirmationRoute(iouType, report.reportID)); @@ -82,10 +82,10 @@ function MoneyRequestTagPage({route, report, policyTags, iou}) { shouldEnableMaxHeight > - {translate('iou.tagSelection', {tagName: tagListName || translate('common.tag')})} + {translate('iou.tagSelection', {tagName: tagListName})} Date: Wed, 20 Sep 2023 09:37:52 +0100 Subject: [PATCH 16/19] fix: change variable names --- src/libs/OptionsListUtils.js | 8 ++++---- src/libs/actions/IOU.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 20a9f01a08ae..ef8ac660e773 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -801,9 +801,9 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput const selectedOptionNames = _.map(selectedOptions, (selectedOption) => selectedOption.name); const filteredRecentlyUsedTags = _.map( - _.filter(recentlyUsedTags, (tag) => { - const tagObject = _.find(tags, (t) => t.name === tag); - return Boolean(tagObject && tagObject.enabled) && !_.includes(selectedOptionNames, tag); + _.filter(recentlyUsedTags, (recentlyUsedTag) => { + const tagObject = _.find(tags, (tag) => tag.name === recentlyUsedTag); + return Boolean(tagObject && tagObject.enabled) && !_.includes(selectedOptionNames, recentlyUsedTag); }), (tag) => ({name: tag, enabled: true}), ); @@ -811,7 +811,7 @@ function getTagListSections(tags, recentlyUsedTags, selectedOptions, searchInput if (!_.isEmpty(selectedOptions)) { const selectedTagOptions = _.map(selectedOptions, (option) => { - const tagObject = _.find(tags, (t) => t.name === option.name); + const tagObject = _.find(tags, (tag) => tag.name === option.name); return { name: option.name, enabled: Boolean(tagObject && tagObject.enabled), diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index c53fccdcff11..4a6f80162de8 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -55,7 +55,7 @@ let allRecentlyUsedTags = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, waitForCollectionCallback: true, - callback: (val) => (allRecentlyUsedTags = val), + callback: (value) => (allRecentlyUsedTags = value), }); let userAccountID = ''; From ec0ffa6948ea5de621b72a2d743d1e4f46e14b97 Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Wed, 20 Sep 2023 14:45:12 +0100 Subject: [PATCH 17/19] fix: check for recentlyUsedPolicyTags and default value --- src/components/TagPicker/index.js | 2 +- src/libs/actions/IOU.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 46ceb205d560..6ad9a253c084 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -15,7 +15,7 @@ function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, onSubm const [searchValue, setSearchValue] = useState(''); const policyRecentlyUsedTagsList = lodashGet(policyRecentlyUsedTags, tag, []); - const policyTagList = lodashGet(policyTags, [tag, 'tags'], []); + const policyTagList = lodashGet(policyTags, [tag, 'tags'], {}); const policyTagsCount = _.size(_.filter(policyTagList, (policyTag) => policyTag.enabled)); const isTagsCountBelowThreshold = policyTagsCount < CONST.TAG_LIST_THRESHOLD; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 2f95b5de6204..76ff89cb4b61 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -467,9 +467,10 @@ function getMoneyRequestInformation( const optimisticPolicyRecentlyUsedCategories = [category, ...uniquePolicyRecentlyUsedCategories]; const optimisticPolicyRecentlyUsedTags = {}; - if (allRecentlyUsedTags) { + const recentlyUsedPolicyTags = allRecentlyUsedTags[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`]; + + if (recentlyUsedPolicyTags) { // For now it only uses the first tag of the policy, since multi-tags are not yet supported - const recentlyUsedPolicyTags = allRecentlyUsedTags[`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${iouReport.policyID}`]; const recentlyUsedTagListKey = _.first(_.keys(recentlyUsedPolicyTags)); const uniquePolicyRecentlyUsedTags = _.filter(recentlyUsedPolicyTags[recentlyUsedTagListKey], (recentlyUsedPolicyTag) => recentlyUsedPolicyTag !== tag); optimisticPolicyRecentlyUsedTags[recentlyUsedTagListKey] = [tag, ...uniquePolicyRecentlyUsedTags]; From 2f4c295b9d7a41add07395521ea25fc9459cecaa Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Wed, 20 Sep 2023 14:51:02 +0100 Subject: [PATCH 18/19] refactor: rename getNewChatOptions to getFilteredOptions --- src/components/CategoryPicker/index.js | 2 +- src/components/TagPicker/index.js | 2 +- src/libs/OptionsListUtils.js | 4 +- src/pages/NewChatPage.js | 4 +- .../MoneyRequestParticipantsSelector.js | 4 +- src/pages/tasks/TaskAssigneeSelectorModal.js | 2 +- tests/unit/OptionsListUtilsTest.js | 90 +++++++++---------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 91c7e82e7887..9d4e0747cc18 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -46,7 +46,7 @@ function CategoryPicker({policyCategories, reportID, iouType, iou, policyRecentl }, [policyCategories, selectedOptions, isCategoriesCountBelowThreshold]); const sections = useMemo( - () => OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, true, policyCategories, policyRecentlyUsedCategories, false).categoryOptions, + () => OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, true, policyCategories, policyRecentlyUsedCategories, false).categoryOptions, [policyCategories, policyRecentlyUsedCategories, searchValue, selectedOptions], ); diff --git a/src/components/TagPicker/index.js b/src/components/TagPicker/index.js index 6ad9a253c084..c46ca1b57b22 100644 --- a/src/components/TagPicker/index.js +++ b/src/components/TagPicker/index.js @@ -48,7 +48,7 @@ function TagPicker({selectedTag, tag, policyTags, policyRecentlyUsedTags, onSubm const sections = useMemo( () => - OptionsListUtils.getNewChatOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, policyTagList, policyRecentlyUsedTagsList, false).tagOptions, + OptionsListUtils.getFilteredOptions({}, {}, [], searchValue, selectedOptions, [], false, false, false, {}, [], true, policyTagList, policyRecentlyUsedTagsList, false).tagOptions, [searchValue, selectedOptions, policyTagList, policyRecentlyUsedTagsList], ); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 3d209f3230e7..45bc6dffd67a 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1270,7 +1270,7 @@ function getIOUConfirmationOptionsFromParticipants(participants, amountText) { * @param {boolean} [canInviteUser] * @returns {Object} */ -function getNewChatOptions( +function getFilteredOptions( reports, personalDetails, betas = [], @@ -1458,7 +1458,7 @@ export { isCurrentUser, isPersonalDetailsReady, getSearchOptions, - getNewChatOptions, + getFilteredOptions, getShareDestinationOptions, getMemberInviteOptions, getHeaderMessage, diff --git a/src/pages/NewChatPage.js b/src/pages/NewChatPage.js index edbae46a3207..cb54aa8e5a7b 100755 --- a/src/pages/NewChatPage.js +++ b/src/pages/NewChatPage.js @@ -124,7 +124,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) recentReports, personalDetails: newChatPersonalDetails, userToInvite, - } = OptionsListUtils.getNewChatOptions(reports, personalDetails, betas, searchTerm, newSelectedOptions, excludedGroupEmails); + } = OptionsListUtils.getFilteredOptions(reports, personalDetails, betas, searchTerm, newSelectedOptions, excludedGroupEmails); setSelectedOptions(newSelectedOptions); setFilteredRecentReports(recentReports); @@ -159,7 +159,7 @@ function NewChatPage({betas, isGroupChat, personalDetails, reports, translate}) recentReports, personalDetails: newChatPersonalDetails, userToInvite, - } = OptionsListUtils.getNewChatOptions(reports, personalDetails, betas, searchTerm, selectedOptions, isGroupChat ? excludedGroupEmails : []); + } = OptionsListUtils.getFilteredOptions(reports, personalDetails, betas, searchTerm, selectedOptions, isGroupChat ? excludedGroupEmails : []); setFilteredRecentReports(recentReports); setFilteredPersonalDetails(newChatPersonalDetails); setFilteredUserToInvite(userToInvite); diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js index 4f761e92eaf5..77ead4bf5a85 100755 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsSelector.js @@ -193,7 +193,7 @@ function MoneyRequestParticipantsSelector({ onAddParticipants(newSelectedOptions); - const chatOptions = OptionsListUtils.getNewChatOptions( + const chatOptions = OptionsListUtils.getFilteredOptions( reports, personalDetails, betas, @@ -228,7 +228,7 @@ function MoneyRequestParticipantsSelector({ const isOptionsDataReady = ReportUtils.isReportDataReady() && OptionsListUtils.isPersonalDetailsReady(personalDetails); useEffect(() => { - const chatOptions = OptionsListUtils.getNewChatOptions( + const chatOptions = OptionsListUtils.getFilteredOptions( reports, personalDetails, betas, diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.js b/src/pages/tasks/TaskAssigneeSelectorModal.js index 9535cf64011f..2e7ddea1926d 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.js +++ b/src/pages/tasks/TaskAssigneeSelectorModal.js @@ -83,7 +83,7 @@ function TaskAssigneeSelectorModal(props) { const optionRef = useRef(); const updateOptions = useCallback(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getNewChatOptions( + const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getFilteredOptions( props.reports, props.personalDetails, props.betas, diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index b7612d2cc6a6..637dc1e18376 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -320,12 +320,12 @@ describe('OptionsListUtils', () => { }); }); - it('getNewChatOptions()', () => { + it('getFilteredOptions()', () => { // maxRecentReportsToShow in src/libs/OptionsListUtils.js const MAX_RECENT_REPORTS = 5; - // When we call getNewChatOptions() with no search value - let results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], ''); + // When we call getFilteredOptions() with no search value + let results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], ''); // We should expect maximimum of 5 recent reports to be returned expect(results.recentReports.length).toBe(MAX_RECENT_REPORTS); @@ -345,7 +345,7 @@ describe('OptionsListUtils', () => { expect(personalDetailWithExistingReport.reportID).toBe(2); // When we only pass personal details - results = OptionsListUtils.getNewChatOptions([], PERSONAL_DETAILS, [], ''); + results = OptionsListUtils.getFilteredOptions([], PERSONAL_DETAILS, [], ''); // We should expect personal details sorted alphabetically expect(results.personalDetails[0].text).toBe('Black Panther'); @@ -354,13 +354,13 @@ describe('OptionsListUtils', () => { expect(results.personalDetails[3].text).toBe('Invisible Woman'); // When we provide a search value that does not match any personal details - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'magneto'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'magneto'); // Then no options will be returned expect(results.personalDetails.length).toBe(0); // When we provide a search value that matches an email - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'peterparker@expensify.com'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'peterparker@expensify.com'); // Then one recentReports will be returned and it will be the correct option // personalDetails should be empty array @@ -369,7 +369,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails.length).toBe(0); // When we provide a search value that matches a partial display name or email - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '.com'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '.com'); // Then several options will be returned and they will be each have the search string in their email or name // even though the currently logged in user matches they should not show. @@ -382,7 +382,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports[2].text).toBe('Black Panther'); // Test for Concierge's existence in chat options - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE); // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the 5 that are already showing and the currently logged in user) @@ -390,30 +390,30 @@ describe('OptionsListUtils', () => { expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE, [], '', [], [CONST.EMAIL.CONCIERGE]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE, [], '', [], [CONST.EMAIL.CONCIERGE]); // All the personalDetails should be returned minus the currently logged in user and Concierge expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CONCIERGE) - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Chronos from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CHRONOS, PERSONAL_DETAILS_WITH_CHRONOS, [], '', [], [CONST.EMAIL.CHRONOS]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CHRONOS, PERSONAL_DETAILS_WITH_CHRONOS, [], '', [], [CONST.EMAIL.CHRONOS]); // All the personalDetails should be returned minus the currently logged in user and Concierge expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_CHRONOS) - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); // Test by excluding Receipts from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_RECEIPTS, PERSONAL_DETAILS_WITH_RECEIPTS, [], '', [], [CONST.EMAIL.RECEIPTS]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_RECEIPTS, PERSONAL_DETAILS_WITH_RECEIPTS, [], '', [], [CONST.EMAIL.RECEIPTS]); // All the personalDetails should be returned minus the currently logged in user and Concierge expect(results.personalDetails.length).toBe(_.size(PERSONAL_DETAILS_WITH_RECEIPTS) - 2 - MAX_RECENT_REPORTS); expect(results.personalDetails).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'receipts@expensify.com'})])); }); - it('getNewChatOptions() for group Chat', () => { - // When we call getNewChatOptions() with no search value - let results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], ''); + it('getFilteredOptions() for group Chat', () => { + // When we call getFilteredOptions() with no search value + let results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], ''); // Then we should expect only a maxmimum of 5 recent reports to be returned expect(results.recentReports.length).toBe(5); @@ -434,7 +434,7 @@ describe('OptionsListUtils', () => { expect(personalDetailsOverlapWithReports).toBe(false); // When we search for an option that is only in a personalDetail with no existing report - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'hulk'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'hulk'); // Then reports should return no results expect(results.recentReports.length).toBe(0); @@ -444,7 +444,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails[0].login).toBe('brucebanner@expensify.com'); // When we search for an option that matches things in both personalDetails and reports - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '.com'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '.com'); // Then all single participant reports that match will show up in the recentReports array, Recently used contact should be at the top expect(results.recentReports.length).toBe(5); @@ -454,16 +454,16 @@ describe('OptionsListUtils', () => { expect(results.personalDetails.length).toBe(4); expect(results.personalDetails[0].login).toBe('natasharomanoff@expensify.com'); - // When we provide no selected options to getNewChatOptions() - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '', []); + // When we provide no selected options to getFilteredOptions() + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '', []); // Then one of our older report options (not in our five most recent) should appear in the personalDetails // but not in recentReports expect(_.every(results.recentReports, (option) => option.login !== 'peterparker@expensify.com')).toBe(true); expect(_.every(results.personalDetails, (option) => option.login !== 'peterparker@expensify.com')).toBe(false); - // When we provide a "selected" option to getNewChatOptions() - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '', [{login: 'peterparker@expensify.com'}]); + // When we provide a "selected" option to getFilteredOptions() + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '', [{login: 'peterparker@expensify.com'}]); // Then the option should not appear anywhere in either list expect(_.every(results.recentReports, (option) => option.login !== 'peterparker@expensify.com')).toBe(true); @@ -471,7 +471,7 @@ describe('OptionsListUtils', () => { // When we add a search term for which no options exist and the searchValue itself // is not a potential email or phone - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'marc@expensify'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'marc@expensify'); // Then we should have no options or personal details at all and also that there is no userToInvite expect(results.recentReports.length).toBe(0); @@ -480,7 +480,7 @@ describe('OptionsListUtils', () => { // When we add a search term for which no options exist and the searchValue itself // is a potential email - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'marc@expensify.com'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'marc@expensify.com'); // Then we should have no options or personal details at all but there should be a userToInvite expect(results.recentReports.length).toBe(0); @@ -488,7 +488,7 @@ describe('OptionsListUtils', () => { expect(results.userToInvite).not.toBe(null); // When we add a search term with a period, with options for it that don't contain the period - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], 'peter.parker@expensify.com'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], 'peter.parker@expensify.com'); // Then we should have no options at all but there should be a userToInvite expect(results.recentReports.length).toBe(0); @@ -496,7 +496,7 @@ describe('OptionsListUtils', () => { // When we add a search term for which no options exist and the searchValue itself // is a potential phone number without country code added - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '5005550006'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '5005550006'); // Then we should have no options or personal details at all but there should be a userToInvite and the login // should have the country code included @@ -507,7 +507,7 @@ describe('OptionsListUtils', () => { // When we add a search term for which no options exist and the searchValue itself // is a potential phone number with country code added - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '+15005550006'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '+15005550006'); // Then we should have no options or personal details at all but there should be a userToInvite and the login // should have the country code included @@ -518,7 +518,7 @@ describe('OptionsListUtils', () => { // When we add a search term for which no options exist and the searchValue itself // is a potential phone number with special characters added - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '+1 (800)324-3233'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '+1 (800)324-3233'); // Then we should have no options or personal details at all but there should be a userToInvite and the login // should have the country code included @@ -528,7 +528,7 @@ describe('OptionsListUtils', () => { expect(results.userToInvite.login).toBe('+18003243233'); // When we use a search term for contact number that contains alphabet characters - results = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], '998243aaaa'); + results = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], '998243aaaa'); // Then we shouldn't have any results or user to invite expect(results.recentReports.length).toBe(0); @@ -536,7 +536,7 @@ describe('OptionsListUtils', () => { expect(results.userToInvite).toBe(null); // Test Concierge's existence in new group options - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE); // Concierge is included in the results by default. We should expect all the personalDetails to show // (minus the 5 that are already showing and the currently logged in user) @@ -544,7 +544,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Concierge from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE, [], '', [], [CONST.EMAIL.CONCIERGE]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CONCIERGE, PERSONAL_DETAILS_WITH_CONCIERGE, [], '', [], [CONST.EMAIL.CONCIERGE]); // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) @@ -553,7 +553,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'concierge@expensify.com'})])); // Test by excluding Chronos from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_CHRONOS, PERSONAL_DETAILS_WITH_CHRONOS, [], '', [], [CONST.EMAIL.CHRONOS]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_CHRONOS, PERSONAL_DETAILS_WITH_CHRONOS, [], '', [], [CONST.EMAIL.CHRONOS]); // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) @@ -562,7 +562,7 @@ describe('OptionsListUtils', () => { expect(results.recentReports).not.toEqual(expect.arrayContaining([expect.objectContaining({login: 'chronos@expensify.com'})])); // Test by excluding Receipts from the results - results = OptionsListUtils.getNewChatOptions(REPORTS_WITH_RECEIPTS, PERSONAL_DETAILS_WITH_RECEIPTS, [], '', [], [CONST.EMAIL.RECEIPTS]); + results = OptionsListUtils.getFilteredOptions(REPORTS_WITH_RECEIPTS, PERSONAL_DETAILS_WITH_RECEIPTS, [], '', [], [CONST.EMAIL.RECEIPTS]); // We should expect all the personalDetails to show (minus the 5 that are already showing, // the currently logged in user and Concierge) @@ -652,7 +652,7 @@ describe('OptionsListUtils', () => { expect(results.personalDetails[0].text).toBe('Spider-Man'); }); - it('getNewChatOptions() for categories', () => { + it('getFilteredOptions() for categories', () => { const search = 'Food'; const emptySearch = ''; const wrongSearch = 'bla bla'; @@ -1001,16 +1001,16 @@ describe('OptionsListUtils', () => { }, ]; - const smallResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, true, smallCategoriesList); + const smallResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, true, smallCategoriesList); expect(smallResult.categoryOptions).toStrictEqual(smallResultList); - const smallSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], search, [], [], false, false, true, smallCategoriesList); + const smallSearchResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], search, [], [], false, false, true, smallCategoriesList); expect(smallSearchResult.categoryOptions).toStrictEqual(smallSearchResultList); - const smallWrongSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], wrongSearch, [], [], false, false, true, smallCategoriesList); + const smallWrongSearchResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], wrongSearch, [], [], false, false, true, smallCategoriesList); expect(smallWrongSearchResult.categoryOptions).toStrictEqual(smallWrongSearchResultList); - const largeResult = OptionsListUtils.getNewChatOptions( + const largeResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], @@ -1025,7 +1025,7 @@ describe('OptionsListUtils', () => { ); expect(largeResult.categoryOptions).toStrictEqual(largeResultList); - const largeSearchResult = OptionsListUtils.getNewChatOptions( + const largeSearchResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], @@ -1040,7 +1040,7 @@ describe('OptionsListUtils', () => { ); expect(largeSearchResult.categoryOptions).toStrictEqual(largeSearchResultList); - const largeWrongSearchResult = OptionsListUtils.getNewChatOptions( + const largeWrongSearchResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], @@ -1056,7 +1056,7 @@ describe('OptionsListUtils', () => { expect(largeWrongSearchResult.categoryOptions).toStrictEqual(largeWrongSearchResultList); }); - it('getNewChatOptions() for tags', () => { + it('getFilteredOptions() for tags', () => { const search = 'ing'; const emptySearch = ''; const wrongSearch = 'bla bla'; @@ -1303,16 +1303,16 @@ describe('OptionsListUtils', () => { }, ]; - const smallResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, false, {}, [], true, smallTagsList); + const smallResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], emptySearch, [], [], false, false, false, {}, [], true, smallTagsList); expect(smallResult.tagOptions).toStrictEqual(smallResultList); - const smallSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], search, [], [], false, false, false, {}, [], true, smallTagsList); + const smallSearchResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], search, [], [], false, false, false, {}, [], true, smallTagsList); expect(smallSearchResult.tagOptions).toStrictEqual(smallSearchResultList); - const smallWrongSearchResult = OptionsListUtils.getNewChatOptions(REPORTS, PERSONAL_DETAILS, [], wrongSearch, [], [], false, false, false, {}, [], true, smallTagsList); + const smallWrongSearchResult = OptionsListUtils.getFilteredOptions(REPORTS, PERSONAL_DETAILS, [], wrongSearch, [], [], false, false, false, {}, [], true, smallTagsList); expect(smallWrongSearchResult.tagOptions).toStrictEqual(smallWrongSearchResultList); - const largeResult = OptionsListUtils.getNewChatOptions( + const largeResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], @@ -1330,7 +1330,7 @@ describe('OptionsListUtils', () => { ); expect(largeResult.tagOptions).toStrictEqual(largeResultList); - const largeSearchResult = OptionsListUtils.getNewChatOptions( + const largeSearchResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], @@ -1348,7 +1348,7 @@ describe('OptionsListUtils', () => { ); expect(largeSearchResult.tagOptions).toStrictEqual(largeSearchResultList); - const largeWrongSearchResult = OptionsListUtils.getNewChatOptions( + const largeWrongSearchResult = OptionsListUtils.getFilteredOptions( REPORTS, PERSONAL_DETAILS, [], From 993b4c233742e6065287a6bd99289b5407672c3e Mon Sep 17 00:00:00 2001 From: Ana Margarida Silva Date: Wed, 20 Sep 2023 14:55:51 +0100 Subject: [PATCH 19/19] fix: adapt to null value --- src/libs/actions/IOU.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 76ff89cb4b61..6212b48e806f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -55,7 +55,14 @@ let allRecentlyUsedTags = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS, waitForCollectionCallback: true, - callback: (value) => (allRecentlyUsedTags = value), + callback: (value) => { + if (!value) { + allRecentlyUsedTags = {}; + return; + } + + allRecentlyUsedTags = value; + }, }); let userAccountID = '';