Skip to content

Commit d29962b

Browse files
anickeNiklas Aronsson
andauthored
Statuspage updates (#367)
* feat(statuspage): add NFS support To configure what is show on the "/statuspage" use the config. app: extensions: - page:statuspage: config: name: mystatuspageinstance * chore(statuspage-backend): remove the usage of cross-fetch Let's use the native fetch according the ADR14. * feat(statuspage): use EntityInfoCard for the entity card. The entity card is now using EntityInfoCard as the root component. --------- Co-authored-by: Niklas Aronsson <niklasar@axis.com>
1 parent bb83b10 commit d29962b

12 files changed

Lines changed: 339 additions & 13 deletions

File tree

.changeset/stupid-tips-follow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@axis-backstage/plugin-statuspage': patch
3+
---
4+
5+
Use EntityInfoCard as the entity card wrapper

.changeset/violet-taxes-matter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@axis-backstage/plugin-statuspage-backend': patch
3+
---
4+
5+
Use native fetch instead of cross-fetch according to ADR14

.changeset/warm-spiders-vanish.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@axis-backstage/plugin-statuspage': minor
3+
---
4+
5+
Add support for the new frontend system (NFS). The plugin can now be imported from the `./alpha` entry point and registered as a feature in a new-system app.
6+
7+
The plugin provides three extensions out of the box:
8+
9+
- `api:statuspage` — registers the `StatuspageClient`
10+
- `page:statuspage` — mounts the full statuspage at `/statuspage`
11+
- `entity-card:statuspage` — renders the `StatuspageEntityCard` on entity pages
12+
13+
Configure the instance name for the page extension in `app-config.yaml`:
14+
15+
```yaml
16+
app:
17+
extensions:
18+
- page:statuspage:
19+
config:
20+
name: mystatuspageinstance
21+
```

packages/app-next/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@axis-backstage/plugin-jira-dashboard": "workspace:^",
1818
"@axis-backstage/plugin-jira-dashboard-common": "workspace:^",
1919
"@axis-backstage/plugin-readme": "workspace:^",
20+
"@axis-backstage/plugin-statuspage": "workspace:^",
2021
"@backstage/app-defaults": "^1.7.7",
2122
"@backstage/catalog-model": "^1.8.0",
2223
"@backstage/cli": "^0.36.1",

plugins/statuspage-backend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"@backstage/backend-plugin-api": "^1.9.0",
3434
"@backstage/config": "^1.3.7",
3535
"@types/express": "*",
36-
"cross-fetch": "^4.0.0",
3736
"express": "^4.17.1",
3837
"express-promise-router": "^4.1.0"
3938
},

plugins/statuspage-backend/src/service/api.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import fetch from 'cross-fetch';
21
import type {
32
Component,
43
ComponentGroup,

plugins/statuspage/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,39 @@ The `component_id` could be the id of either a component or a component group. T
101101
102102
<Route path="/statuspage" element={<StatuspagePage name="myid" />} />
103103
```
104+
105+
## New Frontend System
106+
107+
1. First, install the plugin into your app:
108+
109+
```bash
110+
# From your Backstage root directory
111+
yarn --cwd packages/app add @axis-backstage/plugin-statuspage
112+
```
113+
114+
2. If [feature discovery](https://backstage.io/docs/frontend-system/architecture/app/#feature-discovery) is enabled in your app, the plugin will be automatically discovered. If not, add it manually:
115+
116+
```ts
117+
// packages/app/src/App.tsx
118+
import statuspagePlugin from '@axis-backstage/plugin-statuspage/alpha';
119+
120+
const app = createApp({
121+
features: [
122+
// ...
123+
statuspagePlugin,
124+
],
125+
});
126+
```
127+
128+
3. Configure the statuspage instance name for the page extension in `app-config.yaml`:
129+
130+
```yaml
131+
# app-config.yaml
132+
app:
133+
extensions:
134+
- page:statuspage:
135+
config:
136+
name: mystatuspageinstance
137+
```
138+
139+
The `StatuspageEntityCard` is automatically added to entity pages via the `entity-card:statuspage` extension. No additional wiring in `EntityPage.tsx` is needed. The entity annotation setup remains the same — see [Integration with the Catalog](#integration-with-the-catalog) above.

plugins/statuspage/package.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
{
22
"name": "@axis-backstage/plugin-statuspage",
33
"version": "0.10.2",
4+
"exports": {
5+
".": "./src/index.ts",
6+
"./alpha": "./src/alpha.tsx",
7+
"./package.json": "./package.json"
8+
},
49
"main": "src/index.ts",
510
"types": "src/index.ts",
11+
"typesVersions": {
12+
"*": {
13+
"alpha": [
14+
"src/alpha.tsx"
15+
],
16+
"package.json": [
17+
"package.json"
18+
]
19+
}
20+
},
621
"license": "Apache-2.0",
722
"publishConfig": {
823
"access": "public",
@@ -34,10 +49,12 @@
3449
"@backstage/catalog-model": "^1.8.0",
3550
"@backstage/core-components": "^0.18.9",
3651
"@backstage/core-plugin-api": "^1.12.5",
52+
"@backstage/frontend-plugin-api": "^0.16.2",
3753
"@backstage/plugin-catalog-react": "^2.1.4",
3854
"@mui/icons-material": "^5.15.7",
3955
"@mui/material": "^5.15.7",
40-
"react-use": "^17.2.4"
56+
"react-use": "^17.2.4",
57+
"zod": "^4.3.6"
4158
},
4259
"peerDependencies": {
4360
"@types/react": "^17.0.0 || ^18.0.0",
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
## API Report File for "@axis-backstage/plugin-statuspage"
2+
3+
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
4+
5+
```ts
6+
import { AnyApiFactory } from '@backstage/frontend-plugin-api';
7+
import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
8+
import { ApiFactory } from '@backstage/frontend-plugin-api';
9+
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
10+
import { Entity } from '@backstage/catalog-model';
11+
import { EntityCardType } from '@backstage/plugin-catalog-react/alpha';
12+
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
13+
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
14+
import { ExtensionInput } from '@backstage/frontend-plugin-api';
15+
import { FilterPredicate } from '@backstage/filter-predicates';
16+
import { IconElement } from '@backstage/frontend-plugin-api';
17+
import { JSX as JSX_2 } from 'react';
18+
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
19+
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
20+
import { RouteRef } from '@backstage/core-plugin-api';
21+
import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
22+
23+
// @public
24+
const _default: OverridableFrontendPlugin<
25+
{
26+
root: RouteRef<undefined>;
27+
},
28+
{},
29+
{
30+
'api:statuspage': OverridableExtensionDefinition<{
31+
kind: 'api';
32+
name: undefined;
33+
config: {};
34+
configInput: {};
35+
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
36+
inputs: {};
37+
params: <
38+
TApi,
39+
TImpl extends TApi,
40+
TDeps extends { [name in string]: unknown },
41+
>(
42+
params: ApiFactory<TApi, TImpl, TDeps>,
43+
) => ExtensionBlueprintParams<AnyApiFactory>;
44+
}>;
45+
'entity-card:statuspage': OverridableExtensionDefinition<{
46+
kind: 'entity-card';
47+
name: undefined;
48+
config: {
49+
filter: FilterPredicate | undefined;
50+
type: 'content' | 'info' | undefined;
51+
};
52+
configInput: {
53+
filter?: FilterPredicate | undefined;
54+
type?: 'content' | 'info' | undefined;
55+
};
56+
output:
57+
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
58+
| ExtensionDataRef<
59+
(entity: Entity) => boolean,
60+
'catalog.entity-filter-function',
61+
{
62+
optional: true;
63+
}
64+
>
65+
| ExtensionDataRef<
66+
string,
67+
'catalog.entity-filter-expression',
68+
{
69+
optional: true;
70+
}
71+
>
72+
| ExtensionDataRef<
73+
EntityCardType,
74+
'catalog.entity-card-type',
75+
{
76+
optional: true;
77+
}
78+
>;
79+
inputs: {};
80+
params: {
81+
loader: () => Promise<JSX.Element>;
82+
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
83+
type?: EntityCardType;
84+
};
85+
}>;
86+
'page:statuspage': OverridableExtensionDefinition<{
87+
config: {
88+
name: string;
89+
path: string | undefined;
90+
title: string | undefined;
91+
};
92+
configInput: {
93+
name?: string | undefined;
94+
path?: string | undefined | undefined;
95+
title?: string | undefined | undefined;
96+
};
97+
output:
98+
| ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
99+
| ExtensionDataRef<string, 'core.routing.path', {}>
100+
| ExtensionDataRef<
101+
RouteRef_2<AnyRouteRefParams>,
102+
'core.routing.ref',
103+
{
104+
optional: true;
105+
}
106+
>
107+
| ExtensionDataRef<
108+
string,
109+
'core.title',
110+
{
111+
optional: true;
112+
}
113+
>
114+
| ExtensionDataRef<
115+
IconElement,
116+
'core.icon',
117+
{
118+
optional: true;
119+
}
120+
>;
121+
inputs: {
122+
pages: ExtensionInput<
123+
| ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
124+
| ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
125+
| ConfigurableExtensionDataRef<
126+
RouteRef_2<AnyRouteRefParams>,
127+
'core.routing.ref',
128+
{
129+
optional: true;
130+
}
131+
>
132+
| ConfigurableExtensionDataRef<
133+
string,
134+
'core.title',
135+
{
136+
optional: true;
137+
}
138+
>
139+
| ConfigurableExtensionDataRef<
140+
IconElement,
141+
'core.icon',
142+
{
143+
optional: true;
144+
}
145+
>,
146+
{
147+
singleton: false;
148+
optional: false;
149+
internal: false;
150+
}
151+
>;
152+
};
153+
kind: 'page';
154+
name: undefined;
155+
params: {
156+
path: string;
157+
title?: string;
158+
icon?: IconElement;
159+
loader?: () => Promise<JSX_2.Element>;
160+
routeRef?: RouteRef_2;
161+
noHeader?: boolean;
162+
};
163+
}>;
164+
}
165+
>;
166+
export default _default;
167+
```

plugins/statuspage/src/alpha.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Frontend plugin that allows visualization of component statuses from statuspage.io.
3+
*
4+
* @packageDocumentation
5+
*/
6+
7+
import {
8+
ApiBlueprint,
9+
createFrontendPlugin,
10+
discoveryApiRef,
11+
fetchApiRef,
12+
PageBlueprint,
13+
} from '@backstage/frontend-plugin-api';
14+
import { statuspageApiRef } from './api/StatuspageApi';
15+
import { StatuspageClient } from './api/StatuspageClient';
16+
import { EntityCardBlueprint } from '@backstage/plugin-catalog-react/alpha';
17+
import { rootRouteRef } from './routes';
18+
import { z } from 'zod';
19+
20+
const statuspageApi = ApiBlueprint.make({
21+
params: defineParams =>
22+
defineParams({
23+
api: statuspageApiRef,
24+
deps: {
25+
discoveryApi: discoveryApiRef,
26+
fetchApi: fetchApiRef,
27+
},
28+
factory: ({ discoveryApi, fetchApi }) =>
29+
new StatuspageClient({ discoveryApi, fetchApi }),
30+
}),
31+
});
32+
33+
const statuspagePage = PageBlueprint.makeWithOverrides({
34+
configSchema: {
35+
/**
36+
* The name of the statuspage instance as configured in `app-config.yaml`.
37+
*
38+
* @example
39+
* ```yaml
40+
* # app-config.yaml
41+
* app:
42+
* extensions:
43+
* - page:statuspage:
44+
* config:
45+
* name: mystatuspageinstance
46+
* ```
47+
*/
48+
name: z.string().default(''),
49+
},
50+
factory(originalFactory, { config }) {
51+
return originalFactory({
52+
path: '/statuspage',
53+
routeRef: rootRouteRef,
54+
loader: async () =>
55+
import('./components/StatuspageComponent').then(m => (
56+
<m.StatuspageComponent name={config.name} />
57+
)),
58+
});
59+
},
60+
});
61+
62+
const statuspageEntityCard = EntityCardBlueprint.make({
63+
params: {
64+
loader: async () =>
65+
import('./components/StatuspageEntityCard').then(m => (
66+
<m.StatuspageEntityCard />
67+
)),
68+
},
69+
});
70+
71+
export default createFrontendPlugin({
72+
pluginId: 'statuspage',
73+
extensions: [statuspageApi, statuspagePage, statuspageEntityCard],
74+
routes: {
75+
root: rootRouteRef,
76+
},
77+
});

0 commit comments

Comments
 (0)