Skip to content

Commit 254f263

Browse files
committed
fix duplicated personal details #53579
This is a more effective implementation for https://github.com/Expensify/App/pull/54147/files which i reverted in this PR
1 parent e6d69b3 commit 254f263

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

src/hooks/useFastSearchFromOptions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ function useFastSearchFromOptions(
4242
const displayName = option.participantsList?.[0]?.displayName ?? '';
4343
return [option.login ?? '', option.login !== displayName ? displayName : ''].join();
4444
},
45+
uniqueId: (option) => option.login,
4546
},
4647
{
4748
data: options.recentReports,

src/libs/FastSearch.ts

+29
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ type SearchableData<T> = {
1313
* If you have multiple fields that should be searchable, simply concat them to the string and return it.
1414
*/
1515
toSearchableString: (data: T) => string;
16+
17+
/**
18+
* Gives the possibility to identify data by a unique attribute. Assume you have to search results with the same text they might be valid
19+
* and represent different data. In this case, you can provide a function that returns a unique identifier for the data.
20+
* If multiple items with the same identifier are found, only the first one will be returned.
21+
* This fixes: https://github.com/Expensify/App/issues/53579
22+
*/
23+
uniqueId?: (data: T) => string | undefined;
1624
};
1725

1826
// There are certain characters appear very often in our search data (email addresses), which we don't need to search for.
@@ -72,6 +80,7 @@ function createFastSearch<T>(dataSets: Array<SearchableData<T>>) {
7280
const result = tree.findSubstring(Array.from(numeric));
7381

7482
const resultsByDataSet = Array.from({length: dataSets.length}, () => new Set<T>());
83+
const uniqueMap: Record<number, Record<string, T>> = {};
7584
// eslint-disable-next-line @typescript-eslint/prefer-for-of
7685
for (let i = 0; i < result.length; i++) {
7786
const occurrenceIndex = result[i];
@@ -85,6 +94,26 @@ function createFastSearch<T>(dataSets: Array<SearchableData<T>>) {
8594
if (!item) {
8695
throw new Error(`[FastSearch] The item with index ${itemIndexInDataSet} in dataset ${dataSetIndex} is not defined`);
8796
}
97+
98+
// Check for uniqueness eventually
99+
const getUniqueId = dataSets[dataSetIndex].uniqueId;
100+
if (getUniqueId) {
101+
const uniqueId = getUniqueId(item);
102+
if (!uniqueId) {
103+
// eslint-disable-next-line no-continue
104+
continue;
105+
}
106+
const hasId = uniqueMap[dataSetIndex]?.[uniqueId];
107+
if (hasId) {
108+
// eslint-disable-next-line no-continue
109+
continue;
110+
}
111+
if (!uniqueMap[dataSetIndex]) {
112+
uniqueMap[dataSetIndex] = {};
113+
}
114+
uniqueMap[dataSetIndex][uniqueId] = item;
115+
}
116+
88117
resultsByDataSet[dataSetIndex].add(item);
89118
}
90119

tests/unit/FastSearchTest.ts

+51
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,55 @@ describe('FastSearch', () => {
115115
expect(search('test.user')).toEqual([['test.user@example.com']]);
116116
expect(search('examplecom')).toEqual([['test.user@example.com']]);
117117
});
118+
119+
it('should filter duplicate IDs', () => {
120+
const {search} = FastSearch.createFastSearch([
121+
{
122+
data: [
123+
{
124+
text: 'qa.guide@team.expensify.com',
125+
alternateText: 'qa.guide@team.expensify.com',
126+
keyForList: '14365522',
127+
isSelected: false,
128+
isDisabled: false,
129+
accountID: 14365522,
130+
login: 'qa.guide@team.expensify.com',
131+
icons: [
132+
{
133+
source: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/default-avatar_11.png',
134+
type: 'avatar',
135+
name: 'qa.guide@team.expensify.com',
136+
id: 14365522,
137+
},
138+
],
139+
reportID: '',
140+
},
141+
{
142+
text: 'qa.guide@team.expensify.com',
143+
alternateText: 'qa.guide@team.expensify.com',
144+
keyForList: '714749267',
145+
isSelected: false,
146+
isDisabled: false,
147+
accountID: 714749267,
148+
login: 'qa.guide@team.expensify.com',
149+
icons: [
150+
{
151+
source: 'ƒ SvgFallbackAvatar(props)',
152+
type: 'avatar',
153+
name: 'qa.guide@team.expensify.com',
154+
id: 714749267,
155+
},
156+
],
157+
reportID: '',
158+
},
159+
],
160+
toSearchableString: (data) => data.text,
161+
uniqueId: (data) => data.login,
162+
},
163+
]);
164+
165+
const [result] = search('qa.g');
166+
// The both items are represented using the same string.
167+
expect(result).toHaveLength(1);
168+
});
118169
});

0 commit comments

Comments
 (0)