@backstage/plugin-catalog-react 0.6.12 → 0.6.14-next.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,57 @@
1
1
  # @backstage/plugin-catalog-react
2
2
 
3
+ ## 0.6.14-next.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 680e7c7452: Updated `useEntityListProvider` and catalog pickers to respond to external changes to query parameters in the URL, such as two sidebar links that apply different catalog filters.
8
+ - 7bb1bde7f6: Minor API cleanups
9
+ - Updated dependencies
10
+ - @backstage/core-components@0.8.8-next.0
11
+
12
+ ## 0.6.13
13
+
14
+ ### Patch Changes
15
+
16
+ - f7257dff6f: The `<Link />` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations.
17
+ - 300f8cdaee: Fix bug: previously the filter would be set to "all" on page load, even if the
18
+ `initiallySelectedFilter` on the `DefaultCatalogPage` was set to something else,
19
+ or a different query parameter was supplied. Now, the prop and query parameters
20
+ control the filter as expected. Additionally, after this change any filters
21
+ which match 0 items will be disabled, and the filter will be reverted to 'all'
22
+ if they're set on page load.
23
+ - 6acc8f7db7: Add caching to the useEntityPermission hook
24
+
25
+ The hook now caches the authorization decision based on the permission + the entity, and returns the cache match value as the default `allowed` value while loading. This helps avoid flicker in UI elements that would be conditionally rendered based on the `allowed` result of this hook.
26
+
27
+ - Updated dependencies
28
+ - @backstage/core-components@0.8.7
29
+
30
+ ## 0.6.13-next.1
31
+
32
+ ### Patch Changes
33
+
34
+ - f7257dff6f: The `<Link />` component now accepts a `noTrack` prop, which prevents the `click` event from being captured by the Analytics API. This can be used if tracking is explicitly not warranted, or in order to use custom link tracking in specific situations.
35
+ - 300f8cdaee: Fix bug: previously the filter would be set to "all" on page load, even if the
36
+ `initiallySelectedFilter` on the `DefaultCatalogPage` was set to something else,
37
+ or a different query parameter was supplied. Now, the prop and query parameters
38
+ control the filter as expected. Additionally, after this change any filters
39
+ which match 0 items will be disabled, and the filter will be reverted to 'all'
40
+ if they're set on page load.
41
+ - 6acc8f7db7: Add caching to the useEntityPermission hook
42
+
43
+ The hook now caches the authorization decision based on the permission + the entity, and returns the cache match value as the default `allowed` value while loading. This helps avoid flicker in UI elements that would be conditionally rendered based on the `allowed` result of this hook.
44
+
45
+ - Updated dependencies
46
+ - @backstage/core-components@0.8.7-next.1
47
+
48
+ ## 0.6.13-next.0
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies
53
+ - @backstage/core-components@0.8.7-next.0
54
+
3
55
  ## 0.6.12
4
56
 
5
57
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -294,16 +294,18 @@ const EntityListProvider = ({
294
294
  const isMounted = useMountedState__default["default"]();
295
295
  const catalogApi = corePluginApi.useApi(catalogApiRef);
296
296
  const [requestedFilters, setRequestedFilters] = React.useState({});
297
- const [outputState, setOutputState] = React.useState(() => {
297
+ const location = reactRouter.useLocation();
298
+ const queryParameters = React.useMemo(() => {
298
299
  var _a;
299
- const query = qs__default["default"].parse(window.location.search, {
300
+ return (_a = qs__default["default"].parse(location.search, {
300
301
  ignoreQueryPrefix: true
301
- });
302
+ }).filters) != null ? _a : {};
303
+ }, [location]);
304
+ const [outputState, setOutputState] = React.useState(() => {
302
305
  return {
303
306
  appliedFilters: {},
304
307
  entities: [],
305
- backendEntities: [],
306
- queryParameters: (_a = query.filters) != null ? _a : {}
308
+ backendEntities: []
307
309
  };
308
310
  });
309
311
  const [{ loading, error }, refresh] = useAsyncFn__default["default"](async () => {
@@ -326,26 +328,24 @@ const EntityListProvider = ({
326
328
  setOutputState({
327
329
  appliedFilters: requestedFilters,
328
330
  backendEntities: response.items,
329
- entities: response.items.filter(entityFilter),
330
- queryParameters: queryParams
331
+ entities: response.items.filter(entityFilter)
331
332
  });
332
333
  } else {
333
334
  setOutputState({
334
335
  appliedFilters: requestedFilters,
335
336
  backendEntities: outputState.backendEntities,
336
- entities: outputState.backendEntities.filter(entityFilter),
337
- queryParameters: queryParams
337
+ entities: outputState.backendEntities.filter(entityFilter)
338
338
  });
339
339
  }
340
340
  if (isMounted()) {
341
- const oldParams = qs__default["default"].parse(window.location.search, {
341
+ const oldParams = qs__default["default"].parse(location.search, {
342
342
  ignoreQueryPrefix: true
343
343
  });
344
344
  const newParams = qs__default["default"].stringify({ ...oldParams, filters: queryParams }, { addQueryPrefix: true });
345
345
  const newUrl = `${window.location.pathname}${newParams}`;
346
346
  (_a = window.history) == null ? void 0 : _a.replaceState(null, document.title, newUrl);
347
347
  }
348
- }, [catalogApi, requestedFilters, outputState], { loading: true });
348
+ }, [catalogApi, queryParameters, requestedFilters, outputState], { loading: true });
349
349
  useDebounce__default["default"](refresh, 10, [requestedFilters]);
350
350
  const updateFilters = React.useCallback((update) => {
351
351
  setRequestedFilters((prevFilters) => {
@@ -358,10 +358,10 @@ const EntityListProvider = ({
358
358
  entities: outputState.entities,
359
359
  backendEntities: outputState.backendEntities,
360
360
  updateFilters,
361
- queryParameters: outputState.queryParameters,
361
+ queryParameters,
362
362
  loading,
363
363
  error
364
- }), [outputState, updateFilters, loading, error]);
364
+ }), [outputState, updateFilters, queryParameters, loading, error]);
365
365
  return /* @__PURE__ */ React__default["default"].createElement(EntityListContext.Provider, {
366
366
  value
367
367
  }, children);
@@ -548,8 +548,13 @@ function useEntityTypeFilter() {
548
548
  queryParameters,
549
549
  updateFilters
550
550
  } = useEntityListProvider();
551
- const queryParamTypes = [queryParameters.type].flat().filter(Boolean);
551
+ const queryParamTypes = React.useMemo(() => [queryParameters.type].flat().filter(Boolean), [queryParameters]);
552
552
  const [selectedTypes, setSelectedTypes] = React.useState(queryParamTypes.length ? queryParamTypes : (_a = typeFilter == null ? void 0 : typeFilter.getTypes()) != null ? _a : []);
553
+ React.useEffect(() => {
554
+ if (queryParamTypes.length) {
555
+ setSelectedTypes(queryParamTypes);
556
+ }
557
+ }, [queryParamTypes]);
553
558
  const [availableTypes, setAvailableTypes] = React.useState([]);
554
559
  const kind = React.useMemo(() => kindFilter == null ? void 0 : kindFilter.value, [kindFilter]);
555
560
  const {
@@ -796,11 +801,9 @@ function useEntityPermission(permission) {
796
801
  return { loading: false, allowed, error: permissionError };
797
802
  }
798
803
 
799
- const EntityKindPicker = ({
800
- initialFilter,
801
- hidden
802
- }) => {
804
+ const EntityKindPicker = (props) => {
803
805
  var _a;
806
+ const { initialFilter, hidden } = props;
804
807
  const { updateFilters, queryParameters } = useEntityListProvider();
805
808
  const [selectedKind] = React.useState((_a = [queryParameters.kind].flat()[0]) != null ? _a : initialFilter);
806
809
  React.useEffect(() => {
@@ -830,8 +833,13 @@ const EntityLifecyclePicker = () => {
830
833
  var _a, _b;
831
834
  const classes = useStyles$6();
832
835
  const { updateFilters, backendEntities, filters, queryParameters } = useEntityListProvider();
833
- const queryParamLifecycles = [queryParameters.lifecycles].flat().filter(Boolean);
836
+ const queryParamLifecycles = React.useMemo(() => [queryParameters.lifecycles].flat().filter(Boolean), [queryParameters]);
834
837
  const [selectedLifecycles, setSelectedLifecycles] = React.useState(queryParamLifecycles.length ? queryParamLifecycles : (_b = (_a = filters.lifecycles) == null ? void 0 : _a.values) != null ? _b : []);
838
+ React.useEffect(() => {
839
+ if (queryParamLifecycles.length) {
840
+ setSelectedLifecycles(queryParamLifecycles);
841
+ }
842
+ }, [queryParamLifecycles]);
835
843
  React.useEffect(() => {
836
844
  updateFilters({
837
845
  lifecycles: selectedLifecycles.length ? new EntityLifecycleFilter(selectedLifecycles) : void 0
@@ -891,8 +899,13 @@ const EntityOwnerPicker = () => {
891
899
  var _a, _b;
892
900
  const classes = useStyles$5();
893
901
  const { updateFilters, backendEntities, filters, queryParameters } = useEntityListProvider();
894
- const queryParamOwners = [queryParameters.owners].flat().filter(Boolean);
902
+ const queryParamOwners = React.useMemo(() => [queryParameters.owners].flat().filter(Boolean), [queryParameters]);
895
903
  const [selectedOwners, setSelectedOwners] = React.useState(queryParamOwners.length ? queryParamOwners : (_b = (_a = filters.owners) == null ? void 0 : _a.values) != null ? _b : []);
904
+ React.useEffect(() => {
905
+ if (queryParamOwners.length) {
906
+ setSelectedOwners(queryParamOwners);
907
+ }
908
+ }, [queryParamOwners]);
896
909
  React.useEffect(() => {
897
910
  updateFilters({
898
911
  owners: selectedOwners.length ? new EntityOwnerFilter(selectedOwners) : void 0
@@ -976,9 +989,8 @@ const EntitySearchBar = () => {
976
989
  })));
977
990
  };
978
991
 
979
- function createEntityRefColumn({
980
- defaultKind
981
- }) {
992
+ function createEntityRefColumn(options) {
993
+ const { defaultKind } = options;
982
994
  function formatContent(entity) {
983
995
  var _a;
984
996
  return ((_a = entity.metadata) == null ? void 0 : _a.title) || formatEntityRefTitle(entity, {
@@ -1118,13 +1130,14 @@ const useStyles$3 = core.makeStyles((theme) => ({
1118
1130
  justifyContent: "center"
1119
1131
  }
1120
1132
  }));
1121
- function EntityTable({
1122
- entities,
1123
- title,
1124
- emptyContent,
1125
- variant = "gridItem",
1126
- columns
1127
- }) {
1133
+ function EntityTable(props) {
1134
+ const {
1135
+ entities,
1136
+ title,
1137
+ emptyContent,
1138
+ variant = "gridItem",
1139
+ columns
1140
+ } = props;
1128
1141
  const classes = useStyles$3();
1129
1142
  const tableStyle = {
1130
1143
  minWidth: "0",
@@ -1168,8 +1181,13 @@ const EntityTagPicker = () => {
1168
1181
  var _a, _b;
1169
1182
  const classes = useStyles$2();
1170
1183
  const { updateFilters, backendEntities, filters, queryParameters } = useEntityListProvider();
1171
- const queryParamTags = [queryParameters.tags].flat().filter(Boolean);
1184
+ const queryParamTags = React.useMemo(() => [queryParameters.tags].flat().filter(Boolean), [queryParameters]);
1172
1185
  const [selectedTags, setSelectedTags] = React.useState(queryParamTags.length ? queryParamTags : (_b = (_a = filters.tags) == null ? void 0 : _a.values) != null ? _b : []);
1186
+ React.useEffect(() => {
1187
+ if (queryParamTags.length) {
1188
+ setSelectedTags(queryParamTags);
1189
+ }
1190
+ }, [queryParamTags]);
1173
1191
  React.useEffect(() => {
1174
1192
  updateFilters({
1175
1193
  tags: selectedTags.length ? new EntityTagFilter(selectedTags) : void 0
@@ -1516,11 +1534,11 @@ const UserListPicker = ({
1516
1534
  initialFilter,
1517
1535
  availableFilters
1518
1536
  }) => {
1519
- var _a, _b;
1537
+ var _a;
1520
1538
  const classes = useStyles();
1521
1539
  const configApi = corePluginApi.useApi(corePluginApi.configApiRef);
1522
1540
  const orgName = (_a = configApi.getOptionalString("organization.name")) != null ? _a : "Company";
1523
- const { filters, updateFilters, backendEntities, queryParameters } = useEntityListProvider();
1541
+ const { filters, updateFilters, backendEntities, queryParameters, loading } = useEntityListProvider();
1524
1542
  const userAndGroupFilterIds = ["starred", "all"];
1525
1543
  const filterGroups = getFilterGroups(orgName).map((filterGroup) => ({
1526
1544
  ...filterGroup,
@@ -1530,28 +1548,29 @@ const UserListPicker = ({
1530
1548
  const { isOwnedEntity } = useEntityOwnership();
1531
1549
  const ownedFilter = React.useMemo(() => new UserListFilter("owned", isOwnedEntity, isStarredEntity), [isOwnedEntity, isStarredEntity]);
1532
1550
  const starredFilter = React.useMemo(() => new UserListFilter("starred", isOwnedEntity, isStarredEntity), [isOwnedEntity, isStarredEntity]);
1533
- const [entitiesWithoutUserFilter, setEntitiesWithoutUserFilter] = React.useState(backendEntities);
1534
- const totalOwnedUserEntities = entitiesWithoutUserFilter.filter((entity) => ownedFilter.filterEntity(entity)).length;
1535
- const [selectedUserFilter, setSelectedUserFilter] = React.useState(totalOwnedUserEntities > 0 ? (_b = [queryParameters.user].flat()[0]) != null ? _b : initialFilter : "all");
1551
+ const queryParamUserFilter = React.useMemo(() => [queryParameters.user].flat()[0], [queryParameters]);
1552
+ const [selectedUserFilter, setSelectedUserFilter] = React.useState(queryParamUserFilter != null ? queryParamUserFilter : initialFilter);
1553
+ const entitiesWithoutUserFilter = React.useMemo(() => backendEntities.filter(reduceEntityFilters(lodash.compact(Object.values({ ...filters, user: void 0 })))), [filters, backendEntities]);
1554
+ const filterCounts = React.useMemo(() => ({
1555
+ all: entitiesWithoutUserFilter.length,
1556
+ starred: entitiesWithoutUserFilter.filter((entity) => starredFilter.filterEntity(entity)).length,
1557
+ owned: entitiesWithoutUserFilter.filter((entity) => ownedFilter.filterEntity(entity)).length
1558
+ }), [entitiesWithoutUserFilter, starredFilter, ownedFilter]);
1559
+ React.useEffect(() => {
1560
+ if (queryParamUserFilter) {
1561
+ setSelectedUserFilter(queryParamUserFilter);
1562
+ }
1563
+ }, [queryParamUserFilter]);
1564
+ React.useEffect(() => {
1565
+ if (!loading && !!selectedUserFilter && selectedUserFilter !== "all" && filterCounts[selectedUserFilter] === 0) {
1566
+ setSelectedUserFilter("all");
1567
+ }
1568
+ }, [loading, filterCounts, selectedUserFilter, setSelectedUserFilter]);
1536
1569
  React.useEffect(() => {
1537
1570
  updateFilters({
1538
1571
  user: selectedUserFilter ? new UserListFilter(selectedUserFilter, isOwnedEntity, isStarredEntity) : void 0
1539
1572
  });
1540
1573
  }, [selectedUserFilter, isOwnedEntity, isStarredEntity, updateFilters]);
1541
- React.useEffect(() => {
1542
- const filterFn = reduceEntityFilters(lodash.compact(Object.values({ ...filters, user: void 0 })));
1543
- setEntitiesWithoutUserFilter(backendEntities.filter(filterFn));
1544
- }, [filters, backendEntities]);
1545
- function getFilterCount(id) {
1546
- switch (id) {
1547
- case "owned":
1548
- return totalOwnedUserEntities;
1549
- case "starred":
1550
- return entitiesWithoutUserFilter.filter((entity) => starredFilter.filterEntity(entity)).length;
1551
- default:
1552
- return entitiesWithoutUserFilter.length;
1553
- }
1554
- }
1555
1574
  return /* @__PURE__ */ React__default["default"].createElement(core.Card, {
1556
1575
  className: classes.root
1557
1576
  }, filterGroups.map((group) => /* @__PURE__ */ React__default["default"].createElement(React.Fragment, {
@@ -1565,22 +1584,23 @@ const UserListPicker = ({
1565
1584
  disablePadding: true,
1566
1585
  dense: true
1567
1586
  }, group.items.map((item) => {
1568
- var _a2, _b2;
1587
+ var _a2, _b;
1569
1588
  return /* @__PURE__ */ React__default["default"].createElement(core.MenuItem, {
1570
1589
  key: item.id,
1571
1590
  button: true,
1572
1591
  divider: true,
1573
1592
  onClick: () => setSelectedUserFilter(item.id),
1574
1593
  selected: item.id === ((_a2 = filters.user) == null ? void 0 : _a2.value),
1575
- className: classes.menuItem
1594
+ className: classes.menuItem,
1595
+ disabled: filterCounts[item.id] === 0,
1596
+ "data-testid": `user-picker-${item.id}`
1576
1597
  }, item.icon && /* @__PURE__ */ React__default["default"].createElement(core.ListItemIcon, {
1577
1598
  className: classes.listIcon
1578
1599
  }, /* @__PURE__ */ React__default["default"].createElement(item.icon, {
1579
1600
  fontSize: "small"
1580
1601
  })), /* @__PURE__ */ React__default["default"].createElement(core.ListItemText, null, /* @__PURE__ */ React__default["default"].createElement(core.Typography, {
1581
- variant: "body1",
1582
- "data-testid": `user-picker-${item.id}`
1583
- }, item.label)), /* @__PURE__ */ React__default["default"].createElement(core.ListItemSecondaryAction, null, (_b2 = getFilterCount(item.id)) != null ? _b2 : "-"));
1602
+ variant: "body1"
1603
+ }, item.label)), /* @__PURE__ */ React__default["default"].createElement(core.ListItemSecondaryAction, null, (_b = filterCounts[item.id]) != null ? _b : "-"));
1584
1604
  }))))));
1585
1605
  };
1586
1606
 
@@ -1596,17 +1616,25 @@ const MockEntityListContextProvider = ({
1596
1616
  return { ...prevFilters, ...newFilters };
1597
1617
  });
1598
1618
  }, []);
1599
- const defaultContext = {
1619
+ const defaultValues = React.useMemo(() => ({
1600
1620
  entities: [],
1601
1621
  backendEntities: [],
1602
- updateFilters,
1603
- filters,
1604
- loading: false,
1605
1622
  queryParameters: {}
1606
- };
1607
- const { filters: _, ...otherContextFields } = value != null ? value : {};
1623
+ }), []);
1624
+ const resolvedValue = React.useMemo(() => {
1625
+ var _a2, _b, _c, _d, _e;
1626
+ return {
1627
+ entities: (_a2 = value == null ? void 0 : value.entities) != null ? _a2 : defaultValues.entities,
1628
+ backendEntities: (_b = value == null ? void 0 : value.backendEntities) != null ? _b : defaultValues.backendEntities,
1629
+ updateFilters: (_c = value == null ? void 0 : value.updateFilters) != null ? _c : updateFilters,
1630
+ filters,
1631
+ loading: (_d = value == null ? void 0 : value.loading) != null ? _d : false,
1632
+ queryParameters: (_e = value == null ? void 0 : value.queryParameters) != null ? _e : defaultValues.queryParameters,
1633
+ error: value == null ? void 0 : value.error
1634
+ };
1635
+ }, [value, defaultValues, filters, updateFilters]);
1608
1636
  return /* @__PURE__ */ React__default["default"].createElement(EntityListContext.Provider, {
1609
- value: { ...defaultContext, ...otherContextFields }
1637
+ value: resolvedValue
1610
1638
  }, children);
1611
1639
  };
1612
1640