Skip to content

Commit

Permalink
Add fuzzy filter/search logic for name column.
Browse files Browse the repository at this point in the history
  • Loading branch information
cvanem committed Nov 21, 2024
1 parent 6ca9d64 commit ecf4f78
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 13 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "app-map-db",
"version": "2.1.31",
"version": "2.1.32",
"private": true,
"homepage": "https://mindapps.org",
"enableLogRocket": true,
Expand Down Expand Up @@ -66,7 +66,8 @@
"redux-persist": "6.0.0",
"redux-thunk": "2.4.1",
"styled-components": "5.3.3",
"typescript": "4.5.5"
"typescript": "4.5.5",
"fuzzysort": "3.1.0"
},
"devDependencies": {
"google-play-scraper": "9.1.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import logo from '../../../../images/default_app_icon.png';
import { useSelector } from 'react-redux';
import { useAdminMode } from '../../../layout/store';
import { getDescription } from '../ApplicationsGrid/ExpandableDescription';
import { fuzzySortFilter } from '../../../pages/useAppTableData';

export const isMatch = (filters, value) => filters.reduce((t, c) => (t = t && value?.includes(c)), true);

Expand Down Expand Up @@ -154,7 +155,7 @@ export const useAppData = table => {
isMatch(ClinicalFoundations, r.clinicalFoundations) &&
isMatch(DeveloperTypes, r.developerTypes);

return useTableFilter(filteredData, table, customFilter);
return useTableFilter(filteredData, table, customFilter, fuzzySortFilter);
};

export const useNewerMemberCount = (groupId, created) => {
Expand Down Expand Up @@ -262,5 +263,5 @@ export const usePendingAppData = (table, showDeleted = false, email = undefined,
isMatch(ClinicalFoundations, r.clinicalFoundations) &&
isMatch(DeveloperTypes, r.developerTypes);

return useTableFilter(filteredData, table, customFilter);
return useTableFilter(filteredData, table, customFilter, fuzzySortFilter);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AppleStoreProps } from '../../DialogField/AppleStore';
import logo from '../../../../images/default_app_icon.png';
import { useSelector } from 'react-redux';
import { useAdminMode } from '../../../layout/store';
import { fuzzySortFilter } from '../../../pages/useAppTableData';

const isMatch = (filters, value) => filters.reduce((t, c) => (t = t && value?.includes(c)), true);

Expand Down Expand Up @@ -140,7 +141,7 @@ export const useAppData = table => {
isMatch(ClinicalFoundations, r.clinicalFoundations) &&
isMatch(DeveloperTypes, r.developerTypes);

return useTableFilter(filteredData, table, customFilter);
return useTableFilter(filteredData, table, customFilter, fuzzySortFilter);
};

export const useNewerMemberCount = (groupId, created) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { AppleStoreProps } from '../../DialogField/AppleStore';
import logo from '../../../../images/default_app_icon.png';
import { useSelector } from 'react-redux';
import { useAdminMode } from '../../../layout/store';
import { fuzzySortFilter } from '../../../pages/useAppTableData';

const isMatch = (filters, value) => filters.reduce((t, c) => (t = t && value?.includes(c)), true);

Expand Down Expand Up @@ -140,7 +141,7 @@ export const useAppData = table => {
isMatch(ClinicalFoundations, r.clinicalFoundations) &&
isMatch(DeveloperTypes, r.developerTypes);

return useTableFilter(filteredData, table, customFilter);
return useTableFilter(filteredData, table, customFilter, fuzzySortFilter);
};

export const useNewerMemberCount = (groupId, created) => {
Expand Down
18 changes: 12 additions & 6 deletions src/components/application/GenericTable/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const isMatch = (obj, re) => {
return obj.getSearchValues().match(re);
} else {
for (var attrname in obj) {
if (['_id', 'id', 'key', 'getValues', 'edit', 'action'].includes(attrname.toLowerCase())) continue;
if (['_id', 'id', 'key', 'getValues', 'getSearchValues', 'edit', 'action'].includes(attrname.toLowerCase())) continue;
if (obj[attrname])
if (isNaN(obj[attrname])) {
//only search non numeric values
Expand Down Expand Up @@ -74,7 +74,7 @@ export const tableFilter = (data: any, state: AppState, props: GenericTableConta
return table && table.orderBy ? stableSort(filtered, getSorting(table.orderDirection, table.orderBy, table.sortComparator)) : filtered;
};

export const useTableFilter = (data: any, name: string, customFilter = undefined) => {
export const useTableFilter = (data: any, name: string, customFilter = undefined, fuzzyFilter = undefined) => {
//Extract the table information from the redux store
const table = TableStore.useTable(name);
const searchtext = table?.searchtext;
Expand All @@ -86,24 +86,30 @@ export const useTableFilter = (data: any, name: string, customFilter = undefined
column: table && table.columnfiltercolumn
};

const filtered = table_filter(data, columnfilter, searchtext, customFilter);
const filtered = table_filter(data, columnfilter, searchtext, customFilter, fuzzyFilter);
return table && table.orderBy ? stableSort(filtered, getSorting(table.orderDirection, table.orderBy, table.sortComparator)) : filtered;
};

export const table_filter = (data, columnfilter: ColumnFilter, searchtext = '', customFilter = undefined) => {
export const table_filter = (data, columnfilter: ColumnFilter, searchtext = '', customFilter = undefined, fuzzyFilter = undefined) => {
var performcolumnfilter = false;
if (columnfilter) if (columnfilter.column) if (columnfilter.value !== 'All' && columnfilter.value !== '') performcolumnfilter = true;

const performtextfilter = searchtext && searchtext !== '' ? true : false;

const re = escapeRegex(searchtext);

return data.filter(
const filtered = data.filter(
x =>
(performcolumnfilter ? isColumnExactMatch(x, columnfilter) : true) &&
(performtextfilter ? isMatch(x, re) : true) &&
(customFilter ? customFilter(x) : true)
(customFilter ? customFilter(x, searchtext) : true)
);

if (fuzzyFilter) {
return fuzzyFilter(data, filtered, searchtext, customFilter);
} else {
return filtered;
}
};

export function lowerDesc(a, b, orderBy) {
Expand Down
25 changes: 24 additions & 1 deletion src/components/pages/useAppTableData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,33 @@ import { getAppCompany, getAppName } from '../application/GenericTable/Applicati
import { useTableFilter } from '../application/GenericTable/helpers';
import { useAdminMode } from '../layout/store';
import { getDescription } from '../application/GenericTable/ApplicationsGrid/ExpandableDescription';
import fuzzysort from 'fuzzysort';

const table = 'Applications';
const isMatch = (filters, value) => filters.reduce((t, c) => (t = t && value?.includes(c)), true);

export const fuzzySortFilter = (data, filtered, searchtext, customFilter) => {
if (filtered?.length < 10) {
// Only perform fuzzy filtering if there are < 10 exact match results
const fuzzyResults = fuzzysort.go(searchtext, data, { key: 'name', limit: 20 }) as any;
console.log({ fuzzyResults, searchtext });
var combined = [...filtered];
fuzzyResults?.forEach(fr => {
if (!combined.find(r => r._id === fr?.obj?._id)) {
// If no custom filter or custom filters (platform tags) match, then add fuzzy results
// This prevents non matching platform tags from showing in the results
if (!customFilter || customFilter(fr.obj, searchtext)) {
console.log('Adding fuzzy search result', fr);
combined = combined.concat(fr.obj);
}
}
});
return combined;
} else {
return filtered;
}
};

export default function useAppTableData({ trigger = true, triggerWhenEmpty = false } = {}) {
const [apps, setApps] = useApplications();
const [loading, setLoading] = React.useState(false);
Expand Down Expand Up @@ -208,7 +231,7 @@ export default function useAppTableData({ trigger = true, triggerWhenEmpty = fal
isMatch(ClinicalFoundations, r.clinicalFoundations) &&
isMatch(DeveloperTypes, r.developerTypes);

var filtered = useTableFilter(filteredData, table, customFilter);
var filtered = useTableFilter(filteredData, table, customFilter, fuzzySortFilter);

return { filtered, loading, apps, setApps, handleRefresh, handleGetRow };
}

0 comments on commit ecf4f78

Please sign in to comment.