@backstage/plugin-catalog-react 1.6.0-next.2 → 1.7.0-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 +66 -0
- package/alpha/package.json +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.esm.js +135 -90
- package/dist/index.esm.js.map +1 -1
- package/package.json +10 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-react
|
|
2
2
|
|
|
3
|
+
## 1.7.0-next.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- cb4c15989b6b: The `EntityOwnerPicker` component has undergone improvements to enhance its performance.
|
|
8
|
+
|
|
9
|
+
The component now loads entities asynchronously, resulting in improved performance and responsiveness. Instead of loading all entities upfront, they are now loaded in batches as the user scrolls.
|
|
10
|
+
The previous implementation inferred users and groups displayed by the `EntityOwnerPicker` component based on the entities available in the `EntityListContext`. The updated version no longer relies on the `EntityListContext` for inference, allowing for better decoupling and improved performance.
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @backstage/catalog-client@1.4.2-next.0
|
|
16
|
+
- @backstage/theme@0.4.0-next.0
|
|
17
|
+
- @backstage/integration@1.4.5
|
|
18
|
+
- @backstage/core-components@0.13.2-next.0
|
|
19
|
+
- @backstage/core-plugin-api@1.5.1
|
|
20
|
+
- @backstage/plugin-permission-react@0.4.12
|
|
21
|
+
- @backstage/catalog-model@1.3.0
|
|
22
|
+
- @backstage/errors@1.1.5
|
|
23
|
+
- @backstage/types@1.0.2
|
|
24
|
+
- @backstage/version-bridge@1.0.4
|
|
25
|
+
- @backstage/plugin-catalog-common@1.0.13
|
|
26
|
+
- @backstage/plugin-permission-common@0.7.5
|
|
27
|
+
|
|
28
|
+
## 1.6.0
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- 2258dcae970: Added an entity namespace filter and column on the default catalog page.
|
|
33
|
+
|
|
34
|
+
If you have a custom version of the catalog page, you can add this filter in your CatalogPage code:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
<CatalogFilterLayout>
|
|
38
|
+
<CatalogFilterLayout.Filters>
|
|
39
|
+
<EntityTypePicker />
|
|
40
|
+
<UserListPicker initialFilter={initiallySelectedFilter} />
|
|
41
|
+
<EntityTagPicker />
|
|
42
|
+
/* if you want namespace picker */
|
|
43
|
+
<EntityNamespacePicker />
|
|
44
|
+
</CatalogFilterLayout.Filters>
|
|
45
|
+
<CatalogFilterLayout.Content>
|
|
46
|
+
<CatalogTable columns={columns} actions={actions} />
|
|
47
|
+
</CatalogFilterLayout.Content>
|
|
48
|
+
</CatalogFilterLayout>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The namespace column can be added using `createNamespaceColumn();`. This is only needed if you customized the columns for CatalogTable.
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- Updated dependencies
|
|
56
|
+
- @backstage/theme@0.3.0
|
|
57
|
+
- @backstage/integration@1.4.5
|
|
58
|
+
- @backstage/core-components@0.13.1
|
|
59
|
+
- @backstage/catalog-client@1.4.1
|
|
60
|
+
- @backstage/catalog-model@1.3.0
|
|
61
|
+
- @backstage/core-plugin-api@1.5.1
|
|
62
|
+
- @backstage/errors@1.1.5
|
|
63
|
+
- @backstage/types@1.0.2
|
|
64
|
+
- @backstage/version-bridge@1.0.4
|
|
65
|
+
- @backstage/plugin-catalog-common@1.0.13
|
|
66
|
+
- @backstage/plugin-permission-common@0.7.5
|
|
67
|
+
- @backstage/plugin-permission-react@0.4.12
|
|
68
|
+
|
|
3
69
|
## 1.6.0-next.2
|
|
4
70
|
|
|
5
71
|
### Patch Changes
|
package/alpha/package.json
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -143,7 +143,8 @@ declare function EntityRefLinks<TRef extends string | CompoundEntityRef | Entity
|
|
|
143
143
|
* @param defaultNamespace - if set to false then namespace is never omitted,
|
|
144
144
|
* if set to string which matches namespace of entity then omitted
|
|
145
145
|
*
|
|
146
|
-
* @public
|
|
146
|
+
* @public
|
|
147
|
+
**/
|
|
147
148
|
declare function humanizeEntityRef(entityRef: Entity | CompoundEntityRef, opts?: {
|
|
148
149
|
defaultKind?: string;
|
|
149
150
|
defaultNamespace?: string | false;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
|
|
2
|
-
import { createApiRef, useApi, identityApiRef, alertApiRef,
|
|
2
|
+
import { createApiRef, useApi, identityApiRef, alertApiRef, createRouteRef, useRouteRef, useApiHolder, useApp, configApiRef } from '@backstage/core-plugin-api';
|
|
3
3
|
import ObservableImpl from 'zen-observable';
|
|
4
4
|
import React, { useState, createContext, useMemo, useCallback, useContext, useEffect, useRef, forwardRef, memo, useLayoutEffect, Fragment } from 'react';
|
|
5
5
|
import { Grid, useMediaQuery, useTheme, Button, Drawer, Box, Typography, makeStyles, FormControlLabel, Checkbox, TextField, Tooltip, IconButton, Card, CardContent, Chip, CardActions, Toolbar, FormControl, Input, InputAdornment, withStyles, DialogContentText, ListItemText as ListItemText$1, ListSubheader as ListSubheader$1, ListItem, ListItemIcon, List, Dialog, DialogTitle, DialogContent, Tabs, Tab, DialogActions, Divider, MenuItem, ListItemSecondaryAction } from '@material-ui/core';
|
|
@@ -22,6 +22,9 @@ import CheckBoxIcon from '@material-ui/icons/CheckBox';
|
|
|
22
22
|
import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
|
|
23
23
|
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
|
24
24
|
import { Autocomplete, Alert } from '@material-ui/lab';
|
|
25
|
+
import { useDebouncedEffect } from '@react-hookz/web';
|
|
26
|
+
import PersonIcon from '@material-ui/icons/Person';
|
|
27
|
+
import GroupIcon from '@material-ui/icons/Group';
|
|
25
28
|
import get from 'lodash/get';
|
|
26
29
|
import { getOrCreateGlobalSingleton } from '@backstage/version-bridge';
|
|
27
30
|
import HoverPopover from 'material-ui-popup-state/HoverPopover';
|
|
@@ -838,14 +841,14 @@ function humanizeEntityRef(entityRef, opts) {
|
|
|
838
841
|
kind = defaultKind && defaultKind.toLocaleLowerCase("en-US") === kind ? void 0 : kind;
|
|
839
842
|
return `${kind ? `${kind}:` : ""}${namespace ? `${namespace}/` : ""}${name}`;
|
|
840
843
|
}
|
|
841
|
-
function humanizeEntity(entity,
|
|
844
|
+
function humanizeEntity(entity, defaultName) {
|
|
842
845
|
for (const path of ["spec.profile.displayName", "metadata.title"]) {
|
|
843
846
|
const value = get(entity, path);
|
|
844
847
|
if (value && typeof value === "string") {
|
|
845
848
|
return value;
|
|
846
849
|
}
|
|
847
850
|
}
|
|
848
|
-
return
|
|
851
|
+
return defaultName;
|
|
849
852
|
}
|
|
850
853
|
|
|
851
854
|
const useStyles$e = makeStyles(
|
|
@@ -863,75 +866,53 @@ const EntityOwnerPicker = () => {
|
|
|
863
866
|
const classes = useStyles$e();
|
|
864
867
|
const {
|
|
865
868
|
updateFilters,
|
|
866
|
-
backendEntities,
|
|
867
869
|
filters,
|
|
868
870
|
queryParameters: { owners: ownersParameter }
|
|
869
871
|
} = useEntityList();
|
|
870
872
|
const catalogApi = useApi(catalogApiRef);
|
|
871
|
-
const
|
|
873
|
+
const [text, setText] = useState("");
|
|
874
|
+
const [{ value, loading }, handleFetch] = useAsyncFn(
|
|
875
|
+
async (request) => {
|
|
876
|
+
const initialRequest = request;
|
|
877
|
+
const cursorRequest = request;
|
|
878
|
+
const limit = 20;
|
|
879
|
+
if (cursorRequest.cursor) {
|
|
880
|
+
const response = await catalogApi.queryEntities({
|
|
881
|
+
cursor: cursorRequest.cursor,
|
|
882
|
+
limit
|
|
883
|
+
});
|
|
884
|
+
return {
|
|
885
|
+
...response,
|
|
886
|
+
items: [...cursorRequest.prev, ...response.items]
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
return catalogApi.queryEntities({
|
|
890
|
+
fullTextFilter: {
|
|
891
|
+
term: initialRequest.text || "",
|
|
892
|
+
fields: [
|
|
893
|
+
"metadata.name",
|
|
894
|
+
"kind",
|
|
895
|
+
"spec.profile.displayname",
|
|
896
|
+
"metadata.title"
|
|
897
|
+
]
|
|
898
|
+
},
|
|
899
|
+
filter: { kind: ["User", "Group"] },
|
|
900
|
+
orderFields: [{ field: "metadata.name", order: "asc" }],
|
|
901
|
+
limit
|
|
902
|
+
});
|
|
903
|
+
},
|
|
904
|
+
[text]
|
|
905
|
+
);
|
|
906
|
+
useDebouncedEffect(() => handleFetch({ text }), [text], 250);
|
|
907
|
+
const availableOwners = (value == null ? void 0 : value.items) || [];
|
|
872
908
|
const queryParamOwners = useMemo(
|
|
873
909
|
() => [ownersParameter].flat().filter(Boolean),
|
|
874
910
|
[ownersParameter]
|
|
875
911
|
);
|
|
876
912
|
const [selectedOwners, setSelectedOwners] = useState(
|
|
877
|
-
queryParamOwners.length ?
|
|
913
|
+
queryParamOwners.length ? queryParamOwners : (_b = (_a = filters.owners) == null ? void 0 : _a.values) != null ? _b : []
|
|
878
914
|
);
|
|
879
|
-
const {
|
|
880
|
-
loading,
|
|
881
|
-
error,
|
|
882
|
-
value: ownerEntities
|
|
883
|
-
} = useAsync(async () => {
|
|
884
|
-
const ownerEntityRefs = [
|
|
885
|
-
...new Set(
|
|
886
|
-
backendEntities.flatMap(
|
|
887
|
-
(e) => getEntityRelations(e, RELATION_OWNED_BY).map(
|
|
888
|
-
(o) => stringifyEntityRef(o)
|
|
889
|
-
)
|
|
890
|
-
).filter(Boolean)
|
|
891
|
-
)
|
|
892
|
-
];
|
|
893
|
-
const { items: ownerEntitiesOrNull } = await catalogApi.getEntitiesByRefs({
|
|
894
|
-
entityRefs: ownerEntityRefs,
|
|
895
|
-
fields: [
|
|
896
|
-
"kind",
|
|
897
|
-
"metadata.name",
|
|
898
|
-
"metadata.title",
|
|
899
|
-
"metadata.namespace",
|
|
900
|
-
"spec.profile.displayName"
|
|
901
|
-
]
|
|
902
|
-
});
|
|
903
|
-
const owners = ownerEntitiesOrNull.map((entity, index) => {
|
|
904
|
-
if (entity) {
|
|
905
|
-
return {
|
|
906
|
-
label: humanizeEntity(entity, { defaultKind: "Group" }),
|
|
907
|
-
entityRef: stringifyEntityRef(entity)
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
return {
|
|
911
|
-
label: humanizeEntityRef(parseEntityRef(ownerEntityRefs[index]), {
|
|
912
|
-
defaultKind: "group"
|
|
913
|
-
}),
|
|
914
|
-
entityRef: ownerEntityRefs[index]
|
|
915
|
-
};
|
|
916
|
-
});
|
|
917
|
-
return owners.sort(
|
|
918
|
-
(a, b) => a.label.localeCompare(b.label, "en-US", {
|
|
919
|
-
ignorePunctuation: true,
|
|
920
|
-
caseFirst: "upper"
|
|
921
|
-
})
|
|
922
|
-
);
|
|
923
|
-
}, [backendEntities]);
|
|
924
|
-
useEffect(() => {
|
|
925
|
-
if (error) {
|
|
926
|
-
errorApi.post(
|
|
927
|
-
{
|
|
928
|
-
...error,
|
|
929
|
-
message: `EntityOwnerPicker failed to initialize: ${error.message}`
|
|
930
|
-
},
|
|
931
|
-
{}
|
|
932
|
-
);
|
|
933
|
-
}
|
|
934
|
-
}, [error, errorApi]);
|
|
915
|
+
const { getEntity, setEntity } = useSelectedOwners(selectedOwners);
|
|
935
916
|
useEffect(() => {
|
|
936
917
|
if (queryParamOwners.length) {
|
|
937
918
|
const filter = new EntityOwnerFilter(queryParamOwners);
|
|
@@ -939,41 +920,64 @@ const EntityOwnerPicker = () => {
|
|
|
939
920
|
}
|
|
940
921
|
}, [queryParamOwners]);
|
|
941
922
|
useEffect(() => {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
923
|
+
updateFilters({
|
|
924
|
+
owners: selectedOwners.length ? new EntityOwnerFilter(selectedOwners) : void 0
|
|
925
|
+
});
|
|
926
|
+
}, [selectedOwners, updateFilters]);
|
|
927
|
+
if (["user", "group"].includes(
|
|
928
|
+
((_c = filters.kind) == null ? void 0 : _c.value.toLocaleLowerCase("en-US")) || ""
|
|
929
|
+
)) {
|
|
949
930
|
return null;
|
|
931
|
+
}
|
|
950
932
|
return /* @__PURE__ */ React.createElement(Box, { pb: 1, pt: 1 }, /* @__PURE__ */ React.createElement(Typography, { variant: "button", component: "label" }, "Owner", /* @__PURE__ */ React.createElement(
|
|
951
933
|
Autocomplete,
|
|
952
934
|
{
|
|
953
935
|
multiple: true,
|
|
954
936
|
disableCloseOnSelect: true,
|
|
955
937
|
loading,
|
|
956
|
-
options:
|
|
957
|
-
value:
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
getOptionLabel: (option) => option.label,
|
|
962
|
-
renderOption: (option, { selected }) => /* @__PURE__ */ React.createElement(
|
|
963
|
-
FormControlLabel,
|
|
964
|
-
{
|
|
965
|
-
control: /* @__PURE__ */ React.createElement(
|
|
966
|
-
Checkbox,
|
|
967
|
-
{
|
|
968
|
-
icon: icon$2,
|
|
969
|
-
checkedIcon: checkedIcon$2,
|
|
970
|
-
checked: selected
|
|
971
|
-
}
|
|
972
|
-
),
|
|
973
|
-
onClick: (event) => event.preventDefault(),
|
|
974
|
-
label: option.label
|
|
938
|
+
options: availableOwners,
|
|
939
|
+
value: selectedOwners,
|
|
940
|
+
getOptionSelected: (o, v) => {
|
|
941
|
+
if (typeof v === "string") {
|
|
942
|
+
return stringifyEntityRef(o) === v;
|
|
975
943
|
}
|
|
976
|
-
|
|
944
|
+
return o === v;
|
|
945
|
+
},
|
|
946
|
+
getOptionLabel: (o) => {
|
|
947
|
+
const entity = typeof o === "string" ? getEntity(o) || o : o;
|
|
948
|
+
return typeof entity === "string" ? entity : humanizeEntity(entity, entity.metadata.name);
|
|
949
|
+
},
|
|
950
|
+
onChange: (_, owners) => {
|
|
951
|
+
setText("");
|
|
952
|
+
setSelectedOwners(
|
|
953
|
+
owners.map((e) => {
|
|
954
|
+
const entityRef = typeof e === "string" ? e : stringifyEntityRef(e);
|
|
955
|
+
if (typeof e !== "string") {
|
|
956
|
+
setEntity(e);
|
|
957
|
+
}
|
|
958
|
+
return entityRef;
|
|
959
|
+
})
|
|
960
|
+
);
|
|
961
|
+
},
|
|
962
|
+
filterOptions: (x) => x,
|
|
963
|
+
renderOption: (entity, { selected }) => {
|
|
964
|
+
const isGroup = entity.kind === "Group";
|
|
965
|
+
return /* @__PURE__ */ React.createElement(
|
|
966
|
+
FormControlLabel,
|
|
967
|
+
{
|
|
968
|
+
control: /* @__PURE__ */ React.createElement(
|
|
969
|
+
Checkbox,
|
|
970
|
+
{
|
|
971
|
+
icon: icon$2,
|
|
972
|
+
checkedIcon: checkedIcon$2,
|
|
973
|
+
checked: selected
|
|
974
|
+
}
|
|
975
|
+
),
|
|
976
|
+
onClick: (event) => event.preventDefault(),
|
|
977
|
+
label: /* @__PURE__ */ React.createElement(Box, { display: "flex", flexWrap: "wrap", alignItems: "center" }, isGroup ? /* @__PURE__ */ React.createElement(GroupIcon, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(PersonIcon, { fontSize: "small" }), "\xA0", humanizeEntity(entity, entity.metadata.name))
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
},
|
|
977
981
|
size: "small",
|
|
978
982
|
popupIcon: /* @__PURE__ */ React.createElement(ExpandMoreIcon, { "data-testid": "owner-picker-expand" }),
|
|
979
983
|
renderInput: (params) => /* @__PURE__ */ React.createElement(
|
|
@@ -981,12 +985,53 @@ const EntityOwnerPicker = () => {
|
|
|
981
985
|
{
|
|
982
986
|
...params,
|
|
983
987
|
className: classes.input,
|
|
988
|
+
onChange: (e) => {
|
|
989
|
+
setText(e.currentTarget.value);
|
|
990
|
+
},
|
|
984
991
|
variant: "outlined"
|
|
985
992
|
}
|
|
986
|
-
)
|
|
993
|
+
),
|
|
994
|
+
ListboxProps: {
|
|
995
|
+
onScroll: (e) => {
|
|
996
|
+
const element = e.currentTarget;
|
|
997
|
+
const hasReachedEnd = Math.abs(
|
|
998
|
+
element.scrollHeight - element.clientHeight - element.scrollTop
|
|
999
|
+
) < 1;
|
|
1000
|
+
if (hasReachedEnd && (value == null ? void 0 : value.pageInfo.nextCursor)) {
|
|
1001
|
+
handleFetch({
|
|
1002
|
+
cursor: value.pageInfo.nextCursor,
|
|
1003
|
+
prev: value.items
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
},
|
|
1007
|
+
"data-testid": "owner-picker-listbox"
|
|
1008
|
+
}
|
|
987
1009
|
}
|
|
988
1010
|
)));
|
|
989
1011
|
};
|
|
1012
|
+
function useSelectedOwners(initialSelectedOwnersRefs) {
|
|
1013
|
+
const allEntities = useRef({});
|
|
1014
|
+
const catalogApi = useApi(catalogApiRef);
|
|
1015
|
+
useAsync(async () => {
|
|
1016
|
+
if (initialSelectedOwnersRefs.length === 0) {
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
const initialSelectedEntities = await catalogApi.getEntitiesByRefs({
|
|
1020
|
+
entityRefs: initialSelectedOwnersRefs
|
|
1021
|
+
});
|
|
1022
|
+
initialSelectedEntities.items.forEach((e) => {
|
|
1023
|
+
if (e) {
|
|
1024
|
+
allEntities.current[stringifyEntityRef(e)] = e;
|
|
1025
|
+
}
|
|
1026
|
+
});
|
|
1027
|
+
}, []);
|
|
1028
|
+
return {
|
|
1029
|
+
getEntity: (entityRef) => allEntities.current[entityRef],
|
|
1030
|
+
setEntity: (entity) => {
|
|
1031
|
+
allEntities.current[stringifyEntityRef(entity)] = entity;
|
|
1032
|
+
}
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
990
1035
|
|
|
991
1036
|
const entityRouteRef = getOrCreateGlobalSingleton(
|
|
992
1037
|
"catalog:entity-route-ref",
|