@backstage/plugin-catalog-react 3.0.1-next.0 → 3.1.0

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,45 @@
1
1
  # @backstage/plugin-catalog-react
2
2
 
3
+ ## 3.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - b6d6551: Added optional `getOptionLabel` and `renderOption` props to `EntityAutocompletePicker`, allowing consumers to customize how option labels and option rendering are displayed in the autocomplete dropdown.
8
+ - 5dd532d: Added a `refresh` function to the `useEntityList` hook.
9
+ - 4212b78: Allow full text searching of Location target URLs in catalog tables
10
+
11
+ ### Patch Changes
12
+
13
+ - d8757b1: The entity list provider now fetches the entity list and the total count as two separate parallel requests when using cursor or offset pagination. The list query skips the expensive count computation (using `totalItems: 'exclude'`), so the table populates immediately. The count arrives asynchronously and updates the title. A new `totalItemsLoading` field is exposed on `EntityListContextProps` so consumers can distinguish a stale count from a fresh one.
14
+
15
+ The catalog table now keeps stale rows visible during filter changes and page navigation instead of replacing the entire table body with a spinner. The full-table spinner is only shown on the very first load when no data exists yet. The entity count in the title is dimmed while the count is refreshing, and a small spinner appears next to the title while rows are loading.
16
+
17
+ - e0889a3: chore(deps): bump `qs` from 6.15.1 to 6.15.2
18
+ - 7c20545: Fixed redundant API calls during entity list initialization. Filter components that register their initial state in quick succession (e.g. `EntityKindPicker`, `UserListPicker`, `EntityTagPicker`) no longer trigger multiple identical fetches. Frontend-only filter changes such as toggling the user list are now applied synchronously without a network round-trip.
19
+ - Updated dependencies
20
+ - @backstage/catalog-client@1.16.0
21
+ - @backstage/core-components@0.18.11
22
+ - @backstage/frontend-plugin-api@0.17.2
23
+ - @backstage/ui@0.16.0
24
+ - @backstage/core-compat-api@0.5.12
25
+ - @backstage/core-plugin-api@1.12.7
26
+ - @backstage/integration-react@1.2.19
27
+ - @backstage/plugin-permission-react@0.5.2
28
+
29
+ ## 3.0.1-next.1
30
+
31
+ ### Patch Changes
32
+
33
+ - Updated dependencies
34
+ - @backstage/catalog-client@1.16.0-next.1
35
+ - @backstage/frontend-plugin-api@0.17.2-next.0
36
+ - @backstage/core-components@0.18.11-next.1
37
+ - @backstage/ui@0.15.1-next.0
38
+ - @backstage/core-compat-api@0.5.12-next.1
39
+ - @backstage/core-plugin-api@1.12.7-next.0
40
+ - @backstage/integration-react@1.2.19-next.1
41
+ - @backstage/plugin-permission-react@0.5.2-next.0
42
+
3
43
  ## 3.0.1-next.0
4
44
 
5
45
  ### Patch Changes
@@ -31,7 +31,9 @@ function EntityAutocompletePicker(props) {
31
31
  InputProps,
32
32
  initialSelectedOptions = [],
33
33
  filtersForAvailableValues = ["kind"],
34
- hidden
34
+ hidden,
35
+ getOptionLabel,
36
+ renderOption
35
37
  } = props;
36
38
  const classes = useStyles();
37
39
  const {
@@ -97,7 +99,8 @@ function EntityAutocompletePicker(props) {
97
99
  value: selectedOptions,
98
100
  TextFieldProps: InputProps,
99
101
  onChange: (_event, options) => setSelectedOptions(options),
100
- renderOption: (option, { selected }) => /* @__PURE__ */ jsx(
102
+ ...getOptionLabel && { getOptionLabel },
103
+ renderOption: renderOption ?? ((option, { selected }) => /* @__PURE__ */ jsx(
101
104
  EntityAutocompletePickerOption,
102
105
  {
103
106
  selected,
@@ -105,7 +108,7 @@ function EntityAutocompletePicker(props) {
105
108
  availableOptions: availableValues,
106
109
  showCounts: !!showCounts
107
110
  }
108
- )
111
+ ))
109
112
  }
110
113
  ) });
111
114
  }
@@ -1 +1 @@
1
- {"version":3,"file":"EntityAutocompletePicker.esm.js","sources":["../../../src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 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 Box from '@material-ui/core/Box';\nimport { TextFieldProps } from '@material-ui/core/TextField';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { useEffect, useMemo, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport useAsync from 'react-use/esm/useAsync';\nimport { catalogApiRef } from '../../api';\nimport { EntityAutocompletePickerOption } from './EntityAutocompletePickerOption';\nimport {\n DefaultEntityFilters,\n useEntityList,\n} from '../../hooks/useEntityListProvider';\nimport { EntityFilter } from '../../types';\nimport { reduceBackendCatalogFilters } from '../../utils/filters';\nimport { CatalogAutocomplete } from '../CatalogAutocomplete';\nimport { isEqual } from 'lodash';\n\n/** @public */\nexport type AllowedEntityFilters<T extends DefaultEntityFilters> = {\n [K in keyof T]-?: NonNullable<T[K]> extends EntityFilter & {\n values: string[];\n }\n ? K\n : never;\n}[keyof T];\n\n/** @public */\nexport type EntityAutocompletePickerProps<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n Name extends AllowedEntityFilters<T> = AllowedEntityFilters<T>,\n> = {\n label: string;\n name: Name;\n path: string;\n showCounts?: boolean;\n Filter: { new (values: string[]): NonNullable<T[Name]> };\n InputProps?: TextFieldProps;\n initialSelectedOptions?: string[];\n filtersForAvailableValues?: Array<keyof T>;\n hidden?: boolean;\n};\n\n/** @public */\nexport type CatalogReactEntityAutocompletePickerClassKey = 'root' | 'label';\n\nconst useStyles = makeStyles(\n {\n root: {},\n label: {\n textTransform: 'none',\n fontWeight: 'bold',\n },\n },\n { name: 'CatalogReactEntityAutocompletePicker' },\n);\n\n/** @public */\nexport function EntityAutocompletePicker<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n Name extends AllowedEntityFilters<T> = AllowedEntityFilters<T>,\n>(props: EntityAutocompletePickerProps<T, Name>) {\n const {\n label,\n name,\n path,\n showCounts,\n Filter,\n InputProps,\n initialSelectedOptions = [],\n filtersForAvailableValues = ['kind'],\n hidden,\n } = props;\n const classes = useStyles();\n\n const {\n updateFilters,\n filters,\n queryParameters: { [name]: queryParameter },\n } = useEntityList<T>();\n\n const catalogApi = useApi(catalogApiRef);\n const availableValuesFilters = filtersForAvailableValues.map(\n f => filters[f] as EntityFilter | undefined,\n );\n const { value: availableValues } = useAsync(async () => {\n const facet = path;\n const { facets } = await catalogApi.getEntityFacets({\n facets: [facet],\n filter: reduceBackendCatalogFilters(\n availableValuesFilters.filter(Boolean) as EntityFilter[],\n ),\n });\n\n return Object.fromEntries(\n facets[facet].map(({ value, count }) => [value, count]),\n );\n }, [...availableValuesFilters]);\n\n const queryParameters = useMemo(\n () => [queryParameter].flat().filter(Boolean) as string[],\n [queryParameter],\n );\n\n const filteredOptions = (filters[name] as unknown as { values: string[] })\n ?.values;\n\n const [selectedOptions, setSelectedOptions] = useState(\n queryParameters.length\n ? queryParameters\n : filteredOptions ?? initialSelectedOptions,\n );\n\n // Set selected options on query parameter updates; this happens at initial page load and from\n // external updates to the page location\n useEffect(() => {\n if (queryParameters.length) {\n setSelectedOptions(queryParameters);\n }\n }, [queryParameters]);\n\n const availableOptions = Object.keys(availableValues ?? {});\n const shouldAddFilter = selectedOptions.length && availableOptions.length;\n\n // Update filter value when selectedOptions change\n useEffect(() => {\n updateFilters({\n [name]: shouldAddFilter ? new Filter(selectedOptions) : undefined,\n } as Partial<T>);\n }, [name, shouldAddFilter, selectedOptions, Filter, updateFilters]);\n\n // Update selected options when filter value changes\n useEffect(() => {\n if (!shouldAddFilter) return;\n\n const newSelectedOptions = filteredOptions ?? [];\n\n // Check value is actually different (not just a different reference) to prevent selectedOptions <> filters loop\n if (!isEqual(newSelectedOptions, selectedOptions)) {\n setSelectedOptions(newSelectedOptions);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Don't re-set filter value when selectedOptions changes\n }, [filteredOptions]);\n\n const filter = filters[name];\n if (\n (filter && typeof filter === 'object' && !('values' in filter)) ||\n !availableOptions.length\n ) {\n return null;\n }\n\n return hidden ? null : (\n <Box className={classes.root} pb={1} pt={1}>\n <CatalogAutocomplete<string, true>\n multiple\n disableCloseOnSelect\n label={label}\n name={`${String(name)}-picker`}\n options={availableOptions}\n value={selectedOptions}\n TextFieldProps={InputProps}\n onChange={(_event: object, options: string[]) =>\n setSelectedOptions(options)\n }\n renderOption={(option, { selected }) => (\n <EntityAutocompletePickerOption\n selected={selected}\n value={option}\n availableOptions={availableValues}\n showCounts={!!showCounts}\n />\n )}\n />\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"EntityAutocompletePicker.esm.js","sources":["../../../src/components/EntityAutocompletePicker/EntityAutocompletePicker.tsx"],"sourcesContent":["/*\n * Copyright 2021 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 Box from '@material-ui/core/Box';\nimport { TextFieldProps } from '@material-ui/core/TextField';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { ReactNode, useEffect, useMemo, useState } from 'react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport useAsync from 'react-use/esm/useAsync';\nimport { catalogApiRef } from '../../api';\nimport { EntityAutocompletePickerOption } from './EntityAutocompletePickerOption';\nimport {\n DefaultEntityFilters,\n useEntityList,\n} from '../../hooks/useEntityListProvider';\nimport { EntityFilter } from '../../types';\nimport { reduceBackendCatalogFilters } from '../../utils/filters';\nimport { CatalogAutocomplete } from '../CatalogAutocomplete';\nimport { AutocompleteRenderOptionState } from '@material-ui/lab/Autocomplete';\nimport { isEqual } from 'lodash';\n\n/** @public */\nexport type AllowedEntityFilters<T extends DefaultEntityFilters> = {\n [K in keyof T]-?: NonNullable<T[K]> extends EntityFilter & {\n values: string[];\n }\n ? K\n : never;\n}[keyof T];\n\n/** @public */\nexport type EntityAutocompletePickerProps<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n Name extends AllowedEntityFilters<T> = AllowedEntityFilters<T>,\n> = {\n label: string;\n name: Name;\n path: string;\n showCounts?: boolean;\n Filter: { new (values: string[]): NonNullable<T[Name]> };\n InputProps?: TextFieldProps;\n initialSelectedOptions?: string[];\n filtersForAvailableValues?: Array<keyof T>;\n hidden?: boolean;\n getOptionLabel?: (option: string) => string;\n renderOption?: (\n option: string,\n state: AutocompleteRenderOptionState,\n ) => ReactNode;\n};\n\n/** @public */\nexport type CatalogReactEntityAutocompletePickerClassKey = 'root' | 'label';\n\nconst useStyles = makeStyles(\n {\n root: {},\n label: {\n textTransform: 'none',\n fontWeight: 'bold',\n },\n },\n { name: 'CatalogReactEntityAutocompletePicker' },\n);\n\n/** @public */\nexport function EntityAutocompletePicker<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n Name extends AllowedEntityFilters<T> = AllowedEntityFilters<T>,\n>(props: EntityAutocompletePickerProps<T, Name>) {\n const {\n label,\n name,\n path,\n showCounts,\n Filter,\n InputProps,\n initialSelectedOptions = [],\n filtersForAvailableValues = ['kind'],\n hidden,\n getOptionLabel,\n renderOption,\n } = props;\n const classes = useStyles();\n\n const {\n updateFilters,\n filters,\n queryParameters: { [name]: queryParameter },\n } = useEntityList<T>();\n\n const catalogApi = useApi(catalogApiRef);\n const availableValuesFilters = filtersForAvailableValues.map(\n f => filters[f] as EntityFilter | undefined,\n );\n const { value: availableValues } = useAsync(async () => {\n const facet = path;\n const { facets } = await catalogApi.getEntityFacets({\n facets: [facet],\n filter: reduceBackendCatalogFilters(\n availableValuesFilters.filter(Boolean) as EntityFilter[],\n ),\n });\n\n return Object.fromEntries(\n facets[facet].map(({ value, count }) => [value, count]),\n );\n }, [...availableValuesFilters]);\n\n const queryParameters = useMemo(\n () => [queryParameter].flat().filter(Boolean) as string[],\n [queryParameter],\n );\n\n const filteredOptions = (filters[name] as unknown as { values: string[] })\n ?.values;\n\n const [selectedOptions, setSelectedOptions] = useState(\n queryParameters.length\n ? queryParameters\n : filteredOptions ?? initialSelectedOptions,\n );\n\n // Set selected options on query parameter updates; this happens at initial page load and from\n // external updates to the page location\n useEffect(() => {\n if (queryParameters.length) {\n setSelectedOptions(queryParameters);\n }\n }, [queryParameters]);\n\n const availableOptions = Object.keys(availableValues ?? {});\n const shouldAddFilter = selectedOptions.length && availableOptions.length;\n\n // Update filter value when selectedOptions change\n useEffect(() => {\n updateFilters({\n [name]: shouldAddFilter ? new Filter(selectedOptions) : undefined,\n } as Partial<T>);\n }, [name, shouldAddFilter, selectedOptions, Filter, updateFilters]);\n\n // Update selected options when filter value changes\n useEffect(() => {\n if (!shouldAddFilter) return;\n\n const newSelectedOptions = filteredOptions ?? [];\n\n // Check value is actually different (not just a different reference) to prevent selectedOptions <> filters loop\n if (!isEqual(newSelectedOptions, selectedOptions)) {\n setSelectedOptions(newSelectedOptions);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Don't re-set filter value when selectedOptions changes\n }, [filteredOptions]);\n\n const filter = filters[name];\n if (\n (filter && typeof filter === 'object' && !('values' in filter)) ||\n !availableOptions.length\n ) {\n return null;\n }\n\n return hidden ? null : (\n <Box className={classes.root} pb={1} pt={1}>\n <CatalogAutocomplete<string, true>\n multiple\n disableCloseOnSelect\n label={label}\n name={`${String(name)}-picker`}\n options={availableOptions}\n value={selectedOptions}\n TextFieldProps={InputProps}\n onChange={(_event: object, options: string[]) =>\n setSelectedOptions(options)\n }\n {...(getOptionLabel && { getOptionLabel })}\n renderOption={\n renderOption ??\n ((option, { selected }) => (\n <EntityAutocompletePickerOption\n selected={selected}\n value={option}\n availableOptions={availableValues}\n showCounts={!!showCounts}\n />\n ))\n }\n />\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -39,7 +39,9 @@ function MockEntityListContextProvider(props) {
39
39
  setLimit: value?.setLimit ?? (() => {
40
40
  }),
41
41
  setOffset: value?.setOffset,
42
- paginationMode: value?.paginationMode ?? "none"
42
+ paginationMode: value?.paginationMode ?? "none",
43
+ refresh: value?.refresh ?? (() => {
44
+ })
43
45
  }),
44
46
  [value, defaultValues, filters, updateFilters]
45
47
  );
@@ -1 +1 @@
1
- {"version":3,"file":"deprecated.esm.js","sources":["../src/deprecated.tsx"],"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 { PropsWithChildren, useCallback, useMemo, useState } from 'react';\nimport { createVersionedValueMap } from '@backstage/version-bridge';\nimport {\n DefaultEntityFilters,\n EntityListContextProps,\n NewEntityListContext,\n OldEntityListContext,\n} from './hooks/useEntityListProvider';\n\n/**\n * @public\n * @deprecated Moved to `@backstage/plugin-catalog-react/testUtils`\n */\nexport function MockEntityListContextProvider<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n>(\n props: PropsWithChildren<{\n value?: Partial<EntityListContextProps<T>>;\n }>,\n) {\n const { children, value } = props;\n\n // Provides a default implementation that stores filter state, for testing components that\n // reflect filter state.\n const [filters, setFilters] = useState<T>(value?.filters ?? ({} as T));\n\n const updateFilters = useCallback(\n (update: Partial<T> | ((prevFilters: T) => Partial<T>)) => {\n setFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [],\n );\n\n // Memoize the default values since pickers have useEffect triggers on these; naively defaulting\n // below with `?? <X>` breaks referential equality on subsequent updates.\n const defaultValues = useMemo(\n () => ({\n entities: [],\n backendEntities: [],\n queryParameters: {},\n }),\n [],\n );\n\n const resolvedValue: EntityListContextProps<T> = useMemo(\n () => ({\n entities: value?.entities ?? defaultValues.entities,\n backendEntities: value?.backendEntities ?? defaultValues.backendEntities,\n updateFilters: value?.updateFilters ?? updateFilters,\n filters,\n loading: value?.loading ?? false,\n queryParameters: value?.queryParameters ?? defaultValues.queryParameters,\n error: value?.error,\n totalItems:\n value?.totalItems ?? (value?.entities ?? defaultValues.entities).length,\n totalItemsLoading: value?.totalItemsLoading ?? false,\n limit: value?.limit ?? 20,\n offset: value?.offset,\n setLimit: value?.setLimit ?? (() => {}),\n setOffset: value?.setOffset,\n paginationMode: value?.paginationMode ?? 'none',\n }),\n [value, defaultValues, filters, updateFilters],\n );\n\n return (\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: resolvedValue })}\n >\n {children}\n </NewEntityListContext.Provider>\n );\n}\n\n/**\n * Creates new context for entity listing and filtering.\n *\n * @public\n * @deprecated Please use `EntityListProvider` and `EntityListProvider` instead.\n */\nexport const EntityListContext = OldEntityListContext;\n"],"names":[],"mappings":";;;;;AA6BO,SAAS,8BAGd,KAAA,EAGA;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,EAAM,GAAI,KAAA;AAI5B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAY,KAAA,EAAO,OAAA,IAAY,EAAQ,CAAA;AAErE,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,MAAA,KAA0D;AACzD,MAAA,UAAA,CAAW,CAAA,WAAA,KAAe;AACxB,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;AAAC,GACH;AAIA,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,iBAAiB;AAAC,KACpB,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,MAAM,aAAA,GAA2C,OAAA;AAAA,IAC/C,OAAO;AAAA,MACL,QAAA,EAAU,KAAA,EAAO,QAAA,IAAY,aAAA,CAAc,QAAA;AAAA,MAC3C,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,aAAA,EAAe,OAAO,aAAA,IAAiB,aAAA;AAAA,MACvC,OAAA;AAAA,MACA,OAAA,EAAS,OAAO,OAAA,IAAW,KAAA;AAAA,MAC3B,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,OAAO,KAAA,EAAO,KAAA;AAAA,MACd,YACE,KAAA,EAAO,UAAA,IAAA,CAAe,KAAA,EAAO,QAAA,IAAY,cAAc,QAAA,EAAU,MAAA;AAAA,MACnE,iBAAA,EAAmB,OAAO,iBAAA,IAAqB,KAAA;AAAA,MAC/C,KAAA,EAAO,OAAO,KAAA,IAAS,EAAA;AAAA,MACvB,QAAQ,KAAA,EAAO,MAAA;AAAA,MACf,QAAA,EAAU,KAAA,EAAO,QAAA,KAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MACrC,WAAW,KAAA,EAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB;AAAA,KAC3C,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,aAAA,EAAe,OAAA,EAAS,aAAa;AAAA,GAC/C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,oBAAA,CAAqB,QAAA;AAAA,IAArB;AAAA,MACC,KAAA,EAAO,uBAAA,CAAwB,EAAE,CAAA,EAAG,eAAe,CAAA;AAAA,MAElD;AAAA;AAAA,GACH;AAEJ;AAQO,MAAM,iBAAA,GAAoB;;;;"}
1
+ {"version":3,"file":"deprecated.esm.js","sources":["../src/deprecated.tsx"],"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 { PropsWithChildren, useCallback, useMemo, useState } from 'react';\nimport { createVersionedValueMap } from '@backstage/version-bridge';\nimport {\n DefaultEntityFilters,\n EntityListContextProps,\n NewEntityListContext,\n OldEntityListContext,\n} from './hooks/useEntityListProvider';\n\n/**\n * @public\n * @deprecated Moved to `@backstage/plugin-catalog-react/testUtils`\n */\nexport function MockEntityListContextProvider<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n>(\n props: PropsWithChildren<{\n value?: Partial<EntityListContextProps<T>>;\n }>,\n) {\n const { children, value } = props;\n\n // Provides a default implementation that stores filter state, for testing components that\n // reflect filter state.\n const [filters, setFilters] = useState<T>(value?.filters ?? ({} as T));\n\n const updateFilters = useCallback(\n (update: Partial<T> | ((prevFilters: T) => Partial<T>)) => {\n setFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [],\n );\n\n // Memoize the default values since pickers have useEffect triggers on these; naively defaulting\n // below with `?? <X>` breaks referential equality on subsequent updates.\n const defaultValues = useMemo(\n () => ({\n entities: [],\n backendEntities: [],\n queryParameters: {},\n }),\n [],\n );\n\n const resolvedValue: EntityListContextProps<T> = useMemo(\n () => ({\n entities: value?.entities ?? defaultValues.entities,\n backendEntities: value?.backendEntities ?? defaultValues.backendEntities,\n updateFilters: value?.updateFilters ?? updateFilters,\n filters,\n loading: value?.loading ?? false,\n queryParameters: value?.queryParameters ?? defaultValues.queryParameters,\n error: value?.error,\n totalItems:\n value?.totalItems ?? (value?.entities ?? defaultValues.entities).length,\n totalItemsLoading: value?.totalItemsLoading ?? false,\n limit: value?.limit ?? 20,\n offset: value?.offset,\n setLimit: value?.setLimit ?? (() => {}),\n setOffset: value?.setOffset,\n paginationMode: value?.paginationMode ?? 'none',\n refresh: value?.refresh ?? (() => {}),\n }),\n [value, defaultValues, filters, updateFilters],\n );\n\n return (\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: resolvedValue })}\n >\n {children}\n </NewEntityListContext.Provider>\n );\n}\n\n/**\n * Creates new context for entity listing and filtering.\n *\n * @public\n * @deprecated Please use `EntityListProvider` and `EntityListProvider` instead.\n */\nexport const EntityListContext = OldEntityListContext;\n"],"names":[],"mappings":";;;;;AA6BO,SAAS,8BAGd,KAAA,EAGA;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,EAAM,GAAI,KAAA;AAI5B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAY,KAAA,EAAO,OAAA,IAAY,EAAQ,CAAA;AAErE,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,MAAA,KAA0D;AACzD,MAAA,UAAA,CAAW,CAAA,WAAA,KAAe;AACxB,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;AAAC,GACH;AAIA,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,iBAAiB;AAAC,KACpB,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,MAAM,aAAA,GAA2C,OAAA;AAAA,IAC/C,OAAO;AAAA,MACL,QAAA,EAAU,KAAA,EAAO,QAAA,IAAY,aAAA,CAAc,QAAA;AAAA,MAC3C,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,aAAA,EAAe,OAAO,aAAA,IAAiB,aAAA;AAAA,MACvC,OAAA;AAAA,MACA,OAAA,EAAS,OAAO,OAAA,IAAW,KAAA;AAAA,MAC3B,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,OAAO,KAAA,EAAO,KAAA;AAAA,MACd,YACE,KAAA,EAAO,UAAA,IAAA,CAAe,KAAA,EAAO,QAAA,IAAY,cAAc,QAAA,EAAU,MAAA;AAAA,MACnE,iBAAA,EAAmB,OAAO,iBAAA,IAAqB,KAAA;AAAA,MAC/C,KAAA,EAAO,OAAO,KAAA,IAAS,EAAA;AAAA,MACvB,QAAQ,KAAA,EAAO,MAAA;AAAA,MACf,QAAA,EAAU,KAAA,EAAO,QAAA,KAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MACrC,WAAW,KAAA,EAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB,MAAA;AAAA,MACzC,OAAA,EAAS,KAAA,EAAO,OAAA,KAAY,MAAM;AAAA,MAAC,CAAA;AAAA,KACrC,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,aAAA,EAAe,OAAA,EAAS,aAAa;AAAA,GAC/C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,oBAAA,CAAqB,QAAA;AAAA,IAArB;AAAA,MACC,KAAA,EAAO,uBAAA,CAAwB,EAAE,CAAA,EAAG,eAAe,CAAA;AAAA,MAElD;AAAA;AAAA,GACH;AAEJ;AAQO,MAAM,iBAAA,GAAoB;;;;"}
@@ -54,10 +54,13 @@ class EntityTextFilter {
54
54
  filterEntity(entity) {
55
55
  const words = this.toUpperArray(this.value.split(/\s/));
56
56
  const exactMatch = this.toUpperArray([entity.metadata.tags]);
57
+ const targets = entity.spec?.targets;
57
58
  const partialMatch = this.toUpperArray([
58
59
  entity.metadata.name,
59
60
  entity.metadata.title,
60
- entity.spec?.profile?.displayName
61
+ entity.spec?.profile?.displayName,
62
+ entity.spec?.target,
63
+ ...Array.isArray(targets) ? targets : []
61
64
  ]);
62
65
  for (const word of words) {
63
66
  if (exactMatch.every((m) => m !== word) && partialMatch.every((m) => !m.includes(word))) {
@@ -70,14 +73,20 @@ class EntityTextFilter {
70
73
  return {
71
74
  term: this.value,
72
75
  // Update this to be more dynamic based on table columns.
73
- fields: ["metadata.name", "metadata.title", "spec.profile.displayName"]
76
+ fields: [
77
+ "metadata.name",
78
+ "metadata.title",
79
+ "spec.profile.displayName",
80
+ "spec.target",
81
+ "spec.targets"
82
+ ]
74
83
  };
75
84
  }
76
85
  toQueryValue() {
77
86
  return this.value;
78
87
  }
79
88
  toUpperArray(value) {
80
- return value.flat().filter((m) => Boolean(m)).map((m) => m.toLocaleUpperCase("en-US"));
89
+ return value.flat().filter((m) => Boolean(m) && typeof m === "string").map((m) => m.toLocaleUpperCase("en-US"));
81
90
  }
82
91
  }
83
92
  class EntityOwnerFilter {
@@ -1 +1 @@
1
- {"version":3,"file":"filters.esm.js","sources":["../src/filters.ts"],"sourcesContent":["/*\n * Copyright 2021 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 Entity,\n parseEntityRef,\n RELATION_OWNED_BY,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { AlphaEntity } from '@backstage/catalog-model/alpha';\nimport { EntityFilter, UserListFilterKind } from './types';\nimport { getEntityRelations } from './utils/getEntityRelations';\nimport { EntityOrderQuery } from '@backstage/catalog-client';\n\n/**\n * Filter entities based on Kind.\n * @public\n */\nexport class EntityKindFilter implements EntityFilter {\n readonly value: string;\n readonly label: string;\n\n constructor(value: string, label: string) {\n this.value = value;\n this.label = label;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { kind: this.value };\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based on type\n * @public\n */\nexport class EntityTypeFilter implements EntityFilter {\n readonly value: string | string[];\n\n constructor(value: string | string[]) {\n this.value = value;\n }\n\n // Simplify `string | string[]` for consumers, always returns an array\n getTypes(): string[] {\n return Array.isArray(this.value) ? this.value : [this.value];\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'spec.type': this.getTypes() };\n }\n\n toQueryValue(): string[] {\n return this.getTypes();\n }\n}\n\n/**\n * Filters entities based on tag.\n * @public\n */\nexport class EntityTagFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.every(v => (entity.metadata.tags ?? []).includes(v));\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'metadata.tags': this.values };\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities where the text matches spec, title or tags.\n * @public\n */\nexport class EntityTextFilter implements EntityFilter {\n readonly value: string;\n\n constructor(value: string) {\n this.value = value;\n }\n\n filterEntity(entity: Entity): boolean {\n const words = this.toUpperArray(this.value.split(/\\s/));\n const exactMatch = this.toUpperArray([entity.metadata.tags]);\n const partialMatch = this.toUpperArray([\n entity.metadata.name,\n entity.metadata.title,\n (entity.spec?.profile as { displayName?: string })?.displayName,\n ]);\n\n for (const word of words) {\n if (\n exactMatch.every(m => m !== word) &&\n partialMatch.every(m => !m.includes(word))\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n getFullTextFilters() {\n return {\n term: this.value,\n // Update this to be more dynamic based on table columns.\n fields: ['metadata.name', 'metadata.title', 'spec.profile.displayName'],\n };\n }\n\n toQueryValue() {\n return this.value;\n }\n\n private toUpperArray(\n value: Array<string | string[] | undefined>,\n ): Array<string> {\n return value\n .flat()\n .filter((m): m is string => Boolean(m))\n .map(m => m.toLocaleUpperCase('en-US'));\n }\n}\n\n/**\n * Filter matching entities that are owned by group.\n * @public\n *\n * CAUTION: This class may contain both full and partial entity refs.\n */\nexport class EntityOwnerFilter implements EntityFilter {\n readonly values: string[];\n constructor(values: string[]) {\n this.values = values.reduce((fullRefs, ref) => {\n // Attempt to remove bad entity references here.\n try {\n fullRefs.push(\n stringifyEntityRef(parseEntityRef(ref, { defaultKind: 'Group' })),\n );\n return fullRefs;\n } catch (err) {\n return fullRefs;\n }\n }, [] as string[]);\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'relations.ownedBy': this.values };\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.some(v =>\n getEntityRelations(entity, RELATION_OWNED_BY).some(\n o => stringifyEntityRef(o) === v,\n ),\n );\n }\n\n /**\n * Get the URL query parameter value. May be a mix of full and humanized entity refs.\n * @returns list of entity refs.\n */\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities on lifecycle.\n * @public\n */\nexport class EntityLifecycleFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'spec.lifecycle': this.values };\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.some(v => entity.spec?.lifecycle === v);\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities to those within the given namespace(s).\n * @public\n */\nexport class EntityNamespaceFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'metadata.namespace': this.values };\n }\n filterEntity(entity: Entity): boolean {\n return this.values.some(v => entity.metadata.namespace === v);\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * @public\n */\nexport class EntityUserFilter implements EntityFilter {\n readonly value: UserListFilterKind;\n readonly refs?: string[];\n\n private constructor(value: UserListFilterKind, refs?: string[]) {\n this.value = value;\n this.refs = refs;\n }\n\n static owned(ownershipEntityRefs: string[]) {\n return new EntityUserFilter('owned', ownershipEntityRefs);\n }\n\n static all() {\n return new EntityUserFilter('all');\n }\n\n static starred(starredEntityRefs: string[]) {\n return new EntityUserFilter('starred', starredEntityRefs);\n }\n\n getCatalogFilters(): Record<string, string[]> {\n if (this.value === 'owned') {\n return { 'relations.ownedBy': this.refs ?? [] };\n }\n if (this.value === 'starred') {\n return {\n 'metadata.name': this.refs?.map(e => parseEntityRef(e).name) ?? [],\n };\n }\n return {};\n }\n\n filterEntity(entity: Entity) {\n if (this.value === 'starred') {\n return this.refs?.includes(stringifyEntityRef(entity)) ?? true;\n }\n // used only for retro-compatibility with non paginated data.\n // This is supposed to return always true for paginated\n // owned entities, since the filters are applied server side.\n if (this.value === 'owned') {\n const relations = getEntityRelations(entity, RELATION_OWNED_BY);\n\n return (\n this.refs?.some(v =>\n relations.some(o => stringifyEntityRef(o) === v),\n ) ?? false\n );\n }\n return true;\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based on whatever the user has starred or owns them.\n * @deprecated use EntityUserFilter\n * @public\n */\nexport class UserListFilter implements EntityFilter {\n readonly value: UserListFilterKind;\n readonly isOwnedEntity: (entity: Entity) => boolean;\n readonly isStarredEntity: (entity: Entity) => boolean;\n\n constructor(\n value: UserListFilterKind,\n isOwnedEntity: (entity: Entity) => boolean,\n isStarredEntity: (entity: Entity) => boolean,\n ) {\n this.value = value;\n this.isOwnedEntity = isOwnedEntity;\n this.isStarredEntity = isStarredEntity;\n }\n\n filterEntity(entity: Entity): boolean {\n switch (this.value) {\n case 'owned':\n return this.isOwnedEntity(entity);\n case 'starred':\n return this.isStarredEntity(entity);\n default:\n return true;\n }\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based if it is an orphan or not.\n * @public\n */\nexport class EntityOrphanFilter implements EntityFilter {\n readonly value: boolean;\n\n constructor(value: boolean) {\n this.value = value;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n if (this.value) {\n return { 'metadata.annotations.backstage.io/orphan': String(this.value) };\n }\n return {};\n }\n\n filterEntity(entity: Entity): boolean {\n const orphan = entity.metadata.annotations?.['backstage.io/orphan'];\n return orphan !== undefined && this.value.toString() === orphan;\n }\n}\n\n/**\n * Filters entities based on if it has errors or not.\n * @public\n */\nexport class EntityErrorFilter implements EntityFilter {\n readonly value: boolean;\n\n constructor(value: boolean) {\n this.value = value;\n }\n\n filterEntity(entity: Entity): boolean {\n const error =\n ((entity as AlphaEntity)?.status?.items?.length as number) > 0;\n return error !== undefined && this.value === error;\n }\n}\n\n/**\n * Sort entities by a given field/column.\n * @public\n */\nexport class EntityOrderFilter implements EntityFilter {\n readonly values: [string, 'asc' | 'desc'][];\n\n constructor(values: [string, 'asc' | 'desc'][]) {\n this.values = values;\n }\n\n getOrderFilters(): EntityOrderQuery {\n return this.values.map(([field, order]) => ({ field, order }));\n }\n\n toQueryValue(): string[] {\n return this.values.flat();\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"filters.esm.js","sources":["../src/filters.ts"],"sourcesContent":["/*\n * Copyright 2021 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 Entity,\n parseEntityRef,\n RELATION_OWNED_BY,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport { AlphaEntity } from '@backstage/catalog-model/alpha';\nimport { EntityFilter, UserListFilterKind } from './types';\nimport { getEntityRelations } from './utils/getEntityRelations';\nimport { EntityOrderQuery } from '@backstage/catalog-client';\n\n/**\n * Filter entities based on Kind.\n * @public\n */\nexport class EntityKindFilter implements EntityFilter {\n readonly value: string;\n readonly label: string;\n\n constructor(value: string, label: string) {\n this.value = value;\n this.label = label;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { kind: this.value };\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based on type\n * @public\n */\nexport class EntityTypeFilter implements EntityFilter {\n readonly value: string | string[];\n\n constructor(value: string | string[]) {\n this.value = value;\n }\n\n // Simplify `string | string[]` for consumers, always returns an array\n getTypes(): string[] {\n return Array.isArray(this.value) ? this.value : [this.value];\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'spec.type': this.getTypes() };\n }\n\n toQueryValue(): string[] {\n return this.getTypes();\n }\n}\n\n/**\n * Filters entities based on tag.\n * @public\n */\nexport class EntityTagFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.every(v => (entity.metadata.tags ?? []).includes(v));\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'metadata.tags': this.values };\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities where the text matches spec, title or tags.\n * @public\n */\nexport class EntityTextFilter implements EntityFilter {\n readonly value: string;\n\n constructor(value: string) {\n this.value = value;\n }\n\n filterEntity(entity: Entity): boolean {\n const words = this.toUpperArray(this.value.split(/\\s/));\n const exactMatch = this.toUpperArray([entity.metadata.tags]);\n const targets = (entity.spec as { targets?: unknown })?.targets;\n const partialMatch = this.toUpperArray([\n entity.metadata.name,\n entity.metadata.title,\n (entity.spec?.profile as { displayName?: string })?.displayName,\n (entity.spec as { target?: string })?.target,\n ...(Array.isArray(targets) ? targets : []),\n ]);\n\n for (const word of words) {\n if (\n exactMatch.every(m => m !== word) &&\n partialMatch.every(m => !m.includes(word))\n ) {\n return false;\n }\n }\n\n return true;\n }\n\n getFullTextFilters() {\n return {\n term: this.value,\n // Update this to be more dynamic based on table columns.\n fields: [\n 'metadata.name',\n 'metadata.title',\n 'spec.profile.displayName',\n 'spec.target',\n 'spec.targets',\n ],\n };\n }\n\n toQueryValue() {\n return this.value;\n }\n\n private toUpperArray(\n value: Array<string | string[] | undefined>,\n ): Array<string> {\n return value\n .flat()\n .filter((m): m is string => Boolean(m) && typeof m === 'string')\n .map(m => m.toLocaleUpperCase('en-US'));\n }\n}\n\n/**\n * Filter matching entities that are owned by group.\n * @public\n *\n * CAUTION: This class may contain both full and partial entity refs.\n */\nexport class EntityOwnerFilter implements EntityFilter {\n readonly values: string[];\n constructor(values: string[]) {\n this.values = values.reduce((fullRefs, ref) => {\n // Attempt to remove bad entity references here.\n try {\n fullRefs.push(\n stringifyEntityRef(parseEntityRef(ref, { defaultKind: 'Group' })),\n );\n return fullRefs;\n } catch (err) {\n return fullRefs;\n }\n }, [] as string[]);\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'relations.ownedBy': this.values };\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.some(v =>\n getEntityRelations(entity, RELATION_OWNED_BY).some(\n o => stringifyEntityRef(o) === v,\n ),\n );\n }\n\n /**\n * Get the URL query parameter value. May be a mix of full and humanized entity refs.\n * @returns list of entity refs.\n */\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities on lifecycle.\n * @public\n */\nexport class EntityLifecycleFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'spec.lifecycle': this.values };\n }\n\n filterEntity(entity: Entity): boolean {\n return this.values.some(v => entity.spec?.lifecycle === v);\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * Filters entities to those within the given namespace(s).\n * @public\n */\nexport class EntityNamespaceFilter implements EntityFilter {\n readonly values: string[];\n\n constructor(values: string[]) {\n this.values = values;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n return { 'metadata.namespace': this.values };\n }\n filterEntity(entity: Entity): boolean {\n return this.values.some(v => entity.metadata.namespace === v);\n }\n\n toQueryValue(): string[] {\n return this.values;\n }\n}\n\n/**\n * @public\n */\nexport class EntityUserFilter implements EntityFilter {\n readonly value: UserListFilterKind;\n readonly refs?: string[];\n\n private constructor(value: UserListFilterKind, refs?: string[]) {\n this.value = value;\n this.refs = refs;\n }\n\n static owned(ownershipEntityRefs: string[]) {\n return new EntityUserFilter('owned', ownershipEntityRefs);\n }\n\n static all() {\n return new EntityUserFilter('all');\n }\n\n static starred(starredEntityRefs: string[]) {\n return new EntityUserFilter('starred', starredEntityRefs);\n }\n\n getCatalogFilters(): Record<string, string[]> {\n if (this.value === 'owned') {\n return { 'relations.ownedBy': this.refs ?? [] };\n }\n if (this.value === 'starred') {\n return {\n 'metadata.name': this.refs?.map(e => parseEntityRef(e).name) ?? [],\n };\n }\n return {};\n }\n\n filterEntity(entity: Entity) {\n if (this.value === 'starred') {\n return this.refs?.includes(stringifyEntityRef(entity)) ?? true;\n }\n // used only for retro-compatibility with non paginated data.\n // This is supposed to return always true for paginated\n // owned entities, since the filters are applied server side.\n if (this.value === 'owned') {\n const relations = getEntityRelations(entity, RELATION_OWNED_BY);\n\n return (\n this.refs?.some(v =>\n relations.some(o => stringifyEntityRef(o) === v),\n ) ?? false\n );\n }\n return true;\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based on whatever the user has starred or owns them.\n * @deprecated use EntityUserFilter\n * @public\n */\nexport class UserListFilter implements EntityFilter {\n readonly value: UserListFilterKind;\n readonly isOwnedEntity: (entity: Entity) => boolean;\n readonly isStarredEntity: (entity: Entity) => boolean;\n\n constructor(\n value: UserListFilterKind,\n isOwnedEntity: (entity: Entity) => boolean,\n isStarredEntity: (entity: Entity) => boolean,\n ) {\n this.value = value;\n this.isOwnedEntity = isOwnedEntity;\n this.isStarredEntity = isStarredEntity;\n }\n\n filterEntity(entity: Entity): boolean {\n switch (this.value) {\n case 'owned':\n return this.isOwnedEntity(entity);\n case 'starred':\n return this.isStarredEntity(entity);\n default:\n return true;\n }\n }\n\n toQueryValue(): string {\n return this.value;\n }\n}\n\n/**\n * Filters entities based if it is an orphan or not.\n * @public\n */\nexport class EntityOrphanFilter implements EntityFilter {\n readonly value: boolean;\n\n constructor(value: boolean) {\n this.value = value;\n }\n\n getCatalogFilters(): Record<string, string | string[]> {\n if (this.value) {\n return { 'metadata.annotations.backstage.io/orphan': String(this.value) };\n }\n return {};\n }\n\n filterEntity(entity: Entity): boolean {\n const orphan = entity.metadata.annotations?.['backstage.io/orphan'];\n return orphan !== undefined && this.value.toString() === orphan;\n }\n}\n\n/**\n * Filters entities based on if it has errors or not.\n * @public\n */\nexport class EntityErrorFilter implements EntityFilter {\n readonly value: boolean;\n\n constructor(value: boolean) {\n this.value = value;\n }\n\n filterEntity(entity: Entity): boolean {\n const error =\n ((entity as AlphaEntity)?.status?.items?.length as number) > 0;\n return error !== undefined && this.value === error;\n }\n}\n\n/**\n * Sort entities by a given field/column.\n * @public\n */\nexport class EntityOrderFilter implements EntityFilter {\n readonly values: [string, 'asc' | 'desc'][];\n\n constructor(values: [string, 'asc' | 'desc'][]) {\n this.values = values;\n }\n\n getOrderFilters(): EntityOrderQuery {\n return this.values.map(([field, order]) => ({ field, order }));\n }\n\n toQueryValue(): string[] {\n return this.values.flat();\n }\n}\n"],"names":[],"mappings":";;;AA+BO,MAAM,gBAAA,CAAyC;AAAA,EAC3C,KAAA;AAAA,EACA,KAAA;AAAA,EAET,WAAA,CAAY,OAAe,KAAA,EAAe;AACxC,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,IAAA,EAAM,IAAA,CAAK,KAAA,EAAM;AAAA,EAC5B;AAAA,EAEA,YAAA,GAAuB;AACrB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF;AAMO,MAAM,gBAAA,CAAyC;AAAA,EAC3C,KAAA;AAAA,EAET,YAAY,KAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA;AAAA,EAGA,QAAA,GAAqB;AACnB,IAAA,OAAO,KAAA,CAAM,QAAQ,IAAA,CAAK,KAAK,IAAI,IAAA,CAAK,KAAA,GAAQ,CAAC,IAAA,CAAK,KAAK,CAAA;AAAA,EAC7D;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,WAAA,EAAa,IAAA,CAAK,QAAA,EAAS,EAAE;AAAA,EACxC;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,KAAK,QAAA,EAAS;AAAA,EACvB;AACF;AAMO,MAAM,eAAA,CAAwC;AAAA,EAC1C,MAAA;AAAA,EAET,YAAY,MAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,CAAA,KAAA,CAAM,MAAA,CAAO,QAAA,CAAS,IAAA,IAAQ,EAAC,EAAG,QAAA,CAAS,CAAC,CAAC,CAAA;AAAA,EACxE;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,eAAA,EAAiB,IAAA,CAAK,MAAA,EAAO;AAAA,EACxC;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAMO,MAAM,gBAAA,CAAyC;AAAA,EAC3C,KAAA;AAAA,EAET,YAAY,KAAA,EAAe;AACzB,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,MAAM,QAAQ,IAAA,CAAK,YAAA,CAAa,KAAK,KAAA,CAAM,KAAA,CAAM,IAAI,CAAC,CAAA;AACtD,IAAA,MAAM,aAAa,IAAA,CAAK,YAAA,CAAa,CAAC,MAAA,CAAO,QAAA,CAAS,IAAI,CAAC,CAAA;AAC3D,IAAA,MAAM,OAAA,GAAW,OAAO,IAAA,EAAgC,OAAA;AACxD,IAAA,MAAM,YAAA,GAAe,KAAK,YAAA,CAAa;AAAA,MACrC,OAAO,QAAA,CAAS,IAAA;AAAA,MAChB,OAAO,QAAA,CAAS,KAAA;AAAA,MACf,MAAA,CAAO,MAAM,OAAA,EAAsC,WAAA;AAAA,MACnD,OAAO,IAAA,EAA8B,MAAA;AAAA,MACtC,GAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,GAAI,UAAU;AAAC,KACzC,CAAA;AAED,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,IACE,UAAA,CAAW,KAAA,CAAM,CAAA,CAAA,KAAK,CAAA,KAAM,IAAI,CAAA,IAChC,YAAA,CAAa,KAAA,CAAM,CAAA,CAAA,KAAK,CAAC,CAAA,CAAE,QAAA,CAAS,IAAI,CAAC,CAAA,EACzC;AACA,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,IACF;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,kBAAA,GAAqB;AACnB,IAAA,OAAO;AAAA,MACL,MAAM,IAAA,CAAK,KAAA;AAAA;AAAA,MAEX,MAAA,EAAQ;AAAA,QACN,eAAA;AAAA,QACA,gBAAA;AAAA,QACA,0BAAA;AAAA,QACA,aAAA;AAAA,QACA;AAAA;AACF,KACF;AAAA,EACF;AAAA,EAEA,YAAA,GAAe;AACb,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEQ,aACN,KAAA,EACe;AACf,IAAA,OAAO,MACJ,IAAA,EAAK,CACL,OAAO,CAAC,CAAA,KAAmB,QAAQ,CAAC,CAAA,IAAK,OAAO,CAAA,KAAM,QAAQ,CAAA,CAC9D,GAAA,CAAI,OAAK,CAAA,CAAE,iBAAA,CAAkB,OAAO,CAAC,CAAA;AAAA,EAC1C;AACF;AAQO,MAAM,iBAAA,CAA0C;AAAA,EAC5C,MAAA;AAAA,EACT,YAAY,MAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA,CAAO,MAAA,CAAO,CAAC,UAAU,GAAA,KAAQ;AAE7C,MAAA,IAAI;AACF,QAAA,QAAA,CAAS,IAAA;AAAA,UACP,mBAAmB,cAAA,CAAe,GAAA,EAAK,EAAE,WAAA,EAAa,OAAA,EAAS,CAAC;AAAA,SAClE;AACA,QAAA,OAAO,QAAA;AAAA,MACT,SAAS,GAAA,EAAK;AACZ,QAAA,OAAO,QAAA;AAAA,MACT;AAAA,IACF,CAAA,EAAG,EAAc,CAAA;AAAA,EACnB;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,mBAAA,EAAqB,IAAA,CAAK,MAAA,EAAO;AAAA,EAC5C;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,OAAO,KAAK,MAAA,CAAO,IAAA;AAAA,MAAK,CAAA,CAAA,KACtB,kBAAA,CAAmB,MAAA,EAAQ,iBAAiB,CAAA,CAAE,IAAA;AAAA,QAC5C,CAAA,CAAA,KAAK,kBAAA,CAAmB,CAAC,CAAA,KAAM;AAAA;AACjC,KACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAMO,MAAM,qBAAA,CAA8C;AAAA,EAChD,MAAA;AAAA,EAET,YAAY,MAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,gBAAA,EAAkB,IAAA,CAAK,MAAA,EAAO;AAAA,EACzC;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,OAAO,KAAK,MAAA,CAAO,IAAA,CAAK,OAAK,MAAA,CAAO,IAAA,EAAM,cAAc,CAAC,CAAA;AAAA,EAC3D;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAMO,MAAM,qBAAA,CAA8C;AAAA,EAChD,MAAA;AAAA,EAET,YAAY,MAAA,EAAkB;AAC5B,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,OAAO,EAAE,oBAAA,EAAsB,IAAA,CAAK,MAAA,EAAO;AAAA,EAC7C;AAAA,EACA,aAAa,MAAA,EAAyB;AACpC,IAAA,OAAO,KAAK,MAAA,CAAO,IAAA,CAAK,OAAK,MAAA,CAAO,QAAA,CAAS,cAAc,CAAC,CAAA;AAAA,EAC9D;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AACF;AAKO,MAAM,gBAAA,CAAyC;AAAA,EAC3C,KAAA;AAAA,EACA,IAAA;AAAA,EAED,WAAA,CAAY,OAA2B,IAAA,EAAiB;AAC9D,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AAAA,EAEA,OAAO,MAAM,mBAAA,EAA+B;AAC1C,IAAA,OAAO,IAAI,gBAAA,CAAiB,OAAA,EAAS,mBAAmB,CAAA;AAAA,EAC1D;AAAA,EAEA,OAAO,GAAA,GAAM;AACX,IAAA,OAAO,IAAI,iBAAiB,KAAK,CAAA;AAAA,EACnC;AAAA,EAEA,OAAO,QAAQ,iBAAA,EAA6B;AAC1C,IAAA,OAAO,IAAI,gBAAA,CAAiB,SAAA,EAAW,iBAAiB,CAAA;AAAA,EAC1D;AAAA,EAEA,iBAAA,GAA8C;AAC5C,IAAA,IAAI,IAAA,CAAK,UAAU,OAAA,EAAS;AAC1B,MAAA,OAAO,EAAE,mBAAA,EAAqB,IAAA,CAAK,IAAA,IAAQ,EAAC,EAAE;AAAA,IAChD;AACA,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC5B,MAAA,OAAO;AAAA,QACL,eAAA,EAAiB,IAAA,CAAK,IAAA,EAAM,GAAA,CAAI,CAAA,CAAA,KAAK,eAAe,CAAC,CAAA,CAAE,IAAI,CAAA,IAAK;AAAC,OACnE;AAAA,IACF;AACA,IAAA,OAAO,EAAC;AAAA,EACV;AAAA,EAEA,aAAa,MAAA,EAAgB;AAC3B,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC5B,MAAA,OAAO,KAAK,IAAA,EAAM,QAAA,CAAS,kBAAA,CAAmB,MAAM,CAAC,CAAA,IAAK,IAAA;AAAA,IAC5D;AAIA,IAAA,IAAI,IAAA,CAAK,UAAU,OAAA,EAAS;AAC1B,MAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,MAAA,EAAQ,iBAAiB,CAAA;AAE9D,MAAA,OACE,KAAK,IAAA,EAAM,IAAA;AAAA,QAAK,OACd,SAAA,CAAU,IAAA,CAAK,OAAK,kBAAA,CAAmB,CAAC,MAAM,CAAC;AAAA,OACjD,IAAK,KAAA;AAAA,IAET;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,YAAA,GAAuB;AACrB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF;AAOO,MAAM,cAAA,CAAuC;AAAA,EACzC,KAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EAET,WAAA,CACE,KAAA,EACA,aAAA,EACA,eAAA,EACA;AACA,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,IAAA,CAAK,aAAA,GAAgB,aAAA;AACrB,IAAA,IAAA,CAAK,eAAA,GAAkB,eAAA;AAAA,EACzB;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,QAAQ,KAAK,KAAA;AAAO,MAClB,KAAK,OAAA;AACH,QAAA,OAAO,IAAA,CAAK,cAAc,MAAM,CAAA;AAAA,MAClC,KAAK,SAAA;AACH,QAAA,OAAO,IAAA,CAAK,gBAAgB,MAAM,CAAA;AAAA,MACpC;AACE,QAAA,OAAO,IAAA;AAAA;AACX,EACF;AAAA,EAEA,YAAA,GAAuB;AACrB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF;AAMO,MAAM,kBAAA,CAA2C;AAAA,EAC7C,KAAA;AAAA,EAET,YAAY,KAAA,EAAgB;AAC1B,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA,EAEA,iBAAA,GAAuD;AACrD,IAAA,IAAI,KAAK,KAAA,EAAO;AACd,MAAA,OAAO,EAAE,0CAAA,EAA4C,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAE;AAAA,IAC1E;AACA,IAAA,OAAO,EAAC;AAAA,EACV;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,qBAAqB,CAAA;AAClE,IAAA,OAAO,MAAA,KAAW,MAAA,IAAa,IAAA,CAAK,KAAA,CAAM,UAAS,KAAM,MAAA;AAAA,EAC3D;AACF;AAMO,MAAM,iBAAA,CAA0C;AAAA,EAC5C,KAAA;AAAA,EAET,YAAY,KAAA,EAAgB;AAC1B,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AAAA,EACf;AAAA,EAEA,aAAa,MAAA,EAAyB;AACpC,IAAA,MAAM,KAAA,GACF,MAAA,EAAwB,MAAA,EAAQ,KAAA,EAAO,MAAA,GAAoB,CAAA;AAC/D,IAAA,OAAO,KAAA,KAAU,MAAA,IAAa,IAAA,CAAK,KAAA,KAAU,KAAA;AAAA,EAC/C;AACF;AAMO,MAAM,iBAAA,CAA0C;AAAA,EAC5C,MAAA;AAAA,EAET,YAAY,MAAA,EAAoC;AAC9C,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,eAAA,GAAoC;AAClC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,CAAC,CAAC,KAAA,EAAO,KAAK,CAAA,MAAO,EAAE,KAAA,EAAO,KAAA,EAAM,CAAE,CAAA;AAAA,EAC/D;AAAA,EAEA,YAAA,GAAyB;AACvB,IAAA,OAAO,IAAA,CAAK,OAAO,IAAA,EAAK;AAAA,EAC1B;AACF;;;;"}
@@ -60,6 +60,7 @@ const EntityListProvider = (props) => {
60
60
  });
61
61
  const [loading, setLoading] = useState(true);
62
62
  const [error, setError] = useState();
63
+ const [refreshToken, setRefreshToken] = useState(0);
63
64
  const lastFetchParamsRef = useRef(void 0);
64
65
  const fetchGenRef = useRef(0);
65
66
  const adjustedFilters = useMemo(() => {
@@ -139,7 +140,13 @@ const EntityListProvider = (props) => {
139
140
  }
140
141
  }
141
142
  };
142
- useDebounce(refresh, 10, [adjustedFilters, cursor, limit, offset]);
143
+ useDebounce(refresh, 10, [
144
+ adjustedFilters,
145
+ cursor,
146
+ limit,
147
+ offset,
148
+ refreshToken
149
+ ]);
143
150
  const [{ value: totalItems, loading: totalItemsLoading }, refreshCount] = useAsyncFn(async () => {
144
151
  if (paginationMode === "none") {
145
152
  return void 0;
@@ -159,7 +166,7 @@ const EntityListProvider = (props) => {
159
166
  return void 0;
160
167
  }
161
168
  }, [catalogApi, paginationMode, adjustedFilters]);
162
- useDebounce(refreshCount, 10, [adjustedFilters]);
169
+ useDebounce(refreshCount, 10, [adjustedFilters, refreshToken]);
163
170
  const entities = useMemo(() => {
164
171
  const compacted = compact(Object.values(adjustedFilters));
165
172
  const entityFilter = reduceEntityFilters(compacted);
@@ -213,6 +220,10 @@ const EntityListProvider = (props) => {
213
220
  },
214
221
  [paginationMode]
215
222
  );
223
+ const triggerRefresh = useCallback(() => {
224
+ lastFetchParamsRef.current = void 0;
225
+ setRefreshToken((t) => t + 1);
226
+ }, []);
216
227
  const pageInfo = useMemo(() => {
217
228
  if (paginationMode !== "cursor") {
218
229
  return void 0;
@@ -240,7 +251,8 @@ const EntityListProvider = (props) => {
240
251
  offset,
241
252
  setLimit,
242
253
  setOffset,
243
- paginationMode
254
+ paginationMode,
255
+ refresh: triggerRefresh
244
256
  }),
245
257
  [
246
258
  requestedFilters,
@@ -257,7 +269,8 @@ const EntityListProvider = (props) => {
257
269
  limit,
258
270
  offset,
259
271
  setLimit,
260
- setOffset
272
+ setOffset,
273
+ triggerRefresh
261
274
  ]
262
275
  );
263
276
  return /* @__PURE__ */ jsx(OldEntityListContext.Provider, { value, children: /* @__PURE__ */ jsx(
@@ -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 { QueryEntitiesResponse } from '@backstage/catalog-client';\nimport { Entity } from '@backstage/catalog-model';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n createVersionedContext,\n createVersionedValueMap,\n useVersionedContext,\n} from '@backstage/version-bridge';\nimport { compact, isEqual } from 'lodash';\nimport qs from 'qs';\nimport {\n createContext,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\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 totalItemsLoading: boolean;\n limit: number;\n offset?: number;\n setLimit: (limit: number) => void;\n setOffset?: (offset: number) => void;\n paginationMode: PaginationMode;\n};\n\n// This context has support for multiple concurrent versions of this package.\n// It is currently used in parallel with the old context in order to provide\n// a smooth transition, but will eventually be the only context we use.\nexport const NewEntityListContext = createVersionedContext<{\n 1: EntityListContextProps<any>;\n}>('entity-list-context');\n\n/**\n * Creates new context for entity listing and filtering.\n */\nexport const OldEntityListContext = createContext<\n EntityListContextProps<any> | undefined\n>(undefined);\n\ntype BackendState = {\n backendEntities: Entity[];\n pageInfo?: QueryEntitiesResponse['pageInfo'];\n totalItems?: 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 let paginationMode: PaginationMode = 'none';\n if (props.pagination === true) {\n paginationMode = 'cursor';\n } else if (typeof props.pagination === 'object') {\n paginationMode = props.pagination.mode ?? 'cursor';\n }\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 arrayLimit: 10000,\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 [backendState, setBackendState] = useState<BackendState>({\n backendEntities: [],\n });\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>();\n\n // Tracks the params of the last API call so identical requests are\n // skipped even when requestedFilters changes (e.g. a label change\n // or a frontend-only filter addition).\n const lastFetchParamsRef = useRef<unknown>(undefined);\n // Generation counter — only the most recent fetch updates state,\n // so out-of-order responses from overlapping requests are discarded.\n const fetchGenRef = useRef(0);\n\n // Adjusted filters remove the owners filter for user/group kinds,\n // since ownership is not meaningful for those entity types.\n const adjustedFilters = useMemo(() => {\n const kindValue = requestedFilters.kind?.value?.toLocaleLowerCase('en-US');\n return kindValue === 'user' || kindValue === 'group'\n ? { ...requestedFilters, owners: undefined }\n : requestedFilters;\n }, [requestedFilters]);\n\n const refresh = async () => {\n const compacted = compact(Object.values(adjustedFilters));\n\n let fetchParams: unknown;\n let doFetch: () => Promise<BackendState>;\n\n if (paginationMode !== 'none') {\n if (cursor) {\n fetchParams = { cursor, limit };\n doFetch = async () => {\n const response = await catalogApi.queryEntities({\n cursor,\n limit,\n totalItems: 'exclude',\n });\n return {\n backendEntities: response.items,\n pageInfo: response.pageInfo,\n };\n };\n } else {\n const backendFilter = reduceCatalogFilters(compacted);\n fetchParams = { ...backendFilter, limit, offset };\n doFetch = async () => {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit,\n offset,\n totalItems: 'exclude',\n });\n return {\n backendEntities: response.items,\n pageInfo: response.pageInfo,\n };\n };\n }\n } else {\n const backendFilter = reduceBackendCatalogFilters(compacted);\n const { orderFields } = reduceCatalogFilters(compacted);\n fetchParams = { filter: backendFilter, order: orderFields };\n doFetch = async () => {\n const response = await catalogApi.getEntities({\n filter: backendFilter,\n order: orderFields,\n });\n return { backendEntities: response.items };\n };\n }\n\n // No filters registered yet — wait for filter components to\n // call updateFilters before making the first request.\n // Exception: a cursor in the URL is a self-contained page reference\n // that doesn't need filter state to be valid.\n if (\n compacted.length === 0 &&\n lastFetchParamsRef.current === undefined &&\n !cursor\n ) {\n setLoading(false);\n return;\n }\n\n if (isEqual(fetchParams, lastFetchParamsRef.current)) {\n return;\n }\n lastFetchParamsRef.current = fetchParams;\n\n const gen = ++fetchGenRef.current;\n setLoading(true);\n setError(undefined);\n\n try {\n const result = await doFetch();\n if (gen === fetchGenRef.current) {\n setBackendState(result);\n }\n } catch (e) {\n if (gen === fetchGenRef.current) {\n // Clear the ref so the same params can be retried, and so\n // a response discarded due to a concurrent request (gen mismatch)\n // doesn't permanently block fetching those params again.\n lastFetchParamsRef.current = undefined;\n setError(e as Error);\n }\n } finally {\n if (gen === fetchGenRef.current) {\n setLoading(false);\n }\n }\n };\n\n // Slight debounce on the refresh, since (especially on page load)\n // several filters will be calling updateFilters in rapid succession.\n useDebounce(refresh, 10, [adjustedFilters, cursor, limit, offset]);\n\n // Fetch the total count separately, only when filters change. This is\n // decoupled from the main list fetch so that page navigation doesn't\n // re-run the expensive count query, and so that the count can arrive\n // asynchronously without blocking the list response.\n const [{ value: totalItems, loading: totalItemsLoading }, refreshCount] =\n useAsyncFn(async () => {\n if (paginationMode === 'none') {\n return undefined;\n }\n const compacted = compact(Object.values(adjustedFilters));\n if (compacted.length === 0) {\n return undefined;\n }\n const backendFilter = reduceCatalogFilters(compacted);\n try {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit: 0,\n });\n return response.totalItems;\n } catch {\n return undefined;\n }\n }, [catalogApi, paginationMode, adjustedFilters]);\n\n useDebounce(refreshCount, 10, [adjustedFilters]);\n\n // Frontend filtering — synchronous, no debounce needed. Updates\n // instantly when requestedFilters or backendEntities change.\n const entities = useMemo(() => {\n const compacted = compact(Object.values(adjustedFilters));\n const entityFilter = reduceEntityFilters(compacted);\n return backendState.backendEntities.filter(entityFilter);\n }, [adjustedFilters, backendState.backendEntities]);\n\n // Sync filter state to URL query parameters. We use direct history\n // manipulation since useSearchParams and useNavigate in\n // react-router-dom cause unnecessary extra rerenders. Also make sure\n // to replace the state rather than pushing, since we don't want\n // there to be back/forward slots for every single filter change.\n useEffect(() => {\n if (!isMounted() || Object.keys(requestedFilters).length === 0) {\n return;\n }\n const queryParams = Object.keys(requestedFilters).reduce((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 }, {} as Record<string, string | string[]>);\n\n const oldParams = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n arrayLimit: 10000,\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 window.history?.replaceState(null, document.title, newUrl);\n }, [\n cursor,\n isMounted,\n limit,\n location.search,\n offset,\n requestedFilters,\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 = backendState.pageInfo?.prevCursor;\n const nextCursor = backendState.pageInfo?.nextCursor;\n return {\n prev: prevCursor ? () => setCursor(prevCursor) : undefined,\n next: nextCursor ? () => setCursor(nextCursor) : undefined,\n };\n }, [paginationMode, backendState.pageInfo]);\n\n const value = useMemo(\n () => ({\n filters: requestedFilters,\n entities,\n backendEntities: backendState.backendEntities,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n totalItems:\n paginationMode === 'none'\n ? entities.length\n : totalItems ?? backendState.totalItems,\n totalItemsLoading: paginationMode !== 'none' && totalItemsLoading,\n limit,\n offset,\n setLimit,\n setOffset,\n paginationMode,\n }),\n [\n requestedFilters,\n entities,\n backendState,\n totalItems,\n totalItemsLoading,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n paginationMode,\n limit,\n offset,\n setLimit,\n setOffset,\n ],\n );\n\n return (\n <OldEntityListContext.Provider value={value}>\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: value })}\n >\n {props.children}\n </NewEntityListContext.Provider>\n </OldEntityListContext.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 versionedHolder = useVersionedContext<{\n 1: EntityListContextProps<any>;\n }>('entity-list-context');\n const oldContext = useContext(OldEntityListContext);\n\n if (versionedHolder) {\n const value = versionedHolder.atVersion(1);\n if (!value) {\n throw new Error('EntityListContext v1 not available');\n }\n return value;\n }\n\n if (oldContext) {\n return oldContext;\n }\n\n throw new Error('useEntityList must be used within EntityListProvider');\n}\n"],"names":["limit","offset"],"mappings":";;;;;;;;;;;;;AAsIO,MAAM,oBAAA,GAAuB,uBAEjC,qBAAqB;AAKjB,MAAM,oBAAA,GAAuB,cAElC,MAAS;AAmBJ,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,IAAI,cAAA,GAAiC,MAAA;AACrC,EAAA,IAAI,KAAA,CAAM,eAAe,IAAA,EAAM;AAC7B,IAAA,cAAA,GAAiB,QAAA;AAAA,EACnB,CAAA,MAAA,IAAW,OAAO,KAAA,CAAM,UAAA,KAAe,QAAA,EAAU;AAC/C,IAAA,cAAA,GAAiB,KAAA,CAAM,WAAW,IAAA,IAAQ,QAAA;AAAA,EAC5C;AACA,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,IAAA;AAAA,MACnB,UAAA,EAAY;AAAA,KACb,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,YAAA,EAAc,eAAe,CAAA,GAAI,QAAA,CAAuB;AAAA,IAC7D,iBAAiB;AAAC,GACnB,CAAA;AACD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,EAA4B;AAKtD,EAAA,MAAM,kBAAA,GAAqB,OAAgB,MAAS,CAAA;AAGpD,EAAA,MAAM,WAAA,GAAc,OAAO,CAAC,CAAA;AAI5B,EAAA,MAAM,eAAA,GAAkB,QAAQ,MAAM;AACpC,IAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,IAAA,EAAM,KAAA,EAAO,kBAAkB,OAAO,CAAA;AACzE,IAAA,OAAO,SAAA,KAAc,UAAU,SAAA,KAAc,OAAA,GACzC,EAAE,GAAG,gBAAA,EAAkB,MAAA,EAAQ,MAAA,EAAU,GACzC,gBAAA;AAAA,EACN,CAAA,EAAG,CAAC,gBAAgB,CAAC,CAAA;AAErB,EAAA,MAAM,UAAU,YAAY;AAC1B,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,eAAe,CAAC,CAAA;AAExD,IAAA,IAAI,WAAA;AACJ,IAAA,IAAI,OAAA;AAEJ,IAAA,IAAI,mBAAmB,MAAA,EAAQ;AAC7B,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,WAAA,GAAc,EAAE,QAAQ,KAAA,EAAM;AAC9B,QAAA,OAAA,GAAU,YAAY;AACpB,UAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,aAAA,CAAc;AAAA,YAC9C,MAAA;AAAA,YACA,KAAA;AAAA,YACA,UAAA,EAAY;AAAA,WACb,CAAA;AACD,UAAA,OAAO;AAAA,YACL,iBAAiB,QAAA,CAAS,KAAA;AAAA,YAC1B,UAAU,QAAA,CAAS;AAAA,WACrB;AAAA,QACF,CAAA;AAAA,MACF,CAAA,MAAO;AACL,QAAA,MAAM,aAAA,GAAgB,qBAAqB,SAAS,CAAA;AACpD,QAAA,WAAA,GAAc,EAAE,GAAG,aAAA,EAAe,KAAA,EAAO,MAAA,EAAO;AAChD,QAAA,OAAA,GAAU,YAAY;AACpB,UAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,aAAA,CAAc;AAAA,YAC9C,GAAG,aAAA;AAAA,YACH,KAAA;AAAA,YACA,MAAA;AAAA,YACA,UAAA,EAAY;AAAA,WACb,CAAA;AACD,UAAA,OAAO;AAAA,YACL,iBAAiB,QAAA,CAAS,KAAA;AAAA,YAC1B,UAAU,QAAA,CAAS;AAAA,WACrB;AAAA,QACF,CAAA;AAAA,MACF;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,aAAA,GAAgB,4BAA4B,SAAS,CAAA;AAC3D,MAAA,MAAM,EAAE,WAAA,EAAY,GAAI,oBAAA,CAAqB,SAAS,CAAA;AACtD,MAAA,WAAA,GAAc,EAAE,MAAA,EAAQ,aAAA,EAAe,KAAA,EAAO,WAAA,EAAY;AAC1D,MAAA,OAAA,GAAU,YAAY;AACpB,QAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,WAAA,CAAY;AAAA,UAC5C,MAAA,EAAQ,aAAA;AAAA,UACR,KAAA,EAAO;AAAA,SACR,CAAA;AACD,QAAA,OAAO,EAAE,eAAA,EAAiB,QAAA,CAAS,KAAA,EAAM;AAAA,MAC3C,CAAA;AAAA,IACF;AAMA,IAAA,IACE,UAAU,MAAA,KAAW,CAAA,IACrB,mBAAmB,OAAA,KAAY,MAAA,IAC/B,CAAC,MAAA,EACD;AACA,MAAA,UAAA,CAAW,KAAK,CAAA;AAChB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,OAAA,CAAQ,WAAA,EAAa,kBAAA,CAAmB,OAAO,CAAA,EAAG;AACpD,MAAA;AAAA,IACF;AACA,IAAA,kBAAA,CAAmB,OAAA,GAAU,WAAA;AAE7B,IAAA,MAAM,GAAA,GAAM,EAAE,WAAA,CAAY,OAAA;AAC1B,IAAA,UAAA,CAAW,IAAI,CAAA;AACf,IAAA,QAAA,CAAS,MAAS,CAAA;AAElB,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,OAAA,EAAQ;AAC7B,MAAA,IAAI,GAAA,KAAQ,YAAY,OAAA,EAAS;AAC/B,QAAA,eAAA,CAAgB,MAAM,CAAA;AAAA,MACxB;AAAA,IACF,SAAS,CAAA,EAAG;AACV,MAAA,IAAI,GAAA,KAAQ,YAAY,OAAA,EAAS;AAI/B,QAAA,kBAAA,CAAmB,OAAA,GAAU,MAAA;AAC7B,QAAA,QAAA,CAAS,CAAU,CAAA;AAAA,MACrB;AAAA,IACF,CAAA,SAAE;AACA,MAAA,IAAI,GAAA,KAAQ,YAAY,OAAA,EAAS;AAC/B,QAAA,UAAA,CAAW,KAAK,CAAA;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAA;AAIA,EAAA,WAAA,CAAY,SAAS,EAAA,EAAI,CAAC,iBAAiB,MAAA,EAAQ,KAAA,EAAO,MAAM,CAAC,CAAA;AAMjE,EAAA,MAAM,CAAC,EAAE,KAAA,EAAO,UAAA,EAAY,OAAA,EAAS,mBAAkB,EAAG,YAAY,CAAA,GACpE,UAAA,CAAW,YAAY;AACrB,IAAA,IAAI,mBAAmB,MAAA,EAAQ;AAC7B,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,eAAe,CAAC,CAAA;AACxD,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,MAAM,aAAA,GAAgB,qBAAqB,SAAS,CAAA;AACpD,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,UAAA,CAAW,aAAA,CAAc;AAAA,QAC9C,GAAG,aAAA;AAAA,QACH,KAAA,EAAO;AAAA,OACR,CAAA;AACD,MAAA,OAAO,QAAA,CAAS,UAAA;AAAA,IAClB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF,CAAA,EAAG,CAAC,UAAA,EAAY,cAAA,EAAgB,eAAe,CAAC,CAAA;AAElD,EAAA,WAAA,CAAY,YAAA,EAAc,EAAA,EAAI,CAAC,eAAe,CAAC,CAAA;AAI/C,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,eAAe,CAAC,CAAA;AACxD,IAAA,MAAM,YAAA,GAAe,oBAAoB,SAAS,CAAA;AAClD,IAAA,OAAO,YAAA,CAAa,eAAA,CAAgB,MAAA,CAAO,YAAY,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,eAAA,EAAiB,YAAA,CAAa,eAAe,CAAC,CAAA;AAOlD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,WAAU,IAAK,MAAA,CAAO,KAAK,gBAAgB,CAAA,CAAE,WAAW,CAAA,EAAG;AAC9D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,WAAA,GAAc,OAAO,IAAA,CAAK,gBAAgB,EAAE,MAAA,CAAO,CAAC,QAAQ,GAAA,KAAQ;AACxE,MAAA,MAAM,MAAA,GAAS,iBAAiB,GAA0B,CAAA;AAG1D,MAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,QAAA,MAAA,CAAO,GAAG,CAAA,GAAI,MAAA,CAAO,YAAA,EAAa;AAAA,MACpC;AACA,MAAA,OAAO,MAAA;AAAA,IACT,CAAA,EAAG,EAAuC,CAAA;AAE1C,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,MAAA,EAAQ;AAAA,MAC1C,iBAAA,EAAmB,IAAA;AAAA,MACnB,UAAA,EAAY;AAAA,KACb,CAAA;AACD,IAAA,MAAM,YAAY,EAAA,CAAG,SAAA;AAAA,MACnB;AAAA,QACE,GAAG,SAAA;AAAA,QACH,OAAA,EAAS,WAAA;AAAA,QACT,GAAI,mBAAmB,MAAA,GAAS,KAAK,EAAE,MAAA,EAAQ,OAAO,MAAA;AAAO,OAC/D;AAAA,MACA,EAAE,cAAA,EAAgB,IAAA,EAAM,WAAA,EAAa,QAAA;AAAS,KAChD;AACA,IAAA,MAAM,SAAS,CAAA,EAAG,MAAA,CAAO,QAAA,CAAS,QAAQ,GAAG,SAAS,CAAA,CAAA;AACtD,IAAA,MAAA,CAAO,OAAA,EAAS,YAAA,CAAa,IAAA,EAAM,QAAA,CAAS,OAAO,MAAM,CAAA;AAAA,EAC3D,CAAA,EAAG;AAAA,IACD,MAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA,QAAA,CAAS,MAAA;AAAA,IACT,MAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,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,aAAa,QAAA,EAAU,UAAA;AAC1C,IAAA,MAAM,UAAA,GAAa,aAAa,QAAA,EAAU,UAAA;AAC1C,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,YAAA,CAAa,QAAQ,CAAC,CAAA;AAE1C,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,OAAO;AAAA,MACL,OAAA,EAAS,gBAAA;AAAA,MACT,QAAA;AAAA,MACA,iBAAiB,YAAA,CAAa,eAAA;AAAA,MAC9B,aAAA;AAAA,MACA,eAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA,YACE,cAAA,KAAmB,MAAA,GACf,QAAA,CAAS,MAAA,GACT,cAAc,YAAA,CAAa,UAAA;AAAA,MACjC,iBAAA,EAAmB,mBAAmB,MAAA,IAAU,iBAAA;AAAA,MAChD,KAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA,SAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA,IACA;AAAA,MACE,gBAAA;AAAA,MACA,QAAA;AAAA,MACA,YAAA;AAAA,MACA,UAAA;AAAA,MACA,iBAAA;AAAA,MACA,aAAA;AAAA,MACA,eAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA;AAAA,MACA,QAAA;AAAA,MACA,cAAA;AAAA,MACA,KAAA;AAAA,MACA,MAAA;AAAA,MACA,QAAA;AAAA,MACA;AAAA;AACF,GACF;AAEA,EAAA,uBACE,GAAA,CAAC,oBAAA,CAAqB,QAAA,EAArB,EAA8B,KAAA,EAC7B,QAAA,kBAAA,GAAA;AAAA,IAAC,oBAAA,CAAqB,QAAA;AAAA,IAArB;AAAA,MACC,KAAA,EAAO,uBAAA,CAAwB,EAAE,CAAA,EAAG,OAAO,CAAA;AAAA,MAE1C,QAAA,EAAA,KAAA,CAAM;AAAA;AAAA,GACT,EACF,CAAA;AAEJ;AAMO,SAAS,aAAA,GAE2B;AACzC,EAAA,MAAM,eAAA,GAAkB,oBAErB,qBAAqB,CAAA;AACxB,EAAA,MAAM,UAAA,GAAa,WAAW,oBAAoB,CAAA;AAElD,EAAA,IAAI,eAAA,EAAiB;AACnB,IAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,SAAA,CAAU,CAAC,CAAA;AACzC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,MAAM,IAAI,MAAM,oCAAoC,CAAA;AAAA,IACtD;AACA,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,IAAI,UAAA,EAAY;AACd,IAAA,OAAO,UAAA;AAAA,EACT;AAEA,EAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AACxE;;;;"}
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 {\n createVersionedContext,\n createVersionedValueMap,\n useVersionedContext,\n} from '@backstage/version-bridge';\nimport { compact, isEqual } from 'lodash';\nimport qs from 'qs';\nimport {\n createContext,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\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 totalItemsLoading: boolean;\n limit: number;\n offset?: number;\n setLimit: (limit: number) => void;\n setOffset?: (offset: number) => void;\n paginationMode: PaginationMode;\n\n /**\n * Trigger refresh of entities and entity totals.\n */\n refresh?: () => void;\n};\n\n// This context has support for multiple concurrent versions of this package.\n// It is currently used in parallel with the old context in order to provide\n// a smooth transition, but will eventually be the only context we use.\nexport const NewEntityListContext = createVersionedContext<{\n 1: EntityListContextProps<any>;\n}>('entity-list-context');\n\n/**\n * Creates new context for entity listing and filtering.\n */\nexport const OldEntityListContext = createContext<\n EntityListContextProps<any> | undefined\n>(undefined);\n\ntype BackendState = {\n backendEntities: Entity[];\n pageInfo?: QueryEntitiesResponse['pageInfo'];\n totalItems?: 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 let paginationMode: PaginationMode = 'none';\n if (props.pagination === true) {\n paginationMode = 'cursor';\n } else if (typeof props.pagination === 'object') {\n paginationMode = props.pagination.mode ?? 'cursor';\n }\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 arrayLimit: 10000,\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 [backendState, setBackendState] = useState<BackendState>({\n backendEntities: [],\n });\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<Error | undefined>();\n const [refreshToken, setRefreshToken] = useState(0);\n\n // Tracks the params of the last API call so identical requests are\n // skipped even when requestedFilters changes (e.g. a label change\n // or a frontend-only filter addition).\n const lastFetchParamsRef = useRef<unknown>(undefined);\n // Generation counter — only the most recent fetch updates state,\n // so out-of-order responses from overlapping requests are discarded.\n const fetchGenRef = useRef(0);\n\n // Adjusted filters remove the owners filter for user/group kinds,\n // since ownership is not meaningful for those entity types.\n const adjustedFilters = useMemo(() => {\n const kindValue = requestedFilters.kind?.value?.toLocaleLowerCase('en-US');\n return kindValue === 'user' || kindValue === 'group'\n ? { ...requestedFilters, owners: undefined }\n : requestedFilters;\n }, [requestedFilters]);\n\n const refresh = async () => {\n const compacted = compact(Object.values(adjustedFilters));\n\n let fetchParams: unknown;\n let doFetch: () => Promise<BackendState>;\n\n if (paginationMode !== 'none') {\n if (cursor) {\n fetchParams = { cursor, limit };\n doFetch = async () => {\n const response = await catalogApi.queryEntities({\n cursor,\n limit,\n totalItems: 'exclude',\n });\n return {\n backendEntities: response.items,\n pageInfo: response.pageInfo,\n };\n };\n } else {\n const backendFilter = reduceCatalogFilters(compacted);\n fetchParams = { ...backendFilter, limit, offset };\n doFetch = async () => {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit,\n offset,\n totalItems: 'exclude',\n });\n return {\n backendEntities: response.items,\n pageInfo: response.pageInfo,\n };\n };\n }\n } else {\n const backendFilter = reduceBackendCatalogFilters(compacted);\n const { orderFields } = reduceCatalogFilters(compacted);\n fetchParams = { filter: backendFilter, order: orderFields };\n doFetch = async () => {\n const response = await catalogApi.getEntities({\n filter: backendFilter,\n order: orderFields,\n });\n return { backendEntities: response.items };\n };\n }\n\n // No filters registered yet — wait for filter components to\n // call updateFilters before making the first request.\n // Exception: a cursor in the URL is a self-contained page reference\n // that doesn't need filter state to be valid.\n if (\n compacted.length === 0 &&\n lastFetchParamsRef.current === undefined &&\n !cursor\n ) {\n setLoading(false);\n return;\n }\n\n if (isEqual(fetchParams, lastFetchParamsRef.current)) {\n return;\n }\n lastFetchParamsRef.current = fetchParams;\n\n const gen = ++fetchGenRef.current;\n setLoading(true);\n setError(undefined);\n\n try {\n const result = await doFetch();\n if (gen === fetchGenRef.current) {\n setBackendState(result);\n }\n } catch (e) {\n if (gen === fetchGenRef.current) {\n // Clear the ref so the same params can be retried, and so\n // a response discarded due to a concurrent request (gen mismatch)\n // doesn't permanently block fetching those params again.\n lastFetchParamsRef.current = undefined;\n setError(e as Error);\n }\n } finally {\n if (gen === fetchGenRef.current) {\n setLoading(false);\n }\n }\n };\n\n // Slight debounce on the refresh, since (especially on page load)\n // several filters will be calling updateFilters in rapid succession.\n useDebounce(refresh, 10, [\n adjustedFilters,\n cursor,\n limit,\n offset,\n refreshToken,\n ]);\n\n // Fetch the total count separately, only when filters change. This is\n // decoupled from the main list fetch so that page navigation doesn't\n // re-run the expensive count query, and so that the count can arrive\n // asynchronously without blocking the list response.\n const [{ value: totalItems, loading: totalItemsLoading }, refreshCount] =\n useAsyncFn(async () => {\n if (paginationMode === 'none') {\n return undefined;\n }\n const compacted = compact(Object.values(adjustedFilters));\n if (compacted.length === 0) {\n return undefined;\n }\n const backendFilter = reduceCatalogFilters(compacted);\n try {\n const response = await catalogApi.queryEntities({\n ...backendFilter,\n limit: 0,\n });\n return response.totalItems;\n } catch {\n return undefined;\n }\n }, [catalogApi, paginationMode, adjustedFilters]);\n\n useDebounce(refreshCount, 10, [adjustedFilters, refreshToken]);\n\n // Frontend filtering — synchronous, no debounce needed. Updates\n // instantly when requestedFilters or backendEntities change.\n const entities = useMemo(() => {\n const compacted = compact(Object.values(adjustedFilters));\n const entityFilter = reduceEntityFilters(compacted);\n return backendState.backendEntities.filter(entityFilter);\n }, [adjustedFilters, backendState.backendEntities]);\n\n // Sync filter state to URL query parameters. We use direct history\n // manipulation since useSearchParams and useNavigate in\n // react-router-dom cause unnecessary extra rerenders. Also make sure\n // to replace the state rather than pushing, since we don't want\n // there to be back/forward slots for every single filter change.\n useEffect(() => {\n if (!isMounted() || Object.keys(requestedFilters).length === 0) {\n return;\n }\n const queryParams = Object.keys(requestedFilters).reduce((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 }, {} as Record<string, string | string[]>);\n\n const oldParams = qs.parse(location.search, {\n ignoreQueryPrefix: true,\n arrayLimit: 10000,\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 window.history?.replaceState(null, document.title, newUrl);\n }, [\n cursor,\n isMounted,\n limit,\n location.search,\n offset,\n requestedFilters,\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 triggerRefresh = useCallback(() => {\n // Clear the ref to allow refetching with the same params.\n lastFetchParamsRef.current = undefined;\n setRefreshToken(t => t + 1);\n }, []);\n\n const pageInfo = useMemo(() => {\n if (paginationMode !== 'cursor') {\n return undefined;\n }\n\n const prevCursor = backendState.pageInfo?.prevCursor;\n const nextCursor = backendState.pageInfo?.nextCursor;\n return {\n prev: prevCursor ? () => setCursor(prevCursor) : undefined,\n next: nextCursor ? () => setCursor(nextCursor) : undefined,\n };\n }, [paginationMode, backendState.pageInfo]);\n\n const value = useMemo(\n () => ({\n filters: requestedFilters,\n entities,\n backendEntities: backendState.backendEntities,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n totalItems:\n paginationMode === 'none'\n ? entities.length\n : totalItems ?? backendState.totalItems,\n totalItemsLoading: paginationMode !== 'none' && totalItemsLoading,\n limit,\n offset,\n setLimit,\n setOffset,\n paginationMode,\n refresh: triggerRefresh,\n }),\n [\n requestedFilters,\n entities,\n backendState,\n totalItems,\n totalItemsLoading,\n updateFilters,\n queryParameters,\n loading,\n error,\n pageInfo,\n paginationMode,\n limit,\n offset,\n setLimit,\n setOffset,\n triggerRefresh,\n ],\n );\n\n return (\n <OldEntityListContext.Provider value={value}>\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: value })}\n >\n {props.children}\n </NewEntityListContext.Provider>\n </OldEntityListContext.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 versionedHolder = useVersionedContext<{\n 1: EntityListContextProps<any>;\n }>('entity-list-context');\n const oldContext = useContext(OldEntityListContext);\n\n if (versionedHolder) {\n const value = versionedHolder.atVersion(1);\n if (!value) {\n throw new Error('EntityListContext v1 not available');\n }\n return value;\n }\n\n if (oldContext) {\n return oldContext;\n }\n\n throw new Error('useEntityList must be used within EntityListProvider');\n}\n"],"names":["limit","offset"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ import { Observable } from '@backstage/types';
9
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
10
10
  import { TypographyProps } from '@material-ui/core/Typography';
11
11
  import { OutlinedTextFieldProps, TextFieldProps } from '@material-ui/core/TextField';
12
- import { AutocompleteProps } from '@material-ui/lab/Autocomplete';
12
+ import { AutocompleteProps, AutocompleteRenderOptionState } from '@material-ui/lab/Autocomplete';
13
13
  import * as react from 'react';
14
14
  import { ReactNode, PropsWithChildren, ComponentProps } from 'react';
15
15
  import { LinkProps, InfoCardVariants, TableColumn, TableOptions } from '@backstage/core-components';
@@ -886,6 +886,10 @@ type EntityListContextProps<EntityFilters extends DefaultEntityFilters = Default
886
886
  setLimit: (limit: number) => void;
887
887
  setOffset?: (offset: number) => void;
888
888
  paginationMode: PaginationMode;
889
+ /**
890
+ * Trigger refresh of entities and entity totals.
891
+ */
892
+ refresh?: () => void;
889
893
  };
890
894
  /**
891
895
  * @public
@@ -923,6 +927,8 @@ type EntityAutocompletePickerProps<T extends DefaultEntityFilters = DefaultEntit
923
927
  initialSelectedOptions?: string[];
924
928
  filtersForAvailableValues?: Array<keyof T>;
925
929
  hidden?: boolean;
930
+ getOptionLabel?: (option: string) => string;
931
+ renderOption?: (option: string, state: AutocompleteRenderOptionState) => ReactNode;
926
932
  };
927
933
  /** @public */
928
934
  type CatalogReactEntityAutocompletePickerClassKey = 'root' | 'label';
@@ -39,7 +39,9 @@ function MockEntityListContextProvider(props) {
39
39
  setLimit: value?.setLimit ?? (() => {
40
40
  }),
41
41
  setOffset: value?.setOffset,
42
- paginationMode: value?.paginationMode ?? "none"
42
+ paginationMode: value?.paginationMode ?? "none",
43
+ refresh: value?.refresh ?? (() => {
44
+ })
43
45
  }),
44
46
  [value, defaultValues, filters, updateFilters]
45
47
  );
@@ -1 +1 @@
1
- {"version":3,"file":"MockEntityListContextProvider.esm.js","sources":["../../src/testUtils/MockEntityListContextProvider.tsx"],"sourcesContent":["/*\n * Copyright 2021 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 { PropsWithChildren, useCallback, useMemo, useState } from 'react';\nimport {\n DefaultEntityFilters,\n EntityListContextProps,\n} from '@backstage/plugin-catalog-react';\nimport { createVersionedValueMap } from '@backstage/version-bridge';\nimport { NewEntityListContext } from '../hooks/useEntityListProvider';\n\n/**\n * Simplifies testing of code that uses the entity list hooks.\n *\n * @public\n */\nexport function MockEntityListContextProvider<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n>(\n props: PropsWithChildren<{\n value?: Partial<EntityListContextProps<T>>;\n }>,\n) {\n const { children, value } = props;\n\n // Provides a default implementation that stores filter state, for testing components that\n // reflect filter state.\n const [filters, setFilters] = useState<T>(value?.filters ?? ({} as T));\n\n const updateFilters = useCallback(\n (update: Partial<T> | ((prevFilters: T) => Partial<T>)) => {\n setFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [],\n );\n\n // Memoize the default values since pickers have useEffect triggers on these; naively defaulting\n // below with `?? <X>` breaks referential equality on subsequent updates.\n const defaultValues = useMemo(\n () => ({\n entities: [],\n backendEntities: [],\n queryParameters: {},\n }),\n [],\n );\n\n const resolvedValue: EntityListContextProps<T> = useMemo(\n () => ({\n entities: value?.entities ?? defaultValues.entities,\n backendEntities: value?.backendEntities ?? defaultValues.backendEntities,\n updateFilters: value?.updateFilters ?? updateFilters,\n filters,\n loading: value?.loading ?? false,\n queryParameters: value?.queryParameters ?? defaultValues.queryParameters,\n error: value?.error,\n totalItems:\n value?.totalItems ?? (value?.entities ?? defaultValues.entities).length,\n totalItemsLoading: value?.totalItemsLoading ?? false,\n limit: value?.limit ?? 20,\n offset: value?.offset,\n setLimit: value?.setLimit ?? (() => {}),\n setOffset: value?.setOffset,\n paginationMode: value?.paginationMode ?? 'none',\n }),\n [value, defaultValues, filters, updateFilters],\n );\n\n return (\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: resolvedValue })}\n >\n {children}\n </NewEntityListContext.Provider>\n );\n}\n"],"names":[],"mappings":";;;;;AA6BO,SAAS,8BAGd,KAAA,EAGA;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,EAAM,GAAI,KAAA;AAI5B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAY,KAAA,EAAO,OAAA,IAAY,EAAQ,CAAA;AAErE,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,MAAA,KAA0D;AACzD,MAAA,UAAA,CAAW,CAAA,WAAA,KAAe;AACxB,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;AAAC,GACH;AAIA,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,iBAAiB;AAAC,KACpB,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,MAAM,aAAA,GAA2C,OAAA;AAAA,IAC/C,OAAO;AAAA,MACL,QAAA,EAAU,KAAA,EAAO,QAAA,IAAY,aAAA,CAAc,QAAA;AAAA,MAC3C,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,aAAA,EAAe,OAAO,aAAA,IAAiB,aAAA;AAAA,MACvC,OAAA;AAAA,MACA,OAAA,EAAS,OAAO,OAAA,IAAW,KAAA;AAAA,MAC3B,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,OAAO,KAAA,EAAO,KAAA;AAAA,MACd,YACE,KAAA,EAAO,UAAA,IAAA,CAAe,KAAA,EAAO,QAAA,IAAY,cAAc,QAAA,EAAU,MAAA;AAAA,MACnE,iBAAA,EAAmB,OAAO,iBAAA,IAAqB,KAAA;AAAA,MAC/C,KAAA,EAAO,OAAO,KAAA,IAAS,EAAA;AAAA,MACvB,QAAQ,KAAA,EAAO,MAAA;AAAA,MACf,QAAA,EAAU,KAAA,EAAO,QAAA,KAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MACrC,WAAW,KAAA,EAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB;AAAA,KAC3C,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,aAAA,EAAe,OAAA,EAAS,aAAa;AAAA,GAC/C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,oBAAA,CAAqB,QAAA;AAAA,IAArB;AAAA,MACC,KAAA,EAAO,uBAAA,CAAwB,EAAE,CAAA,EAAG,eAAe,CAAA;AAAA,MAElD;AAAA;AAAA,GACH;AAEJ;;;;"}
1
+ {"version":3,"file":"MockEntityListContextProvider.esm.js","sources":["../../src/testUtils/MockEntityListContextProvider.tsx"],"sourcesContent":["/*\n * Copyright 2021 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 { PropsWithChildren, useCallback, useMemo, useState } from 'react';\nimport {\n DefaultEntityFilters,\n EntityListContextProps,\n} from '@backstage/plugin-catalog-react';\nimport { createVersionedValueMap } from '@backstage/version-bridge';\nimport { NewEntityListContext } from '../hooks/useEntityListProvider';\n\n/**\n * Simplifies testing of code that uses the entity list hooks.\n *\n * @public\n */\nexport function MockEntityListContextProvider<\n T extends DefaultEntityFilters = DefaultEntityFilters,\n>(\n props: PropsWithChildren<{\n value?: Partial<EntityListContextProps<T>>;\n }>,\n) {\n const { children, value } = props;\n\n // Provides a default implementation that stores filter state, for testing components that\n // reflect filter state.\n const [filters, setFilters] = useState<T>(value?.filters ?? ({} as T));\n\n const updateFilters = useCallback(\n (update: Partial<T> | ((prevFilters: T) => Partial<T>)) => {\n setFilters(prevFilters => {\n const newFilters =\n typeof update === 'function' ? update(prevFilters) : update;\n return { ...prevFilters, ...newFilters };\n });\n },\n [],\n );\n\n // Memoize the default values since pickers have useEffect triggers on these; naively defaulting\n // below with `?? <X>` breaks referential equality on subsequent updates.\n const defaultValues = useMemo(\n () => ({\n entities: [],\n backendEntities: [],\n queryParameters: {},\n }),\n [],\n );\n\n const resolvedValue: EntityListContextProps<T> = useMemo(\n () => ({\n entities: value?.entities ?? defaultValues.entities,\n backendEntities: value?.backendEntities ?? defaultValues.backendEntities,\n updateFilters: value?.updateFilters ?? updateFilters,\n filters,\n loading: value?.loading ?? false,\n queryParameters: value?.queryParameters ?? defaultValues.queryParameters,\n error: value?.error,\n totalItems:\n value?.totalItems ?? (value?.entities ?? defaultValues.entities).length,\n totalItemsLoading: value?.totalItemsLoading ?? false,\n limit: value?.limit ?? 20,\n offset: value?.offset,\n setLimit: value?.setLimit ?? (() => {}),\n setOffset: value?.setOffset,\n paginationMode: value?.paginationMode ?? 'none',\n refresh: value?.refresh ?? (() => {}),\n }),\n [value, defaultValues, filters, updateFilters],\n );\n\n return (\n <NewEntityListContext.Provider\n value={createVersionedValueMap({ 1: resolvedValue })}\n >\n {children}\n </NewEntityListContext.Provider>\n );\n}\n"],"names":[],"mappings":";;;;;AA6BO,SAAS,8BAGd,KAAA,EAGA;AACA,EAAA,MAAM,EAAE,QAAA,EAAU,KAAA,EAAM,GAAI,KAAA;AAI5B,EAAA,MAAM,CAAC,SAAS,UAAU,CAAA,GAAI,SAAY,KAAA,EAAO,OAAA,IAAY,EAAQ,CAAA;AAErE,EAAA,MAAM,aAAA,GAAgB,WAAA;AAAA,IACpB,CAAC,MAAA,KAA0D;AACzD,MAAA,UAAA,CAAW,CAAA,WAAA,KAAe;AACxB,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;AAAC,GACH;AAIA,EAAA,MAAM,aAAA,GAAgB,OAAA;AAAA,IACpB,OAAO;AAAA,MACL,UAAU,EAAC;AAAA,MACX,iBAAiB,EAAC;AAAA,MAClB,iBAAiB;AAAC,KACpB,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,MAAM,aAAA,GAA2C,OAAA;AAAA,IAC/C,OAAO;AAAA,MACL,QAAA,EAAU,KAAA,EAAO,QAAA,IAAY,aAAA,CAAc,QAAA;AAAA,MAC3C,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,aAAA,EAAe,OAAO,aAAA,IAAiB,aAAA;AAAA,MACvC,OAAA;AAAA,MACA,OAAA,EAAS,OAAO,OAAA,IAAW,KAAA;AAAA,MAC3B,eAAA,EAAiB,KAAA,EAAO,eAAA,IAAmB,aAAA,CAAc,eAAA;AAAA,MACzD,OAAO,KAAA,EAAO,KAAA;AAAA,MACd,YACE,KAAA,EAAO,UAAA,IAAA,CAAe,KAAA,EAAO,QAAA,IAAY,cAAc,QAAA,EAAU,MAAA;AAAA,MACnE,iBAAA,EAAmB,OAAO,iBAAA,IAAqB,KAAA;AAAA,MAC/C,KAAA,EAAO,OAAO,KAAA,IAAS,EAAA;AAAA,MACvB,QAAQ,KAAA,EAAO,MAAA;AAAA,MACf,QAAA,EAAU,KAAA,EAAO,QAAA,KAAa,MAAM;AAAA,MAAC,CAAA,CAAA;AAAA,MACrC,WAAW,KAAA,EAAO,SAAA;AAAA,MAClB,cAAA,EAAgB,OAAO,cAAA,IAAkB,MAAA;AAAA,MACzC,OAAA,EAAS,KAAA,EAAO,OAAA,KAAY,MAAM;AAAA,MAAC,CAAA;AAAA,KACrC,CAAA;AAAA,IACA,CAAC,KAAA,EAAO,aAAA,EAAe,OAAA,EAAS,aAAa;AAAA,GAC/C;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,oBAAA,CAAqB,QAAA;AAAA,IAArB;AAAA,MACC,KAAA,EAAO,uBAAA,CAAwB,EAAE,CAAA,EAAG,eAAe,CAAA;AAAA,MAElD;AAAA;AAAA,GACH;AAEJ;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-react",
3
- "version": "3.0.1-next.0",
3
+ "version": "3.1.0",
4
4
  "description": "A frontend library that helps other Backstage plugins interact with the catalog",
5
5
  "backstage": {
6
6
  "role": "web-library",
@@ -73,20 +73,20 @@
73
73
  "test": "backstage-cli package test"
74
74
  },
75
75
  "dependencies": {
76
- "@backstage/catalog-client": "1.16.0-next.0",
77
- "@backstage/catalog-model": "1.9.0",
78
- "@backstage/core-compat-api": "0.5.12-next.0",
79
- "@backstage/core-components": "0.18.11-next.0",
80
- "@backstage/core-plugin-api": "1.12.6",
81
- "@backstage/errors": "1.3.1",
82
- "@backstage/filter-predicates": "0.1.3",
83
- "@backstage/frontend-plugin-api": "0.17.0",
84
- "@backstage/integration-react": "1.2.19-next.0",
85
- "@backstage/plugin-permission-common": "0.9.9",
86
- "@backstage/plugin-permission-react": "0.5.1",
87
- "@backstage/types": "1.2.2",
88
- "@backstage/ui": "0.15.0",
89
- "@backstage/version-bridge": "1.0.12",
76
+ "@backstage/catalog-client": "^1.16.0",
77
+ "@backstage/catalog-model": "^1.9.0",
78
+ "@backstage/core-compat-api": "^0.5.12",
79
+ "@backstage/core-components": "^0.18.11",
80
+ "@backstage/core-plugin-api": "^1.12.7",
81
+ "@backstage/errors": "^1.3.1",
82
+ "@backstage/filter-predicates": "^0.1.3",
83
+ "@backstage/frontend-plugin-api": "^0.17.2",
84
+ "@backstage/integration-react": "^1.2.19",
85
+ "@backstage/plugin-permission-common": "^0.9.9",
86
+ "@backstage/plugin-permission-react": "^0.5.2",
87
+ "@backstage/types": "^1.2.2",
88
+ "@backstage/ui": "^0.16.0",
89
+ "@backstage/version-bridge": "^1.0.12",
90
90
  "@material-ui/core": "^4.12.2",
91
91
  "@material-ui/icons": "^4.9.1",
92
92
  "@material-ui/lab": "4.0.0-alpha.61",
@@ -102,12 +102,12 @@
102
102
  "zod": "^4.0.0"
103
103
  },
104
104
  "devDependencies": {
105
- "@backstage/cli": "0.36.3-next.0",
106
- "@backstage/core-app-api": "1.20.1",
107
- "@backstage/frontend-test-utils": "0.6.1-next.0",
108
- "@backstage/plugin-catalog-common": "1.1.10",
109
- "@backstage/plugin-scaffolder-common": "2.2.1-next.0",
110
- "@backstage/test-utils": "1.7.18",
105
+ "@backstage/cli": "^0.36.3",
106
+ "@backstage/core-app-api": "^1.20.2",
107
+ "@backstage/frontend-test-utils": "^0.6.1",
108
+ "@backstage/plugin-catalog-common": "^1.1.10",
109
+ "@backstage/plugin-scaffolder-common": "^2.2.1",
110
+ "@backstage/test-utils": "^1.7.19",
111
111
  "@testing-library/dom": "^10.0.0",
112
112
  "@testing-library/jest-dom": "^6.0.0",
113
113
  "@testing-library/react": "^16.0.0",
@@ -120,7 +120,7 @@
120
120
  "react-test-renderer": "^16.13.1"
121
121
  },
122
122
  "peerDependencies": {
123
- "@backstage/frontend-test-utils": "0.6.1-next.0",
123
+ "@backstage/frontend-test-utils": "^0.6.1",
124
124
  "@types/react": "^17.0.0 || ^18.0.0",
125
125
  "react": "^17.0.0 || ^18.0.0",
126
126
  "react-dom": "^17.0.0 || ^18.0.0",