@backstage/plugin-catalog-react 0.5.2 → 0.6.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,26 @@
1
1
  # @backstage/plugin-catalog-react
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 82fbda923e: Introduce a new `StarredEntitiesApi` that is used in the `useStarredEntities` hook.
8
+ The `@backstage/plugin-catalog` installs a default implementation that is backed by the `StorageApi`, but one can also override the `starredEntitiesApiRef`.
9
+
10
+ This change also updates the storage format from a custom string to an entity reference and moves the location in the local storage.
11
+ A migration will convert the previously starred entities to the location on the first load of Backstage.
12
+
13
+ ### Patch Changes
14
+
15
+ - 0366c9b667: Introduce a `useStarredEntity` hook to check if a single entity is starred.
16
+ It provides a more efficient implementation compared to the `useStarredEntities` hook, because the rendering is only triggered if the selected entity is starred, not if _any_ entity is starred.
17
+ - 4aca84f86b: Support `material-ui` overrides in plugin-catalog-react components
18
+ - b03b9f19e0: added sorting in entity `Name` column by `metadata.title` if present
19
+ - Updated dependencies
20
+ - @backstage/integration@0.6.8
21
+ - @backstage/core-app-api@0.1.17
22
+ - @backstage/core-components@0.7.0
23
+
3
24
  ## 0.5.2
4
25
 
5
26
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -4,13 +4,14 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var catalogClient = require('@backstage/catalog-client');
6
6
  var corePluginApi = require('@backstage/core-plugin-api');
7
+ var ObservableImpl = require('zen-observable');
8
+ var catalogModel = require('@backstage/catalog-model');
9
+ var lodash = require('lodash');
7
10
  var React = require('react');
8
11
  var lab = require('@material-ui/lab');
9
12
  var versionBridge = require('@backstage/version-bridge');
10
13
  var reactRouter = require('react-router');
11
14
  var reactUse = require('react-use');
12
- var catalogModel = require('@backstage/catalog-model');
13
- var lodash = require('lodash');
14
15
  var qs = require('qs');
15
16
  var coreComponents = require('@backstage/core-components');
16
17
  var core = require('@material-ui/core');
@@ -21,13 +22,14 @@ var ExpandMoreIcon = require('@material-ui/icons/ExpandMore');
21
22
  var Clear = require('@material-ui/icons/Clear');
22
23
  var Search = require('@material-ui/icons/Search');
23
24
  var capitalize = require('lodash/capitalize');
24
- var StarBorder = require('@material-ui/icons/StarBorder');
25
25
  var Star = require('@material-ui/icons/Star');
26
+ var StarBorder = require('@material-ui/icons/StarBorder');
26
27
  var Alert = require('@material-ui/lab/Alert');
27
28
  var SettingsIcon = require('@material-ui/icons/Settings');
28
29
 
29
30
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
30
31
 
32
+ var ObservableImpl__default = /*#__PURE__*/_interopDefaultLegacy(ObservableImpl);
31
33
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
32
34
  var qs__default = /*#__PURE__*/_interopDefaultLegacy(qs);
33
35
  var jwtDecoder__default = /*#__PURE__*/_interopDefaultLegacy(jwtDecoder);
@@ -37,8 +39,8 @@ var ExpandMoreIcon__default = /*#__PURE__*/_interopDefaultLegacy(ExpandMoreIcon)
37
39
  var Clear__default = /*#__PURE__*/_interopDefaultLegacy(Clear);
38
40
  var Search__default = /*#__PURE__*/_interopDefaultLegacy(Search);
39
41
  var capitalize__default = /*#__PURE__*/_interopDefaultLegacy(capitalize);
40
- var StarBorder__default = /*#__PURE__*/_interopDefaultLegacy(StarBorder);
41
42
  var Star__default = /*#__PURE__*/_interopDefaultLegacy(Star);
43
+ var StarBorder__default = /*#__PURE__*/_interopDefaultLegacy(StarBorder);
42
44
  var Alert__default = /*#__PURE__*/_interopDefaultLegacy(Alert);
43
45
  var SettingsIcon__default = /*#__PURE__*/_interopDefaultLegacy(SettingsIcon);
44
46
 
@@ -46,6 +48,69 @@ const catalogApiRef = corePluginApi.createApiRef({
46
48
  id: "plugin.catalog.service"
47
49
  });
48
50
 
51
+ async function performMigrationToTheNewBucket({
52
+ storageApi
53
+ }) {
54
+ var _a;
55
+ const source = storageApi.forBucket("settings");
56
+ const target = storageApi.forBucket("starredEntities");
57
+ const oldStarredEntities = source.get("starredEntities");
58
+ if (!lodash.isArray(oldStarredEntities)) {
59
+ return;
60
+ }
61
+ const targetEntities = new Set((_a = target.get("entityRefs")) != null ? _a : []);
62
+ oldStarredEntities.filter(lodash.isString).map((old) => old.split(":")).filter((split) => split.length === 4 && split[0] === "entity").map(([_, kind, namespace, name]) => catalogModel.stringifyEntityRef({kind, namespace, name})).forEach((e) => targetEntities.add(e));
63
+ await target.set("entityRefs", Array.from(targetEntities));
64
+ await source.remove("starredEntities");
65
+ }
66
+
67
+ class DefaultStarredEntitiesApi {
68
+ constructor(opts) {
69
+ this.subscribers = new Set();
70
+ this.observable = new ObservableImpl__default['default']((subscriber) => {
71
+ subscriber.next(this.starredEntities);
72
+ this.subscribers.add(subscriber);
73
+ return () => {
74
+ this.subscribers.delete(subscriber);
75
+ };
76
+ });
77
+ var _a;
78
+ performMigrationToTheNewBucket(opts).then();
79
+ this.settingsStore = opts.storageApi.forBucket("starredEntities");
80
+ this.starredEntities = new Set((_a = this.settingsStore.get("entityRefs")) != null ? _a : []);
81
+ this.settingsStore.observe$("entityRefs").subscribe({
82
+ next: (next) => {
83
+ var _a2;
84
+ this.starredEntities = new Set((_a2 = next.newValue) != null ? _a2 : []);
85
+ this.notifyChanges();
86
+ }
87
+ });
88
+ }
89
+ async toggleStarred(entityRef) {
90
+ if (this.starredEntities.has(entityRef)) {
91
+ this.starredEntities.delete(entityRef);
92
+ } else {
93
+ this.starredEntities.add(entityRef);
94
+ }
95
+ await this.settingsStore.set("entityRefs", Array.from(this.starredEntities));
96
+ }
97
+ starredEntitie$() {
98
+ return this.observable;
99
+ }
100
+ isStarred(entityRef) {
101
+ return this.starredEntities.has(entityRef);
102
+ }
103
+ notifyChanges() {
104
+ for (const subscription of this.subscribers) {
105
+ subscription.next(this.starredEntities);
106
+ }
107
+ }
108
+ }
109
+
110
+ const starredEntitiesApiRef = corePluginApi.createApiRef({
111
+ id: "catalog-react.starred-entities"
112
+ });
113
+
49
114
  const NoIcon = () => null;
50
115
  const rootRoute = corePluginApi.createRouteRef({
51
116
  icon: NoIcon,
@@ -598,43 +663,43 @@ function useRelatedEntities(entity, {type, kind}) {
598
663
  };
599
664
  }
600
665
 
601
- const buildEntityKey = (component) => {
602
- var _a;
603
- return `entity:${component.kind}:${(_a = component.metadata.namespace) != null ? _a : "default"}:${component.metadata.name}`;
604
- };
605
- const useStarredEntities = () => {
606
- var _a;
607
- const storageApi = corePluginApi.useApi(corePluginApi.storageApiRef);
608
- const settingsStore = storageApi.forBucket("settings");
609
- const rawStarredEntityKeys = (_a = settingsStore.get("starredEntities")) != null ? _a : [];
610
- const [starredEntities, setStarredEntities] = React.useState(new Set(rawStarredEntityKeys));
611
- const observedItems = reactUse.useObservable(settingsStore.observe$("starredEntities"));
612
- React.useEffect(() => {
613
- var _a2;
614
- if (observedItems == null ? void 0 : observedItems.newValue) {
615
- const currentValue = (_a2 = observedItems == null ? void 0 : observedItems.newValue) != null ? _a2 : [];
616
- setStarredEntities(new Set(currentValue));
617
- }
618
- }, [observedItems == null ? void 0 : observedItems.newValue]);
619
- const toggleStarredEntity = React.useCallback((entity) => {
620
- const entityKey = buildEntityKey(entity);
621
- if (starredEntities.has(entityKey)) {
622
- starredEntities.delete(entityKey);
623
- } else {
624
- starredEntities.add(entityKey);
625
- }
626
- settingsStore.set("starredEntities", Array.from(starredEntities));
627
- }, [starredEntities, settingsStore]);
628
- const isStarredEntity = React.useCallback((entity) => {
629
- const entityKey = buildEntityKey(entity);
630
- return starredEntities.has(entityKey);
631
- }, [starredEntities]);
666
+ function getEntityRef$1(entityOrRef) {
667
+ return typeof entityOrRef === "string" ? entityOrRef : catalogModel.stringifyEntityRef(entityOrRef);
668
+ }
669
+ function useStarredEntities() {
670
+ const starredEntitiesApi = corePluginApi.useApi(starredEntitiesApiRef);
671
+ const starredEntities = reactUse.useObservable(starredEntitiesApi.starredEntitie$(), new Set());
672
+ const isStarredEntity = React.useCallback((entityOrRef) => starredEntities.has(getEntityRef$1(entityOrRef)), [starredEntities]);
673
+ const toggleStarredEntity = React.useCallback((entityOrRef) => starredEntitiesApi.toggleStarred(getEntityRef$1(entityOrRef)).then(), [starredEntitiesApi]);
632
674
  return {
633
675
  starredEntities,
634
676
  toggleStarredEntity,
635
677
  isStarredEntity
636
678
  };
637
- };
679
+ }
680
+
681
+ function getEntityRef(entityOrRef) {
682
+ return typeof entityOrRef === "string" ? entityOrRef : catalogModel.stringifyEntityRef(entityOrRef);
683
+ }
684
+ function useStarredEntity(entityOrRef) {
685
+ const starredEntitiesApi = corePluginApi.useApi(starredEntitiesApiRef);
686
+ const [isStarredEntity, setIsStarredEntity] = React.useState(false);
687
+ React.useEffect(() => {
688
+ const subscription = starredEntitiesApi.starredEntitie$().subscribe({
689
+ next(starredEntities) {
690
+ setIsStarredEntity(starredEntities.has(getEntityRef(entityOrRef)));
691
+ }
692
+ });
693
+ return () => {
694
+ subscription.unsubscribe();
695
+ };
696
+ }, [entityOrRef, starredEntitiesApi]);
697
+ const toggleStarredEntity = React.useCallback(() => starredEntitiesApi.toggleStarred(getEntityRef(entityOrRef)).then(), [entityOrRef, starredEntitiesApi]);
698
+ return {
699
+ toggleStarredEntity,
700
+ isStarredEntity
701
+ };
702
+ }
638
703
 
639
704
  function extendUserId(id) {
640
705
  try {
@@ -723,6 +788,11 @@ const EntityKindPicker = ({
723
788
  }, "Kind filter not yet available");
724
789
  };
725
790
 
791
+ const useStyles$6 = core.makeStyles({
792
+ input: {}
793
+ }, {
794
+ name: "CatalogReactEntityLifecyclePicker"
795
+ });
726
796
  const icon$2 = /* @__PURE__ */ React__default['default'].createElement(CheckBoxOutlineBlankIcon__default['default'], {
727
797
  fontSize: "small"
728
798
  });
@@ -731,6 +801,7 @@ const checkedIcon$2 = /* @__PURE__ */ React__default['default'].createElement(Ch
731
801
  });
732
802
  const EntityLifecyclePicker = () => {
733
803
  var _a, _b;
804
+ const classes = useStyles$6();
734
805
  const {updateFilters, backendEntities, filters, queryParameters} = useEntityListProvider();
735
806
  const queryParamLifecycles = [queryParameters.lifecycles].flat().filter(Boolean);
736
807
  const [selectedLifecycles, setSelectedLifecycles] = React.useState(queryParamLifecycles.length ? queryParamLifecycles : (_b = (_a = filters.lifecycles) == null ? void 0 : _a.values) != null ? _b : []);
@@ -772,11 +843,17 @@ const EntityLifecyclePicker = () => {
772
843
  }),
773
844
  renderInput: (params) => /* @__PURE__ */ React__default['default'].createElement(core.TextField, {
774
845
  ...params,
846
+ className: classes.input,
775
847
  variant: "outlined"
776
848
  })
777
849
  }));
778
850
  };
779
851
 
852
+ const useStyles$5 = core.makeStyles({
853
+ input: {}
854
+ }, {
855
+ name: "CatalogReactEntityOwnerPicker"
856
+ });
780
857
  const icon$1 = /* @__PURE__ */ React__default['default'].createElement(CheckBoxOutlineBlankIcon__default['default'], {
781
858
  fontSize: "small"
782
859
  });
@@ -785,6 +862,7 @@ const checkedIcon$1 = /* @__PURE__ */ React__default['default'].createElement(Ch
785
862
  });
786
863
  const EntityOwnerPicker = () => {
787
864
  var _a, _b;
865
+ const classes = useStyles$5();
788
866
  const {updateFilters, backendEntities, filters, queryParameters} = useEntityListProvider();
789
867
  const queryParamOwners = [queryParameters.owners].flat().filter(Boolean);
790
868
  const [selectedOwners, setSelectedOwners] = React.useState(queryParamOwners.length ? queryParamOwners : (_b = (_a = filters.owners) == null ? void 0 : _a.values) != null ? _b : []);
@@ -823,20 +901,24 @@ const EntityOwnerPicker = () => {
823
901
  }),
824
902
  renderInput: (params) => /* @__PURE__ */ React__default['default'].createElement(core.TextField, {
825
903
  ...params,
904
+ className: classes.input,
826
905
  variant: "outlined"
827
906
  })
828
907
  }));
829
908
  };
830
909
 
831
- const useStyles$3 = core.makeStyles((_theme) => ({
910
+ const useStyles$4 = core.makeStyles((_theme) => ({
832
911
  searchToolbar: {
833
912
  paddingLeft: 0,
834
913
  paddingRight: 0
835
- }
836
- }));
914
+ },
915
+ input: {}
916
+ }), {
917
+ name: "CatalogReactEntitySearchBar"
918
+ });
837
919
  const EntitySearchBar = () => {
838
920
  var _a, _b;
839
- const styles = useStyles$3();
921
+ const classes = useStyles$4();
840
922
  const {filters, updateFilters} = useEntityListProvider();
841
923
  const [search, setSearch] = React.useState((_b = (_a = filters.text) == null ? void 0 : _a.value) != null ? _b : "");
842
924
  reactUse.useDebounce(() => {
@@ -845,9 +927,10 @@ const EntitySearchBar = () => {
845
927
  });
846
928
  }, 250, [search, updateFilters]);
847
929
  return /* @__PURE__ */ React__default['default'].createElement(core.Toolbar, {
848
- className: styles.searchToolbar
930
+ className: classes.searchToolbar
849
931
  }, /* @__PURE__ */ React__default['default'].createElement(core.FormControl, null, /* @__PURE__ */ React__default['default'].createElement(core.Input, {
850
932
  id: "input-with-icon-adornment",
933
+ className: classes.input,
851
934
  placeholder: "Search",
852
935
  autoComplete: "off",
853
936
  onChange: (event) => setSearch(event.target.value),
@@ -870,7 +953,8 @@ function createEntityRefColumn({
870
953
  defaultKind
871
954
  }) {
872
955
  function formatContent(entity) {
873
- return formatEntityRefTitle(entity, {
956
+ var _a;
957
+ return ((_a = entity.metadata) == null ? void 0 : _a.title) || formatEntityRefTitle(entity, {
874
958
  defaultKind
875
959
  });
876
960
  }
@@ -1000,7 +1084,7 @@ const componentEntityColumns = [
1000
1084
  createMetadataDescriptionColumn()
1001
1085
  ];
1002
1086
 
1003
- const useStyles$2 = core.makeStyles((theme) => ({
1087
+ const useStyles$3 = core.makeStyles((theme) => ({
1004
1088
  empty: {
1005
1089
  padding: theme.spacing(2),
1006
1090
  display: "flex",
@@ -1014,7 +1098,7 @@ function EntityTable({
1014
1098
  variant = "gridItem",
1015
1099
  columns
1016
1100
  }) {
1017
- const classes = useStyles$2();
1101
+ const classes = useStyles$3();
1018
1102
  const tableStyle = {
1019
1103
  minWidth: "0",
1020
1104
  width: "100%"
@@ -1042,6 +1126,11 @@ EntityTable.columns = columnFactories;
1042
1126
  EntityTable.systemEntityColumns = systemEntityColumns;
1043
1127
  EntityTable.componentEntityColumns = componentEntityColumns;
1044
1128
 
1129
+ const useStyles$2 = core.makeStyles({
1130
+ input: {}
1131
+ }, {
1132
+ name: "CatalogReactEntityTagPicker"
1133
+ });
1045
1134
  const icon = /* @__PURE__ */ React__default['default'].createElement(CheckBoxOutlineBlankIcon__default['default'], {
1046
1135
  fontSize: "small"
1047
1136
  });
@@ -1050,6 +1139,7 @@ const checkedIcon = /* @__PURE__ */ React__default['default'].createElement(Chec
1050
1139
  });
1051
1140
  const EntityTagPicker = () => {
1052
1141
  var _a, _b;
1142
+ const classes = useStyles$2();
1053
1143
  const {updateFilters, backendEntities, filters, queryParameters} = useEntityListProvider();
1054
1144
  const queryParamTags = [queryParameters.tags].flat().filter(Boolean);
1055
1145
  const [selectedTags, setSelectedTags] = React.useState(queryParamTags.length ? queryParamTags : (_b = (_a = filters.tags) == null ? void 0 : _a.values) != null ? _b : []);
@@ -1088,6 +1178,7 @@ const EntityTagPicker = () => {
1088
1178
  }),
1089
1179
  renderInput: (params) => /* @__PURE__ */ React__default['default'].createElement(core.TextField, {
1090
1180
  ...params,
1181
+ className: classes.input,
1091
1182
  variant: "outlined"
1092
1183
  })
1093
1184
  }));
@@ -1133,15 +1224,14 @@ const YellowStar = core.withStyles({
1133
1224
  const favoriteEntityTooltip = (isStarred) => isStarred ? "Remove from favorites" : "Add to favorites";
1134
1225
  const favoriteEntityIcon = (isStarred) => isStarred ? /* @__PURE__ */ React__default['default'].createElement(YellowStar, null) : /* @__PURE__ */ React__default['default'].createElement(StarBorder__default['default'], null);
1135
1226
  const FavoriteEntity = (props) => {
1136
- const {toggleStarredEntity, isStarredEntity} = useStarredEntities();
1137
- const isStarred = isStarredEntity(props.entity);
1227
+ const {toggleStarredEntity, isStarredEntity} = useStarredEntity(props.entity);
1138
1228
  return /* @__PURE__ */ React__default['default'].createElement(core.IconButton, {
1139
1229
  color: "inherit",
1140
1230
  ...props,
1141
- onClick: () => toggleStarredEntity(props.entity)
1231
+ onClick: () => toggleStarredEntity()
1142
1232
  }, /* @__PURE__ */ React__default['default'].createElement(core.Tooltip, {
1143
- title: favoriteEntityTooltip(isStarred)
1144
- }, favoriteEntityIcon(isStarred)));
1233
+ title: favoriteEntityTooltip(isStarredEntity)
1234
+ }, favoriteEntityIcon(isStarredEntity)));
1145
1235
  };
1146
1236
 
1147
1237
  function useUnregisterEntityDialogState(entity) {
@@ -1358,7 +1448,9 @@ const useStyles = core.makeStyles((theme) => ({
1358
1448
  groupWrapper: {
1359
1449
  margin: theme.spacing(1, 1, 2, 1)
1360
1450
  }
1361
- }));
1451
+ }), {
1452
+ name: "CatalogReactUserListPicker"
1453
+ });
1362
1454
  function getFilterGroups(orgName) {
1363
1455
  return [
1364
1456
  {
@@ -1490,6 +1582,7 @@ Object.defineProperty(exports, 'CATALOG_FILTER_EXISTS', {
1490
1582
  }
1491
1583
  });
1492
1584
  exports.AsyncEntityProvider = AsyncEntityProvider;
1585
+ exports.DefaultStarredEntitiesApi = DefaultStarredEntitiesApi;
1493
1586
  exports.EntityContext = EntityContext;
1494
1587
  exports.EntityKindFilter = EntityKindFilter;
1495
1588
  exports.EntityKindPicker = EntityKindPicker;
@@ -1530,6 +1623,7 @@ exports.isOwnerOf = isOwnerOf;
1530
1623
  exports.reduceCatalogFilters = reduceCatalogFilters;
1531
1624
  exports.reduceEntityFilters = reduceEntityFilters;
1532
1625
  exports.rootRoute = rootRoute;
1626
+ exports.starredEntitiesApiRef = starredEntitiesApiRef;
1533
1627
  exports.useEntity = useEntity;
1534
1628
  exports.useEntityCompoundName = useEntityCompoundName;
1535
1629
  exports.useEntityFromUrl = useEntityFromUrl;
@@ -1540,4 +1634,5 @@ exports.useEntityTypeFilter = useEntityTypeFilter;
1540
1634
  exports.useOwnUser = useOwnUser;
1541
1635
  exports.useRelatedEntities = useRelatedEntities;
1542
1636
  exports.useStarredEntities = useStarredEntities;
1637
+ exports.useStarredEntity = useStarredEntity;
1543
1638
  //# sourceMappingURL=index.cjs.js.map