@thecodingsheikh/backstage-plugin-multi-owner 1.0.2
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/README.md +88 -0
- package/dist/components/EntityMultiOwnerCard/EntityMultiOwnerCard.esm.js +103 -0
- package/dist/components/EntityMultiOwnerCard/EntityMultiOwnerCard.esm.js.map +1 -0
- package/dist/components/EntityMultiOwnerCard/index.esm.js +2 -0
- package/dist/components/EntityMultiOwnerCard/index.esm.js.map +1 -0
- package/dist/hooks/useMultiOwners.esm.js +25 -0
- package/dist/hooks/useMultiOwners.esm.js.map +1 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.esm.js +3 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +22 -0
- package/dist/plugin.esm.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @thecodingsheikh/backstage-plugin-multi-owner
|
|
2
|
+
|
|
3
|
+
A frontend plugin that displays multiple owners for Backstage entities, with optional role labels.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **EntityMultiOwnerCard** — An info card showing all owners with clickable entity reference links
|
|
10
|
+
- **Role chips** — Optional role labels displayed as Material UI chips
|
|
11
|
+
- **Smart icons** — Group icon for groups, person icon for users
|
|
12
|
+
- **Fallback** — Falls back to `spec.owner` when `spec.owners` is not configured
|
|
13
|
+
- **EntitySwitch guard** — `isMultiOwnerAvailable()` for conditional rendering
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add @thecodingsheikh/backstage-plugin-multi-owner
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Entity Page
|
|
24
|
+
|
|
25
|
+
Add the card to your entity pages in `packages/app/src/components/catalog/EntityPage.tsx`:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import {
|
|
29
|
+
EntityMultiOwnerCard,
|
|
30
|
+
isMultiOwnerAvailable,
|
|
31
|
+
} from '@thecodingsheikh/backstage-plugin-multi-owner';
|
|
32
|
+
|
|
33
|
+
// In your entity page layout:
|
|
34
|
+
const overviewContent = (
|
|
35
|
+
<Grid container spacing={3}>
|
|
36
|
+
{/* ... other cards ... */}
|
|
37
|
+
|
|
38
|
+
<EntitySwitch>
|
|
39
|
+
<EntitySwitch.Case if={isMultiOwnerAvailable}>
|
|
40
|
+
<Grid item md={6}>
|
|
41
|
+
<EntityMultiOwnerCard />
|
|
42
|
+
</Grid>
|
|
43
|
+
</EntitySwitch.Case>
|
|
44
|
+
</EntitySwitch>
|
|
45
|
+
</Grid>
|
|
46
|
+
);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Props
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| `title` | `string` | `"Owners"` | Card title |
|
|
54
|
+
| `variant` | `'gridItem' \| 'fullHeight'` | — | Card variant |
|
|
55
|
+
|
|
56
|
+
### Custom Hook
|
|
57
|
+
|
|
58
|
+
You can also use the `useMultiOwners` hook directly:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { useMultiOwners } from '@thecodingsheikh/backstage-plugin-multi-owner';
|
|
62
|
+
|
|
63
|
+
function MyComponent() {
|
|
64
|
+
const { owners, loading } = useMultiOwners();
|
|
65
|
+
|
|
66
|
+
if (loading) return <Progress />;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<ul>
|
|
70
|
+
{owners.map(owner => (
|
|
71
|
+
<li key={owner.name}>
|
|
72
|
+
{owner.name} {owner.role && `(${owner.role})`}
|
|
73
|
+
</li>
|
|
74
|
+
))}
|
|
75
|
+
</ul>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Requirements
|
|
81
|
+
|
|
82
|
+
This plugin requires the backend processor module to be installed:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
yarn add @thecodingsheikh/backstage-plugin-catalog-backend-module-multi-owner-processor
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See the [backend module README](../catalog-backend-module-multi-owner-processor/README.md) for setup instructions.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { InfoCard } from '@backstage/core-components';
|
|
3
|
+
import { EntityRefLinks } from '@backstage/plugin-catalog-react';
|
|
4
|
+
import { makeStyles, Typography, List, ListItem, ListItemIcon, ListItemText, Chip, createStyles } from '@material-ui/core';
|
|
5
|
+
import PeopleIcon from '@material-ui/icons/People';
|
|
6
|
+
import PersonIcon from '@material-ui/icons/Person';
|
|
7
|
+
import { parseEntityRef } from '@backstage/catalog-model';
|
|
8
|
+
import { useMultiOwners } from '../../hooks/useMultiOwners.esm.js';
|
|
9
|
+
|
|
10
|
+
const useStyles = makeStyles(
|
|
11
|
+
(theme) => createStyles({
|
|
12
|
+
list: {
|
|
13
|
+
padding: 0
|
|
14
|
+
},
|
|
15
|
+
listItem: {
|
|
16
|
+
paddingLeft: theme.spacing(1),
|
|
17
|
+
paddingRight: theme.spacing(1),
|
|
18
|
+
"&:not(:last-child)": {
|
|
19
|
+
borderBottom: `1px solid ${theme.palette.divider}`
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
chip: {
|
|
23
|
+
marginLeft: theme.spacing(1),
|
|
24
|
+
height: 22,
|
|
25
|
+
fontSize: "0.75rem",
|
|
26
|
+
fontWeight: 500
|
|
27
|
+
},
|
|
28
|
+
ownerContent: {
|
|
29
|
+
display: "flex",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
flexWrap: "wrap",
|
|
32
|
+
gap: theme.spacing(0.5)
|
|
33
|
+
},
|
|
34
|
+
emptyState: {
|
|
35
|
+
padding: theme.spacing(2),
|
|
36
|
+
textAlign: "center"
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
function EntityMultiOwnerCard(props) {
|
|
41
|
+
const { title = "Owners", variant } = props;
|
|
42
|
+
const classes = useStyles();
|
|
43
|
+
const { owners } = useMultiOwners();
|
|
44
|
+
if (owners.length === 0) {
|
|
45
|
+
return /* @__PURE__ */ jsx(InfoCard, { title, variant, children: /* @__PURE__ */ jsx(
|
|
46
|
+
Typography,
|
|
47
|
+
{
|
|
48
|
+
variant: "body2",
|
|
49
|
+
color: "textSecondary",
|
|
50
|
+
className: classes.emptyState,
|
|
51
|
+
children: "No owners defined"
|
|
52
|
+
}
|
|
53
|
+
) });
|
|
54
|
+
}
|
|
55
|
+
return /* @__PURE__ */ jsx(InfoCard, { title, variant, children: /* @__PURE__ */ jsx(List, { className: classes.list, children: owners.map((owner, index) => {
|
|
56
|
+
const isGroup = getOwnerKind(owner.name) === "group";
|
|
57
|
+
return /* @__PURE__ */ jsxs(
|
|
58
|
+
ListItem,
|
|
59
|
+
{
|
|
60
|
+
className: classes.listItem,
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ jsx(ListItemIcon, { children: isGroup ? /* @__PURE__ */ jsx(PeopleIcon, {}) : /* @__PURE__ */ jsx(PersonIcon, {}) }),
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
ListItemText,
|
|
65
|
+
{
|
|
66
|
+
disableTypography: true,
|
|
67
|
+
primary: /* @__PURE__ */ jsxs("div", { className: classes.ownerContent, children: [
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
EntityRefLinks,
|
|
70
|
+
{
|
|
71
|
+
entityRefs: [owner.name],
|
|
72
|
+
defaultKind: "group"
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
owner.role && /* @__PURE__ */ jsx(
|
|
76
|
+
Chip,
|
|
77
|
+
{
|
|
78
|
+
label: owner.role,
|
|
79
|
+
size: "small",
|
|
80
|
+
color: "primary",
|
|
81
|
+
variant: "outlined",
|
|
82
|
+
className: classes.chip
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
] })
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
`${owner.name}-${index}`
|
|
91
|
+
);
|
|
92
|
+
}) }) });
|
|
93
|
+
}
|
|
94
|
+
function getOwnerKind(ref) {
|
|
95
|
+
try {
|
|
96
|
+
return parseEntityRef(ref, { defaultKind: "group", defaultNamespace: "default" }).kind.toLowerCase();
|
|
97
|
+
} catch {
|
|
98
|
+
return "group";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { EntityMultiOwnerCard };
|
|
103
|
+
//# sourceMappingURL=EntityMultiOwnerCard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityMultiOwnerCard.esm.js","sources":["../../../src/components/EntityMultiOwnerCard/EntityMultiOwnerCard.tsx"],"sourcesContent":["import {\n InfoCard,\n} from '@backstage/core-components';\nimport { EntityRefLinks } from '@backstage/plugin-catalog-react';\nimport {\n List,\n ListItem,\n ListItemText,\n ListItemIcon,\n Chip,\n makeStyles,\n createStyles,\n Theme,\n Typography,\n} from '@material-ui/core';\nimport PeopleIcon from '@material-ui/icons/People';\nimport PersonIcon from '@material-ui/icons/Person';\nimport { parseEntityRef } from '@backstage/catalog-model';\nimport { useMultiOwners } from '../../hooks/useMultiOwners';\n\nconst useStyles = makeStyles((theme: Theme) =>\n createStyles({\n list: {\n padding: 0,\n },\n listItem: {\n paddingLeft: theme.spacing(1),\n paddingRight: theme.spacing(1),\n '&:not(:last-child)': {\n borderBottom: `1px solid ${theme.palette.divider}`,\n },\n },\n chip: {\n marginLeft: theme.spacing(1),\n height: 22,\n fontSize: '0.75rem',\n fontWeight: 500,\n },\n ownerContent: {\n display: 'flex',\n alignItems: 'center',\n flexWrap: 'wrap',\n gap: theme.spacing(0.5),\n },\n emptyState: {\n padding: theme.spacing(2),\n textAlign: 'center',\n },\n }),\n);\n\n/** Props for the {@link EntityMultiOwnerCard} component. */\nexport interface EntityMultiOwnerCardProps {\n /** Optional card title override. Defaults to `\"Owners\"`. */\n title?: string;\n /** Optional card variant. */\n variant?: 'gridItem' | 'fullHeight';\n}\n\n/**\n * An info card that displays multiple owners for an entity, each with an\n * optional role label. Owners are rendered as clickable entity reference\n * links that navigate to the owner's entity page.\n *\n * @remarks\n * Requires the entity to have the `backstage.io/owners` annotation,\n * typically set by the backend {@link MultiOwnerEntitiesProcessor}.\n * Falls back to `spec.owner` if the annotation is absent.\n */\nexport function EntityMultiOwnerCard(props: EntityMultiOwnerCardProps) {\n const { title = 'Owners', variant } = props;\n const classes = useStyles();\n const { owners } = useMultiOwners();\n\n if (owners.length === 0) {\n return (\n <InfoCard title={title} variant={variant}>\n <Typography\n variant=\"body2\"\n color=\"textSecondary\"\n className={classes.emptyState}\n >\n No owners defined\n </Typography>\n </InfoCard>\n );\n }\n\n return (\n <InfoCard title={title} variant={variant}>\n <List className={classes.list}>\n {owners.map((owner, index) => {\n const isGroup = getOwnerKind(owner.name) === 'group';\n\n return (\n <ListItem\n key={`${owner.name}-${index}`}\n className={classes.listItem}\n >\n <ListItemIcon>\n {isGroup ? <PeopleIcon /> : <PersonIcon />}\n </ListItemIcon>\n <ListItemText\n disableTypography\n primary={\n <div className={classes.ownerContent}>\n <EntityRefLinks\n entityRefs={[owner.name]}\n defaultKind=\"group\"\n />\n {owner.role && (\n <Chip\n label={owner.role}\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n className={classes.chip}\n />\n )}\n </div>\n }\n />\n </ListItem>\n );\n })}\n </List>\n </InfoCard>\n );\n}\n\n/** Extracts the kind from an entity reference string, defaulting to \"group\". */\nfunction getOwnerKind(ref: string): string {\n try {\n return parseEntityRef(ref, { defaultKind: 'group', defaultNamespace: 'default' }).kind.toLowerCase();\n } catch {\n return 'group';\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
2
|
+
import { MULTI_OWNER_ANNOTATION, parseOwners } from '@thecodingsheikh/backstage-plugin-multi-owner-common';
|
|
3
|
+
|
|
4
|
+
function useMultiOwners() {
|
|
5
|
+
const { entity } = useEntity();
|
|
6
|
+
const annotation = entity.metadata.annotations?.[MULTI_OWNER_ANNOTATION];
|
|
7
|
+
if (!annotation) {
|
|
8
|
+
const specOwner = entity.spec?.owner;
|
|
9
|
+
if (typeof specOwner === "string" && specOwner.trim()) {
|
|
10
|
+
return {
|
|
11
|
+
owners: [{ name: specOwner.trim() }]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { owners: [] };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(annotation);
|
|
18
|
+
return { owners: parseOwners(parsed) };
|
|
19
|
+
} catch {
|
|
20
|
+
return { owners: [] };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { useMultiOwners };
|
|
25
|
+
//# sourceMappingURL=useMultiOwners.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMultiOwners.esm.js","sources":["../../src/hooks/useMultiOwners.ts"],"sourcesContent":["import { useEntity } from '@backstage/plugin-catalog-react';\nimport {\n MULTI_OWNER_ANNOTATION,\n parseOwners,\n} from '@thecodingsheikh/backstage-plugin-multi-owner-common';\nimport type { MultiOwnerEntry } from '@thecodingsheikh/backstage-plugin-multi-owner-common';\n\n/**\n * Custom hook that reads the multi-owner annotation from the current\n * entity context and returns a typed array of owner entries.\n *\n * @returns An object containing:\n * - `owners`: The parsed array of {@link MultiOwnerEntry} objects\n *\n * @example\n * ```tsx\n * const { owners } = useMultiOwners();\n * ```\n */\nexport function useMultiOwners(): {\n owners: MultiOwnerEntry[];\n} {\n const { entity } = useEntity();\n\n const annotation =\n entity.metadata.annotations?.[MULTI_OWNER_ANNOTATION];\n\n if (!annotation) {\n // Fall back to spec.owner if present\n const specOwner = (entity.spec as Record<string, unknown> | undefined)\n ?.owner;\n if (typeof specOwner === 'string' && specOwner.trim()) {\n return {\n owners: [{ name: specOwner.trim() }],\n };\n }\n return { owners: [] };\n }\n\n try {\n const parsed = JSON.parse(annotation);\n return { owners: parseOwners(parsed) };\n } catch {\n return { owners: [] };\n }\n}\n"],"names":[],"mappings":";;;AAmBO,SAAS,cAAA,GAEd;AACE,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,SAAA,EAAU;AAE7B,EAAA,MAAM,UAAA,GACF,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,sBAAsB,CAAA;AAExD,EAAA,IAAI,CAAC,UAAA,EAAY;AAEb,IAAA,MAAM,SAAA,GAAa,OAAO,IAAA,EACpB,KAAA;AACN,IAAA,IAAI,OAAO,SAAA,KAAc,QAAA,IAAY,SAAA,CAAU,MAAK,EAAG;AACnD,MAAA,OAAO;AAAA,QACH,QAAQ,CAAC,EAAE,MAAM,SAAA,CAAU,IAAA,IAAQ;AAAA,OACvC;AAAA,IACJ;AACA,IAAA,OAAO,EAAE,MAAA,EAAQ,EAAC,EAAE;AAAA,EACxB;AAEA,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,UAAU,CAAA;AACpC,IAAA,OAAO,EAAE,MAAA,EAAQ,WAAA,CAAY,MAAM,CAAA,EAAE;AAAA,EACzC,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,EAAE,MAAA,EAAQ,EAAC,EAAE;AAAA,EACxB;AACJ;;;;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
3
|
+
import { Entity } from '@backstage/catalog-model';
|
|
4
|
+
import { MultiOwnerEntry } from '@thecodingsheikh/backstage-plugin-multi-owner-common';
|
|
5
|
+
|
|
6
|
+
/** Props for the {@link EntityMultiOwnerCard} component. */
|
|
7
|
+
interface EntityMultiOwnerCardProps {
|
|
8
|
+
/** Optional card title override. Defaults to `"Owners"`. */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Optional card variant. */
|
|
11
|
+
variant?: 'gridItem' | 'fullHeight';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* An info card that displays multiple owners for an entity, each with an
|
|
15
|
+
* optional role label. Owners are rendered as clickable entity reference
|
|
16
|
+
* links that navigate to the owner's entity page.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* Requires the entity to have the `backstage.io/owners` annotation,
|
|
20
|
+
* typically set by the backend {@link MultiOwnerEntitiesProcessor}.
|
|
21
|
+
* Falls back to `spec.owner` if the annotation is absent.
|
|
22
|
+
*/
|
|
23
|
+
declare function EntityMultiOwnerCard$1(props: EntityMultiOwnerCardProps): react_jsx_runtime.JSX.Element;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The multi-owner frontend plugin.
|
|
27
|
+
*/
|
|
28
|
+
declare const multiOwnerPlugin: _backstage_core_plugin_api.BackstagePlugin<{}, {}, {}>;
|
|
29
|
+
/**
|
|
30
|
+
* An info card component that displays the list of owners (with optional roles)
|
|
31
|
+
* for an entity that uses `spec.owners`.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* Place this card on entity pages in your Backstage app:
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <EntitySwitch>
|
|
37
|
+
* <EntitySwitch.Case if={isMultiOwnerAvailable}>
|
|
38
|
+
* <EntityMultiOwnerCard />
|
|
39
|
+
* </EntitySwitch.Case>
|
|
40
|
+
* </EntitySwitch>
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const EntityMultiOwnerCard: typeof EntityMultiOwnerCard$1;
|
|
44
|
+
/**
|
|
45
|
+
* Utility function that checks whether the multi-owner annotation is
|
|
46
|
+
* present on an entity. Use with `EntitySwitch` to conditionally render
|
|
47
|
+
* the card only when relevant.
|
|
48
|
+
*/
|
|
49
|
+
declare function isMultiOwnerAvailable(entity: Entity): boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Custom hook that reads the multi-owner annotation from the current
|
|
53
|
+
* entity context and returns a typed array of owner entries.
|
|
54
|
+
*
|
|
55
|
+
* @returns An object containing:
|
|
56
|
+
* - `owners`: The parsed array of {@link MultiOwnerEntry} objects
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const { owners } = useMultiOwners();
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function useMultiOwners(): {
|
|
64
|
+
owners: MultiOwnerEntry[];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export { EntityMultiOwnerCard, isMultiOwnerAvailable, multiOwnerPlugin, useMultiOwners };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createPlugin, createComponentExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
import { MULTI_OWNER_ANNOTATION } from '@thecodingsheikh/backstage-plugin-multi-owner-common';
|
|
3
|
+
|
|
4
|
+
const multiOwnerPlugin = createPlugin({
|
|
5
|
+
id: "multi-owner"
|
|
6
|
+
});
|
|
7
|
+
const EntityMultiOwnerCard = multiOwnerPlugin.provide(
|
|
8
|
+
createComponentExtension({
|
|
9
|
+
name: "EntityMultiOwnerCard",
|
|
10
|
+
component: {
|
|
11
|
+
lazy: () => import('./components/EntityMultiOwnerCard/index.esm.js').then(
|
|
12
|
+
(m) => m.EntityMultiOwnerCard
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
function isMultiOwnerAvailable(entity) {
|
|
18
|
+
return Boolean(entity.metadata.annotations?.[MULTI_OWNER_ANNOTATION]);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { EntityMultiOwnerCard, isMultiOwnerAvailable, multiOwnerPlugin };
|
|
22
|
+
//# sourceMappingURL=plugin.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n createPlugin,\n createComponentExtension,\n} from '@backstage/core-plugin-api';\nimport { Entity } from '@backstage/catalog-model';\nimport { MULTI_OWNER_ANNOTATION } from '@thecodingsheikh/backstage-plugin-multi-owner-common';\n\n/**\n * The multi-owner frontend plugin.\n */\nexport const multiOwnerPlugin = createPlugin({\n id: 'multi-owner',\n});\n\n/**\n * An info card component that displays the list of owners (with optional roles)\n * for an entity that uses `spec.owners`.\n *\n * @remarks\n * Place this card on entity pages in your Backstage app:\n * ```tsx\n * <EntitySwitch>\n * <EntitySwitch.Case if={isMultiOwnerAvailable}>\n * <EntityMultiOwnerCard />\n * </EntitySwitch.Case>\n * </EntitySwitch>\n * ```\n */\nexport const EntityMultiOwnerCard = multiOwnerPlugin.provide(\n createComponentExtension({\n name: 'EntityMultiOwnerCard',\n component: {\n lazy: () =>\n import('./components/EntityMultiOwnerCard').then(\n m => m.EntityMultiOwnerCard,\n ),\n },\n }),\n);\n\n/**\n * Utility function that checks whether the multi-owner annotation is\n * present on an entity. Use with `EntitySwitch` to conditionally render\n * the card only when relevant.\n */\nexport function isMultiOwnerAvailable(entity: Entity): boolean {\n return Boolean(entity.metadata.annotations?.[MULTI_OWNER_ANNOTATION]);\n}\n"],"names":[],"mappings":";;;AAUO,MAAM,mBAAmB,YAAA,CAAa;AAAA,EACzC,EAAA,EAAI;AACR,CAAC;AAgBM,MAAM,uBAAuB,gBAAA,CAAiB,OAAA;AAAA,EACjD,wBAAA,CAAyB;AAAA,IACrB,IAAA,EAAM,sBAAA;AAAA,IACN,SAAA,EAAW;AAAA,MACP,IAAA,EAAM,MACF,OAAO,gDAAmC,CAAA,CAAE,IAAA;AAAA,QACxC,OAAK,CAAA,CAAE;AAAA;AACX;AACR,GACH;AACL;AAOO,SAAS,sBAAsB,MAAA,EAAyB;AAC3D,EAAA,OAAO,OAAA,CAAQ,MAAA,CAAO,QAAA,CAAS,WAAA,GAAc,sBAAsB,CAAC,CAAA;AACxE;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thecodingsheikh/backstage-plugin-multi-owner",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"main": "./dist/index.esm.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"backstage": {
|
|
11
|
+
"role": "frontend-plugin",
|
|
12
|
+
"pluginId": "multi-owner",
|
|
13
|
+
"pluginPackages": [
|
|
14
|
+
"@thecodingsheikh/backstage-plugin-multi-owner-common",
|
|
15
|
+
"@thecodingsheikh/backstage-plugin-multi-owner",
|
|
16
|
+
"@thecodingsheikh/backstage-plugin-catalog-backend-module-multi-owner-processor"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "backstage-cli package start",
|
|
22
|
+
"build": "backstage-cli package build",
|
|
23
|
+
"lint": "backstage-cli package lint",
|
|
24
|
+
"test": "backstage-cli package test",
|
|
25
|
+
"clean": "backstage-cli package clean",
|
|
26
|
+
"prepack": "backstage-cli package prepack",
|
|
27
|
+
"postpack": "backstage-cli package postpack"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@backstage/catalog-model": "^1.7.3",
|
|
31
|
+
"@backstage/core-components": "^0.18.3",
|
|
32
|
+
"@backstage/core-plugin-api": "^1.12.0",
|
|
33
|
+
"@backstage/plugin-catalog-react": "^1.15.3",
|
|
34
|
+
"@backstage/theme": "^0.7.0",
|
|
35
|
+
"@material-ui/core": "^4.9.13",
|
|
36
|
+
"@material-ui/icons": "^4.9.1",
|
|
37
|
+
"@thecodingsheikh/backstage-plugin-multi-owner-common": "^1.0.2",
|
|
38
|
+
"react-use": "^17.2.4"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@backstage/cli": "^0.34.5",
|
|
45
|
+
"@backstage/core-app-api": "^1.19.2",
|
|
46
|
+
"@backstage/dev-utils": "^1.1.17",
|
|
47
|
+
"@backstage/test-utils": "^1.7.13",
|
|
48
|
+
"@testing-library/jest-dom": "^6.0.0",
|
|
49
|
+
"@testing-library/react": "^14.0.0",
|
|
50
|
+
"@testing-library/user-event": "^14.0.0",
|
|
51
|
+
"msw": "^1.0.0",
|
|
52
|
+
"react": "^16.13.1 || ^17.0.0 || ^18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist"
|
|
56
|
+
],
|
|
57
|
+
"typesVersions": {
|
|
58
|
+
"*": {
|
|
59
|
+
"package.json": [
|
|
60
|
+
"package.json"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"module": "./dist/index.esm.js"
|
|
65
|
+
}
|