@backstage/plugin-catalog-react 1.20.2-next.0 → 1.21.0-next.2

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,47 @@
1
1
  # @backstage/plugin-catalog-react
2
2
 
3
+ ## 1.21.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 0e9ec44: Introduced new `streamEntities` async generator method for the catalog.
8
+
9
+ Catalog API and Catalog Service now includes a `streamEntities` method that allows for streaming entities from the catalog.
10
+ This method is designed to handle large datasets efficiently by processing entities in a stream rather than loading them
11
+ all into memory at once. This is useful when you need to fetch a large number of entities but do not want to use pagination
12
+ or fetch all entities at once.
13
+
14
+ Example usage:
15
+
16
+ ```ts
17
+ const pageStream = catalogClient.streamEntities({ pageSize: 100 }, { token });
18
+ for await (const page of pageStream) {
19
+ // Handle page of entities
20
+ for (const entity of page) {
21
+ console.log(entity);
22
+ }
23
+ }
24
+ ```
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies
29
+ - @backstage/catalog-client@1.12.0-next.0
30
+ - @backstage/core-components@0.17.6-next.1
31
+ - @backstage/core-compat-api@0.5.2-next.2
32
+
33
+ ## 1.20.2-next.1
34
+
35
+ ### Patch Changes
36
+
37
+ - 0174799: Fix a potential race condition in EntityListProvider when selecting filters
38
+ - Updated dependencies
39
+ - @backstage/frontend-plugin-api@0.11.1-next.0
40
+ - @backstage/core-components@0.17.6-next.0
41
+ - @backstage/core-compat-api@0.5.2-next.1
42
+ - @backstage/frontend-test-utils@0.3.6-next.0
43
+ - @backstage/integration-react@1.2.10-next.0
44
+
3
45
  ## 1.20.2-next.0
4
46
 
5
47
  ### Patch Changes
@@ -1,14 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
+ import { useApi } from '@backstage/core-plugin-api';
2
3
  import { compact, isEqual } from 'lodash';
3
4
  import qs from 'qs';
4
- import { createContext, useState, useMemo, useCallback, useContext } from 'react';
5
+ import { createContext, useState, useMemo, useEffect, useCallback, useContext } from 'react';
5
6
  import { useLocation } from 'react-router-dom';
6
7
  import useAsyncFn from 'react-use/esm/useAsyncFn';
7
8
  import useDebounce from 'react-use/esm/useDebounce';
8
9
  import useMountedState from 'react-use/esm/useMountedState';
9
10
  import { catalogApiRef } from '../api.esm.js';
10
11
  import { reduceEntityFilters, reduceCatalogFilters, reduceBackendCatalogFilters } from '../utils/filters.esm.js';
11
- import { useApi } from '@backstage/core-plugin-api';
12
12
 
13
13
  const EntityListContext = createContext(void 0);
14
14
  const EntityListProvider = (props) => {
@@ -65,21 +65,11 @@ const EntityListProvider = (props) => {
65
65
  };
66
66
  }
67
67
  );
68
- const [{ loading, error }, refresh] = useAsyncFn(
68
+ const [{ value: resolvedValue, loading, error }, refresh] = useAsyncFn(
69
69
  async () => {
70
70
  const kindValue = requestedFilters.kind?.value?.toLocaleLowerCase("en-US");
71
71
  const adjustedFilters = kindValue === "user" || kindValue === "group" ? { ...requestedFilters, owners: void 0 } : requestedFilters;
72
72
  const compacted = compact(Object.values(adjustedFilters));
73
- const queryParams = Object.keys(requestedFilters).reduce(
74
- (params, key) => {
75
- const filter = requestedFilters[key];
76
- if (filter?.toQueryValue) {
77
- params[key] = filter.toQueryValue();
78
- }
79
- return params;
80
- },
81
- {}
82
- );
83
73
  if (paginationMode !== "none") {
84
74
  if (cursor) {
85
75
  if (cursor !== outputState.appliedCursor) {
@@ -88,14 +78,14 @@ const EntityListProvider = (props) => {
88
78
  cursor,
89
79
  limit
90
80
  });
91
- setOutputState({
81
+ return {
92
82
  appliedFilters: requestedFilters,
93
83
  appliedCursor: cursor,
94
84
  backendEntities: response.items,
95
85
  entities: response.items.filter(entityFilter),
96
86
  pageInfo: response.pageInfo,
97
87
  totalItems: response.totalItems
98
- });
88
+ };
99
89
  }
100
90
  } else {
101
91
  const entityFilter = reduceEntityFilters(compacted);
@@ -109,7 +99,7 @@ const EntityListProvider = (props) => {
109
99
  limit,
110
100
  offset
111
101
  });
112
- setOutputState({
102
+ return {
113
103
  appliedFilters: requestedFilters,
114
104
  backendEntities: response.items,
115
105
  entities: response.items.filter(entityFilter),
@@ -117,7 +107,7 @@ const EntityListProvider = (props) => {
117
107
  totalItems: response.totalItems,
118
108
  limit,
119
109
  offset
120
- });
110
+ };
121
111
  }
122
112
  }
123
113
  } else {
@@ -130,38 +120,23 @@ const EntityListProvider = (props) => {
130
120
  const response = await catalogApi.getEntities({
131
121
  filter: backendFilter
132
122
  });
133
- const entities = response.items.filter(entityFilter);
134
- setOutputState({
123
+ const entities2 = response.items.filter(entityFilter);
124
+ return {
135
125
  appliedFilters: requestedFilters,
136
126
  backendEntities: response.items,
137
- entities,
138
- totalItems: entities.length
139
- });
140
- } else {
141
- const entities = outputState.backendEntities.filter(entityFilter);
142
- setOutputState({
143
- appliedFilters: requestedFilters,
144
- backendEntities: outputState.backendEntities,
145
- entities,
146
- totalItems: entities.length
147
- });
127
+ entities: entities2,
128
+ totalItems: entities2.length
129
+ };
148
130
  }
131
+ const entities = outputState.backendEntities.filter(entityFilter);
132
+ return {
133
+ appliedFilters: requestedFilters,
134
+ backendEntities: outputState.backendEntities,
135
+ entities,
136
+ totalItems: entities.length
137
+ };
149
138
  }
150
- if (isMounted()) {
151
- const oldParams = qs.parse(location.search, {
152
- ignoreQueryPrefix: true
153
- });
154
- const newParams = qs.stringify(
155
- {
156
- ...oldParams,
157
- filters: queryParams,
158
- ...paginationMode === "none" ? {} : { cursor, limit, offset }
159
- },
160
- { addQueryPrefix: true, arrayFormat: "repeat" }
161
- );
162
- const newUrl = `${window.location.pathname}${newParams}`;
163
- window.history?.replaceState(null, document.title, newUrl);
164
- }
139
+ return void 0;
165
140
  },
166
141
  [
167
142
  catalogApi,
@@ -176,6 +151,46 @@ const EntityListProvider = (props) => {
176
151
  { loading: true }
177
152
  );
178
153
  useDebounce(refresh, 10, [requestedFilters, cursor, limit, offset]);
154
+ useEffect(() => {
155
+ if (resolvedValue === void 0) {
156
+ return;
157
+ }
158
+ setOutputState(resolvedValue);
159
+ if (isMounted()) {
160
+ const queryParams = Object.keys(requestedFilters).reduce(
161
+ (params, key) => {
162
+ const filter = requestedFilters[key];
163
+ if (filter?.toQueryValue) {
164
+ params[key] = filter.toQueryValue();
165
+ }
166
+ return params;
167
+ },
168
+ {}
169
+ );
170
+ const oldParams = qs.parse(location.search, {
171
+ ignoreQueryPrefix: true
172
+ });
173
+ const newParams = qs.stringify(
174
+ {
175
+ ...oldParams,
176
+ filters: queryParams,
177
+ ...paginationMode === "none" ? {} : { cursor, limit, offset }
178
+ },
179
+ { addQueryPrefix: true, arrayFormat: "repeat" }
180
+ );
181
+ const newUrl = `${window.location.pathname}${newParams}`;
182
+ window.history?.replaceState(null, document.title, newUrl);
183
+ }
184
+ }, [
185
+ cursor,
186
+ isMounted,
187
+ limit,
188
+ location.search,
189
+ offset,
190
+ requestedFilters,
191
+ resolvedValue,
192
+ paginationMode
193
+ ]);
179
194
  const updateFilters = useCallback(
180
195
  (update) => {
181
196
  if (paginationMode === "cursor") {
@@ -1 +1 @@
1
- {"version":3,"file":"useEntityListProvider.esm.js","sources":["../../src/hooks/useEntityListProvider.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { Entity } from '@backstage/catalog-model';\nimport { compact, isEqual } from 'lodash';\nimport qs from 'qs';\nimport {\n createContext,\n PropsWithChildren,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from 'react';\nimport { useLocation } from 'react-router-dom';\nimport useAsyncFn from 'react-use/esm/useAsyncFn';\nimport useDebounce from 'react-use/esm/useDebounce';\nimport useMountedState from 'react-use/esm/useMountedState';\nimport { catalogApiRef } from '../api';\nimport {\n EntityErrorFilter,\n EntityKindFilter,\n EntityLifecycleFilter,\n EntityNamespaceFilter,\n EntityOrderFilter,\n EntityOrphanFilter,\n EntityOwnerFilter,\n EntityTagFilter,\n EntityTextFilter,\n EntityTypeFilter,\n EntityUserFilter,\n UserListFilter,\n} from '../filters';\nimport { EntityFilter, EntityListPagination } from '../types';\nimport {\n reduceBackendCatalogFilters,\n reduceCatalogFilters,\n reduceEntityFilters,\n} from '../utils/filters';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { QueryEntitiesResponse } from '@backstage/catalog-client';\n\n/** @public */\nexport type DefaultEntityFilters = {\n kind?: EntityKindFilter;\n type?: EntityTypeFilter;\n user?: UserListFilter | EntityUserFilter;\n owners?: EntityOwnerFilter;\n lifecycles?: EntityLifecycleFilter;\n tags?: EntityTagFilter;\n text?: EntityTextFilter;\n orphan?: EntityOrphanFilter;\n error?: EntityErrorFilter;\n namespace?: EntityNamespaceFilter;\n order?: EntityOrderFilter;\n};\n\n/** @public */\nexport type PaginationMode = 'cursor' | 'offset' | 'none';\n\n/** @public */\nexport type EntityListContextProps<\n EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,\n> = {\n /**\n * The currently registered filters, adhering to the shape of DefaultEntityFilters or an extension\n * of that default (to add custom filter types).\n */\n filters: EntityFilters;\n\n /**\n * The resolved list of catalog entities, after all filters are applied.\n */\n entities: Entity[];\n\n /**\n * The resolved list of catalog entities, after _only catalog-backend_ filters are applied.\n */\n backendEntities: Entity[];\n\n /**\n * Update one or more of the registered filters. Optional filters can be set to `undefined` to\n * reset the filter.\n */\n updateFilters: (\n filters:\n | Partial<EntityFilters>\n | ((prevFilters: EntityFilters) => Partial<EntityFilters>),\n ) => void;\n\n /**\n * Filter values from query parameters.\n */\n queryParameters: Partial<Record<keyof EntityFilters, string | string[]>>;\n\n loading: boolean;\n error?: Error;\n\n pageInfo?: {\n next?: () => void;\n prev?: () => void;\n };\n totalItems?: number;\n limit: number;\n offset?: number;\n setLimit: (limit: number) => void;\n setOffset?: (offset: number) => void;\n paginationMode: PaginationMode;\n};\n\n/**\n * Creates new context for entity listing and filtering.\n * @public\n */\nexport const EntityListContext = createContext<\n EntityListContextProps<any> | undefined\n>(undefined);\n\ntype OutputState<EntityFilters extends DefaultEntityFilters> = {\n appliedFilters: EntityFilters;\n appliedCursor?: string;\n entities: Entity[];\n backendEntities: Entity[];\n pageInfo?: QueryEntitiesResponse['pageInfo'];\n totalItems?: number;\n offset?: number;\n limit?: number;\n};\n\n/**\n * @public\n */\nexport type EntityListProviderProps = PropsWithChildren<{\n pagination?: EntityListPagination;\n}>;\n\n/**\n * Provides entities and filters for a catalog listing.\n * @public\n */\nexport const EntityListProvider = <EntityFilters extends DefaultEntityFilters>(\n props: EntityListProviderProps,\n) => {\n const isMounted = useMountedState();\n const catalogApi = useApi(catalogApiRef);\n const [requestedFilters, setRequestedFilters] = useState<EntityFilters>(\n {} as EntityFilters,\n );\n\n // We use react-router's useLocation hook so updates from external sources trigger an update to\n // the queryParameters in outputState. Updates from this hook use replaceState below and won't\n // trigger a useLocation change; this would instead come from an external source, such as a manual\n // update of the URL or two catalog sidebar links with different catalog filters.\n const location = useLocation();\n\n const getPaginationMode = (): PaginationMode => {\n if (props.pagination === true) {\n return 'cursor';\n }\n return typeof props.pagination === 'object'\n ? props.pagination.mode ?? 'cursor'\n : 'none';\n };\n\n const paginationMode = getPaginationMode();\n const paginationLimit =\n typeof props.pagination === 'object' ? props.pagination.limit ?? 20 : 20;\n\n const {\n queryParameters,\n cursor: initialCursor,\n offset: initialOffset,\n limit: initialLimit,\n } = useMemo(() => {\n const parsed = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n });\n\n let limit = paginationLimit;\n if (typeof parsed.limit === 'string') {\n const queryLimit = Number.parseInt(parsed.limit, 10);\n if (!isNaN(queryLimit)) {\n limit = queryLimit;\n }\n }\n\n const offset =\n typeof parsed.offset === 'string' && paginationMode === 'offset'\n ? Number.parseInt(parsed.offset, 10)\n : undefined;\n\n return {\n queryParameters: (parsed.filters ?? {}) as Record<\n string,\n string | string[]\n >,\n cursor:\n typeof parsed.cursor === 'string' && paginationMode === 'cursor'\n ? parsed.cursor\n : undefined,\n offset:\n paginationMode === 'offset' && offset && !isNaN(offset)\n ? offset\n : undefined,\n limit,\n };\n }, [paginationMode, location.search, paginationLimit]);\n\n const [cursor, setCursor] = useState(initialCursor);\n const [offset, setOffset] = useState<number | undefined>(initialOffset);\n const [limit, setLimit] = useState(initialLimit);\n\n const [outputState, setOutputState] = useState<OutputState<EntityFilters>>(\n () => {\n return {\n appliedFilters: {} as EntityFilters,\n entities: [],\n backendEntities: [],\n pageInfo: {},\n offset,\n limit,\n };\n },\n );\n\n // The main async filter worker. Note that while it has a lot of dependencies\n // in terms of its implementation, the triggering only happens (debounced)\n // based on the requested filters changing.\n const [{ loading, error }, refresh] = useAsyncFn(\n async () => {\n const kindValue =\n requestedFilters.kind?.value?.toLocaleLowerCase('en-US');\n const adjustedFilters =\n kindValue === 'user' || kindValue === 'group'\n ? { ...requestedFilters, owners: undefined }\n : requestedFilters;\n const compacted = compact(Object.values(adjustedFilters));\n\n const queryParams = Object.keys(requestedFilters).reduce(\n (params, key) => {\n const filter = requestedFilters[key as keyof EntityFilters] as\n | EntityFilter\n | undefined;\n if (filter?.toQueryValue) {\n params[key] = filter.toQueryValue();\n }\n return params;\n },\n {} as Record<string, string | string[]>,\n );\n\n if (paginationMode !== 'none') {\n if (cursor) {\n if (cursor !== outputState.appliedCursor) {\n const entityFilter = reduceEntityFilters(compacted);\n const response = await catalogApi.queryEntities({\n cursor,\n limit,\n });\n setOutputState({\n appliedFilters: requestedFilters,\n appliedCursor: cursor,\n backendEntities: response.items,\n entities: response.items.filter(entityFilter),\n pageInfo: response.pageInfo,\n totalItems: response.totalItems,\n });\n }\n } else {\n const entityFilter = reduceEntityFilters(compacted);\n const backendFilter = reduceCatalogFilters(compacted);\n const previousBackendFilter = reduceCatalogFilters(\n compact(Object.values(outputState.appliedFilters)),\n );\n\n if (\n (paginationMode === 'offset' &&\n (outputState.limit !== limit || outputState.offset !== offset)) ||\n !isEqual(previousBackendFilter, backendFilter)\n ) {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit,\n offset,\n });\n setOutputState({\n appliedFilters: requestedFilters,\n backendEntities: response.items,\n entities: response.items.filter(entityFilter),\n pageInfo: response.pageInfo,\n totalItems: response.totalItems,\n limit,\n offset,\n });\n }\n }\n } else {\n const entityFilter = reduceEntityFilters(compacted);\n const backendFilter = reduceBackendCatalogFilters(compacted);\n const previousBackendFilter = reduceBackendCatalogFilters(\n compact(Object.values(outputState.appliedFilters)),\n );\n\n // TODO(mtlewis): currently entities will never be requested unless\n // there's at least one filter, we should allow an initial request\n // to happen with no filters.\n if (!isEqual(previousBackendFilter, backendFilter)) {\n // TODO(timbonicus): should limit fields here, but would need filter\n // fields + table columns\n const response = await catalogApi.getEntities({\n filter: backendFilter,\n });\n const entities = response.items.filter(entityFilter);\n setOutputState({\n appliedFilters: requestedFilters,\n backendEntities: response.items,\n entities,\n totalItems: entities.length,\n });\n } else {\n const entities = outputState.backendEntities.filter(entityFilter);\n setOutputState({\n appliedFilters: requestedFilters,\n backendEntities: outputState.backendEntities,\n entities,\n totalItems: entities.length,\n });\n }\n }\n\n if (isMounted()) {\n const oldParams = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n });\n const newParams = qs.stringify(\n {\n ...oldParams,\n filters: queryParams,\n ...(paginationMode === 'none' ? {} : { cursor, limit, offset }),\n },\n { addQueryPrefix: true, arrayFormat: 'repeat' },\n );\n const newUrl = `${window.location.pathname}${newParams}`;\n // We use direct history manipulation since useSearchParams and\n // useNavigate in react-router-dom cause unnecessary extra rerenders.\n // Also make sure to replace the state rather than pushing, since we\n // don't want there to be back/forward slots for every single filter\n // change.\n window.history?.replaceState(null, document.title, newUrl);\n }\n },\n [\n catalogApi,\n queryParameters,\n requestedFilters,\n outputState,\n cursor,\n paginationMode,\n limit,\n offset,\n ],\n { loading: true },\n );\n\n // Slight debounce on the refresh, since (especially on page load) several\n // filters will be calling this in rapid succession.\n useDebounce(refresh, 10, [requestedFilters, cursor, limit, offset]);\n\n const updateFilters = useCallback(\n (\n update:\n | Partial<EntityFilter>\n | ((prevFilters: EntityFilters) => Partial<EntityFilters>),\n ) => {\n // changing filters will affect pagination, so we need to reset\n // the cursor and start from the first page.\n // TODO(vinzscam): this is currently causing issues at page reload\n // where the state is not kept. Unfortunately we need to rethink\n // the way filters work in order to fix this.\n if (paginationMode === 'cursor') {\n setCursor(undefined);\n } else if (paginationMode === 'offset') {\n // Same thing with offset\n setOffset(0);\n }\n setRequestedFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [paginationMode],\n );\n\n const pageInfo = useMemo(() => {\n if (paginationMode !== 'cursor') {\n return undefined;\n }\n\n const prevCursor = outputState.pageInfo?.prevCursor;\n const nextCursor = outputState.pageInfo?.nextCursor;\n return {\n prev: prevCursor ? () => setCursor(prevCursor) : undefined,\n next: nextCursor ? () => setCursor(nextCursor) : undefined,\n };\n }, [paginationMode, outputState.pageInfo]);\n\n const value = useMemo(\n () => ({\n filters: outputState.appliedFilters,\n entities: outputState.entities,\n backendEntities: outputState.backendEntities,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n totalItems: outputState.totalItems,\n limit,\n offset,\n setLimit,\n setOffset,\n paginationMode,\n }),\n [\n outputState,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n limit,\n offset,\n paginationMode,\n setLimit,\n setOffset,\n ],\n );\n\n return (\n <EntityListContext.Provider value={value}>\n {props.children}\n </EntityListContext.Provider>\n );\n};\n\n/**\n * Hook for interacting with the entity list context provided by the {@link EntityListProvider}.\n * @public\n */\nexport function useEntityList<\n EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,\n>(): EntityListContextProps<EntityFilters> {\n const context = useContext(EntityListContext);\n if (!context)\n throw new Error('useEntityList must be used within EntityListProvider');\n return context;\n}\n"],"names":["limit","offset"],"mappings":";;;;;;;;;;;;AA+HO,MAAM,iBAAA,GAAoB,cAE/B,MAAS;AAwBJ,MAAM,kBAAA,GAAqB,CAChC,KAAA,KACG;AACH,EAAA,MAAM,YAAY,eAAA,EAAgB;AAClC,EAAA,MAAM,UAAA,GAAa,OAAO,aAAa,CAAA;AACvC,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAI,QAAA;AAAA,IAC9C;AAAC,GACH;AAMA,EAAA,MAAM,WAAW,WAAA,EAAY;AAE7B,EAAA,MAAM,oBAAoB,MAAsB;AAC9C,IAAA,IAAI,KAAA,CAAM,eAAe,IAAA,EAAM;AAC7B,MAAA,OAAO,QAAA;AAAA,IACT;AACA,IAAA,OAAO,OAAO,KAAA,CAAM,UAAA,KAAe,WAC/B,KAAA,CAAM,UAAA,CAAW,QAAQ,QAAA,GACzB,MAAA;AAAA,EACN,CAAA;AAEA,EAAA,MAAM,iBAAiB,iBAAA,EAAkB;AACzC,EAAA,MAAM,eAAA,GACJ,OAAO,KAAA,CAAM,UAAA,KAAe,WAAW,KAAA,CAAM,UAAA,CAAW,SAAS,EAAA,GAAK,EAAA;AAExE,EAAA,MAAM;AAAA,IACJ,eAAA;AAAA,IACA,MAAA,EAAQ,aAAA;AAAA,IACR,MAAA,EAAQ,aAAA;AAAA,IACR,KAAA,EAAO;AAAA,GACT,GAAI,QAAQ,MAAM;AAChB,IAAA,MAAM,MAAA,GAAS,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,MAAA,EAAQ;AAAA,MACvC,iBAAA,EAAmB;AAAA,KACpB,CAAA;AAED,IAAA,IAAIA,MAAAA,GAAQ,eAAA;AACZ,IAAA,IAAI,OAAO,MAAA,CAAO,KAAA,KAAU,QAAA,EAAU;AACpC,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,OAAO,EAAE,CAAA;AACnD,MAAA,IAAI,CAAC,KAAA,CAAM,UAAU,CAAA,EAAG;AACtB,QAAAA,MAAAA,GAAQ,UAAA;AAAA,MACV;AAAA,IACF;AAEA,IAAA,MAAMC,OAAAA,GACJ,OAAO,MAAA,CAAO,MAAA,KAAW,QAAA,IAAY,cAAA,KAAmB,QAAA,GACpD,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,MAAA,EAAQ,EAAE,CAAA,GACjC,MAAA;AAEN,IAAA,OAAO;AAAA,MACL,eAAA,EAAkB,MAAA,CAAO,OAAA,IAAW,EAAC;AAAA,MAIrC,MAAA,EACE,OAAO,MAAA,CAAO,MAAA,KAAW,YAAY,cAAA,KAAmB,QAAA,GACpD,OAAO,MAAA,GACP,MAAA;AAAA,MACN,MAAA,EACE,mBAAmB,QAAA,IAAYA,OAAAA,IAAU,CAAC,KAAA,CAAMA,OAAM,IAClDA,OAAAA,GACA,MAAA;AAAA,MACN,KAAA,EAAAD;AAAA,KACF;AAAA,EACF,GAAG,CAAC,cAAA,EAAgB,QAAA,CAAS,MAAA,EAAQ,eAAe,CAAC,CAAA;AAErD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,aAAa,CAAA;AAClD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAA6B,aAAa,CAAA;AACtE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,YAAY,CAAA;AAE/C,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA;AAAA,IACpC,MAAM;AACJ,MAAA,OAAO;AAAA,QACL,gBAAgB,EAAC;AAAA,QACjB,UAAU,EAAC;AAAA,QACX,iBAAiB,EAAC;AAAA,QAClB,UAAU,EAAC;AAAA,QACX,MAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AAAA,GACF;AAKA,EAAA,MAAM,CAAC,EAAE,OAAA,EAAS,KAAA,EAAM,EAAG,OAAO,CAAA,GAAI,UAAA;AAAA,IACpC,YAAY;AACV,MAAA,MAAM,SAAA,GACJ,gBAAA,CAAiB,IAAA,EAAM,KAAA,EAAO,kBAAkB,OAAO,CAAA;AACzD,MAAA,MAAM,eAAA,GACJ,SAAA,KAAc,MAAA,IAAU,SAAA,KAAc,OAAA,GAClC,EAAE,GAAG,gBAAA,EAAkB,MAAA,EAAQ,MAAA,EAAU,GACzC,gBAAA;AACN,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,eAAe,CAAC,CAAA;AAExD,MAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,CAAK,gBAAgB,CAAA,CAAE,MAAA;AAAA,QAChD,CAAC,QAAQ,GAAA,KAAQ;AACf,UAAA,MAAM,MAAA,GAAS,iBAAiB,GAA0B,CAAA;AAG1D,UAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,YAAA,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA,CAAO,YAAA,EAAa;AAAA,UACpC;AACA,UAAA,OAAO,MAAA;AAAA,QACT,CAAA;AAAA,QACA;AAAC,OACH;AAEA,MAAA,IAAI,mBAAmB,MAAA,EAAQ;AAC7B,QAAA,IAAI,MAAA,EAAQ;AACV,UAAA,IAAI,MAAA,KAAW,YAAY,aAAA,EAAe;AACxC,YAAA,MAAM,YAAA,GAAe,oBAAoB,SAAS,CAAA;AAClD,YAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,aAAA,CAAc;AAAA,cAC9C,MAAA;AAAA,cACA;AAAA,aACD,CAAA;AACD,YAAA,cAAA,CAAe;AAAA,cACb,cAAA,EAAgB,gBAAA;AAAA,cAChB,aAAA,EAAe,MAAA;AAAA,cACf,iBAAiB,QAAA,CAAS,KAAA;AAAA,cAC1B,QAAA,EAAU,QAAA,CAAS,KAAA,CAAM,MAAA,CAAO,YAAY,CAAA;AAAA,cAC5C,UAAU,QAAA,CAAS,QAAA;AAAA,cACnB,YAAY,QAAA,CAAS;AAAA,aACtB,CAAA;AAAA,UACH;AAAA,QACF,CAAA,MAAO;AACL,UAAA,MAAM,YAAA,GAAe,oBAAoB,SAAS,CAAA;AAClD,UAAA,MAAM,aAAA,GAAgB,qBAAqB,SAAS,CAAA;AACpD,UAAA,MAAM,qBAAA,GAAwB,oBAAA;AAAA,YAC5B,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,cAAc,CAAC;AAAA,WACnD;AAEA,UAAA,IACG,cAAA,KAAmB,QAAA,KACjB,WAAA,CAAY,KAAA,KAAU,KAAA,IAAS,WAAA,CAAY,MAAA,KAAW,MAAA,CAAA,IACzD,CAAC,OAAA,CAAQ,qBAAA,EAAuB,aAAa,CAAA,EAC7C;AACA,YAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,aAAA,CAAc;AAAA,cAC9C,GAAG,aAAA;AAAA,cACH,KAAA;AAAA,cACA;AAAA,aACD,CAAA;AACD,YAAA,cAAA,CAAe;AAAA,cACb,cAAA,EAAgB,gBAAA;AAAA,cAChB,iBAAiB,QAAA,CAAS,KAAA;AAAA,cAC1B,QAAA,EAAU,QAAA,CAAS,KAAA,CAAM,MAAA,CAAO,YAAY,CAAA;AAAA,cAC5C,UAAU,QAAA,CAAS,QAAA;AAAA,cACnB,YAAY,QAAA,CAAS,UAAA;AAAA,cACrB,KAAA;AAAA,cACA;AAAA,aACD,CAAA;AAAA,UACH;AAAA,QACF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,MAAM,YAAA,GAAe,oBAAoB,SAAS,CAAA;AAClD,QAAA,MAAM,aAAA,GAAgB,4BAA4B,SAAS,CAAA;AAC3D,QAAA,MAAM,qBAAA,GAAwB,2BAAA;AAAA,UAC5B,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,WAAA,CAAY,cAAc,CAAC;AAAA,SACnD;AAKA,QAAA,IAAI,CAAC,OAAA,CAAQ,qBAAA,EAAuB,aAAa,CAAA,EAAG;AAGlD,UAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,WAAA,CAAY;AAAA,YAC5C,MAAA,EAAQ;AAAA,WACT,CAAA;AACD,UAAA,MAAM,QAAA,GAAW,QAAA,CAAS,KAAA,CAAM,MAAA,CAAO,YAAY,CAAA;AACnD,UAAA,cAAA,CAAe;AAAA,YACb,cAAA,EAAgB,gBAAA;AAAA,YAChB,iBAAiB,QAAA,CAAS,KAAA;AAAA,YAC1B,QAAA;AAAA,YACA,YAAY,QAAA,CAAS;AAAA,WACtB,CAAA;AAAA,QACH,CAAA,MAAO;AACL,UAAA,MAAM,QAAA,GAAW,WAAA,CAAY,eAAA,CAAgB,MAAA,CAAO,YAAY,CAAA;AAChE,UAAA,cAAA,CAAe;AAAA,YACb,cAAA,EAAgB,gBAAA;AAAA,YAChB,iBAAiB,WAAA,CAAY,eAAA;AAAA,YAC7B,QAAA;AAAA,YACA,YAAY,QAAA,CAAS;AAAA,WACtB,CAAA;AAAA,QACH;AAAA,MACF;AAEA,MAAA,IAAI,WAAU,EAAG;AACf,QAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,MAAA,EAAQ;AAAA,UAC1C,iBAAA,EAAmB;AAAA,SACpB,CAAA;AACD,QAAA,MAAM,YAAY,EAAA,CAAG,SAAA;AAAA,UACnB;AAAA,YACE,GAAG,SAAA;AAAA,YACH,OAAA,EAAS,WAAA;AAAA,YACT,GAAI,mBAAmB,MAAA,GAAS,KAAK,EAAE,MAAA,EAAQ,OAAO,MAAA;AAAO,WAC/D;AAAA,UACA,EAAE,cAAA,EAAgB,IAAA,EAAM,WAAA,EAAa,QAAA;AAAS,SAChD;AACA,QAAA,MAAM,SAAS,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,QAAQ,GAAG,SAAS,CAAA,CAAA;AAMtD,QAAA,MAAA,CAAO,OAAA,EAAS,YAAA,CAAa,IAAA,EAAM,QAAA,CAAS,OAAO,MAAM,CAAA;AAAA,MAC3D;AAAA,IACF,CAAA;AAAA,IACA;AAAA,MACE,UAAA;AAAA,MACA,eAAA;AAAA,MACA,gBAAA;AAAA,MACA,WAAA;AAAA,MACA,MAAA;AAAA,MACA,cAAA;AAAA,MACA,KAAA;AAAA,MACA;AAAA,KACF;AAAA,IACA,EAAE,SAAS,IAAA;AAAK,GAClB;AAIA,EAAA,WAAA,CAAY,SAAS,EAAA,EAAI,CAAC,kBAAkB,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAC,CAAA;AAElE,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CACE,MAAA,KAGG;AAMH,MAAA,IAAI,mBAAmB,QAAA,EAAU;AAC/B,QAAA,SAAA,CAAU,MAAS,CAAA;AAAA,MACrB,CAAA,MAAA,IAAW,mBAAmB,QAAA,EAAU;AAEtC,QAAA,SAAA,CAAU,CAAC,CAAA;AAAA,MACb;AACA,MAAA,mBAAA,CAAoB,CAAA,WAAA,KAAe;AACjC,QAAA,MAAM,aACJ,OAAO,MAAA,KAAW,UAAA,GAAa,MAAA,CAAO,WAAW,CAAA,GAAI,MAAA;AACvD,QAAA,OAAO,EAAE,GAAG,WAAA,EAAa,GAAG,UAAA,EAAW;AAAA,MACzC,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IACA,CAAC,cAAc;AAAA,GACjB;AAEA,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,mBAAmB,QAAA,EAAU;AAC/B,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,MAAM,UAAA,GAAa,YAAY,QAAA,EAAU,UAAA;AACzC,IAAA,MAAM,UAAA,GAAa,YAAY,QAAA,EAAU,UAAA;AACzC,IAAA,OAAO;AAAA,MACL,IAAA,EAAM,UAAA,GAAa,MAAM,SAAA,CAAU,UAAU,CAAA,GAAI,MAAA;AAAA,MACjD,IAAA,EAAM,UAAA,GAAa,MAAM,SAAA,CAAU,UAAU,CAAA,GAAI;AAAA,KACnD;AAAA,EACF,CAAA,EAAG,CAAC,cAAA,EAAgB,WAAA,CAAY,QAAQ,CAAC,CAAA;AAEzC,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,SAAS,WAAA,CAAY,cAAA;AAAA,MACrB,UAAU,WAAA,CAAY,QAAA;AAAA,MACtB,iBAAiB,WAAA,CAAY,eAAA;AAAA,MAC7B,aAAA;AAAA,MACA,eAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA,YAAY,WAAA,CAAY,UAAA;AAAA,MACxB,KAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,WAAA;AAAA,MACA,aAAA;AAAA,MACA,eAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,cAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,2BACG,iBAAA,CAAkB,QAAA,EAAlB,EAA2B,KAAA,EACzB,gBAAM,QAAA,EACT,CAAA;AAEJ;AAMO,SAAS,aAAA,GAE2B;AACzC,EAAA,MAAM,OAAA,GAAU,WAAW,iBAAiB,CAAA;AAC5C,EAAA,IAAI,CAAC,OAAA;AACH,IAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AACxE,EAAA,OAAO,OAAA;AACT;;;;"}
1
+ {"version":3,"file":"useEntityListProvider.esm.js","sources":["../../src/hooks/useEntityListProvider.tsx"],"sourcesContent":["/*\n * Copyright 2020 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 { QueryEntitiesResponse } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { compact, isEqual } from 'lodash';\nimport qs from 'qs';\nimport {\n createContext,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport { useLocation } from 'react-router-dom';\nimport useAsyncFn from 'react-use/esm/useAsyncFn';\nimport useDebounce from 'react-use/esm/useDebounce';\nimport useMountedState from 'react-use/esm/useMountedState';\nimport { catalogApiRef } from '../api';\nimport {\n EntityErrorFilter,\n EntityKindFilter,\n EntityLifecycleFilter,\n EntityNamespaceFilter,\n EntityOrderFilter,\n EntityOrphanFilter,\n EntityOwnerFilter,\n EntityTagFilter,\n EntityTextFilter,\n EntityTypeFilter,\n EntityUserFilter,\n UserListFilter,\n} from '../filters';\nimport { EntityFilter, EntityListPagination } from '../types';\nimport {\n reduceBackendCatalogFilters,\n reduceCatalogFilters,\n reduceEntityFilters,\n} from '../utils/filters';\n\n/** @public */\nexport type DefaultEntityFilters = {\n kind?: EntityKindFilter;\n type?: EntityTypeFilter;\n user?: UserListFilter | EntityUserFilter;\n owners?: EntityOwnerFilter;\n lifecycles?: EntityLifecycleFilter;\n tags?: EntityTagFilter;\n text?: EntityTextFilter;\n orphan?: EntityOrphanFilter;\n error?: EntityErrorFilter;\n namespace?: EntityNamespaceFilter;\n order?: EntityOrderFilter;\n};\n\n/** @public */\nexport type PaginationMode = 'cursor' | 'offset' | 'none';\n\n/** @public */\nexport type EntityListContextProps<\n EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,\n> = {\n /**\n * The currently registered filters, adhering to the shape of DefaultEntityFilters or an extension\n * of that default (to add custom filter types).\n */\n filters: EntityFilters;\n\n /**\n * The resolved list of catalog entities, after all filters are applied.\n */\n entities: Entity[];\n\n /**\n * The resolved list of catalog entities, after _only catalog-backend_ filters are applied.\n */\n backendEntities: Entity[];\n\n /**\n * Update one or more of the registered filters. Optional filters can be set to `undefined` to\n * reset the filter.\n */\n updateFilters: (\n filters:\n | Partial<EntityFilters>\n | ((prevFilters: EntityFilters) => Partial<EntityFilters>),\n ) => void;\n\n /**\n * Filter values from query parameters.\n */\n queryParameters: Partial<Record<keyof EntityFilters, string | string[]>>;\n\n loading: boolean;\n error?: Error;\n\n pageInfo?: {\n next?: () => void;\n prev?: () => void;\n };\n totalItems?: number;\n limit: number;\n offset?: number;\n setLimit: (limit: number) => void;\n setOffset?: (offset: number) => void;\n paginationMode: PaginationMode;\n};\n\n/**\n * Creates new context for entity listing and filtering.\n * @public\n */\nexport const EntityListContext = createContext<\n EntityListContextProps<any> | undefined\n>(undefined);\n\ntype OutputState<EntityFilters extends DefaultEntityFilters> = {\n appliedFilters: EntityFilters;\n appliedCursor?: string;\n entities: Entity[];\n backendEntities: Entity[];\n pageInfo?: QueryEntitiesResponse['pageInfo'];\n totalItems?: number;\n offset?: number;\n limit?: number;\n};\n\n/**\n * @public\n */\nexport type EntityListProviderProps = PropsWithChildren<{\n pagination?: EntityListPagination;\n}>;\n\n/**\n * Provides entities and filters for a catalog listing.\n * @public\n */\nexport const EntityListProvider = <EntityFilters extends DefaultEntityFilters>(\n props: EntityListProviderProps,\n) => {\n const isMounted = useMountedState();\n const catalogApi = useApi(catalogApiRef);\n const [requestedFilters, setRequestedFilters] = useState<EntityFilters>(\n {} as EntityFilters,\n );\n\n // We use react-router's useLocation hook so updates from external sources trigger an update to\n // the queryParameters in outputState. Updates from this hook use replaceState below and won't\n // trigger a useLocation change; this would instead come from an external source, such as a manual\n // update of the URL or two catalog sidebar links with different catalog filters.\n const location = useLocation();\n\n const getPaginationMode = (): PaginationMode => {\n if (props.pagination === true) {\n return 'cursor';\n }\n return typeof props.pagination === 'object'\n ? props.pagination.mode ?? 'cursor'\n : 'none';\n };\n\n const paginationMode = getPaginationMode();\n const paginationLimit =\n typeof props.pagination === 'object' ? props.pagination.limit ?? 20 : 20;\n\n const {\n queryParameters,\n cursor: initialCursor,\n offset: initialOffset,\n limit: initialLimit,\n } = useMemo(() => {\n const parsed = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n });\n\n let limit = paginationLimit;\n if (typeof parsed.limit === 'string') {\n const queryLimit = Number.parseInt(parsed.limit, 10);\n if (!isNaN(queryLimit)) {\n limit = queryLimit;\n }\n }\n\n const offset =\n typeof parsed.offset === 'string' && paginationMode === 'offset'\n ? Number.parseInt(parsed.offset, 10)\n : undefined;\n\n return {\n queryParameters: (parsed.filters ?? {}) as Record<\n string,\n string | string[]\n >,\n cursor:\n typeof parsed.cursor === 'string' && paginationMode === 'cursor'\n ? parsed.cursor\n : undefined,\n offset:\n paginationMode === 'offset' && offset && !isNaN(offset)\n ? offset\n : undefined,\n limit,\n };\n }, [paginationMode, location.search, paginationLimit]);\n\n const [cursor, setCursor] = useState(initialCursor);\n const [offset, setOffset] = useState<number | undefined>(initialOffset);\n const [limit, setLimit] = useState(initialLimit);\n\n const [outputState, setOutputState] = useState<OutputState<EntityFilters>>(\n () => {\n return {\n appliedFilters: {} as EntityFilters,\n entities: [],\n backendEntities: [],\n pageInfo: {},\n offset,\n limit,\n };\n },\n );\n\n // The main async filter worker. Note that while it has a lot of dependencies\n // in terms of its implementation, the triggering only happens (debounced)\n // based on the requested filters changing.\n const [{ value: resolvedValue, loading, error }, refresh] = useAsyncFn(\n async () => {\n const kindValue =\n requestedFilters.kind?.value?.toLocaleLowerCase('en-US');\n const adjustedFilters =\n kindValue === 'user' || kindValue === 'group'\n ? { ...requestedFilters, owners: undefined }\n : requestedFilters;\n const compacted = compact(Object.values(adjustedFilters));\n\n if (paginationMode !== 'none') {\n if (cursor) {\n if (cursor !== outputState.appliedCursor) {\n const entityFilter = reduceEntityFilters(compacted);\n const response = await catalogApi.queryEntities({\n cursor,\n limit,\n });\n return {\n appliedFilters: requestedFilters,\n appliedCursor: cursor,\n backendEntities: response.items,\n entities: response.items.filter(entityFilter),\n pageInfo: response.pageInfo,\n totalItems: response.totalItems,\n };\n }\n } else {\n const entityFilter = reduceEntityFilters(compacted);\n const backendFilter = reduceCatalogFilters(compacted);\n const previousBackendFilter = reduceCatalogFilters(\n compact(Object.values(outputState.appliedFilters)),\n );\n\n if (\n (paginationMode === 'offset' &&\n (outputState.limit !== limit || outputState.offset !== offset)) ||\n !isEqual(previousBackendFilter, backendFilter)\n ) {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit,\n offset,\n });\n return {\n appliedFilters: requestedFilters,\n backendEntities: response.items,\n entities: response.items.filter(entityFilter),\n pageInfo: response.pageInfo,\n totalItems: response.totalItems,\n limit,\n offset,\n };\n }\n }\n } else {\n const entityFilter = reduceEntityFilters(compacted);\n const backendFilter = reduceBackendCatalogFilters(compacted);\n const previousBackendFilter = reduceBackendCatalogFilters(\n compact(Object.values(outputState.appliedFilters)),\n );\n\n // TODO(mtlewis): currently entities will never be requested unless\n // there's at least one filter, we should allow an initial request\n // to happen with no filters.\n if (!isEqual(previousBackendFilter, backendFilter)) {\n // TODO(timbonicus): should limit fields here, but would need filter\n // fields + table columns\n const response = await catalogApi.getEntities({\n filter: backendFilter,\n });\n const entities = response.items.filter(entityFilter);\n return {\n appliedFilters: requestedFilters,\n backendEntities: response.items,\n entities,\n totalItems: entities.length,\n };\n }\n const entities = outputState.backendEntities.filter(entityFilter);\n return {\n appliedFilters: requestedFilters,\n backendEntities: outputState.backendEntities,\n entities,\n totalItems: entities.length,\n };\n }\n return undefined;\n },\n [\n catalogApi,\n queryParameters,\n requestedFilters,\n outputState,\n cursor,\n paginationMode,\n limit,\n offset,\n ],\n { loading: true },\n );\n\n // Slight debounce on the refresh, since (especially on page load) several\n // filters will be calling this in rapid succession.\n useDebounce(refresh, 10, [requestedFilters, cursor, limit, offset]);\n\n useEffect(() => {\n if (resolvedValue === undefined) {\n return;\n }\n setOutputState(resolvedValue);\n if (isMounted()) {\n const queryParams = Object.keys(requestedFilters).reduce(\n (params, key) => {\n const filter = requestedFilters[key as keyof EntityFilters] as\n | EntityFilter\n | undefined;\n if (filter?.toQueryValue) {\n params[key] = filter.toQueryValue();\n }\n return params;\n },\n {} as Record<string, string | string[]>,\n );\n\n const oldParams = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n });\n const newParams = qs.stringify(\n {\n ...oldParams,\n filters: queryParams,\n ...(paginationMode === 'none' ? {} : { cursor, limit, offset }),\n },\n { addQueryPrefix: true, arrayFormat: 'repeat' },\n );\n const newUrl = `${window.location.pathname}${newParams}`;\n // We use direct history manipulation since useSearchParams and\n // useNavigate in react-router-dom cause unnecessary extra rerenders.\n // Also make sure to replace the state rather than pushing, since we\n // don't want there to be back/forward slots for every single filter\n // change.\n window.history?.replaceState(null, document.title, newUrl);\n }\n }, [\n cursor,\n isMounted,\n limit,\n location.search,\n offset,\n requestedFilters,\n resolvedValue,\n paginationMode,\n ]);\n\n const updateFilters = useCallback(\n (\n update:\n | Partial<EntityFilter>\n | ((prevFilters: EntityFilters) => Partial<EntityFilters>),\n ) => {\n // changing filters will affect pagination, so we need to reset\n // the cursor and start from the first page.\n // TODO(vinzscam): this is currently causing issues at page reload\n // where the state is not kept. Unfortunately we need to rethink\n // the way filters work in order to fix this.\n if (paginationMode === 'cursor') {\n setCursor(undefined);\n } else if (paginationMode === 'offset') {\n // Same thing with offset\n setOffset(0);\n }\n setRequestedFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [paginationMode],\n );\n\n const pageInfo = useMemo(() => {\n if (paginationMode !== 'cursor') {\n return undefined;\n }\n\n const prevCursor = outputState.pageInfo?.prevCursor;\n const nextCursor = outputState.pageInfo?.nextCursor;\n return {\n prev: prevCursor ? () => setCursor(prevCursor) : undefined,\n next: nextCursor ? () => setCursor(nextCursor) : undefined,\n };\n }, [paginationMode, outputState.pageInfo]);\n\n const value = useMemo(\n () => ({\n filters: outputState.appliedFilters,\n entities: outputState.entities,\n backendEntities: outputState.backendEntities,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n totalItems: outputState.totalItems,\n limit,\n offset,\n setLimit,\n setOffset,\n paginationMode,\n }),\n [\n outputState,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n limit,\n offset,\n paginationMode,\n setLimit,\n setOffset,\n ],\n );\n\n return (\n <EntityListContext.Provider value={value}>\n {props.children}\n </EntityListContext.Provider>\n );\n};\n\n/**\n * Hook for interacting with the entity list context provided by the {@link EntityListProvider}.\n * @public\n */\nexport function useEntityList<\n EntityFilters extends DefaultEntityFilters = DefaultEntityFilters,\n>(): EntityListContextProps<EntityFilters> {\n const context = useContext(EntityListContext);\n if (!context)\n throw new Error('useEntityList must be used within EntityListProvider');\n return context;\n}\n"],"names":["limit","offset","entities"],"mappings":"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"}
@@ -48,7 +48,9 @@ function catalogApiMock(options) {
48
48
  removeLocationById: jest.fn(),
49
49
  getLocationByEntity: jest.fn(),
50
50
  validateEntity: jest.fn(),
51
- analyzeLocation: jest.fn()
51
+ analyzeLocation: jest.fn(),
52
+ streamEntities: jest.fn(),
53
+ streamEntityPages: jest.fn()
52
54
  }));
53
55
  })(catalogApiMock || (catalogApiMock = {}));
54
56
 
@@ -1 +1 @@
1
- {"version":3,"file":"catalogApiMock.esm.js","sources":["../../src/testUtils/catalogApiMock.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n ApiFactory,\n ApiRef,\n createApiFactory,\n} from '@backstage/frontend-plugin-api';\nimport { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils';\nimport { Entity } from '@backstage/catalog-model';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { ApiMock } from '@backstage/frontend-test-utils';\n\n/** @internal */\nfunction simpleMock<TApi>(\n ref: ApiRef<TApi>,\n mockFactory: () => jest.Mocked<TApi>,\n): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {\n return partialImpl => {\n const mock = mockFactory();\n if (partialImpl) {\n for (const [key, impl] of Object.entries(partialImpl)) {\n if (typeof impl === 'function') {\n (mock as any)[key].mockImplementation(impl);\n } else {\n (mock as any)[key] = impl;\n }\n }\n }\n return Object.assign(mock, {\n factory: createApiFactory({\n api: ref,\n deps: {},\n factory: () => mock,\n }),\n }) as ApiMock<TApi>;\n };\n}\n\n/**\n * Creates a fake catalog client that handles entities in memory storage. Note\n * that this client may be severely limited in functionality, and advanced\n * functions may not be available at all.\n *\n * @public\n */\nexport function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi {\n return new InMemoryCatalogClient(options);\n}\n\n/**\n * A collection of mock functionality for the catalog service.\n *\n * @public\n */\nexport namespace catalogApiMock {\n /**\n * Creates a fake catalog client that handles entities in memory storage. Note\n * that this client may be severely limited in functionality, and advanced\n * functions may not be available at all.\n */\n export const factory = (options?: {\n entities?: Entity[];\n }): ApiFactory<CatalogApi, CatalogApi, {}> =>\n createApiFactory({\n api: catalogApiRef,\n deps: {},\n factory: () => new InMemoryCatalogClient(options),\n });\n /**\n * Creates a catalog client whose methods are mock functions, possibly with\n * some of them overloaded by the caller.\n */\n export const mock = simpleMock(catalogApiRef, () => ({\n getEntities: jest.fn(),\n getEntitiesByRefs: jest.fn(),\n queryEntities: jest.fn(),\n getEntityAncestors: jest.fn(),\n getEntityByRef: jest.fn(),\n removeEntityByUid: jest.fn(),\n refreshEntity: jest.fn(),\n getEntityFacets: jest.fn(),\n getLocations: jest.fn(),\n getLocationById: jest.fn(),\n getLocationByRef: jest.fn(),\n addLocation: jest.fn(),\n removeLocationById: jest.fn(),\n getLocationByEntity: jest.fn(),\n validateEntity: jest.fn(),\n analyzeLocation: jest.fn(),\n }));\n}\n"],"names":["catalogApiMock"],"mappings":";;;;AA4BA,SAAS,UAAA,CACP,KACA,WAAA,EACgD;AAChD,EAAA,OAAO,CAAA,WAAA,KAAe;AACpB,IAAA,MAAM,OAAO,WAAA,EAAY;AACzB,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACrD,QAAA,IAAI,OAAO,SAAS,UAAA,EAAY;AAC9B,UAAC,IAAA,CAAa,GAAG,CAAA,CAAE,kBAAA,CAAmB,IAAI,CAAA;AAAA,QAC5C,CAAA,MAAO;AACL,UAAC,IAAA,CAAa,GAAG,CAAA,GAAI,IAAA;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AACA,IAAA,OAAO,MAAA,CAAO,OAAO,IAAA,EAAM;AAAA,MACzB,SAAS,gBAAA,CAAiB;AAAA,QACxB,GAAA,EAAK,GAAA;AAAA,QACL,MAAM,EAAC;AAAA,QACP,SAAS,MAAM;AAAA,OAChB;AAAA,KACF,CAAA;AAAA,EACH,CAAA;AACF;AASO,SAAS,eAAe,OAAA,EAA+C;AAC5E,EAAA,OAAO,IAAI,sBAAsB,OAAO,CAAA;AAC1C;AAAA,CAOO,CAAUA,eAAAA,KAAV;AAME,EAAMA,eAAAA,CAAA,OAAA,GAAU,CAAC,OAAA,KAGtB,gBAAA,CAAiB;AAAA,IACf,GAAA,EAAK,aAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,OAAA,EAAS,MAAM,IAAI,qBAAA,CAAsB,OAAO;AAAA,GACjD,CAAA;AAKI,EAAMA,eAAAA,CAAA,IAAA,GAAO,UAAA,CAAW,aAAA,EAAe,OAAO;AAAA,IACnD,WAAA,EAAa,KAAK,EAAA,EAAG;AAAA,IACrB,iBAAA,EAAmB,KAAK,EAAA,EAAG;AAAA,IAC3B,aAAA,EAAe,KAAK,EAAA,EAAG;AAAA,IACvB,kBAAA,EAAoB,KAAK,EAAA,EAAG;AAAA,IAC5B,cAAA,EAAgB,KAAK,EAAA,EAAG;AAAA,IACxB,iBAAA,EAAmB,KAAK,EAAA,EAAG;AAAA,IAC3B,aAAA,EAAe,KAAK,EAAA,EAAG;AAAA,IACvB,eAAA,EAAiB,KAAK,EAAA,EAAG;AAAA,IACzB,YAAA,EAAc,KAAK,EAAA,EAAG;AAAA,IACtB,eAAA,EAAiB,KAAK,EAAA,EAAG;AAAA,IACzB,gBAAA,EAAkB,KAAK,EAAA,EAAG;AAAA,IAC1B,WAAA,EAAa,KAAK,EAAA,EAAG;AAAA,IACrB,kBAAA,EAAoB,KAAK,EAAA,EAAG;AAAA,IAC5B,mBAAA,EAAqB,KAAK,EAAA,EAAG;AAAA,IAC7B,cAAA,EAAgB,KAAK,EAAA,EAAG;AAAA,IACxB,eAAA,EAAiB,KAAK,EAAA;AAAG,GAC3B,CAAE,CAAA;AAAA,CAAA,EAnCa,cAAA,KAAA,cAAA,GAAA,EAAA,CAAA,CAAA;;;;"}
1
+ {"version":3,"file":"catalogApiMock.esm.js","sources":["../../src/testUtils/catalogApiMock.ts"],"sourcesContent":["/*\n * Copyright 2024 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 {\n ApiFactory,\n ApiRef,\n createApiFactory,\n} from '@backstage/frontend-plugin-api';\nimport { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils';\nimport { Entity } from '@backstage/catalog-model';\nimport { catalogApiRef } from '@backstage/plugin-catalog-react';\nimport { CatalogApi } from '@backstage/catalog-client';\nimport { ApiMock } from '@backstage/frontend-test-utils';\n\n/** @internal */\nfunction simpleMock<TApi>(\n ref: ApiRef<TApi>,\n mockFactory: () => jest.Mocked<TApi>,\n): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {\n return partialImpl => {\n const mock = mockFactory();\n if (partialImpl) {\n for (const [key, impl] of Object.entries(partialImpl)) {\n if (typeof impl === 'function') {\n (mock as any)[key].mockImplementation(impl);\n } else {\n (mock as any)[key] = impl;\n }\n }\n }\n return Object.assign(mock, {\n factory: createApiFactory({\n api: ref,\n deps: {},\n factory: () => mock,\n }),\n }) as ApiMock<TApi>;\n };\n}\n\n/**\n * Creates a fake catalog client that handles entities in memory storage. Note\n * that this client may be severely limited in functionality, and advanced\n * functions may not be available at all.\n *\n * @public\n */\nexport function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi {\n return new InMemoryCatalogClient(options);\n}\n\n/**\n * A collection of mock functionality for the catalog service.\n *\n * @public\n */\nexport namespace catalogApiMock {\n /**\n * Creates a fake catalog client that handles entities in memory storage. Note\n * that this client may be severely limited in functionality, and advanced\n * functions may not be available at all.\n */\n export const factory = (options?: {\n entities?: Entity[];\n }): ApiFactory<CatalogApi, CatalogApi, {}> =>\n createApiFactory({\n api: catalogApiRef,\n deps: {},\n factory: () => new InMemoryCatalogClient(options),\n });\n /**\n * Creates a catalog client whose methods are mock functions, possibly with\n * some of them overloaded by the caller.\n */\n export const mock = simpleMock(catalogApiRef, () => ({\n getEntities: jest.fn(),\n getEntitiesByRefs: jest.fn(),\n queryEntities: jest.fn(),\n getEntityAncestors: jest.fn(),\n getEntityByRef: jest.fn(),\n removeEntityByUid: jest.fn(),\n refreshEntity: jest.fn(),\n getEntityFacets: jest.fn(),\n getLocations: jest.fn(),\n getLocationById: jest.fn(),\n getLocationByRef: jest.fn(),\n addLocation: jest.fn(),\n removeLocationById: jest.fn(),\n getLocationByEntity: jest.fn(),\n validateEntity: jest.fn(),\n analyzeLocation: jest.fn(),\n streamEntities: jest.fn(),\n streamEntityPages: jest.fn(),\n }));\n}\n"],"names":["catalogApiMock"],"mappings":";;;;AA4BA,SAAS,UAAA,CACP,KACA,WAAA,EACgD;AAChD,EAAA,OAAO,CAAA,WAAA,KAAe;AACpB,IAAA,MAAM,OAAO,WAAA,EAAY;AACzB,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACrD,QAAA,IAAI,OAAO,SAAS,UAAA,EAAY;AAC9B,UAAC,IAAA,CAAa,GAAG,CAAA,CAAE,kBAAA,CAAmB,IAAI,CAAA;AAAA,QAC5C,CAAA,MAAO;AACL,UAAC,IAAA,CAAa,GAAG,CAAA,GAAI,IAAA;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AACA,IAAA,OAAO,MAAA,CAAO,OAAO,IAAA,EAAM;AAAA,MACzB,SAAS,gBAAA,CAAiB;AAAA,QACxB,GAAA,EAAK,GAAA;AAAA,QACL,MAAM,EAAC;AAAA,QACP,SAAS,MAAM;AAAA,OAChB;AAAA,KACF,CAAA;AAAA,EACH,CAAA;AACF;AASO,SAAS,eAAe,OAAA,EAA+C;AAC5E,EAAA,OAAO,IAAI,sBAAsB,OAAO,CAAA;AAC1C;AAAA,CAOO,CAAUA,eAAAA,KAAV;AAME,EAAMA,eAAAA,CAAA,OAAA,GAAU,CAAC,OAAA,KAGtB,gBAAA,CAAiB;AAAA,IACf,GAAA,EAAK,aAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,OAAA,EAAS,MAAM,IAAI,qBAAA,CAAsB,OAAO;AAAA,GACjD,CAAA;AAKI,EAAMA,eAAAA,CAAA,IAAA,GAAO,UAAA,CAAW,aAAA,EAAe,OAAO;AAAA,IACnD,WAAA,EAAa,KAAK,EAAA,EAAG;AAAA,IACrB,iBAAA,EAAmB,KAAK,EAAA,EAAG;AAAA,IAC3B,aAAA,EAAe,KAAK,EAAA,EAAG;AAAA,IACvB,kBAAA,EAAoB,KAAK,EAAA,EAAG;AAAA,IAC5B,cAAA,EAAgB,KAAK,EAAA,EAAG;AAAA,IACxB,iBAAA,EAAmB,KAAK,EAAA,EAAG;AAAA,IAC3B,aAAA,EAAe,KAAK,EAAA,EAAG;AAAA,IACvB,eAAA,EAAiB,KAAK,EAAA,EAAG;AAAA,IACzB,YAAA,EAAc,KAAK,EAAA,EAAG;AAAA,IACtB,eAAA,EAAiB,KAAK,EAAA,EAAG;AAAA,IACzB,gBAAA,EAAkB,KAAK,EAAA,EAAG;AAAA,IAC1B,WAAA,EAAa,KAAK,EAAA,EAAG;AAAA,IACrB,kBAAA,EAAoB,KAAK,EAAA,EAAG;AAAA,IAC5B,mBAAA,EAAqB,KAAK,EAAA,EAAG;AAAA,IAC7B,cAAA,EAAgB,KAAK,EAAA,EAAG;AAAA,IACxB,eAAA,EAAiB,KAAK,EAAA,EAAG;AAAA,IACzB,cAAA,EAAgB,KAAK,EAAA,EAAG;AAAA,IACxB,iBAAA,EAAmB,KAAK,EAAA;AAAG,GAC7B,CAAE,CAAA;AAAA,CAAA,EArCa,cAAA,KAAA,cAAA,GAAA,EAAA,CAAA,CAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-react",
3
- "version": "1.20.2-next.0",
3
+ "version": "1.21.0-next.2",
4
4
  "description": "A frontend library that helps other Backstage plugins interact with the catalog",
5
5
  "backstage": {
6
6
  "role": "web-library",
@@ -73,15 +73,15 @@
73
73
  "test": "backstage-cli package test"
74
74
  },
75
75
  "dependencies": {
76
- "@backstage/catalog-client": "1.11.0",
76
+ "@backstage/catalog-client": "1.12.0-next.0",
77
77
  "@backstage/catalog-model": "1.7.5",
78
- "@backstage/core-compat-api": "0.5.2-next.0",
79
- "@backstage/core-components": "0.17.5",
78
+ "@backstage/core-compat-api": "0.5.2-next.2",
79
+ "@backstage/core-components": "0.17.6-next.1",
80
80
  "@backstage/core-plugin-api": "1.10.9",
81
81
  "@backstage/errors": "1.2.7",
82
- "@backstage/frontend-plugin-api": "0.11.0",
83
- "@backstage/frontend-test-utils": "0.3.5",
84
- "@backstage/integration-react": "1.2.9",
82
+ "@backstage/frontend-plugin-api": "0.11.1-next.0",
83
+ "@backstage/frontend-test-utils": "0.3.6-next.0",
84
+ "@backstage/integration-react": "1.2.10-next.0",
85
85
  "@backstage/plugin-catalog-common": "1.1.5",
86
86
  "@backstage/plugin-permission-common": "0.9.1",
87
87
  "@backstage/plugin-permission-react": "0.4.36",
@@ -100,10 +100,10 @@
100
100
  "zen-observable": "^0.10.0"
101
101
  },
102
102
  "devDependencies": {
103
- "@backstage/cli": "0.34.1-next.0",
103
+ "@backstage/cli": "0.34.2-next.2",
104
104
  "@backstage/core-app-api": "1.18.0",
105
105
  "@backstage/plugin-catalog-common": "1.1.5",
106
- "@backstage/plugin-scaffolder-common": "1.7.0",
106
+ "@backstage/plugin-scaffolder-common": "1.7.1-next.0",
107
107
  "@backstage/test-utils": "1.7.11",
108
108
  "@testing-library/dom": "^10.0.0",
109
109
  "@testing-library/jest-dom": "^6.0.0",