@axis-backstage/plugin-readme 0.1.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/README.md +72 -0
- package/dist/esm/index-0b47704b.esm.js +75 -0
- package/dist/esm/index-0b47704b.esm.js.map +1 -0
- package/dist/esm/index-4c75dc93.esm.js +103 -0
- package/dist/esm/index-4c75dc93.esm.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.esm.js +3 -0
- package/dist/index.esm.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Readme plugin
|
|
2
|
+
|
|
3
|
+
Welcome to the readme plugin!
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Introduction
|
|
8
|
+
|
|
9
|
+
The `README-plugin` enables easy access and viewing of the README.md file. By having information such as the project's purpose, usage instructions, or installation details as a central part of the EntityPage, we hope to improve the `onboarding and understanding of entities`.
|
|
10
|
+
|
|
11
|
+
The README.md file is always retrieved from the same directory as the `catalog-info.yaml file`, also known as the `entity source location`. If you wish to view the path where the plugin looks for your README.md file, you can find it in the backstage.io/source-location annotation in the catalog-info.yaml file (see the example below). This annotation is automatically added to your entity, so there is no need to add it manually.
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
annotations:
|
|
15
|
+
backstage.io/source-location: url:https://github.com/AxisCommunications/backstage-plugins/blob/main/
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Currently, placing your README.md file elsewhere than in the same directory as the `catalog-info.yaml file` repository is not supported.
|
|
19
|
+
|
|
20
|
+
The displays README files with one of the following file types:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
{ name: 'README', type: 'text/plain' },
|
|
24
|
+
{ name: 'README.md', type: 'text/markdown' },
|
|
25
|
+
{ name: 'README.rst', type: 'text/plain' },
|
|
26
|
+
{ name: 'README.txt', type: 'text/plain' },
|
|
27
|
+
{ name: 'README.MD', type: 'text/markdown' },
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The plugin can also handle symlinks in the README file.
|
|
31
|
+
|
|
32
|
+
## Note
|
|
33
|
+
|
|
34
|
+
You will **need** to also perform the installation instructions in [Readme Backend](https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme-backend) in order for this plugin to work.
|
|
35
|
+
|
|
36
|
+
## Getting started
|
|
37
|
+
|
|
38
|
+
1. First, install the plugin into your app:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# From your Backstage root directory
|
|
42
|
+
yarn add --cwd packages/app @axis-backstage/plugin-readme
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. Then, modify your entity page in `EntityPage.tsx` to include the `ReadmeCard` component that is exported from the plugin to `overviewContent``.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// In packages/app/src/components/catalog/Entity.tsx
|
|
49
|
+
import { ReadmeCard } from '@axis-backstage/plugin-readme';
|
|
50
|
+
|
|
51
|
+
const overviewContent = (
|
|
52
|
+
...
|
|
53
|
+
<Grid item md={6} sx={12}>
|
|
54
|
+
<ReadmeCard />
|
|
55
|
+
</Grid>
|
|
56
|
+
...
|
|
57
|
+
)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Layout
|
|
61
|
+
|
|
62
|
+
The readme card is located in the overview page on the entity page. From the card header it is also possible to open a dialog displaying the full README.md.
|
|
63
|
+
|
|
64
|
+

|
|
65
|
+
|
|
66
|
+

|
|
67
|
+
|
|
68
|
+
### Troubleshooting
|
|
69
|
+
|
|
70
|
+
#### "No README.md file found at the source location..."
|
|
71
|
+
|
|
72
|
+
This message indicates that the backend cannot find your README.md file. Ensure that the README.md file is indeed located in the same directory as the `catalog-info.yaml file`. If you are still unable to locate it, try scheduling an entity refresh by clicking the "Schedule Entity Refresh" button in the AboutCard of the entity.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createRouteRef, createApiRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, identityApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
|
|
3
|
+
const rootRouteRef = createRouteRef({
|
|
4
|
+
id: "readme"
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
const readmeApiRef = createApiRef({
|
|
8
|
+
id: "plugin.readme"
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
13
|
+
var __publicField = (obj, key, value) => {
|
|
14
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
15
|
+
return value;
|
|
16
|
+
};
|
|
17
|
+
class ReadmeClient {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
__publicField(this, "discoveryApi");
|
|
20
|
+
__publicField(this, "fetchApi");
|
|
21
|
+
__publicField(this, "identityApi");
|
|
22
|
+
this.discoveryApi = options.discoveryApi;
|
|
23
|
+
this.fetchApi = options.fetchApi;
|
|
24
|
+
this.identityApi = options.identityApi;
|
|
25
|
+
}
|
|
26
|
+
async getReadmeContent(entityRef) {
|
|
27
|
+
const { token } = await this.identityApi.getCredentials();
|
|
28
|
+
const baseUrl = await this.discoveryApi.getBaseUrl("readme");
|
|
29
|
+
const resp = await this.fetchApi.fetch(
|
|
30
|
+
`${baseUrl}/${encodeURIComponent(entityRef)}`,
|
|
31
|
+
{
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
if (resp.ok) {
|
|
37
|
+
return [
|
|
38
|
+
await resp.text(),
|
|
39
|
+
resp.headers.get("Content-Type") || "text/plain"
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
if (resp.status === 404) {
|
|
43
|
+
throw new Error("404");
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`${resp.status}: ${resp.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const readmePlugin = createPlugin({
|
|
50
|
+
id: "readme",
|
|
51
|
+
apis: [
|
|
52
|
+
createApiFactory({
|
|
53
|
+
api: readmeApiRef,
|
|
54
|
+
deps: {
|
|
55
|
+
discoveryApi: discoveryApiRef,
|
|
56
|
+
fetchApi: fetchApiRef,
|
|
57
|
+
identityApi: identityApiRef
|
|
58
|
+
},
|
|
59
|
+
factory: ({ discoveryApi, fetchApi, identityApi }) => new ReadmeClient({ discoveryApi, fetchApi, identityApi })
|
|
60
|
+
})
|
|
61
|
+
],
|
|
62
|
+
routes: {
|
|
63
|
+
root: rootRouteRef
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const ReadmeCard = readmePlugin.provide(
|
|
67
|
+
createRoutableExtension({
|
|
68
|
+
name: "ReadmeCard",
|
|
69
|
+
component: () => import('./index-4c75dc93.esm.js').then((m) => m.ReadmeCard),
|
|
70
|
+
mountPoint: rootRouteRef
|
|
71
|
+
})
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export { ReadmeCard as R, readmePlugin as a, readmeApiRef as r };
|
|
75
|
+
//# sourceMappingURL=index-0b47704b.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-0b47704b.esm.js","sources":["../../src/routes.ts","../../src/api/ReadmeApi.tsx","../../src/api/ReadmeClient.tsx","../../src/plugin.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'readme',\n});\n","import { createApiRef } from '@backstage/core-plugin-api';\n\nexport const readmeApiRef = createApiRef<ReadmeApi>({\n id: 'plugin.readme',\n});\n\nexport type ReadmeApi = {\n getReadmeContent(entityRef: string): Promise<[string, string]>;\n};\n","import {\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\nimport { ReadmeApi } from './ReadmeApi';\n\nexport class ReadmeClient implements ReadmeApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n private readonly identityApi: IdentityApi;\n\n constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n identityApi: IdentityApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n this.identityApi = options.identityApi;\n }\n\n async getReadmeContent(entityRef: string): Promise<[string, string]> {\n const { token } = await this.identityApi.getCredentials();\n const baseUrl = await this.discoveryApi.getBaseUrl('readme');\n\n const resp = await this.fetchApi.fetch(\n `${baseUrl}/${encodeURIComponent(entityRef)}`,\n {\n method: 'GET',\n headers: { Authorization: `Bearer ${token}` },\n },\n );\n if (resp.ok) {\n return [\n await resp.text(),\n resp.headers.get('Content-Type') || 'text/plain',\n ];\n }\n if (resp.status === 404) {\n throw new Error('404');\n }\n throw new Error(`${resp.status}: ${resp.statusText}`);\n }\n}\n","import {\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n fetchApiRef,\n identityApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { rootRouteRef } from './routes';\nimport { readmeApiRef } from './api/ReadmeApi';\nimport { ReadmeClient } from './api/ReadmeClient';\n\n/**\n * Plugin that provides the Readme api\n * @public */\nexport const readmePlugin = createPlugin({\n id: 'readme',\n apis: [\n createApiFactory({\n api: readmeApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n identityApi: identityApiRef,\n },\n factory: ({ discoveryApi, fetchApi, identityApi }) =>\n new ReadmeClient({ discoveryApi, fetchApi, identityApi }),\n }),\n ],\n routes: {\n root: rootRouteRef,\n },\n});\n\n/**\n * Readme card exported from the Readme plugin\n * @public */\nexport const ReadmeCard = readmePlugin.provide(\n createRoutableExtension({\n name: 'ReadmeCard',\n component: () => import('./components/ReadmeCard').then(m => m.ReadmeCard),\n mountPoint: rootRouteRef,\n }),\n);\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAe,CAAA;AAAA,EACzC,EAAI,EAAA,QAAA;AACN,CAAC,CAAA;;ACFM,MAAM,eAAe,YAAwB,CAAA;AAAA,EAClD,EAAI,EAAA,eAAA;AACN,CAAC;;;;;;;;ACGM,MAAM,YAAkC,CAAA;AAAA,EAK7C,YAAY,OAIT,EAAA;AARH,IAAiB,aAAA,CAAA,IAAA,EAAA,cAAA,CAAA,CAAA;AACjB,IAAiB,aAAA,CAAA,IAAA,EAAA,UAAA,CAAA,CAAA;AACjB,IAAiB,aAAA,CAAA,IAAA,EAAA,aAAA,CAAA,CAAA;AAOf,IAAA,IAAA,CAAK,eAAe,OAAQ,CAAA,YAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAQ,CAAA,QAAA,CAAA;AACxB,IAAA,IAAA,CAAK,cAAc,OAAQ,CAAA,WAAA,CAAA;AAAA,GAC7B;AAAA,EAEA,MAAM,iBAAiB,SAA8C,EAAA;AACnE,IAAA,MAAM,EAAE,KAAM,EAAA,GAAI,MAAM,IAAA,CAAK,YAAY,cAAe,EAAA,CAAA;AACxD,IAAA,MAAM,OAAU,GAAA,MAAM,IAAK,CAAA,YAAA,CAAa,WAAW,QAAQ,CAAA,CAAA;AAE3D,IAAM,MAAA,IAAA,GAAO,MAAM,IAAA,CAAK,QAAS,CAAA,KAAA;AAAA,MAC/B,CAAG,EAAA,OAAO,CAAI,CAAA,EAAA,kBAAA,CAAmB,SAAS,CAAC,CAAA,CAAA;AAAA,MAC3C;AAAA,QACE,MAAQ,EAAA,KAAA;AAAA,QACR,OAAS,EAAA,EAAE,aAAe,EAAA,CAAA,OAAA,EAAU,KAAK,CAAG,CAAA,EAAA;AAAA,OAC9C;AAAA,KACF,CAAA;AACA,IAAA,IAAI,KAAK,EAAI,EAAA;AACX,MAAO,OAAA;AAAA,QACL,MAAM,KAAK,IAAK,EAAA;AAAA,QAChB,IAAK,CAAA,OAAA,CAAQ,GAAI,CAAA,cAAc,CAAK,IAAA,YAAA;AAAA,OACtC,CAAA;AAAA,KACF;AACA,IAAI,IAAA,IAAA,CAAK,WAAW,GAAK,EAAA;AACvB,MAAM,MAAA,IAAI,MAAM,KAAK,CAAA,CAAA;AAAA,KACvB;AACA,IAAM,MAAA,IAAI,MAAM,CAAG,EAAA,IAAA,CAAK,MAAM,CAAK,EAAA,EAAA,IAAA,CAAK,UAAU,CAAE,CAAA,CAAA,CAAA;AAAA,GACtD;AACF;;AC5BO,MAAM,eAAe,YAAa,CAAA;AAAA,EACvC,EAAI,EAAA,QAAA;AAAA,EACJ,IAAM,EAAA;AAAA,IACJ,gBAAiB,CAAA;AAAA,MACf,GAAK,EAAA,YAAA;AAAA,MACL,IAAM,EAAA;AAAA,QACJ,YAAc,EAAA,eAAA;AAAA,QACd,QAAU,EAAA,WAAA;AAAA,QACV,WAAa,EAAA,cAAA;AAAA,OACf;AAAA,MACA,OAAS,EAAA,CAAC,EAAE,YAAA,EAAc,QAAU,EAAA,WAAA,EAClC,KAAA,IAAI,YAAa,CAAA,EAAE,YAAc,EAAA,QAAA,EAAU,aAAa,CAAA;AAAA,KAC3D,CAAA;AAAA,GACH;AAAA,EACA,MAAQ,EAAA;AAAA,IACN,IAAM,EAAA,YAAA;AAAA,GACR;AACF,CAAC,EAAA;AAKM,MAAM,aAAa,YAAa,CAAA,OAAA;AAAA,EACrC,uBAAwB,CAAA;AAAA,IACtB,IAAM,EAAA,YAAA;AAAA,IACN,SAAA,EAAW,MAAM,OAAO,yBAAyB,EAAE,IAAK,CAAA,CAAA,CAAA,KAAK,EAAE,UAAU,CAAA;AAAA,IACzE,UAAY,EAAA,YAAA;AAAA,GACb,CAAA;AACH;;;;"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Box, Typography, Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton } from '@material-ui/core';
|
|
3
|
+
import { Progress, Link, ErrorPanel, MarkdownContent, CodeSnippet, InfoCard } from '@backstage/core-components';
|
|
4
|
+
import { useEntity } from '@backstage/plugin-catalog-react';
|
|
5
|
+
import { useApi } from '@backstage/core-plugin-api';
|
|
6
|
+
import useAsync from 'react-use/lib/useAsync';
|
|
7
|
+
import { r as readmeApiRef } from './index-0b47704b.esm.js';
|
|
8
|
+
import { getEntitySourceLocation, stringifyEntityRef } from '@backstage/catalog-model';
|
|
9
|
+
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
|
10
|
+
|
|
11
|
+
const FetchComponent = () => {
|
|
12
|
+
var _a;
|
|
13
|
+
const { entity } = useEntity();
|
|
14
|
+
const readmeApi = useApi(readmeApiRef);
|
|
15
|
+
let location;
|
|
16
|
+
try {
|
|
17
|
+
const { target } = getEntitySourceLocation(entity);
|
|
18
|
+
location = target != null ? target : target;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
location = ": Unknown";
|
|
21
|
+
}
|
|
22
|
+
const {
|
|
23
|
+
value: content,
|
|
24
|
+
loading,
|
|
25
|
+
error
|
|
26
|
+
} = useAsync(
|
|
27
|
+
async () => await readmeApi.getReadmeContent(stringifyEntityRef(entity)),
|
|
28
|
+
[entity]
|
|
29
|
+
);
|
|
30
|
+
if (loading) {
|
|
31
|
+
return /* @__PURE__ */ React.createElement(Progress, null);
|
|
32
|
+
}
|
|
33
|
+
if ((error == null ? void 0 : error.message) === "404") {
|
|
34
|
+
return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "body2", style: { paddingBottom: 15 } }, "No README.md file found at source location:", " ", location && /* @__PURE__ */ React.createElement("strong", null, location)), /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, "Need help? Go to our", " ", /* @__PURE__ */ React.createElement(Link, { to: "https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md" }, "documentation")));
|
|
35
|
+
}
|
|
36
|
+
if (error) {
|
|
37
|
+
return /* @__PURE__ */ React.createElement(ErrorPanel, { error });
|
|
38
|
+
}
|
|
39
|
+
if (!content) {
|
|
40
|
+
return /* @__PURE__ */ React.createElement(ErrorPanel, { error: Error("Unknown error") });
|
|
41
|
+
}
|
|
42
|
+
if (!content[1] || content[1] === "error") {
|
|
43
|
+
return /* @__PURE__ */ React.createElement(ErrorPanel, { error: Error((_a = content[1]) != null ? _a : "Unknown error") });
|
|
44
|
+
} else if (content[1].startsWith("text/markdown")) {
|
|
45
|
+
return /* @__PURE__ */ React.createElement(MarkdownContent, { content: content[0] });
|
|
46
|
+
}
|
|
47
|
+
return /* @__PURE__ */ React.createElement("div", { "data-testid": "readme-content" }, /* @__PURE__ */ React.createElement(CodeSnippet, { text: content[0], language: "plaintext" }));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const ReadmeDialog = ({ open, onClose }) => {
|
|
51
|
+
return /* @__PURE__ */ React.createElement(
|
|
52
|
+
Dialog,
|
|
53
|
+
{
|
|
54
|
+
maxWidth: "md",
|
|
55
|
+
open,
|
|
56
|
+
onClose,
|
|
57
|
+
"data-testid": "content-dialog"
|
|
58
|
+
},
|
|
59
|
+
/* @__PURE__ */ React.createElement(DialogTitle, null, "README"),
|
|
60
|
+
/* @__PURE__ */ React.createElement(DialogContent, { dividers: true }, /* @__PURE__ */ React.createElement(FetchComponent, null)),
|
|
61
|
+
/* @__PURE__ */ React.createElement(DialogActions, null, /* @__PURE__ */ React.createElement(
|
|
62
|
+
Button,
|
|
63
|
+
{
|
|
64
|
+
variant: "contained",
|
|
65
|
+
color: "primary",
|
|
66
|
+
"aria-label": "close",
|
|
67
|
+
onClick: onClose
|
|
68
|
+
},
|
|
69
|
+
"Close"
|
|
70
|
+
))
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const ReadmeCard = () => {
|
|
75
|
+
const [displayDialog, setDisplayDialog] = useState(false);
|
|
76
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
77
|
+
InfoCard,
|
|
78
|
+
{
|
|
79
|
+
title: "README",
|
|
80
|
+
variant: "flex",
|
|
81
|
+
action: /* @__PURE__ */ React.createElement(
|
|
82
|
+
IconButton,
|
|
83
|
+
{
|
|
84
|
+
onClick: () => setDisplayDialog(true),
|
|
85
|
+
"aria-label": "open dialog",
|
|
86
|
+
role: "button",
|
|
87
|
+
title: "Open in dialog"
|
|
88
|
+
},
|
|
89
|
+
/* @__PURE__ */ React.createElement(OpenInNewIcon, null)
|
|
90
|
+
)
|
|
91
|
+
},
|
|
92
|
+
/* @__PURE__ */ React.createElement("div", { style: { overflow: "auto" } }, /* @__PURE__ */ React.createElement(Box, { sx: { maxHeight: 235 } }, /* @__PURE__ */ React.createElement(FetchComponent, null)))
|
|
93
|
+
), /* @__PURE__ */ React.createElement(
|
|
94
|
+
ReadmeDialog,
|
|
95
|
+
{
|
|
96
|
+
open: displayDialog,
|
|
97
|
+
onClose: () => setDisplayDialog(false)
|
|
98
|
+
}
|
|
99
|
+
));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export { ReadmeCard };
|
|
103
|
+
//# sourceMappingURL=index-4c75dc93.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-4c75dc93.esm.js","sources":["../../src/components/FetchComponent/FetchComponent.tsx","../../src/components/ReadmeDialog/ReadmeDialog.tsx","../../src/components/ReadmeCard/ReadmeCard.tsx"],"sourcesContent":["import React from 'react';\nimport { useEntity } from '@backstage/plugin-catalog-react';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n CodeSnippet,\n ErrorPanel,\n Link,\n MarkdownContent,\n Progress,\n} from '@backstage/core-components';\nimport useAsync from 'react-use/lib/useAsync';\nimport { readmeApiRef } from '../../api/ReadmeApi';\nimport { stringifyEntityRef } from '@backstage/catalog-model';\nimport { getEntitySourceLocation } from '@backstage/catalog-model';\nimport { Box, Typography } from '@material-ui/core';\n\nexport const FetchComponent = () => {\n const { entity } = useEntity();\n const readmeApi = useApi(readmeApiRef);\n let location;\n\n try {\n const { target } = getEntitySourceLocation(entity);\n location = target ?? target;\n } catch (err) {\n location = ': Unknown';\n }\n\n const {\n value: content,\n loading,\n error,\n } = useAsync(\n async (): Promise<string[]> =>\n await readmeApi.getReadmeContent(stringifyEntityRef(entity)),\n [entity],\n );\n\n if (loading) {\n return <Progress />;\n }\n if (error?.message === '404') {\n return (\n <Box>\n <Typography variant=\"body2\" style={{ paddingBottom: 15 }}>\n No README.md file found at source location:{' '}\n {location && <strong>{location}</strong>}\n </Typography>\n <Typography variant=\"body2\">\n Need help? Go to our{' '}\n <Link to=\"https://github.com/AxisCommunications/backstage-plugins/blob/main/plugins/readme/README.md\">\n documentation\n </Link>\n </Typography>\n </Box>\n );\n }\n\n if (error) {\n return <ErrorPanel error={error} />;\n }\n if (!content) {\n return <ErrorPanel error={Error('Unknown error')} />;\n }\n if (!content[1] || content[1] === 'error') {\n return <ErrorPanel error={Error(content[1] ?? 'Unknown error')} />;\n } else if (content[1].startsWith('text/markdown')) {\n return <MarkdownContent content={content[0]} />;\n }\n // probably text/plain\n return (\n <div data-testid=\"readme-content\">\n <CodeSnippet text={content[0]} language=\"plaintext\" />\n </div>\n );\n};\n","import React from 'react';\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n} from '@material-ui/core';\nimport { FetchComponent } from '../FetchComponent';\n\ntype Props = {\n open: boolean;\n onClose: () => void;\n};\n\nexport const ReadmeDialog = ({ open, onClose }: Props) => {\n return (\n <Dialog\n maxWidth=\"md\"\n open={open}\n onClose={onClose}\n data-testid=\"content-dialog\"\n >\n <DialogTitle>README</DialogTitle>\n <DialogContent dividers>\n <FetchComponent />\n </DialogContent>\n <DialogActions>\n <Button\n variant=\"contained\"\n color=\"primary\"\n aria-label=\"close\"\n onClick={onClose}\n >\n Close\n </Button>\n </DialogActions>\n </Dialog>\n );\n};\n","import React, { useState } from 'react';\nimport { Box, IconButton } from '@material-ui/core';\nimport { InfoCard } from '@backstage/core-components';\nimport { FetchComponent } from '../FetchComponent';\nimport OpenInNewIcon from '@material-ui/icons/OpenInNew';\nimport { ReadmeDialog } from '../ReadmeDialog/ReadmeDialog';\n\nexport const ReadmeCard = () => {\n const [displayDialog, setDisplayDialog] = useState(false);\n\n return (\n <>\n <InfoCard\n title=\"README\"\n variant=\"flex\"\n action={\n <IconButton\n onClick={() => setDisplayDialog(true)}\n aria-label=\"open dialog\"\n role=\"button\"\n title=\"Open in dialog\"\n >\n <OpenInNewIcon />\n </IconButton>\n }\n >\n <div style={{ overflow: 'auto' }}>\n <Box sx={{ maxHeight: 235 }}>\n <FetchComponent />\n </Box>\n </div>\n </InfoCard>\n\n <ReadmeDialog\n open={displayDialog}\n onClose={() => setDisplayDialog(false)}\n />\n </>\n );\n};\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin that provides the Readme api
|
|
7
|
+
* @public */
|
|
8
|
+
declare const readmePlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
9
|
+
root: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
10
|
+
}, {}, {}>;
|
|
11
|
+
/**
|
|
12
|
+
* Readme card exported from the Readme plugin
|
|
13
|
+
* @public */
|
|
14
|
+
declare const ReadmeCard: () => react.JSX.Element;
|
|
15
|
+
|
|
16
|
+
export { ReadmeCard, readmePlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@axis-backstage/plugin-readme",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "dist/index.esm.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"main": "dist/index.esm.js",
|
|
10
|
+
"types": "dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"backstage": {
|
|
13
|
+
"role": "frontend-plugin"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "backstage-cli package start",
|
|
18
|
+
"build": "backstage-cli package build",
|
|
19
|
+
"lint": "backstage-cli package lint",
|
|
20
|
+
"test": "backstage-cli package test",
|
|
21
|
+
"clean": "backstage-cli package clean",
|
|
22
|
+
"prepack": "backstage-cli package prepack",
|
|
23
|
+
"postpack": "backstage-cli package postpack"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@backstage/catalog-model": "^1.4.3",
|
|
27
|
+
"@backstage/core-components": "^0.13.6",
|
|
28
|
+
"@backstage/core-plugin-api": "^1.7.0",
|
|
29
|
+
"@backstage/plugin-catalog-react": "^1.9.0",
|
|
30
|
+
"@backstage/theme": "^0.4.3",
|
|
31
|
+
"@material-ui/core": "^4.12.2",
|
|
32
|
+
"@material-ui/icons": "^4.9.1",
|
|
33
|
+
"@material-ui/lab": "4.0.0-alpha.61",
|
|
34
|
+
"react-use": "^17.2.4"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": "^16.13.1 || ^17.0.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@backstage/cli": "^0.23.0",
|
|
41
|
+
"@backstage/core-app-api": "^1.11.0",
|
|
42
|
+
"@backstage/dev-utils": "^1.0.22",
|
|
43
|
+
"@backstage/test-utils": "^1.4.4",
|
|
44
|
+
"@testing-library/jest-dom": "^5.10.1",
|
|
45
|
+
"@testing-library/react": "^12.1.3",
|
|
46
|
+
"@testing-library/user-event": "^14.0.0",
|
|
47
|
+
"msw": "^1.0.0"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist"
|
|
51
|
+
],
|
|
52
|
+
"module": "./dist/index.esm.js"
|
|
53
|
+
}
|