@gooddata/sdk-ui-semantic-search 11.26.0-alpha.2 → 11.26.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,6 +9,12 @@ To learn more, check [the source monorepo](https://github.com/gooddata/gooddata-
9
9
 
10
10
  This package provides a set of React-based UI components and hooks for semantic search within your metadata.
11
11
 
12
+ ## Components
13
+
14
+ ### SemanticSearch
15
+
16
+ The `SemanticSearch` component provides a search input with semantic search capabilities.
17
+
12
18
  Example usage:
13
19
 
14
20
  ```tsx
@@ -28,6 +34,106 @@ const App = () => {
28
34
 
29
35
  You can also use providers for backend and workspace.
30
36
 
37
+ ## Hooks
38
+
39
+ ### useHybridSearch
40
+
41
+ The `useHybridSearch` hook is a powerful tool that combines traditional keyword-based search with AI-powered semantic search. It handles debouncing of the search query and manages the state of both search types.
42
+
43
+ #### Basic Usage
44
+
45
+ ```tsx
46
+ import { useHybridSearch, SearchItem, SearchItemGroup } from "@gooddata/sdk-ui-semantic-search";
47
+
48
+ const MySearchComponent = ({ items }: { items: SearchItem[] }) => {
49
+ const { searchState, semanticSearchState, search, onSearchQueryChange } = useHybridSearch({
50
+ itemBuilder: (item, { ref, type }) => {
51
+ // Transform semantic search result to your SearchItem format
52
+ return items.find((i) => areObjRefsEqual(i.ref, ref));
53
+ },
54
+ debounceMs: 200,
55
+ });
56
+
57
+ // Perform the combined search
58
+ const results = search({ items });
59
+
60
+ return (
61
+ <div>
62
+ <input
63
+ type="text"
64
+ value={searchState.query}
65
+ onChange={(e) => onSearchQueryChange(e.target.value)}
66
+ placeholder="Search..."
67
+ />
68
+
69
+ {searchState.state === "searching" && <div>Searching...</div>}
70
+
71
+ <h3>Results</h3>
72
+ <ul>
73
+ {results.searchItems.map((item) => (
74
+ <li key={objRefToString(item.ref)}>{item.title}</li>
75
+ ))}
76
+ </ul>
77
+
78
+ {semanticSearchState.state === "loading" && <div>Loading semantic results...</div>}
79
+
80
+ {results.searchRelatedItems.length > 0 && (
81
+ <>
82
+ <h3>Related (Semantic) Results</h3>
83
+ <ul>
84
+ {results.searchRelatedItems.map((item) => (
85
+ <li key={objRefToString(item.ref)}>{item.title}</li>
86
+ ))}
87
+ </ul>
88
+ </>
89
+ )}
90
+ </div>
91
+ );
92
+ };
93
+ ```
94
+
95
+ #### Configuration Options
96
+
97
+ The `useHybridSearch` hook accepts an options object (`IUseHybridSearchOptions`):
98
+
99
+ | Property | Type | Description |
100
+ | :------------------- | :------------------------ | :---------------------------------------------------------------------------------------------- |
101
+ | `itemBuilder` | `HybridSearchItemBuilder` | **Required.** A function to transform semantic search results into your local item format. |
102
+ | `backend` | `IAnalyticalBackend` | Optional. Backend to use. If not provided, it's retrieved from `BackendContext`. |
103
+ | `workspace` | `string` | Optional. Workspace ID to use. If not provided, it's retrieved from `WorkspaceContext`. |
104
+ | `debounceMs` | `number` | Optional. Debounce time for the search query in milliseconds (default: 150ms). |
105
+ | `allowSematicSearch` | `boolean` | Optional. Enable or disable AI-powered semantic search (default: true). |
106
+ | `limit` | `number` | Optional. Maximum number of semantic search results to return. |
107
+ | `deepSearch` | `boolean` | Optional. Whether to perform a deep semantic search. |
108
+ | `objectTypes` | `ObjectType[]` | Optional. Restrict semantic search to specific object types (e.g., `'insight'`, `'dashboard'`). |
109
+ | `includeTags` | `string[]` | Optional. Tags that must be present on the searched objects. |
110
+ | `excludeTags` | `string[]` | Optional. Tags that must not be present on the searched objects. |
111
+ | `matcher` | `HybridSearchMatcher` | Optional. A custom matching function for local keyword search. |
112
+
113
+ #### Hook Return Value
114
+
115
+ The hook returns an object (`IHybridSearchResult`) with the following properties:
116
+
117
+ | Property | Type | Description |
118
+ | :-------------------- | :--------------------- | :-------------------------------------------------------------------------- |
119
+ | `searchState` | `ISearchState` | Current state of the keyword search (`query`, `debouncedQuery`, `state`). |
120
+ | `semanticSearchState` | `ISemanticSearchState` | Current state of the semantic search (`state`, `message`, `error`). |
121
+ | `search` | `Function` | A function that takes your local items and returns combined search results. |
122
+ | `onSearchQueryChange` | `Function` | Callback to update the search query. |
123
+
124
+ The `search` function returns `ICombinedSearchResults`, which extends the standard `SearchResults` with `searchRelatedItems` (items found via semantic search but transformed by your `itemBuilder`).
125
+
126
+ #### Search Function Parameters
127
+
128
+ The `search` function accepts the following properties:
129
+
130
+ | Property | Type | Description |
131
+ | :----------- | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
132
+ | `items` | `ReadonlyArray<I>` | **Required.** The current list of items to search through (e.g., currently filtered or paged items). |
133
+ | `allItems` | `ReadonlyArray<I>` | Optional. The full list of all available items. If provided, it's used to calculate `searchAllItems` in the results. Defaults to `items` if not provided. |
134
+ | `itemGroups` | `ReadonlyArray<G>` | Optional. Groups of items to search through. Used to calculate `searchItemGroups` in the results. |
135
+ | `keywords` | `string[]` | Optional. A list of keywords to match against the search query. Used to calculate `searchKeywords` in the results. |
136
+
31
137
  ## License
32
138
 
33
139
  (C) 2017-2022 GoodData Corporation
@@ -0,0 +1,12 @@
1
+ import type { HybridSearchMatcher, SearchItem, SearchItemGroup } from "./types.js";
2
+ /**
3
+ * @alpha
4
+ * Default matcher for the search. Supports title, name, description, summary.
5
+ */
6
+ export declare function defaultMatcher<I extends SearchItem, G extends SearchItemGroup<I>>(item: I | G | string, searchQueryUpper: string): boolean;
7
+ /**
8
+ * @alpha
9
+ * Custom matcher for the search. Can be used to match any property of the search item.
10
+ */
11
+ export declare function customMatcher<I extends SearchItem, G extends SearchItemGroup<I>>(props: (keyof I & keyof G)[]): HybridSearchMatcher;
12
+ //# sourceMappingURL=matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../../src/hooks/search/matchers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEnF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,EAC7E,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,EACpB,gBAAgB,EAAE,MAAM,GACzB,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,EAC5E,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,GAC7B,mBAAmB,CAcrB"}
@@ -0,0 +1,30 @@
1
+ // (C) 2024-2026 GoodData Corporation
2
+ /**
3
+ * @alpha
4
+ * Default matcher for the search. Supports title, name, description, summary.
5
+ */
6
+ export function defaultMatcher(item, searchQueryUpper) {
7
+ if (typeof item === "string") {
8
+ return isUpperCaseMatch(item, searchQueryUpper);
9
+ }
10
+ const searchIn = [item.title, item.name, item.description, item.summary].filter(Boolean);
11
+ return searchIn.some((name) => isUpperCaseMatch(name, searchQueryUpper));
12
+ }
13
+ /**
14
+ * @alpha
15
+ * Custom matcher for the search. Can be used to match any property of the search item.
16
+ */
17
+ export function customMatcher(props) {
18
+ return (item, searchQueryUpper) => {
19
+ if (typeof item === "string") {
20
+ return isUpperCaseMatch(item, searchQueryUpper);
21
+ }
22
+ const values = props.map((prop) => item[prop]);
23
+ const searchIn = values.filter(Boolean);
24
+ return searchIn.some((name) => isUpperCaseMatch(name, searchQueryUpper));
25
+ };
26
+ }
27
+ function isUpperCaseMatch(keyword, searchQueryUpper) {
28
+ return keyword.toUpperCase().includes(searchQueryUpper);
29
+ }
30
+ //# sourceMappingURL=matchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../../src/hooks/search/matchers.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAIrC;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC1B,IAAoB,EACpB,gBAAwB,EACjB;IACP,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC;IAErG,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAAA,CAC5E;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CACzB,KAA4B,EACT;IACnB,OAAO,CACH,IAAoB,EACpB,gBAAwB,EACjB,EAAE,CAAC;QACV,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAE,IAAgC,CAAC,IAAc,CAAC,CAAa,CAAC;QAClG,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAExC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAAA,CAC5E,CAAC;AAAA,CACL;AAED,SAAS,gBAAgB,CAAC,OAAe,EAAE,gBAAwB,EAAE;IACjE,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AAAA,CAC3D"}
@@ -0,0 +1,4 @@
1
+ import { type ISemanticSearchResultItem } from "@gooddata/sdk-model";
2
+ import { type HybridSearchItemBuilder, type SearchItem } from "./types.js";
3
+ export declare function doFilterRelatedItems<I extends SearchItem>(items: ReadonlyArray<I>, searchResults: ISemanticSearchResultItem[], itemBuilder: HybridSearchItemBuilder<I>): ReadonlyArray<I>;
4
+ //# sourceMappingURL=related.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related.d.ts","sourceRoot":"","sources":["../../../src/hooks/search/related.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,yBAAyB,EAKjC,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,uBAAuB,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3E,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,UAAU,EACrD,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,aAAa,EAAE,yBAAyB,EAAE,EAC1C,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACxC,aAAa,CAAC,CAAC,CAAC,CAelB"}
@@ -0,0 +1,47 @@
1
+ // (C) 2024-2026 GoodData Corporation
2
+ import { areObjRefsEqual, assertNever, } from "@gooddata/sdk-model";
3
+ export function doFilterRelatedItems(items, searchResults, itemBuilder) {
4
+ const rest = searchResults.filter((item) => {
5
+ const objRef = createIdentifierRef(item);
6
+ return !items.some((searchItem) => areObjRefsEqual(searchItem.ref, objRef));
7
+ });
8
+ return rest
9
+ .map((item) => {
10
+ const objRef = createIdentifierRef(item);
11
+ return itemBuilder(item, {
12
+ ref: objRef,
13
+ type: convertGenAiTypeToObjectType(item.type),
14
+ });
15
+ })
16
+ .filter(Boolean);
17
+ }
18
+ function createIdentifierRef(item) {
19
+ return {
20
+ type: convertGenAiTypeToObjectType(item.type),
21
+ identifier: item.id,
22
+ };
23
+ }
24
+ function convertGenAiTypeToObjectType(type) {
25
+ switch (type) {
26
+ case "attribute":
27
+ return "attribute";
28
+ case "fact":
29
+ return "fact";
30
+ case "dashboard":
31
+ return "analyticalDashboard";
32
+ case "dataset":
33
+ return "dataSet";
34
+ case "date":
35
+ return "dataSet";
36
+ case "label":
37
+ return "displayForm";
38
+ case "metric":
39
+ return "measure";
40
+ case "visualization":
41
+ return "insight";
42
+ default:
43
+ assertNever(type);
44
+ throw new Error(`Unknown type: ${type}`);
45
+ }
46
+ }
47
+ //# sourceMappingURL=related.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"related.js","sourceRoot":"","sources":["../../../src/hooks/search/related.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,OAAO,EAKH,eAAe,EACf,WAAW,GACd,MAAM,qBAAqB,CAAC;AAI7B,MAAM,UAAU,oBAAoB,CAChC,KAAuB,EACvB,aAA0C,EAC1C,WAAuC,EACvB;IAChB,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAAA,CAC/E,CAAC,CAAC;IAEH,OAAO,IAAI;SACN,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,IAAI,EAAE;YACrB,GAAG,EAAE,MAAM;YACX,IAAI,EAAE,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC;SAChD,CAAC,CAAC;IAAA,CACN,CAAC;SACD,MAAM,CAAC,OAAO,CAAQ,CAAC;AAAA,CAC/B;AAED,SAAS,mBAAmB,CAAC,IAA+B,EAAiB;IACzE,OAAO;QACH,IAAI,EAAE,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC;QAC7C,UAAU,EAAE,IAAI,CAAC,EAAE;KACtB,CAAC;AAAA,CACL;AAED,SAAS,4BAA4B,CAAC,IAAqB,EAAc;IACrE,QAAQ,IAAI,EAAE,CAAC;QACX,KAAK,WAAW;YACZ,OAAO,WAAW,CAAC;QACvB,KAAK,MAAM;YACP,OAAO,MAAM,CAAC;QAClB,KAAK,WAAW;YACZ,OAAO,qBAAqB,CAAC;QACjC,KAAK,SAAS;YACV,OAAO,SAAS,CAAC;QACrB,KAAK,MAAM;YACP,OAAO,SAAS,CAAC;QACrB,KAAK,OAAO;YACR,OAAO,aAAa,CAAC;QACzB,KAAK,QAAQ;YACT,OAAO,SAAS,CAAC;QACrB,KAAK,eAAe;YAChB,OAAO,SAAS,CAAC;QACrB;YACI,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IACjD,CAAC;AAAA,CACJ"}
@@ -0,0 +1,4 @@
1
+ import { defaultMatcher } from "./matchers.js";
2
+ import { type SearchItem, type SearchItemGroup, type SearchResults } from "./types.js";
3
+ export declare function doSearch<I extends SearchItem, G extends SearchItemGroup<I>>(items: ReadonlyArray<I>, allItems: ReadonlyArray<I>, itemGroups: ReadonlyArray<G>, keywords: string[], searchQuery: string, matcher?: typeof defaultMatcher): SearchResults<I, G>;
4
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/hooks/search/search.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAEH,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,aAAa,EACrB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,QAAQ,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,EACvE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAC1B,UAAU,EAAE,aAAa,CAAC,CAAC,CAAC,EAC5B,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,EAAE,MAAM,EACnB,OAAO,wBAAiB,GACzB,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAYrB"}
@@ -0,0 +1,36 @@
1
+ // (C) 2024-2026 GoodData Corporation
2
+ import { defaultMatcher } from "./matchers.js";
3
+ export function doSearch(items, allItems, itemGroups, keywords, searchQuery, matcher = defaultMatcher) {
4
+ const searchItems = isMatchingItems(items, itemGroups, searchQuery, matcher);
5
+ const searchAllItems = isMatchingItems(allItems, itemGroups, searchQuery, matcher);
6
+ const searchItemGroups = isMatchingItemGroups(itemGroups, searchQuery, matcher);
7
+ const searchKeywords = isMatchingKeywords(keywords, searchQuery, matcher);
8
+ return {
9
+ searchItems,
10
+ searchAllItems,
11
+ searchItemGroups,
12
+ searchKeywords,
13
+ };
14
+ }
15
+ function isMatchingItems(items, itemGroups, searchQuery, matcher) {
16
+ const searchQueryUpper = upper(searchQuery);
17
+ if (searchQuery === "") {
18
+ if (itemGroups.length === 0) {
19
+ return items;
20
+ }
21
+ return [];
22
+ }
23
+ return items.filter((item) => matcher(item, searchQueryUpper));
24
+ }
25
+ function isMatchingItemGroups(itemGroups, searchQuery, matcher) {
26
+ const searchQueryUpper = upper(searchQuery);
27
+ return itemGroups.filter((itemGroup) => matcher(itemGroup, searchQueryUpper));
28
+ }
29
+ function isMatchingKeywords(keywords, searchQuery, matcher) {
30
+ const searchQueryUpper = upper(searchQuery);
31
+ return keywords.filter((keyword) => matcher(keyword, searchQueryUpper));
32
+ }
33
+ function upper(searchQuery) {
34
+ return searchQuery.toUpperCase();
35
+ }
36
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/hooks/search/search.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C,MAAM,UAAU,QAAQ,CACpB,KAAuB,EACvB,QAA0B,EAC1B,UAA4B,EAC5B,QAAkB,EAClB,WAAmB,EACnB,OAAO,GAAG,cAAc,EACL;IACnB,MAAM,WAAW,GAAG,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAC7E,MAAM,cAAc,GAAG,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IACnF,MAAM,gBAAgB,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAChF,MAAM,cAAc,GAAG,kBAAkB,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;IAE1E,OAAO;QACH,WAAW;QACX,cAAc;QACd,gBAAgB;QAChB,cAAc;KACjB,CAAC;AAAA,CACL;AAED,SAAS,eAAe,CACpB,KAAuB,EACvB,UAA4B,EAC5B,WAAmB,EACnB,OAA4B,EAC9B;IACE,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAE5C,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACrB,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,EAAE,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAAA,CAClE;AAED,SAAS,oBAAoB,CACzB,UAA4B,EAC5B,WAAmB,EACnB,OAA4B,EAC9B;IACE,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAAA,CACjF;AAED,SAAS,kBAAkB,CAAC,QAAkB,EAAE,WAAmB,EAAE,OAA4B,EAAE;IAC/F,MAAM,gBAAgB,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAAA,CAC3E;AAED,SAAS,KAAK,CAAC,WAAmB,EAAE;IAChC,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC;AAAA,CACpC"}
@@ -0,0 +1,47 @@
1
+ import { type ISemanticSearchResultItem, type ObjRef, type ObjectType } from "@gooddata/sdk-model";
2
+ /**
3
+ * @alpha
4
+ * Defines the shape of the search item.
5
+ */
6
+ export type SearchItem = {
7
+ ref: ObjRef;
8
+ title?: string;
9
+ name?: string;
10
+ description?: string;
11
+ summary?: string;
12
+ };
13
+ /**
14
+ * @alpha
15
+ * Defines the shape of the search item group.
16
+ */
17
+ export type SearchItemGroup<I> = {
18
+ title?: string;
19
+ name?: string;
20
+ description?: string;
21
+ summary?: string;
22
+ items?: I[];
23
+ };
24
+ /**
25
+ * @alpha
26
+ * Defines the shape of the search results.
27
+ */
28
+ export type SearchResults<I extends SearchItem, G extends SearchItemGroup<I>> = {
29
+ searchItems: ReadonlyArray<I>;
30
+ searchAllItems: ReadonlyArray<I>;
31
+ searchItemGroups: ReadonlyArray<G>;
32
+ searchKeywords: ReadonlyArray<string>;
33
+ };
34
+ /**
35
+ * @alpha
36
+ * Defines the shape of the search matcher.
37
+ */
38
+ export type HybridSearchMatcher = <I extends SearchItem, G extends SearchItemGroup<I>>(item: I | G | string, searchQueryUpper: string) => boolean;
39
+ /**
40
+ * @alpha
41
+ * Defines the shape of the item builder.
42
+ */
43
+ export type HybridSearchItemBuilder<I extends SearchItem> = (item: ISemanticSearchResultItem, props: {
44
+ ref: ObjRef;
45
+ type: ObjectType;
46
+ }) => I | null | undefined;
47
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/hooks/search/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEnG;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;CACf,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,IAAI;IAC5E,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC9B,cAAc,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACjC,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACnC,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACzC,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,SAAS,UAAU,EAAE,CAAC,SAAS,eAAe,CAAC,CAAC,CAAC,EACjF,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,MAAM,EACpB,gBAAgB,EAAE,MAAM,KACvB,OAAO,CAAC;AAEb;;;GAGG;AACH,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,UAAU,IAAI,CACxD,IAAI,EAAE,yBAAyB,EAC/B,KAAK,EAAE;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,UAAU,CAAC;CACpB,KACA,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ // (C) 2024-2026 GoodData Corporation
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/hooks/search/types.ts"],"names":[],"mappings":"AAAA,qCAAqC"}
@@ -0,0 +1,125 @@
1
+ import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
2
+ import { type HybridSearchItemBuilder, type HybridSearchMatcher, type SearchItem, type SearchItemGroup, type SearchResults } from "./search/types.js";
3
+ import { type SemanticSearchHookInput } from "./useSemanticSearch.js";
4
+ /**
5
+ * @alpha
6
+ * On search query change callback.
7
+ */
8
+ export type OnSearchQueryChanged = (searchQuery: string) => void;
9
+ /**
10
+ * @alpha
11
+ * Options for the useHybridSearch hook.
12
+ */
13
+ export interface IUseHybridSearchOptions<I extends SearchItem> extends Pick<SemanticSearchHookInput, "deepSearch" | "limit" | "includeTags" | "excludeTags" | "objectTypes"> {
14
+ /**
15
+ * The backend to use for the search.
16
+ * If omitted, will be retrieved from the context.
17
+ */
18
+ backend?: IAnalyticalBackend;
19
+ /**
20
+ * The workspace to use for the search.
21
+ * If omitted, will be retrieved from the context.
22
+ */
23
+ workspace?: string;
24
+ /**
25
+ * Allow semantic search.
26
+ * default: true
27
+ */
28
+ allowSematicSearch?: boolean;
29
+ /**
30
+ * Debounce time in milliseconds for search query.
31
+ * default: 150
32
+ */
33
+ debounceMs?: number;
34
+ /**
35
+ * Custom matcher for the search results.
36
+ * If provided, the matcher will be used to filter the search results.
37
+ * The matcher should return true if the item should be included in the search results,
38
+ * and false otherwise.
39
+ */
40
+ matcher?: HybridSearchMatcher;
41
+ /**
42
+ * Item builder for the search results.
43
+ * If provided, the item builder will be used to transform the search results into a different format.
44
+ * The item builder should return the transformed item.
45
+ */
46
+ itemBuilder: HybridSearchItemBuilder<I>;
47
+ }
48
+ /**
49
+ * @alpha
50
+ * State of the search.
51
+ */
52
+ export interface ISearchState {
53
+ /**
54
+ * The current search query.
55
+ * This is the query that is currently being typed by the user.
56
+ * It may be different from the debouncedQuery if the user is still typing.
57
+ */
58
+ query: string;
59
+ /**
60
+ * The debounced search query.
61
+ * This is the query that is currently being used for the search.
62
+ * It may be different from the query if the user is still typing.
63
+ */
64
+ debouncedQuery: string;
65
+ /**
66
+ * The current state of the search.
67
+ * - idle - means the user has not typed anything yet
68
+ * - searching - means the user is currently typing
69
+ * - completed - means the user has stopped typing and the search is complete
70
+ */
71
+ state: "idle" | "searching" | "completed";
72
+ }
73
+ /**
74
+ * @alpha
75
+ * State of semantic search.
76
+ */
77
+ export interface ISemanticSearchState {
78
+ /**
79
+ * The current state of semantic search.
80
+ * - idle - means the semantic search is not running
81
+ * - loading - means the semantic search is running
82
+ * - error - means the semantic search failed
83
+ * - success - means the semantic search succeeded
84
+ */
85
+ state: "idle" | "loading" | "error" | "success";
86
+ /**
87
+ * The message to show to the user that came from the backend.
88
+ */
89
+ message: string;
90
+ /**
91
+ * The error message if the semantic search failed.
92
+ */
93
+ error?: string;
94
+ }
95
+ /**
96
+ * @alpha
97
+ * Result of the useHybridSearch hook.
98
+ */
99
+ export interface IHybridSearchResult<I extends SearchItem, G extends SearchItemGroup<I>> {
100
+ searchState: ISearchState;
101
+ semanticSearchState: ISemanticSearchState;
102
+ search: (props: {
103
+ items: ReadonlyArray<I>;
104
+ allItems?: ReadonlyArray<I>;
105
+ itemGroups?: ReadonlyArray<G>;
106
+ keywords?: string[];
107
+ }) => ICombinedSearchResults<I, G>;
108
+ onSearchQueryChange: OnSearchQueryChanged;
109
+ }
110
+ /**
111
+ * @alpha
112
+ * Search results that are combined from semantic search and normal search.
113
+ */
114
+ export interface ICombinedSearchResults<I extends SearchItem, G extends SearchItemGroup<I>> extends SearchResults<I, G> {
115
+ searchRelatedItems: ReadonlyArray<I>;
116
+ searchState: ISearchState;
117
+ semanticSearchState: ISemanticSearchState;
118
+ }
119
+ /**
120
+ * @alpha
121
+ * Use a hybrid search implementation that debounce the search query and provide semantic search
122
+ * related results
123
+ */
124
+ export declare function useHybridSearch<I extends SearchItem, G extends SearchItemGroup<I>>({ limit, workspace, backend, debounceMs, allowSematicSearch, deepSearch, objectTypes, includeTags, excludeTags, matcher, itemBuilder }: IUseHybridSearchOptions<I>): IHybridSearchResult<I, G>;
125
+ //# sourceMappingURL=useHybridSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHybridSearch.d.ts","sourceRoot":"","sources":["../../src/hooks/useHybridSearch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ // (C) 2024-2026 GoodData Corporation
2
+ import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+ import { useBackendStrict, useWorkspaceStrict } from "@gooddata/sdk-ui";
4
+ import { doFilterRelatedItems } from "./search/related.js";
5
+ import { doSearch } from "./search/search.js";
6
+ import { useSemanticSearch } from "./useSemanticSearch.js";
7
+ const SEARCH_DEBOUNCE_MS = 150;
8
+ /**
9
+ * @alpha
10
+ * Use a hybrid search implementation that debounce the search query and provide semantic search
11
+ * related results
12
+ */
13
+ export function useHybridSearch({ limit, workspace, backend, debounceMs = SEARCH_DEBOUNCE_MS, allowSematicSearch = true, deepSearch, objectTypes, includeTags, excludeTags, matcher, itemBuilder, }) {
14
+ const [searchQuery, setSearchQuery] = useState("");
15
+ const debouncedSearchQuery = useDebouncedValue(searchQuery, debounceMs);
16
+ const onSearchQueryChange = useOnSearchQueryChangeCallback(setSearchQuery);
17
+ const semanticSearchProps = useMemo(() => {
18
+ return {
19
+ objectTypes,
20
+ excludeTags,
21
+ includeTags,
22
+ };
23
+ // eslint-disable-next-line react-hooks/exhaustive-deps
24
+ }, [objectTypes?.join(), includeTags?.join(), excludeTags?.join()]);
25
+ const effectiveBackend = useBackendStrict(backend);
26
+ const effectiveWorkspace = useWorkspaceStrict(workspace);
27
+ const { searchStatus, searchError, searchResults, searchMessage } = useSemanticSearch({
28
+ searchTerm: allowSematicSearch ? debouncedSearchQuery : "",
29
+ backend: effectiveBackend,
30
+ workspace: effectiveWorkspace,
31
+ deepSearch,
32
+ limit,
33
+ ...semanticSearchProps,
34
+ });
35
+ const searchState = useMemo(() => ({
36
+ query: searchQuery,
37
+ debouncedQuery: debouncedSearchQuery,
38
+ state: getSearchState(searchQuery, debouncedSearchQuery),
39
+ }), [debouncedSearchQuery, searchQuery]);
40
+ const semanticSearchState = useMemo(() => ({
41
+ state: searchStatus,
42
+ error: searchError,
43
+ message: searchMessage,
44
+ }), [searchStatus, searchError, searchMessage]);
45
+ const search = useCallback((({ items, allItems = items, itemGroups = [], keywords = [] }) => {
46
+ const results = doSearch(items, allItems, itemGroups, keywords, searchQuery, matcher);
47
+ const searchRelatedItems = doFilterRelatedItems(results.searchAllItems, searchResults, itemBuilder);
48
+ return {
49
+ ...results,
50
+ searchRelatedItems,
51
+ //states
52
+ searchState,
53
+ semanticSearchState,
54
+ };
55
+ }), [searchQuery, searchState, semanticSearchState, searchResults, matcher, itemBuilder]);
56
+ return {
57
+ searchState,
58
+ semanticSearchState,
59
+ search,
60
+ onSearchQueryChange,
61
+ };
62
+ }
63
+ /**
64
+ * Debounce a string value by the given delay.
65
+ * Empty values are applied immediately (clearing search should feel instant).
66
+ */
67
+ function useDebouncedValue(value, delay) {
68
+ const [debouncedValue, setDebouncedValue] = useState(value);
69
+ const timerRef = useRef(undefined);
70
+ useEffect(() => {
71
+ // Clear search immediately — no debounce, no transition
72
+ if (value === "") {
73
+ setDebouncedValue("");
74
+ return undefined;
75
+ }
76
+ timerRef.current = setTimeout(() => {
77
+ // Mark as low-priority so React can interrupt for input events
78
+ startTransition(() => setDebouncedValue(value));
79
+ }, delay);
80
+ return () => {
81
+ if (timerRef.current) {
82
+ clearTimeout(timerRef.current);
83
+ }
84
+ };
85
+ }, [value, delay]);
86
+ return debouncedValue;
87
+ }
88
+ /**
89
+ * Create a callback that updates the search query.
90
+ */
91
+ function useOnSearchQueryChangeCallback(setSearchQuery) {
92
+ return useCallback((query) => {
93
+ setSearchQuery(query);
94
+ }, [setSearchQuery]);
95
+ }
96
+ //utils
97
+ function getSearchState(searchQuery, debouncedSearchQuery) {
98
+ if (searchQuery === "") {
99
+ return "idle";
100
+ }
101
+ if (debouncedSearchQuery !== searchQuery) {
102
+ return "searching";
103
+ }
104
+ return "completed";
105
+ }
106
+ //# sourceMappingURL=useHybridSearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHybridSearch.js","sourceRoot":"","sources":["../../src/hooks/useHybridSearch.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAG3F,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAQ9C,OAAO,EAAgC,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEzF,MAAM,kBAAkB,GAAG,GAAG,CAAC;AA6I/B;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAqD,EAChF,KAAK,EACL,SAAS,EACT,OAAO,EACP,UAAU,GAAG,kBAAkB,EAC/B,kBAAkB,GAAG,IAAI,EACzB,UAAU,EACV,WAAW,EACX,WAAW,EACX,WAAW,EACX,OAAO,EACP,WAAW,GACc,EAA6B;IACtD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,oBAAoB,GAAG,iBAAiB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACxE,MAAM,mBAAmB,GAAG,8BAA8B,CAAC,cAAc,CAAC,CAAC;IAE3E,MAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QACtC,OAAO;YACH,WAAW;YACX,WAAW;YACX,WAAW;SACd,CAAC;QACF,uDAAuD;IADrD,CAEL,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEpE,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACnD,MAAM,kBAAkB,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IACzD,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,iBAAiB,CAAC;QAClF,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE;QAC1D,OAAO,EAAE,gBAAgB;QACzB,SAAS,EAAE,kBAAkB;QAC7B,UAAU;QACV,KAAK;QACL,GAAG,mBAAmB;KACzB,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO,CACvB,GAAG,EAAE,CAAC,CAAC;QACH,KAAK,EAAE,WAAW;QAClB,cAAc,EAAE,oBAAoB;QACpC,KAAK,EAAE,cAAc,CAAC,WAAW,EAAE,oBAAoB,CAAC;KAC3D,CAAC,EACF,CAAC,oBAAoB,EAAE,WAAW,CAAC,CACtC,CAAC;IAEF,MAAM,mBAAmB,GAAG,OAAO,CAC/B,GAAG,EAAE,CAAC,CAAC;QACH,KAAK,EAAE,YAAY;QACnB,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,aAAa;KACzB,CAAC,EACF,CAAC,YAAY,EAAE,WAAW,EAAE,aAAa,CAAC,CAC7C,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CACtB,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,KAAK,EAAE,UAAU,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;QACtF,MAAM,kBAAkB,GAAG,oBAAoB,CAC3C,OAAO,CAAC,cAAc,EACtB,aAAa,EACb,WAAW,CACd,CAAC;QAEF,OAAO;YACH,GAAG,OAAO;YACV,kBAAkB;YAClB,QAAQ;YACR,WAAW;YACX,mBAAmB;SACtB,CAAC;IAAA,CACL,CAAwC,EACzC,CAAC,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,CACvF,CAAC;IAEF,OAAO;QACH,WAAW;QACX,mBAAmB;QACnB,MAAM;QACN,mBAAmB;KACtB,CAAC;AAAA,CACL;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAa,EAAE,KAAa,EAAU;IAC7D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,CAAgC,SAAS,CAAC,CAAC;IAElE,SAAS,CAAC,GAAG,EAAE,CAAC;QACZ,0DAAwD;QACxD,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACf,iBAAiB,CAAC,EAAE,CAAC,CAAC;YACtB,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,QAAQ,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC;YAChC,+DAA+D;YAC/D,eAAe,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;QAAA,CACnD,EAAE,KAAK,CAAC,CAAC;QAEV,OAAO,GAAG,EAAE,CAAC;YACT,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;gBACnB,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACnC,CAAC;QAAA,CACJ,CAAC;IAAA,CACL,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAEnB,OAAO,cAAc,CAAC;AAAA,CACzB;AAED;;GAEG;AACH,SAAS,8BAA8B,CAAC,cAAoC,EAAE;IAC1E,OAAO,WAAW,CACd,CAAC,KAAa,EAAE,EAAE,CAAC;QACf,cAAc,CAAC,KAAK,CAAC,CAAC;IAAA,CACzB,EACD,CAAC,cAAc,CAAC,CACnB,CAAC;AAAA,CACL;AAED,OAAO;AAEP,SAAS,cAAc,CACnB,WAAmB,EACnB,oBAA4B,EACqB;IACjD,IAAI,WAAW,KAAK,EAAE,EAAE,CAAC;QACrB,OAAO,MAAM,CAAC;IAClB,CAAC;IACD,IAAI,oBAAoB,KAAK,WAAW,EAAE,CAAC;QACvC,OAAO,WAAW,CAAC;IACvB,CAAC;IACD,OAAO,WAAW,CAAC;AAAA,CACtB"}
package/esm/index.d.ts CHANGED
@@ -6,4 +6,7 @@
6
6
  export { type SemanticSearchInputResult, type SemanticSearchHookInput, useSemanticSearch, } from "./hooks/useSemanticSearch.js";
7
7
  export { type FooterButtonAiAssistantProps, FooterButtonAiAssistant } from "./FooterButtonAiAssistant.js";
8
8
  export { type SemanticSearchProps, SemanticSearch } from "./SemanticSearch.js";
9
+ export { type ICombinedSearchResults, type IHybridSearchResult, type ISearchState, type ISemanticSearchState, type IUseHybridSearchOptions, useHybridSearch, type OnSearchQueryChanged, } from "./hooks/useHybridSearch.js";
10
+ export type { HybridSearchMatcher, SearchItem, SearchItemGroup, SearchResults, HybridSearchItemBuilder, } from "./hooks/search/types.js";
11
+ export { defaultMatcher, customMatcher } from "./hooks/search/matchers.js";
9
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACH,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,iBAAiB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,KAAK,4BAA4B,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAE,KAAK,mBAAmB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,OAAO,EACH,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,iBAAiB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,KAAK,4BAA4B,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAAE,KAAK,mBAAmB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/E,OAAO,EACH,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAC5B,eAAe,EACf,KAAK,oBAAoB,GAC5B,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACR,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,aAAa,EACb,uBAAuB,GAC1B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC"}
package/esm/index.js CHANGED
@@ -8,4 +8,6 @@
8
8
  export { useSemanticSearch, } from "./hooks/useSemanticSearch.js";
9
9
  export { FooterButtonAiAssistant } from "./FooterButtonAiAssistant.js";
10
10
  export { SemanticSearch } from "./SemanticSearch.js";
11
+ export { useHybridSearch, } from "./hooks/useHybridSearch.js";
12
+ export { defaultMatcher, customMatcher } from "./hooks/search/matchers.js";
11
13
  //# sourceMappingURL=index.js.map
package/esm/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,oDAAoD;AAEpD;;;;GAIG;AAEH,OAAO,EAGH,iBAAiB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAqC,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAA4B,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AAErC,oDAAoD;AAEpD;;;;GAIG;AAEH,OAAO,EAGH,iBAAiB,GACpB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAqC,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAC1G,OAAO,EAA4B,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE/E,OAAO,EAMH,eAAe,GAElB,MAAM,4BAA4B,CAAC;AAUpC,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC"}
@@ -11,8 +11,22 @@ import { ISemanticSearchRelationship } from '@gooddata/sdk-model';
11
11
  import { ISemanticSearchResultItem } from '@gooddata/sdk-model';
12
12
  import { JSX } from 'react/jsx-runtime';
13
13
  import { MouseEvent as MouseEvent_2 } from 'react';
14
+ import { ObjectType } from '@gooddata/sdk-model';
15
+ import { ObjRef } from '@gooddata/sdk-model';
14
16
  import { ReactNode } from 'react';
15
17
 
18
+ /**
19
+ * @alpha
20
+ * Custom matcher for the search. Can be used to match any property of the search item.
21
+ */
22
+ export declare function customMatcher<I extends SearchItem, G extends SearchItemGroup<I>>(props: (keyof I & keyof G)[]): HybridSearchMatcher;
23
+
24
+ /**
25
+ * @alpha
26
+ * Default matcher for the search. Supports title, name, description, summary.
27
+ */
28
+ export declare function defaultMatcher<I extends SearchItem, G extends SearchItemGroup<I>>(item: I | G | string, searchQueryUpper: string): boolean;
29
+
16
30
  /**
17
31
  * @public
18
32
  */
@@ -25,6 +39,177 @@ export declare type FooterButtonAiAssistantProps = {
25
39
  onClick?: (e: MouseEvent_2) => void;
26
40
  };
27
41
 
42
+ /**
43
+ * @alpha
44
+ * Defines the shape of the item builder.
45
+ */
46
+ export declare type HybridSearchItemBuilder<I extends SearchItem> = (item: ISemanticSearchResultItem, props: {
47
+ ref: ObjRef;
48
+ type: ObjectType;
49
+ }) => I | null | undefined;
50
+
51
+ /**
52
+ * @alpha
53
+ * Defines the shape of the search matcher.
54
+ */
55
+ export declare type HybridSearchMatcher = <I extends SearchItem, G extends SearchItemGroup<I>>(item: I | G | string, searchQueryUpper: string) => boolean;
56
+
57
+ /**
58
+ * @alpha
59
+ * Search results that are combined from semantic search and normal search.
60
+ */
61
+ export declare interface ICombinedSearchResults<I extends SearchItem, G extends SearchItemGroup<I>> extends SearchResults<I, G> {
62
+ searchRelatedItems: ReadonlyArray<I>;
63
+ searchState: ISearchState;
64
+ semanticSearchState: ISemanticSearchState;
65
+ }
66
+
67
+ /**
68
+ * @alpha
69
+ * Result of the useHybridSearch hook.
70
+ */
71
+ export declare interface IHybridSearchResult<I extends SearchItem, G extends SearchItemGroup<I>> {
72
+ searchState: ISearchState;
73
+ semanticSearchState: ISemanticSearchState;
74
+ search: (props: {
75
+ items: ReadonlyArray<I>;
76
+ allItems?: ReadonlyArray<I>;
77
+ itemGroups?: ReadonlyArray<G>;
78
+ keywords?: string[];
79
+ }) => ICombinedSearchResults<I, G>;
80
+ onSearchQueryChange: OnSearchQueryChanged;
81
+ }
82
+
83
+ /**
84
+ * @alpha
85
+ * State of the search.
86
+ */
87
+ export declare interface ISearchState {
88
+ /**
89
+ * The current search query.
90
+ * This is the query that is currently being typed by the user.
91
+ * It may be different from the debouncedQuery if the user is still typing.
92
+ */
93
+ query: string;
94
+ /**
95
+ * The debounced search query.
96
+ * This is the query that is currently being used for the search.
97
+ * It may be different from the query if the user is still typing.
98
+ */
99
+ debouncedQuery: string;
100
+ /**
101
+ * The current state of the search.
102
+ * - idle - means the user has not typed anything yet
103
+ * - searching - means the user is currently typing
104
+ * - completed - means the user has stopped typing and the search is complete
105
+ */
106
+ state: "idle" | "searching" | "completed";
107
+ }
108
+
109
+ /**
110
+ * @alpha
111
+ * State of semantic search.
112
+ */
113
+ export declare interface ISemanticSearchState {
114
+ /**
115
+ * The current state of semantic search.
116
+ * - idle - means the semantic search is not running
117
+ * - loading - means the semantic search is running
118
+ * - error - means the semantic search failed
119
+ * - success - means the semantic search succeeded
120
+ */
121
+ state: "idle" | "loading" | "error" | "success";
122
+ /**
123
+ * The message to show to the user that came from the backend.
124
+ */
125
+ message: string;
126
+ /**
127
+ * The error message if the semantic search failed.
128
+ */
129
+ error?: string;
130
+ }
131
+
132
+ /**
133
+ * @alpha
134
+ * Options for the useHybridSearch hook.
135
+ */
136
+ export declare interface IUseHybridSearchOptions<I extends SearchItem> extends Pick<SemanticSearchHookInput, "deepSearch" | "limit" | "includeTags" | "excludeTags" | "objectTypes"> {
137
+ /**
138
+ * The backend to use for the search.
139
+ * If omitted, will be retrieved from the context.
140
+ */
141
+ backend?: IAnalyticalBackend;
142
+ /**
143
+ * The workspace to use for the search.
144
+ * If omitted, will be retrieved from the context.
145
+ */
146
+ workspace?: string;
147
+ /**
148
+ * Allow semantic search.
149
+ * default: true
150
+ */
151
+ allowSematicSearch?: boolean;
152
+ /**
153
+ * Debounce time in milliseconds for search query.
154
+ * default: 150
155
+ */
156
+ debounceMs?: number;
157
+ /**
158
+ * Custom matcher for the search results.
159
+ * If provided, the matcher will be used to filter the search results.
160
+ * The matcher should return true if the item should be included in the search results,
161
+ * and false otherwise.
162
+ */
163
+ matcher?: HybridSearchMatcher;
164
+ /**
165
+ * Item builder for the search results.
166
+ * If provided, the item builder will be used to transform the search results into a different format.
167
+ * The item builder should return the transformed item.
168
+ */
169
+ itemBuilder: HybridSearchItemBuilder<I>;
170
+ }
171
+
172
+ /**
173
+ * @alpha
174
+ * On search query change callback.
175
+ */
176
+ export declare type OnSearchQueryChanged = (searchQuery: string) => void;
177
+
178
+ /**
179
+ * @alpha
180
+ * Defines the shape of the search item.
181
+ */
182
+ export declare type SearchItem = {
183
+ ref: ObjRef;
184
+ title?: string;
185
+ name?: string;
186
+ description?: string;
187
+ summary?: string;
188
+ };
189
+
190
+ /**
191
+ * @alpha
192
+ * Defines the shape of the search item group.
193
+ */
194
+ export declare type SearchItemGroup<I> = {
195
+ title?: string;
196
+ name?: string;
197
+ description?: string;
198
+ summary?: string;
199
+ items?: I[];
200
+ };
201
+
202
+ /**
203
+ * @alpha
204
+ * Defines the shape of the search results.
205
+ */
206
+ export declare type SearchResults<I extends SearchItem, G extends SearchItemGroup<I>> = {
207
+ searchItems: ReadonlyArray<I>;
208
+ searchAllItems: ReadonlyArray<I>;
209
+ searchItemGroups: ReadonlyArray<G>;
210
+ searchKeywords: ReadonlyArray<string>;
211
+ };
212
+
28
213
  /**
29
214
  * Semantic search filed with dropdown for selecting items.
30
215
  * @beta
@@ -175,6 +360,13 @@ export declare type SemanticSearchProps = {
175
360
  }) => ReactNode;
176
361
  };
177
362
 
363
+ /**
364
+ * @alpha
365
+ * Use a hybrid search implementation that debounce the search query and provide semantic search
366
+ * related results
367
+ */
368
+ export declare function useHybridSearch<I extends SearchItem, G extends SearchItemGroup<I>>({ limit, workspace, backend, debounceMs, allowSematicSearch, deepSearch, objectTypes, includeTags, excludeTags, matcher, itemBuilder }: IUseHybridSearchOptions<I>): IHybridSearchResult<I, G>;
369
+
178
370
  /**
179
371
  * Hook to perform semantic search.
180
372
  * Makes the request to server and returns the search results.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gooddata/sdk-ui-semantic-search",
3
- "version": "11.26.0-alpha.2",
3
+ "version": "11.26.0-alpha.3",
4
4
  "description": "GoodData SDK TypeScript & React skeleton",
5
5
  "license": "MIT",
6
6
  "author": "GoodData Corporation",
@@ -35,12 +35,12 @@
35
35
  "lodash-es": "^4.17.23",
36
36
  "react-intl": "7.1.11",
37
37
  "tslib": "2.8.1",
38
- "@gooddata/sdk-model": "11.26.0-alpha.2",
39
- "@gooddata/sdk-backend-spi": "11.26.0-alpha.2",
40
- "@gooddata/sdk-ui": "11.26.0-alpha.2",
41
- "@gooddata/sdk-ui-kit": "11.26.0-alpha.2",
42
- "@gooddata/sdk-ui-theme-provider": "11.26.0-alpha.2",
43
- "@gooddata/util": "11.26.0-alpha.2"
38
+ "@gooddata/sdk-backend-spi": "11.26.0-alpha.3",
39
+ "@gooddata/sdk-model": "11.26.0-alpha.3",
40
+ "@gooddata/sdk-ui-theme-provider": "11.26.0-alpha.3",
41
+ "@gooddata/sdk-ui": "11.26.0-alpha.3",
42
+ "@gooddata/sdk-ui-kit": "11.26.0-alpha.3",
43
+ "@gooddata/util": "11.26.0-alpha.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@microsoft/api-documenter": "^7.17.0",
@@ -80,11 +80,11 @@
80
80
  "typescript": "5.9.3",
81
81
  "vitest": "4.0.8",
82
82
  "vitest-dom": "0.1.1",
83
- "@gooddata/eslint-config": "11.26.0-alpha.2",
84
- "@gooddata/i18n-toolkit": "11.26.0-alpha.2",
85
- "@gooddata/oxlint-config": "11.26.0-alpha.2",
86
- "@gooddata/sdk-backend-mockingbird": "11.26.0-alpha.2",
87
- "@gooddata/stylelint-config": "11.26.0-alpha.2"
83
+ "@gooddata/eslint-config": "11.26.0-alpha.3",
84
+ "@gooddata/i18n-toolkit": "11.26.0-alpha.3",
85
+ "@gooddata/oxlint-config": "11.26.0-alpha.3",
86
+ "@gooddata/stylelint-config": "11.26.0-alpha.3",
87
+ "@gooddata/sdk-backend-mockingbird": "11.26.0-alpha.3"
88
88
  },
89
89
  "peerDependencies": {
90
90
  "react": "^18.0.0 || ^19.0.0",