@backstage/plugin-search-react 1.9.5-next.2 → 1.9.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @backstage/plugin-search-react
2
2
 
3
+ ## 1.9.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 67a3e1a: Implemented AbortController request cancellation for overlapping search requests. This change ensures that when users type quickly, previous search requests are properly canceled before new ones start.
8
+ - Updated dependencies
9
+ - @backstage/core-components@0.18.2
10
+ - @backstage/frontend-plugin-api@0.12.1
11
+ - @backstage/theme@0.7.0
12
+ - @backstage/core-plugin-api@1.11.1
13
+ - @backstage/plugin-search-common@1.2.20
14
+
3
15
  ## 1.9.5-next.2
4
16
 
5
17
  ### Patch Changes
package/dist/api.esm.js CHANGED
@@ -7,7 +7,7 @@ class MockSearchApi {
7
7
  constructor(mockedResults) {
8
8
  this.mockedResults = mockedResults;
9
9
  }
10
- query() {
10
+ query(_query, _options) {
11
11
  return Promise.resolve(this.mockedResults || { results: [] });
12
12
  }
13
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api.esm.js","sources":["../src/api.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';\nimport { createApiRef } from '@backstage/core-plugin-api';\n\n/**\n * @public\n */\nexport const searchApiRef = createApiRef<SearchApi>({\n id: 'plugin.search.queryservice',\n});\n\n/**\n * @public\n */\nexport interface SearchApi {\n query(query: SearchQuery): Promise<SearchResultSet>;\n}\n\n/**\n * @public\n *\n * Search Api Mock that can be used in tests and storybooks\n */\nexport class MockSearchApi implements SearchApi {\n constructor(public mockedResults?: SearchResultSet) {}\n\n query(): Promise<SearchResultSet> {\n return Promise.resolve(this.mockedResults || { results: [] });\n }\n}\n"],"names":[],"mappings":";;AAsBO,MAAM,eAAe,YAAA,CAAwB;AAAA,EAClD,EAAA,EAAI;AACN,CAAC;AAcM,MAAM,aAAA,CAAmC;AAAA,EAC9C,YAAmB,aAAA,EAAiC;AAAjC,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAAA,EAAkC;AAAA,EAErD,KAAA,GAAkC;AAChC,IAAA,OAAO,OAAA,CAAQ,QAAQ,IAAA,CAAK,aAAA,IAAiB,EAAE,OAAA,EAAS,IAAI,CAAA;AAAA,EAC9D;AACF;;;;"}
1
+ {"version":3,"file":"api.esm.js","sources":["../src/api.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SearchQuery, SearchResultSet } from '@backstage/plugin-search-common';\nimport { createApiRef } from '@backstage/core-plugin-api';\n\n/**\n * @public\n */\nexport const searchApiRef = createApiRef<SearchApi>({\n id: 'plugin.search.queryservice',\n});\n\n/**\n * @public\n */\nexport interface SearchApi {\n query(\n query: SearchQuery,\n options?: { signal?: AbortSignal },\n ): Promise<SearchResultSet>;\n}\n\n/**\n * @public\n *\n * Search Api Mock that can be used in tests and storybooks\n */\nexport class MockSearchApi implements SearchApi {\n constructor(public mockedResults?: SearchResultSet) {}\n\n query(\n _query: SearchQuery,\n _options?: { signal?: AbortSignal },\n ): Promise<SearchResultSet> {\n return Promise.resolve(this.mockedResults || { results: [] });\n }\n}\n"],"names":[],"mappings":";;AAsBO,MAAM,eAAe,YAAA,CAAwB;AAAA,EAClD,EAAA,EAAI;AACN,CAAC;AAiBM,MAAM,aAAA,CAAmC;AAAA,EAC9C,YAAmB,aAAA,EAAiC;AAAjC,IAAA,IAAA,CAAA,aAAA,GAAA,aAAA;AAAA,EAAkC;AAAA,EAErD,KAAA,CACE,QACA,QAAA,EAC0B;AAC1B,IAAA,OAAO,OAAA,CAAQ,QAAQ,IAAA,CAAK,aAAA,IAAiB,EAAE,OAAA,EAAS,IAAI,CAAA;AAAA,EAC9D;AACF;;;;"}
@@ -1,6 +1,6 @@
1
1
  import { jsx, Fragment } from 'react/jsx-runtime';
2
2
  import { isEqual } from 'lodash';
3
- import { useContext, useState, useCallback, useEffect } from 'react';
3
+ import { useContext, useState, useRef, useCallback, useEffect } from 'react';
4
4
  import useAsync from 'react-use/esm/useAsync';
5
5
  import usePrevious from 'react-use/esm/usePrevious';
6
6
  import { createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
@@ -44,14 +44,23 @@ const useSearchContextValue = (initialValue = defaultInitialSearchState) => {
44
44
  );
45
45
  const prevTerm = usePrevious(term);
46
46
  const prevFilters = usePrevious(filters);
47
+ const abortControllerRef = useRef(null);
47
48
  const result = useAsync(async () => {
48
- const resultSet = await searchApi.query({
49
- term,
50
- types,
51
- filters,
52
- pageLimit,
53
- pageCursor
54
- });
49
+ if (abortControllerRef.current) {
50
+ abortControllerRef.current.abort();
51
+ }
52
+ const controller = new AbortController();
53
+ abortControllerRef.current = controller;
54
+ const resultSet = await searchApi.query(
55
+ {
56
+ term,
57
+ types,
58
+ filters,
59
+ pageLimit,
60
+ pageCursor
61
+ },
62
+ { signal: controller.signal }
63
+ );
55
64
  if (term) {
56
65
  analytics.captureEvent("search", term, {
57
66
  value: resultSet.numberOfResults
@@ -67,6 +76,13 @@ const useSearchContextValue = (initialValue = defaultInitialSearchState) => {
67
76
  const fetchPreviousPage = useCallback(() => {
68
77
  setPageCursor(result.value?.previousPageCursor);
69
78
  }, [result.value?.previousPageCursor]);
79
+ useEffect(() => {
80
+ return () => {
81
+ if (abortControllerRef.current) {
82
+ abortControllerRef.current.abort();
83
+ }
84
+ };
85
+ }, []);
70
86
  useEffect(() => {
71
87
  if (prevTerm !== void 0 && term !== prevTerm) {
72
88
  setPageCursor(void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"SearchContext.esm.js","sources":["../../src/context/SearchContext.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isEqual } from 'lodash';\nimport {\n SetStateAction,\n Dispatch,\n DispatchWithoutAction,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useState,\n} from 'react';\nimport useAsync, { AsyncState } from 'react-use/esm/useAsync';\nimport usePrevious from 'react-use/esm/usePrevious';\n\nimport {\n createVersionedContext,\n createVersionedValueMap,\n} from '@backstage/version-bridge';\nimport { JsonObject } from '@backstage/types';\nimport {\n AnalyticsContext,\n useApi,\n configApiRef,\n useAnalytics,\n} from '@backstage/core-plugin-api';\nimport { SearchResultSet } from '@backstage/plugin-search-common';\n\nimport { searchApiRef } from '../api';\n\n/**\n *\n * @public\n */\nexport type SearchContextValue = {\n result: AsyncState<SearchResultSet>;\n setTerm: Dispatch<SetStateAction<string>>;\n setTypes: Dispatch<SetStateAction<string[]>>;\n setFilters: Dispatch<SetStateAction<JsonObject>>;\n setPageLimit: Dispatch<SetStateAction<number | undefined>>;\n setPageCursor: Dispatch<SetStateAction<string | undefined>>;\n fetchNextPage?: DispatchWithoutAction;\n fetchPreviousPage?: DispatchWithoutAction;\n} & SearchContextState;\n\n/**\n *\n * @public\n */\nexport type SearchContextState = {\n term: string;\n types: string[];\n filters: JsonObject;\n pageLimit?: number;\n pageCursor?: string;\n};\n\nconst SearchContext = createVersionedContext<{\n 1: SearchContextValue;\n}>('search-context');\n\n/**\n * @public\n *\n * React hook which provides the search context\n */\nexport const useSearch = () => {\n const context = useContext(SearchContext);\n if (!context) {\n throw new Error('useSearch must be used within a SearchContextProvider');\n }\n\n const value = context.atVersion(1);\n if (!value) {\n throw new Error('No SearchContext v1 found');\n }\n return value;\n};\n\n/**\n * @public\n *\n * React hook which checks for an existing search context\n */\nexport const useSearchContextCheck = () => {\n const context = useContext(SearchContext);\n return context !== undefined;\n};\n\n/**\n * The initial state of `SearchContextProvider`.\n *\n */\nconst defaultInitialSearchState: SearchContextState = {\n term: '',\n types: [],\n filters: {},\n pageLimit: undefined,\n pageCursor: undefined,\n};\n\nconst useSearchContextValue = (\n initialValue: SearchContextState = defaultInitialSearchState,\n) => {\n const searchApi = useApi(searchApiRef);\n const analytics = useAnalytics();\n\n const [term, setTerm] = useState<string>(initialValue.term);\n const [types, setTypes] = useState<string[]>(initialValue.types);\n const [filters, setFilters] = useState<JsonObject>(initialValue.filters);\n const [pageLimit, setPageLimit] = useState<number | undefined>(\n initialValue.pageLimit,\n );\n const [pageCursor, setPageCursor] = useState<string | undefined>(\n initialValue.pageCursor,\n );\n\n const prevTerm = usePrevious(term);\n const prevFilters = usePrevious(filters);\n\n const result = useAsync(async () => {\n const resultSet = await searchApi.query({\n term,\n types,\n filters,\n pageLimit,\n pageCursor,\n });\n if (term) {\n analytics.captureEvent('search', term, {\n value: resultSet.numberOfResults,\n });\n }\n return resultSet;\n }, [term, types, filters, pageLimit, pageCursor]);\n\n const hasNextPage =\n !result.loading && !result.error && result.value?.nextPageCursor;\n const hasPreviousPage =\n !result.loading && !result.error && result.value?.previousPageCursor;\n\n const fetchNextPage = useCallback(() => {\n setPageCursor(result.value?.nextPageCursor);\n }, [result.value?.nextPageCursor]);\n\n const fetchPreviousPage = useCallback(() => {\n setPageCursor(result.value?.previousPageCursor);\n }, [result.value?.previousPageCursor]);\n\n useEffect(() => {\n // Any time a term is reset, we want to start from page 0.\n // Only reset the term if it has been modified by the user at least once, the initial state must not reset the term.\n if (prevTerm !== undefined && term !== prevTerm) {\n setPageCursor(undefined);\n }\n }, [term, prevTerm, setPageCursor]);\n\n useEffect(() => {\n // Any time filters is reset, we want to start from page 0.\n // Only reset the page if it has been modified by the user at least once, the initial state must not reset the page.\n if (prevFilters !== undefined && !isEqual(filters, prevFilters)) {\n setPageCursor(undefined);\n }\n }, [filters, prevFilters, setPageCursor]);\n\n const value: SearchContextValue = {\n result,\n term,\n setTerm,\n types,\n setTypes,\n filters,\n setFilters,\n pageLimit,\n setPageLimit,\n pageCursor,\n setPageCursor,\n fetchNextPage: hasNextPage ? fetchNextPage : undefined,\n fetchPreviousPage: hasPreviousPage ? fetchPreviousPage : undefined,\n };\n\n return value;\n};\n\nexport type LocalSearchContextProps = PropsWithChildren<{\n initialState?: SearchContextState;\n}>;\n\nconst LocalSearchContext = (props: SearchContextProviderProps) => {\n const { initialState, children } = props;\n const value = useSearchContextValue(initialState);\n\n return (\n <AnalyticsContext\n attributes={{ searchTypes: value.types.sort().join(',') }}\n >\n <SearchContext.Provider value={createVersionedValueMap({ 1: value })}>\n {children}\n </SearchContext.Provider>\n </AnalyticsContext>\n );\n};\n\n/**\n * Props for {@link SearchContextProvider}\n *\n * @public\n */\nexport type SearchContextProviderProps =\n | PropsWithChildren<{\n /**\n * State initialized by a local context.\n */\n initialState?: SearchContextState;\n /**\n * Do not create an inheritance from the parent, as a new initial state must be defined in a local context.\n */\n inheritParentContextIfAvailable?: never;\n }>\n | PropsWithChildren<{\n /**\n * Does not accept initial state since it is already initialized by parent context.\n */\n initialState?: never;\n /**\n * If true, don't create a child context if there is a parent one already defined.\n * @remarks Defaults to false.\n */\n inheritParentContextIfAvailable?: boolean;\n }>;\n\n/**\n * @public\n * Search context provider which gives you access to shared state between search components\n */\nexport const SearchContextProvider = (props: SearchContextProviderProps) => {\n const { initialState, inheritParentContextIfAvailable, children } = props;\n const hasParentContext = useSearchContextCheck();\n\n const configApi = useApi(configApiRef);\n\n const propsInitialSearchState = initialState ?? {};\n\n const configInitialSearchState = configApi.has('search.query.pageLimit')\n ? { pageLimit: configApi.getNumber('search.query.pageLimit') }\n : {};\n\n const searchContextInitialState = {\n ...defaultInitialSearchState,\n ...propsInitialSearchState,\n ...configInitialSearchState,\n };\n\n return hasParentContext && inheritParentContextIfAvailable ? (\n <>{children}</>\n ) : (\n <LocalSearchContext initialState={searchContextInitialState}>\n {children}\n </LocalSearchContext>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SearchContext.esm.js","sources":["../../src/context/SearchContext.tsx"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isEqual } from 'lodash';\nimport {\n SetStateAction,\n Dispatch,\n DispatchWithoutAction,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport useAsync, { AsyncState } from 'react-use/esm/useAsync';\nimport usePrevious from 'react-use/esm/usePrevious';\n\nimport {\n createVersionedContext,\n createVersionedValueMap,\n} from '@backstage/version-bridge';\nimport { JsonObject } from '@backstage/types';\nimport {\n AnalyticsContext,\n useApi,\n configApiRef,\n useAnalytics,\n} from '@backstage/core-plugin-api';\nimport { SearchResultSet } from '@backstage/plugin-search-common';\n\nimport { searchApiRef } from '../api';\n\n/**\n *\n * @public\n */\nexport type SearchContextValue = {\n result: AsyncState<SearchResultSet>;\n setTerm: Dispatch<SetStateAction<string>>;\n setTypes: Dispatch<SetStateAction<string[]>>;\n setFilters: Dispatch<SetStateAction<JsonObject>>;\n setPageLimit: Dispatch<SetStateAction<number | undefined>>;\n setPageCursor: Dispatch<SetStateAction<string | undefined>>;\n fetchNextPage?: DispatchWithoutAction;\n fetchPreviousPage?: DispatchWithoutAction;\n} & SearchContextState;\n\n/**\n *\n * @public\n */\nexport type SearchContextState = {\n term: string;\n types: string[];\n filters: JsonObject;\n pageLimit?: number;\n pageCursor?: string;\n};\n\nconst SearchContext = createVersionedContext<{\n 1: SearchContextValue;\n}>('search-context');\n\n/**\n * @public\n *\n * React hook which provides the search context\n */\nexport const useSearch = () => {\n const context = useContext(SearchContext);\n if (!context) {\n throw new Error('useSearch must be used within a SearchContextProvider');\n }\n\n const value = context.atVersion(1);\n if (!value) {\n throw new Error('No SearchContext v1 found');\n }\n return value;\n};\n\n/**\n * @public\n *\n * React hook which checks for an existing search context\n */\nexport const useSearchContextCheck = () => {\n const context = useContext(SearchContext);\n return context !== undefined;\n};\n\n/**\n * The initial state of `SearchContextProvider`.\n *\n */\nconst defaultInitialSearchState: SearchContextState = {\n term: '',\n types: [],\n filters: {},\n pageLimit: undefined,\n pageCursor: undefined,\n};\n\nconst useSearchContextValue = (\n initialValue: SearchContextState = defaultInitialSearchState,\n) => {\n const searchApi = useApi(searchApiRef);\n const analytics = useAnalytics();\n\n const [term, setTerm] = useState<string>(initialValue.term);\n const [types, setTypes] = useState<string[]>(initialValue.types);\n const [filters, setFilters] = useState<JsonObject>(initialValue.filters);\n const [pageLimit, setPageLimit] = useState<number | undefined>(\n initialValue.pageLimit,\n );\n const [pageCursor, setPageCursor] = useState<string | undefined>(\n initialValue.pageCursor,\n );\n\n const prevTerm = usePrevious(term);\n const prevFilters = usePrevious(filters);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const result = useAsync(async () => {\n // Here we cancel the previous request before making a new one\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n const controller = new AbortController();\n abortControllerRef.current = controller;\n\n const resultSet = await searchApi.query(\n {\n term,\n types,\n filters,\n pageLimit,\n pageCursor,\n },\n { signal: controller.signal },\n );\n\n if (term) {\n analytics.captureEvent('search', term, {\n value: resultSet.numberOfResults,\n });\n }\n return resultSet;\n }, [term, types, filters, pageLimit, pageCursor]);\n\n const hasNextPage =\n !result.loading && !result.error && result.value?.nextPageCursor;\n const hasPreviousPage =\n !result.loading && !result.error && result.value?.previousPageCursor;\n\n const fetchNextPage = useCallback(() => {\n setPageCursor(result.value?.nextPageCursor);\n }, [result.value?.nextPageCursor]);\n\n const fetchPreviousPage = useCallback(() => {\n setPageCursor(result.value?.previousPageCursor);\n }, [result.value?.previousPageCursor]);\n\n useEffect(() => {\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n useEffect(() => {\n // Any time a term is reset, we want to start from page 0.\n // Only reset the term if it has been modified by the user at least once, the initial state must not reset the term.\n if (prevTerm !== undefined && term !== prevTerm) {\n setPageCursor(undefined);\n }\n }, [term, prevTerm, setPageCursor]);\n\n useEffect(() => {\n // Any time filters is reset, we want to start from page 0.\n // Only reset the page if it has been modified by the user at least once, the initial state must not reset the page.\n if (prevFilters !== undefined && !isEqual(filters, prevFilters)) {\n setPageCursor(undefined);\n }\n }, [filters, prevFilters, setPageCursor]);\n\n const value: SearchContextValue = {\n result,\n term,\n setTerm,\n types,\n setTypes,\n filters,\n setFilters,\n pageLimit,\n setPageLimit,\n pageCursor,\n setPageCursor,\n fetchNextPage: hasNextPage ? fetchNextPage : undefined,\n fetchPreviousPage: hasPreviousPage ? fetchPreviousPage : undefined,\n };\n\n return value;\n};\n\nexport type LocalSearchContextProps = PropsWithChildren<{\n initialState?: SearchContextState;\n}>;\n\nconst LocalSearchContext = (props: SearchContextProviderProps) => {\n const { initialState, children } = props;\n const value = useSearchContextValue(initialState);\n\n return (\n <AnalyticsContext\n attributes={{ searchTypes: value.types.sort().join(',') }}\n >\n <SearchContext.Provider value={createVersionedValueMap({ 1: value })}>\n {children}\n </SearchContext.Provider>\n </AnalyticsContext>\n );\n};\n\n/**\n * Props for {@link SearchContextProvider}\n *\n * @public\n */\nexport type SearchContextProviderProps =\n | PropsWithChildren<{\n /**\n * State initialized by a local context.\n */\n initialState?: SearchContextState;\n /**\n * Do not create an inheritance from the parent, as a new initial state must be defined in a local context.\n */\n inheritParentContextIfAvailable?: never;\n }>\n | PropsWithChildren<{\n /**\n * Does not accept initial state since it is already initialized by parent context.\n */\n initialState?: never;\n /**\n * If true, don't create a child context if there is a parent one already defined.\n * @remarks Defaults to false.\n */\n inheritParentContextIfAvailable?: boolean;\n }>;\n\n/**\n * @public\n * Search context provider which gives you access to shared state between search components\n */\nexport const SearchContextProvider = (props: SearchContextProviderProps) => {\n const { initialState, inheritParentContextIfAvailable, children } = props;\n const hasParentContext = useSearchContextCheck();\n\n const configApi = useApi(configApiRef);\n\n const propsInitialSearchState = initialState ?? {};\n\n const configInitialSearchState = configApi.has('search.query.pageLimit')\n ? { pageLimit: configApi.getNumber('search.query.pageLimit') }\n : {};\n\n const searchContextInitialState = {\n ...defaultInitialSearchState,\n ...propsInitialSearchState,\n ...configInitialSearchState,\n };\n\n return hasParentContext && inheritParentContextIfAvailable ? (\n <>{children}</>\n ) : (\n <LocalSearchContext initialState={searchContextInitialState}>\n {children}\n </LocalSearchContext>\n );\n};\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -22,7 +22,9 @@ declare const searchApiRef: _backstage_core_plugin_api.ApiRef<SearchApi>;
22
22
  * @public
23
23
  */
24
24
  interface SearchApi {
25
- query(query: SearchQuery): Promise<SearchResultSet>;
25
+ query(query: SearchQuery, options?: {
26
+ signal?: AbortSignal;
27
+ }): Promise<SearchResultSet>;
26
28
  }
27
29
  /**
28
30
  * @public
@@ -32,7 +34,9 @@ interface SearchApi {
32
34
  declare class MockSearchApi implements SearchApi {
33
35
  mockedResults?: SearchResultSet | undefined;
34
36
  constructor(mockedResults?: SearchResultSet | undefined);
35
- query(): Promise<SearchResultSet>;
37
+ query(_query: SearchQuery, _options?: {
38
+ signal?: AbortSignal;
39
+ }): Promise<SearchResultSet>;
36
40
  }
37
41
 
38
42
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-search-react",
3
- "version": "1.9.5-next.2",
3
+ "version": "1.9.5",
4
4
  "backstage": {
5
5
  "role": "web-library",
6
6
  "pluginId": "search",
@@ -64,13 +64,13 @@
64
64
  "test": "backstage-cli package test"
65
65
  },
66
66
  "dependencies": {
67
- "@backstage/core-components": "0.18.2-next.2",
68
- "@backstage/core-plugin-api": "1.11.1-next.0",
69
- "@backstage/frontend-plugin-api": "0.12.1-next.1",
70
- "@backstage/plugin-search-common": "1.2.20-next.0",
71
- "@backstage/theme": "0.6.9-next.0",
72
- "@backstage/types": "1.2.2",
73
- "@backstage/version-bridge": "1.0.11",
67
+ "@backstage/core-components": "^0.18.2",
68
+ "@backstage/core-plugin-api": "^1.11.1",
69
+ "@backstage/frontend-plugin-api": "^0.12.1",
70
+ "@backstage/plugin-search-common": "^1.2.20",
71
+ "@backstage/theme": "^0.7.0",
72
+ "@backstage/types": "^1.2.2",
73
+ "@backstage/version-bridge": "^1.0.11",
74
74
  "@material-ui/core": "^4.12.2",
75
75
  "@material-ui/icons": "^4.9.1",
76
76
  "@material-ui/lab": "4.0.0-alpha.61",
@@ -80,11 +80,11 @@
80
80
  "uuid": "^11.0.2"
81
81
  },
82
82
  "devDependencies": {
83
- "@backstage/cli": "0.34.4-next.2",
84
- "@backstage/core-app-api": "1.19.1-next.0",
85
- "@backstage/frontend-app-api": "0.13.1-next.1",
86
- "@backstage/frontend-test-utils": "0.3.7-next.1",
87
- "@backstage/test-utils": "1.7.12-next.1",
83
+ "@backstage/cli": "^0.34.4",
84
+ "@backstage/core-app-api": "^1.19.1",
85
+ "@backstage/frontend-app-api": "^0.13.1",
86
+ "@backstage/frontend-test-utils": "^0.4.0",
87
+ "@backstage/test-utils": "^1.7.12",
88
88
  "@testing-library/dom": "^10.0.0",
89
89
  "@testing-library/jest-dom": "^6.0.0",
90
90
  "@testing-library/react": "^16.0.0",