From 8a142c43ae6a68c14952550983a3182f56d74c99 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 12 Dec 2024 10:06:32 +0300 Subject: [PATCH] perf: entity group items filter utility refactor (#38108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This pull request introduces a new utility function `filterEntityGroupsBySearchTerm` to replace the existing `fuzzySearchInObjectItems` function. Type inference is significantly improved. Additionally, some renaming was done to convey true meaning and improve readability. Fixes [#37769](https://github.com/appsmithorg/appsmith/issues/37769) ## Automation /ok-to-test tags="@tag.IDE" ### :mag: Cypress test results > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: > Commit: 153de75f01eba1ce8627f1cf955039023a5aaac4 > Cypress dashboard. > Tags: `@tag.IDE` > Spec: >
Wed, 11 Dec 2024 13:55:20 UTC ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No ## Summary by CodeRabbit ## Release Notes - **New Features** - Introduced a new filtering functionality for entity groups based on search terms. - Added a test suite to validate the new filtering function across various scenarios. - **Bug Fixes** - Updated components to use the new filtering method, improving search result accuracy. - **Chores** - Removed obsolete fuzzy search functionality and associated tests to streamline the codebase. - **Refactor** - Renamed variables for clarity and consistency throughout the application. --- .../filterEntityGroupsBySearchTerm.test.ts | 31 +++++++++++ .../utils/filterEntityGroupsBySearchTerm.ts | 43 ++++++++++++++++ app/client/src/IDE/utils/index.ts | 1 + .../pages/Editor/IDE/EditorPane/JS/Add.tsx | 16 +++--- .../pages/Editor/IDE/EditorPane/JS/List.tsx | 17 +++---- .../pages/Editor/IDE/EditorPane/Query/Add.tsx | 15 +++--- .../Editor/IDE/EditorPane/Query/List.tsx | 17 +++---- .../IDE/EditorPane/fuzzySearchInFiles.test.ts | 51 ------------------- .../src/pages/Editor/IDE/EditorPane/utils.ts | 31 ----------- 9 files changed, 108 insertions(+), 114 deletions(-) create mode 100644 app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.test.ts create mode 100644 app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.ts create mode 100644 app/client/src/IDE/utils/index.ts delete mode 100644 app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts diff --git a/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.test.ts b/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.test.ts new file mode 100644 index 000000000000..645e4f4f8595 --- /dev/null +++ b/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.test.ts @@ -0,0 +1,31 @@ +import { filterEntityGroupsBySearchTerm } from "."; + +const groups = [ + { + name: "Group 1", + items: [{ title: "file1" }, { title: "file2" }], + }, + { + title: "Group 2", + items: [{ title: "file3" }, { title: "file4" }], + }, +]; + +describe("filterEntityGroupsBySearchTerm", () => { + test.each([ + ["", groups], + [ + "file1", + [ + { + name: "Group 1", + items: [{ title: "file1" }], + }, + ], + ], + ["notfound", []], + ["file", groups], + ])("%s -> %j", (searchTerm, output) => { + expect(filterEntityGroupsBySearchTerm(searchTerm, groups)).toEqual(output); + }); +}); diff --git a/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.ts b/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.ts new file mode 100644 index 000000000000..fcdbc425c0cb --- /dev/null +++ b/app/client/src/IDE/utils/filterEntityGroupsBySearchTerm.ts @@ -0,0 +1,43 @@ +import Fuse, { type FuseOptions } from "fuse.js"; + +/** Searchable properties. Must be defined in this way to be able to derive union type and satisfy FuseOptions */ +const keys: ["title"] = ["title"]; + +/** Union type to make sure these particular keys are present in collection that's being passed in for search. */ +type Keys = (typeof keys)[number]; + +type BaseGroup = Record; +type BaseItem = Record; +type Group = G & { + items: T[]; +}; + +const FUSE_OPTIONS: FuseOptions = { + shouldSort: true, + threshold: 0.1, + keys, +}; + +/** Filter entity groups by search term using fuse.js */ +export const filterEntityGroupsBySearchTerm = < + G extends BaseGroup, + T extends BaseItem, +>( + searchTerm: string, + groups: Array>, +): Array> => { + if (!searchTerm) { + return groups; + } + + return groups.reduce((result: Array>, group) => { + const { items, ...rest } = group; + const searchResults = new Fuse(items, FUSE_OPTIONS).search(searchTerm); + + if (searchResults.length) { + result.push({ ...rest, items: searchResults } as Group); + } + + return result; + }, []); +}; diff --git a/app/client/src/IDE/utils/index.ts b/app/client/src/IDE/utils/index.ts new file mode 100644 index 000000000000..987455e2e73e --- /dev/null +++ b/app/client/src/IDE/utils/index.ts @@ -0,0 +1 @@ +export { filterEntityGroupsBySearchTerm } from "./filterEntityGroupsBySearchTerm"; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx index 21ce6a223ffc..077100ed5647 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/Add.tsx @@ -11,13 +11,13 @@ import { useJSAdd, } from "ee/pages/Editor/IDE/EditorPane/JS/hooks"; import type { ActionOperation } from "components/editorComponents/GlobalSearch/utils"; -import { createAddClassName, fuzzySearchInObjectItems } from "../utils"; +import { createAddClassName } from "../utils"; import { FocusEntity } from "navigation/FocusEntity"; -import type { GroupedListProps } from "../components/types"; import { EmptySearchResult } from "../components/EmptySearchResult"; import { getIDEViewMode } from "selectors/ideSelectors"; import type { FlexProps } from "@appsmith/ads"; import { EditorViewMode } from "ee/entities/IDE/constants"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; const AddJS = () => { const dispatch = useDispatch(); @@ -53,7 +53,7 @@ const AddJS = () => { } as ListItemProps; }; - const groups = groupedJsOperations.map( + const itemGroups = groupedJsOperations.map( ({ className, operations, title }) => ({ groupTitle: title, className: className, @@ -61,9 +61,9 @@ const AddJS = () => { }), ); - const localGroups = fuzzySearchInObjectItems( + const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, - groups, + itemGroups, ); const extraPadding: FlexProps = @@ -94,8 +94,10 @@ const AddJS = () => { titleMessage={EDITOR_PANE_TEXTS.js_create_tab_title} /> - {localGroups.length > 0 ? : null} - {localGroups.length === 0 && searchTerm !== "" ? ( + {filteredItemGroups.length > 0 ? ( + + ) : null} + {filteredItemGroups.length === 0 && searchTerm !== "" ? ( diff --git a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx index 77fbd42ad966..bdfd8a15c965 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/JS/List.tsx @@ -3,7 +3,6 @@ import { useSelector } from "react-redux"; import { Flex, Text } from "@appsmith/ads"; import styled from "styled-components"; -import type { EditorSegmentList } from "ee/selectors/appIDESelectors"; import { selectJSSegmentEditorList } from "ee/selectors/appIDESelectors"; import { useActiveActionBaseId } from "ee/pages/Editor/Explorer/hooks"; import { @@ -20,9 +19,9 @@ import { useJSAdd } from "ee/pages/Editor/IDE/EditorPane/JS/hooks"; import { JSListItem } from "ee/pages/Editor/IDE/EditorPane/JS/ListItem"; import { BlankState } from "./BlankState"; import { AddAndSearchbar } from "../components/AddAndSearchbar"; -import { fuzzySearchInObjectItems } from "../utils"; import { EmptySearchResult } from "../components/EmptySearchResult"; import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; const JSContainer = styled(Flex)` & .t--entity-item { @@ -34,7 +33,7 @@ const JSContainer = styled(Flex)` const ListJSObjects = () => { const [searchTerm, setSearchTerm] = useState(""); const pageId = useSelector(getCurrentPageId); - const files = useSelector(selectJSSegmentEditorList); + const itemGroups = useSelector(selectJSSegmentEditorList); const activeActionBaseId = useActiveActionBaseId(); const applicationId = useSelector(getCurrentApplicationId); @@ -42,9 +41,9 @@ const ListJSObjects = () => { const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); - const localFiles = fuzzySearchInObjectItems( + const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, - files, + itemGroups, ); const canCreateActions = getHasCreateActionPermission( @@ -64,7 +63,7 @@ const ListJSObjects = () => { px="spaces-3" py="spaces-3" > - {files && files.length > 0 ? ( + {itemGroups && itemGroups.length > 0 ? ( { gap="spaces-4" overflowY="auto" > - {localFiles.map(({ group, items }) => { + {filteredItemGroups.map(({ group, items }) => { return ( {group !== "NA" ? ( @@ -112,7 +111,7 @@ const ListJSObjects = () => { ); })} - {localFiles.length === 0 && searchTerm !== "" ? ( + {filteredItemGroups.length === 0 && searchTerm !== "" ? ( @@ -120,7 +119,7 @@ const ListJSObjects = () => { - {(!files || files.length === 0) && } + {(!itemGroups || itemGroups.length === 0) && } ); }; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx index 60d2051c526f..b28548f12676 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/Add.tsx @@ -9,13 +9,12 @@ import { useGroupedAddQueryOperations, useQueryAdd, } from "ee/pages/Editor/IDE/EditorPane/Query/hooks"; -import { fuzzySearchInObjectItems } from "../utils"; -import type { GroupedListProps } from "../components/types"; import { EmptySearchResult } from "../components/EmptySearchResult"; import { useSelector } from "react-redux"; import { getIDEViewMode } from "selectors/ideSelectors"; import type { FlexProps } from "@appsmith/ads"; import { EditorViewMode } from "ee/entities/IDE/constants"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; const AddQuery = () => { const [searchTerm, setSearchTerm] = useState(""); @@ -24,15 +23,15 @@ const AddQuery = () => { const { closeAddQuery } = useQueryAdd(); const ideViewMode = useSelector(getIDEViewMode); - const groups = groupedActionOperations.map((group) => ({ + const itemGroups = groupedActionOperations.map((group) => ({ groupTitle: group.title, className: group.className, items: getListItems(group.operations), })); - const localGroups = fuzzySearchInObjectItems( + const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, - groups, + itemGroups, ); const extraPadding: FlexProps = @@ -63,8 +62,10 @@ const AddQuery = () => { titleMessage={EDITOR_PANE_TEXTS.query_create_tab_title} /> - {localGroups.length > 0 ? : null} - {localGroups.length === 0 && searchTerm !== "" ? ( + {filteredItemGroups.length > 0 ? ( + + ) : null} + {filteredItemGroups.length === 0 && searchTerm !== "" ? ( diff --git a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx index 00a0c6faba97..0241c779e844 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx +++ b/app/client/src/pages/Editor/IDE/EditorPane/Query/List.tsx @@ -11,7 +11,6 @@ import { } from "selectors/editorSelectors"; import { useFeatureFlag } from "utils/hooks/useFeatureFlag"; import { FEATURE_FLAG } from "ee/entities/FeatureFlag"; -import type { EditorSegmentList } from "ee/selectors/appIDESelectors"; import { selectQuerySegmentEditorList } from "ee/selectors/appIDESelectors"; import { ActionParentEntityType } from "ee/entities/Engine/actionHelpers"; import { FilesContextProvider } from "pages/Editor/Explorer/Files/FilesContextProvider"; @@ -20,21 +19,21 @@ import { QueryListItem } from "ee/pages/Editor/IDE/EditorPane/Query/ListItem"; import { getShowWorkflowFeature } from "ee/selectors/workflowSelectors"; import { BlankState } from "./BlankState"; import { AddAndSearchbar } from "../components/AddAndSearchbar"; -import { fuzzySearchInObjectItems } from "../utils"; import { EmptySearchResult } from "../components/EmptySearchResult"; import { EDITOR_PANE_TEXTS, createMessage } from "ee/constants/messages"; +import { filterEntityGroupsBySearchTerm } from "IDE/utils"; const ListQuery = () => { const [searchTerm, setSearchTerm] = useState(""); const pageId = useSelector(getCurrentPageId) as string; - const files = useSelector(selectQuerySegmentEditorList); + const itemGroups = useSelector(selectQuerySegmentEditorList); const activeActionBaseId = useActiveActionBaseId(); const pagePermissions = useSelector(getPagePermissions); const isFeatureEnabled = useFeatureFlag(FEATURE_FLAG.license_gac_enabled); - const localFiles = fuzzySearchInObjectItems( + const filteredItemGroups = filterEntityGroupsBySearchTerm( searchTerm, - files, + itemGroups, ); const canCreateActions = getHasCreateActionPermission( @@ -55,7 +54,7 @@ const ListQuery = () => { px="spaces-3" py="spaces-3" > - {files.length > 0 ? ( + {itemGroups.length > 0 ? ( { /> ) : null} - {localFiles.map(({ group, items }) => { + {filteredItemGroups.map(({ group, items }) => { return ( @@ -96,14 +95,14 @@ const ListQuery = () => { ); })} - {localFiles.length === 0 && searchTerm !== "" ? ( + {filteredItemGroups.length === 0 && searchTerm !== "" ? ( ) : null} - {Object.keys(files).length === 0 && } + {Object.keys(itemGroups).length === 0 && } ); }; diff --git a/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts b/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts deleted file mode 100644 index 4454723f7b1e..000000000000 --- a/app/client/src/pages/Editor/IDE/EditorPane/fuzzySearchInFiles.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { EditorSegmentList } from "ee/selectors/appIDESelectors"; -import { fuzzySearchInObjectItems } from "./utils"; -import { PluginType } from "entities/Action"; - -const sampleFiles: EditorSegmentList = [ - { - group: "Group 1", - items: [ - { title: "file1.js", type: PluginType.API, key: "file1" }, - { title: "file2.js", type: PluginType.API, key: "file2" }, - ], - }, - { - group: "Group 2", - items: [ - { title: "file3.js", type: PluginType.API, key: "file3" }, - { title: "file4.js", type: PluginType.API, key: "file4" }, - ], - }, -]; - -describe("fuzzySearchInObjectItems", () => { - it("should return all files when the search string is empty", () => { - const result = fuzzySearchInObjectItems("", sampleFiles); - - expect(result).toEqual(sampleFiles); - }); - - it("should return the correct file when the search string exactly matches a file title", () => { - const result = fuzzySearchInObjectItems("file1", sampleFiles); - - expect(result).toEqual([ - { - group: "Group 1", - items: [{ title: "file1.js", type: PluginType.API, key: "file1" }], - }, - ]); - }); - - it("should return an empty array when no files match the search string", () => { - const result = fuzzySearchInObjectItems("nonexistentfile", sampleFiles); - - expect(result).toEqual([]); - }); - - it("should return all files containing the common substring in their titles", () => { - const result = fuzzySearchInObjectItems("file", sampleFiles); - - expect(result).toEqual(sampleFiles); - }); -}); diff --git a/app/client/src/pages/Editor/IDE/EditorPane/utils.ts b/app/client/src/pages/Editor/IDE/EditorPane/utils.ts index cb7a5b0646ad..cb43762c7af7 100644 --- a/app/client/src/pages/Editor/IDE/EditorPane/utils.ts +++ b/app/client/src/pages/Editor/IDE/EditorPane/utils.ts @@ -1,34 +1,3 @@ -import Fuse from "fuse.js"; - export const createAddClassName = (name: string) => { return "t--datasoucre-create-option-" + name.toLowerCase().replace(/ /g, "_"); }; - -const FUSE_OPTIONS = { - shouldSort: true, - threshold: 0.1, - keys: ["title"], -}; - -// TODO: Fix this the next time the file is edited -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const fuzzySearchInObjectItems = ( - searchStr: string, - files: T, -): T => { - if (searchStr && searchStr !== "") { - const newFiles = files - .map((group) => { - const items = group["items"]; - const fuse = new Fuse(items, FUSE_OPTIONS); - const resultItems = fuse.search(searchStr); - - return { ...group, items: resultItems }; - }) - .filter((group) => group.items.length > 0); - - return newFiles as T; - } - - return files; -};