@backstage/plugin-catalog-react 1.2.4-next.1 → 1.2.4

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
+ ## 1.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 37426f6f5e: Fixed bug in `EntityTagPicker` that filtered on unavailable tags for the selected kind.
8
+ - 516b2039b6: Add a reusable pop over `EntityPeekAheadPopover` component. It shows more details about the associated entity. See the playbook here https://backstage.io/storybook/?path=/story/catalog-entitypeekaheadpopover--default
9
+ - 0e33627996: Remove usage of raw span
10
+ - Updated dependencies
11
+ - @backstage/catalog-model@1.1.5
12
+ - @backstage/catalog-client@1.3.0
13
+ - @backstage/core-components@0.12.3
14
+ - @backstage/core-plugin-api@1.3.0
15
+ - @backstage/plugin-permission-react@0.4.9
16
+ - @backstage/errors@1.1.4
17
+ - @backstage/integration@1.4.2
18
+ - @backstage/theme@0.2.16
19
+ - @backstage/types@1.0.2
20
+ - @backstage/version-bridge@1.0.3
21
+ - @backstage/plugin-catalog-common@1.0.10
22
+ - @backstage/plugin-permission-common@0.7.3
23
+
24
+ ## 1.2.4-next.2
25
+
26
+ ### Patch Changes
27
+
28
+ - 516b2039b6: Add a reusable pop over `EntityPeekAheadPopover` component. It shows more details about the associated entity. See the playbook here https://backstage.io/storybook/?path=/story/catalog-entitypeekaheadpopover--default
29
+ - Updated dependencies
30
+ - @backstage/core-plugin-api@1.3.0-next.1
31
+ - @backstage/catalog-client@1.3.0-next.2
32
+ - @backstage/plugin-permission-react@0.4.9-next.1
33
+ - @backstage/catalog-model@1.1.5-next.1
34
+ - @backstage/core-components@0.12.3-next.2
35
+ - @backstage/errors@1.1.4
36
+ - @backstage/integration@1.4.2-next.0
37
+ - @backstage/theme@0.2.16
38
+ - @backstage/types@1.0.2
39
+ - @backstage/version-bridge@1.0.3
40
+ - @backstage/plugin-catalog-common@1.0.10-next.1
41
+ - @backstage/plugin-permission-common@0.7.3-next.0
42
+
3
43
  ## 1.2.4-next.1
4
44
 
5
45
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-catalog-react",
3
- "version": "1.2.4-next.1",
3
+ "version": "1.2.4",
4
4
  "main": "../dist/index.esm.js",
5
5
  "types": "../dist/index.alpha.d.ts"
6
6
  }
@@ -286,6 +286,23 @@ export declare class EntityOwnerFilter implements EntityFilter {
286
286
  /** @public */
287
287
  export declare const EntityOwnerPicker: () => JSX.Element | null;
288
288
 
289
+ /**
290
+ * Shows an entity popover on hover of a component.
291
+ *
292
+ * @public
293
+ */
294
+ export declare const EntityPeekAheadPopover: (props: EntityPeekAheadPopoverProps) => JSX.Element;
295
+
296
+ /**
297
+ * Properties for an entity popover on hover of a component.
298
+ *
299
+ * @public
300
+ */
301
+ export declare type EntityPeekAheadPopoverProps = PropsWithChildren<{
302
+ entityRef: string;
303
+ delayTime?: number;
304
+ }>;
305
+
289
306
  /** @public */
290
307
  export declare const EntityProcessingStatusPicker: () => JSX.Element;
291
308
 
@@ -286,6 +286,23 @@ export declare class EntityOwnerFilter implements EntityFilter {
286
286
  /** @public */
287
287
  export declare const EntityOwnerPicker: () => JSX.Element | null;
288
288
 
289
+ /**
290
+ * Shows an entity popover on hover of a component.
291
+ *
292
+ * @public
293
+ */
294
+ export declare const EntityPeekAheadPopover: (props: EntityPeekAheadPopoverProps) => JSX.Element;
295
+
296
+ /**
297
+ * Properties for an entity popover on hover of a component.
298
+ *
299
+ * @public
300
+ */
301
+ export declare type EntityPeekAheadPopoverProps = PropsWithChildren<{
302
+ entityRef: string;
303
+ delayTime?: number;
304
+ }>;
305
+
289
306
  /** @public */
290
307
  export declare const EntityProcessingStatusPicker: () => JSX.Element;
291
308
 
package/dist/index.d.ts CHANGED
@@ -286,6 +286,23 @@ export declare class EntityOwnerFilter implements EntityFilter {
286
286
  /** @public */
287
287
  export declare const EntityOwnerPicker: () => JSX.Element | null;
288
288
 
289
+ /**
290
+ * Shows an entity popover on hover of a component.
291
+ *
292
+ * @public
293
+ */
294
+ export declare const EntityPeekAheadPopover: (props: EntityPeekAheadPopoverProps) => JSX.Element;
295
+
296
+ /**
297
+ * Properties for an entity popover on hover of a component.
298
+ *
299
+ * @public
300
+ */
301
+ export declare type EntityPeekAheadPopoverProps = PropsWithChildren<{
302
+ entityRef: string;
303
+ delayTime?: number;
304
+ }>;
305
+
289
306
  /** @public */
290
307
  export declare const EntityProcessingStatusPicker: () => JSX.Element;
291
308
 
package/dist/index.esm.js CHANGED
@@ -1,14 +1,14 @@
1
1
  export { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
2
- import { createApiRef, createRouteRef, useRouteRef, useApi, AnalyticsContext, identityApiRef, alertApiRef, useApp, configApiRef } from '@backstage/core-plugin-api';
2
+ import { createApiRef, createRouteRef, useRouteRef, useApi, AnalyticsContext, identityApiRef, alertApiRef, useApiHolder, useApp, configApiRef } from '@backstage/core-plugin-api';
3
3
  import ObservableImpl from 'zen-observable';
4
4
  import React, { useState, forwardRef, createContext, useMemo, useCallback, useContext, useEffect, useRef, useLayoutEffect, Fragment } from 'react';
5
- import { Grid, useMediaQuery, useTheme, Button, Drawer, Box, Typography, Tooltip, makeStyles, FormControlLabel, Checkbox, TextField, Toolbar, FormControl, Input, InputAdornment, IconButton, withStyles, DialogContentText, ListItemText as ListItemText$1, ListSubheader as ListSubheader$1, Card, CardContent, ListItem, ListItemIcon, List, Dialog, DialogTitle, DialogContent, Tabs, Tab, DialogActions, Divider, MenuItem, ListItemSecondaryAction } from '@material-ui/core';
5
+ import { Grid, useMediaQuery, useTheme, Button, Drawer, Box, Typography, Tooltip, makeStyles, FormControlLabel, Checkbox, TextField, 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';
6
6
  import FilterListIcon from '@material-ui/icons/FilterList';
7
- import { Link, Progress, ErrorPanel, Select, OverflowTooltip, Table, ResponseErrorPanel, DependencyGraph, DependencyGraphTypes, CodeSnippet } from '@backstage/core-components';
8
- import { DEFAULT_NAMESPACE, parseEntityRef, ANNOTATION_SOURCE_LOCATION, parseLocationRef, RELATION_MEMBER_OF, getCompoundEntityRef, stringifyEntityRef, RELATION_OWNED_BY, RELATION_PART_OF, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model';
7
+ import { Link, Progress, ErrorPanel, Select, ResponseErrorPanel, OverflowTooltip, Table, DependencyGraph, DependencyGraphTypes, CodeSnippet } from '@backstage/core-components';
8
+ import { DEFAULT_NAMESPACE, parseEntityRef, ANNOTATION_SOURCE_LOCATION, parseLocationRef, RELATION_MEMBER_OF, getCompoundEntityRef, stringifyEntityRef, RELATION_OWNED_BY, isUserEntity, isGroupEntity, RELATION_PART_OF, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model';
9
9
  import { getOrCreateGlobalSingleton, createVersionedContext, createVersionedValueMap, useVersionedContext } from '@backstage/version-bridge';
10
10
  import useAsync from 'react-use/lib/useAsync';
11
- import { compact, isEqual, groupBy, chunk } from 'lodash';
11
+ import { compact, isEqual, groupBy, chunk, debounce } from 'lodash';
12
12
  import qs from 'qs';
13
13
  import { useLocation, useNavigate } from 'react-router-dom';
14
14
  import useAsyncFn from 'react-use/lib/useAsyncFn';
@@ -22,6 +22,10 @@ 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 HoverPopover from 'material-ui-popup-state/HoverPopover';
26
+ import { usePopupState, bindHover, bindPopover } from 'material-ui-popup-state/hooks';
27
+ import InfoIcon from '@material-ui/icons/Info';
28
+ import EmailIcon from '@material-ui/icons/Email';
25
29
  import Clear from '@material-ui/icons/Clear';
26
30
  import Search from '@material-ui/icons/Search';
27
31
  import Star from '@material-ui/icons/Star';
@@ -338,6 +342,7 @@ class EntityTypeFilter {
338
342
  constructor(value) {
339
343
  this.value = value;
340
344
  }
345
+ // Simplify `string | string[]` for consumers, always returns an array
341
346
  getTypes() {
342
347
  return Array.isArray(this.value) ? this.value : [this.value];
343
348
  }
@@ -725,6 +730,7 @@ function useRelatedEntities(entity, relationFilter) {
725
730
  const batchedRelationsByKindAndNamespace = [];
726
731
  for (const rs of relationsByKindAndNamespace) {
727
732
  batchedRelationsByKindAndNamespace.push({
733
+ // All relations in a group have the same kind and namespace, so its arbitrary which we pick
728
734
  kind: rs[0].target.kind,
729
735
  namespace: rs[0].target.namespace,
730
736
  nameBatches: chunk(
@@ -958,7 +964,7 @@ const EntityKindPicker = (props) => {
958
964
  ));
959
965
  };
960
966
 
961
- const useStyles$c = makeStyles(
967
+ const useStyles$d = makeStyles(
962
968
  {
963
969
  input: {}
964
970
  },
@@ -970,7 +976,7 @@ const icon$3 = /* @__PURE__ */ React.createElement(CheckBoxOutlineBlankIcon, { f
970
976
  const checkedIcon$3 = /* @__PURE__ */ React.createElement(CheckBoxIcon, { fontSize: "small" });
971
977
  const EntityLifecyclePicker = () => {
972
978
  var _a, _b;
973
- const classes = useStyles$c();
979
+ const classes = useStyles$d();
974
980
  const {
975
981
  updateFilters,
976
982
  backendEntities,
@@ -1042,7 +1048,7 @@ const EntityLifecyclePicker = () => {
1042
1048
  )));
1043
1049
  };
1044
1050
 
1045
- const useStyles$b = makeStyles(
1051
+ const useStyles$c = makeStyles(
1046
1052
  {
1047
1053
  input: {}
1048
1054
  },
@@ -1054,7 +1060,7 @@ const icon$2 = /* @__PURE__ */ React.createElement(CheckBoxOutlineBlankIcon, { f
1054
1060
  const checkedIcon$2 = /* @__PURE__ */ React.createElement(CheckBoxIcon, { fontSize: "small" });
1055
1061
  const EntityOwnerPicker = () => {
1056
1062
  var _a, _b;
1057
- const classes = useStyles$b();
1063
+ const classes = useStyles$c();
1058
1064
  const {
1059
1065
  updateFilters,
1060
1066
  backendEntities,
@@ -1127,6 +1133,132 @@ const EntityOwnerPicker = () => {
1127
1133
  )));
1128
1134
  };
1129
1135
 
1136
+ const EntityCardActions = (props) => {
1137
+ const entityRoute = useRouteRef(entityRouteRef);
1138
+ return /* @__PURE__ */ React.createElement(
1139
+ IconButton,
1140
+ {
1141
+ component: Link,
1142
+ "aria-label": "Show",
1143
+ title: "Show details",
1144
+ to: entityRoute(getCompoundEntityRef(props.entity))
1145
+ },
1146
+ /* @__PURE__ */ React.createElement(InfoIcon, null)
1147
+ );
1148
+ };
1149
+
1150
+ const EmailCardAction = (props) => {
1151
+ return /* @__PURE__ */ React.createElement(
1152
+ IconButton,
1153
+ {
1154
+ component: Link,
1155
+ "aria-label": "Email",
1156
+ title: `Email ${props.email}`,
1157
+ to: `mailto:${props.email}`
1158
+ },
1159
+ /* @__PURE__ */ React.createElement(EmailIcon, null)
1160
+ );
1161
+ };
1162
+
1163
+ const GroupCardActions = (props) => {
1164
+ var _a;
1165
+ const email = (_a = props.entity.spec.profile) == null ? void 0 : _a.email;
1166
+ return email ? /* @__PURE__ */ React.createElement(EmailCardAction, { email }) : null;
1167
+ };
1168
+
1169
+ const UserCardActions = (props) => {
1170
+ var _a;
1171
+ const email = (_a = props.entity.spec.profile) == null ? void 0 : _a.email;
1172
+ return email ? /* @__PURE__ */ React.createElement(EmailCardAction, { email }) : null;
1173
+ };
1174
+
1175
+ const useStyles$b = makeStyles(() => {
1176
+ return {
1177
+ popoverPaper: {
1178
+ width: "30em"
1179
+ },
1180
+ descriptionTypography: {
1181
+ overflow: "hidden",
1182
+ textOverflow: "ellipsis",
1183
+ display: "-webkit-box",
1184
+ WebkitLineClamp: 2,
1185
+ WebkitBoxOrient: "vertical"
1186
+ }
1187
+ };
1188
+ });
1189
+ const maxTagChips = 4;
1190
+ const EntityPeekAheadPopover = (props) => {
1191
+ var _a, _b, _c;
1192
+ const { entityRef, children, delayTime = 500 } = props;
1193
+ const classes = useStyles$b();
1194
+ const apiHolder = useApiHolder();
1195
+ const popupState = usePopupState({
1196
+ variant: "popover",
1197
+ popupId: "entity-peek-ahead"
1198
+ });
1199
+ const [isHovered, setIsHovered] = useState(false);
1200
+ const debouncedHandleMouseEnter = useMemo(
1201
+ () => debounce(() => setIsHovered(true), delayTime),
1202
+ [delayTime]
1203
+ );
1204
+ const [{ loading, error, value: entity }, load] = useAsyncFn(async () => {
1205
+ const catalogApi = apiHolder.get(catalogApiRef);
1206
+ if (catalogApi) {
1207
+ const retrievedEntity = await catalogApi.getEntityByRef(entityRef);
1208
+ if (!retrievedEntity) {
1209
+ throw new Error(`${entityRef} not found`);
1210
+ }
1211
+ return retrievedEntity;
1212
+ }
1213
+ return void 0;
1214
+ }, [apiHolder, entityRef]);
1215
+ const handleOnMouseLeave = () => {
1216
+ setIsHovered(false);
1217
+ debouncedHandleMouseEnter.cancel();
1218
+ };
1219
+ useEffect(() => {
1220
+ if (popupState.isOpen && !entity && !error && !loading) {
1221
+ load();
1222
+ }
1223
+ }, [popupState.isOpen, load, entity, error, loading]);
1224
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { component: "span", onMouseEnter: debouncedHandleMouseEnter }, /* @__PURE__ */ React.createElement(
1225
+ Typography,
1226
+ {
1227
+ component: "span",
1228
+ "data-testid": "trigger",
1229
+ ...bindHover(popupState)
1230
+ },
1231
+ children
1232
+ )), isHovered && /* @__PURE__ */ React.createElement(
1233
+ HoverPopover,
1234
+ {
1235
+ PaperProps: {
1236
+ className: classes.popoverPaper
1237
+ },
1238
+ ...bindPopover(popupState),
1239
+ anchorOrigin: {
1240
+ vertical: "bottom",
1241
+ horizontal: "center"
1242
+ },
1243
+ transformOrigin: {
1244
+ vertical: "top",
1245
+ horizontal: "center"
1246
+ },
1247
+ onMouseLeave: handleOnMouseLeave
1248
+ },
1249
+ /* @__PURE__ */ React.createElement(Card, null, /* @__PURE__ */ React.createElement(CardContent, null, error && /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error }), loading && /* @__PURE__ */ React.createElement(Progress, null), entity && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Typography, { color: "textSecondary" }, entity.metadata.namespace), /* @__PURE__ */ React.createElement(Typography, { variant: "h5", component: "div" }, entity.metadata.name), /* @__PURE__ */ React.createElement(Typography, { color: "textSecondary", gutterBottom: true }, entity.kind), entity.metadata.description && /* @__PURE__ */ React.createElement(
1250
+ Typography,
1251
+ {
1252
+ className: classes.descriptionTypography,
1253
+ paragraph: true
1254
+ },
1255
+ entity.metadata.description
1256
+ ), /* @__PURE__ */ React.createElement(Typography, null, (_a = entity.spec) == null ? void 0 : _a.type), /* @__PURE__ */ React.createElement(Box, { marginTop: "0.5em" }, (entity.metadata.tags || []).slice(0, maxTagChips).map((tag) => {
1257
+ return /* @__PURE__ */ React.createElement(Chip, { key: tag, size: "small", label: tag });
1258
+ }), ((_b = entity.metadata.tags) == null ? void 0 : _b.length) && ((_c = entity.metadata.tags) == null ? void 0 : _c.length) > maxTagChips && /* @__PURE__ */ React.createElement(Tooltip, { title: "Drill into the entity to see all of the tags." }, /* @__PURE__ */ React.createElement(Chip, { key: "other-tags", size: "small", label: "..." }))))), !error && entity && /* @__PURE__ */ React.createElement(CardActions, null, /* @__PURE__ */ React.createElement(React.Fragment, null, isUserEntity(entity) && /* @__PURE__ */ React.createElement(UserCardActions, { entity }), isGroupEntity(entity) && /* @__PURE__ */ React.createElement(GroupCardActions, { entity }), /* @__PURE__ */ React.createElement(EntityCardActions, { entity }))))
1259
+ ));
1260
+ };
1261
+
1130
1262
  const useStyles$a = makeStyles(
1131
1263
  (_theme) => ({
1132
1264
  searchToolbar: {
@@ -1337,6 +1469,7 @@ const EntityTable = (props) => {
1337
1469
  style: tableStyle,
1338
1470
  emptyContent: emptyContent && /* @__PURE__ */ React.createElement("div", { className: classes.empty }, emptyContent),
1339
1471
  options: {
1472
+ // TODO: Toolbar padding if off compared to other cards, should be: padding: 16px 24px;
1340
1473
  search: false,
1341
1474
  paging: false,
1342
1475
  actionsColumnIndex: -1,
@@ -2248,7 +2381,8 @@ const UserListPicker = (props) => {
2248
2381
  const filterGroups = getFilterGroups(orgName).map((filterGroup) => ({
2249
2382
  ...filterGroup,
2250
2383
  items: filterGroup.items.filter(
2251
- ({ id }) => ["group", "user"].some((kind) => kind === kindParameter) ? userAndGroupFilterIds.includes(id) : !availableFilters || availableFilters.includes(id)
2384
+ ({ id }) => // TODO: avoid hardcoding kinds here
2385
+ ["group", "user"].some((kind) => kind === kindParameter) ? userAndGroupFilterIds.includes(id) : !availableFilters || availableFilters.includes(id)
2252
2386
  )
2253
2387
  })).filter(({ items }) => !!items.length);
2254
2388
  const { isStarredEntity } = useStarredEntities();
@@ -2449,5 +2583,5 @@ const MockEntityListContextProvider = ({
2449
2583
  return /* @__PURE__ */ React.createElement(EntityListContext.Provider, { value: resolvedValue }, children);
2450
2584
  };
2451
2585
 
2452
- export { AsyncEntityProvider, CatalogFilterLayout, EntityErrorFilter, EntityKindFilter, EntityKindPicker, EntityLifecycleFilter, EntityLifecyclePicker, EntityListContext, EntityListProvider, EntityOrphanFilter, EntityOwnerFilter, EntityOwnerPicker, EntityProcessingStatusPicker, EntityProvider, EntityRefLink, EntityRefLinks, EntitySearchBar, EntityTable, EntityTagFilter, EntityTagPicker, EntityTextFilter, EntityTypeFilter, EntityTypePicker, FavoriteEntity, InspectEntityDialog, MockEntityListContextProvider, MockStarredEntitiesApi, UnregisterEntityDialog, UserListFilter, UserListPicker, catalogApiRef, columnFactories, entityRouteParams, entityRouteRef, getEntityRelations, getEntitySourceLocation, humanizeEntityRef, isOwnerOf, starredEntitiesApiRef, useAsyncEntity, useEntity, useEntityList, useEntityOwnership, useEntityPermission, useEntityTypeFilter, useRelatedEntities, useStarredEntities, useStarredEntity };
2586
+ export { AsyncEntityProvider, CatalogFilterLayout, EntityErrorFilter, EntityKindFilter, EntityKindPicker, EntityLifecycleFilter, EntityLifecyclePicker, EntityListContext, EntityListProvider, EntityOrphanFilter, EntityOwnerFilter, EntityOwnerPicker, EntityPeekAheadPopover, EntityProcessingStatusPicker, EntityProvider, EntityRefLink, EntityRefLinks, EntitySearchBar, EntityTable, EntityTagFilter, EntityTagPicker, EntityTextFilter, EntityTypeFilter, EntityTypePicker, FavoriteEntity, InspectEntityDialog, MockEntityListContextProvider, MockStarredEntitiesApi, UnregisterEntityDialog, UserListFilter, UserListPicker, catalogApiRef, columnFactories, entityRouteParams, entityRouteRef, getEntityRelations, getEntitySourceLocation, humanizeEntityRef, isOwnerOf, starredEntitiesApiRef, useAsyncEntity, useEntity, useEntityList, useEntityOwnership, useEntityPermission, useEntityTypeFilter, useRelatedEntities, useStarredEntities, useStarredEntity };
2453
2587
  //# sourceMappingURL=index.esm.js.map