badges: use new error api. revert css layout tweak for CodeSnippet. add basic frontend test. improve doc.

Signed-off-by: Andreas Stenius <andreas.stenius@svenskaspel.se>
This commit is contained in:
Andreas Stenius
2021-03-12 09:00:03 +01:00
committed by Fredrik Adelöw
parent 022610b7b7
commit 97836e51f1
6 changed files with 130 additions and 18 deletions
+54 -4
View File
@@ -1,6 +1,7 @@
# Badges Backend
Backend plugin for serving badges. Default implementation uses
Backend plugin for serving badges to the `@backstage/plugin-badges` plugin.
Default implementation uses
[badge-maker](https://www.npmjs.com/package/badge-maker) for creating the
badges, in SVG.
@@ -8,10 +9,35 @@ Currently, only entity badges are implemented. i.e. badges that may have entity
specific information in them, and as such, are served from a entity specific
endpoint.
## Setup
## Installation
The list of all badges to offer are passed as an object with badge factories to
the badges-backend `createRouter()` during plugin registration.
Install the `@backstage/plugin-badges-backend` package in your backend packages,
and then integrate the plugin using the following default setup for
`src/plugins/badges.ts`:
```ts
import {
createRouter,
createDefaultBadgeFactories,
} from '@backstage/plugin-badges-backend';
import { PluginEnvironment } from '../types';
export default async function createPlugin({
config,
discovery,
}: PluginEnvironment) {
return await createRouter({
config,
discovery,
badgeFactories: createDefaultBadgeFactories(),
});
}
```
The `createDefaultBadgeFactories()` returns an object with badge factories to
the badges-backend `createRouter()` to forward to the default badge builder. To
customize the available badges, provide a custom set of badge factories. See
further down for an example of a custom badge factories function.
## Badge builder
@@ -28,6 +54,30 @@ as examples.
Additional badges may be provided in your application by defining custom badge
factories, and provide them to the badge builder.
### Custom badges
To provide custom badges, create a badges factories function, and use that when
creating the badges backend router.
```ts
import type { Badge, BadgeContext, BadgeFactories } from '@backstage/plugin-badges-backend';
export const createMyCustomBadgeFactories = (): BadgeFactories => ({
<custom-badge-id>: {
createBadge: (context: BadgeContext): Badge | null => {
// ...
return {
label: 'my-badge',
message: 'custom stuff',
// ...
};
},
},
// optional: include the default badges
// ...createDefaultBadgeFactories(),
});
```
## API
The badges backend api exposes two main endpoints for entity badges. (the
+1
View File
@@ -34,6 +34,7 @@
"@backstage/catalog-client": "^0.3.6",
"@backstage/catalog-model": "^0.7.3",
"@backstage/config": "^0.1.3",
"@backstage/errors": "^0.1.1",
"@types/express": "^4.17.6",
"badge-maker": "^3.3.0",
"cors": "^2.8.5",
+11 -8
View File
@@ -22,6 +22,7 @@ import {
} from '@backstage/backend-common';
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { Config } from '@backstage/config';
import { NotFoundError } from '@backstage/errors';
import { BadgeBuilder, DefaultBadgeBuilder } from '../lib/BadgeBuilder';
import { BadgeContext, BadgeFactories } from '../types';
@@ -50,8 +51,9 @@ export async function createRouter(
const { namespace, kind, name } = req.params;
const entity = await catalog.getEntityByName({ namespace, kind, name });
if (!entity) {
res.status(404).send(`Unknown entity`);
return;
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
const context: BadgeContext = {
@@ -86,8 +88,9 @@ export async function createRouter(
const { namespace, kind, name, badgeId } = req.params;
const entity = await catalog.getEntityByName({ namespace, kind, name });
if (!entity) {
res.status(404).send(`Unknown entity`);
return;
throw new NotFoundError(
`No ${kind} entity in ${namespace} named "${name}"`,
);
}
let format =
@@ -109,11 +112,11 @@ export async function createRouter(
});
if (!data) {
res.status(404).send(`Unknown entity badge "${badgeId}"`);
} else {
res.setHeader('Content-Type', format);
res.status(200).send(data);
throw new NotFoundError(`Unknown badge "${badgeId}" for ${kind} entity.`);
}
res.setHeader('Content-Type', format);
res.status(200).send(data);
});
router.use(errorHandler());
+1 -1
View File
@@ -1,4 +1,4 @@
# Badges
# @backstage/plugin-badges
The badges plugin offers a set of badges that can be used outside of
your backstage deployment, showing information related to data from
@@ -0,0 +1,61 @@
/*
* Copyright 2021 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import {
ApiProvider,
ApiRegistry,
ErrorApi,
errorApiRef,
} from '@backstage/core';
import { renderWithEffects } from '@backstage/test-utils';
import { BadgesApi, badgesApiRef } from '../api';
import { EntityBadgesDialog } from './EntityBadgesDialog';
describe('EntityBadgesDialog', () => {
it('should render', async () => {
const mockApi: jest.Mocked<BadgesApi> = {
getEntityBadgeSpecs: jest.fn().mockResolvedValue([
{
id: 'testbadge',
badge: {
label: 'test',
message: 'badge',
},
url: 'http://127.0.0.1/badges/entity/.../testbadge',
markdown: '![test: badge](http://127.0.0.1/catalog/...)',
},
]),
};
const mockEntity = { metadata: { name: 'mock' } } as Entity;
const rendered = await renderWithEffects(
<ApiProvider
apis={ApiRegistry.with(badgesApiRef, mockApi).with(
errorApiRef,
{} as ErrorApi,
)}
>
<EntityBadgesDialog open entity={mockEntity} />
</ApiProvider>,
);
await expect(
rendered.findByText('testbadge badge'),
).resolves.toBeInTheDocument();
});
});
@@ -35,14 +35,11 @@ import { badgesApiRef } from '../api';
type Props = {
open: boolean;
onClose: () => any;
onClose?: () => any;
entity: Entity;
};
const useStyles = makeStyles({
content: {
overflowX: 'hidden',
},
codeBlock: {
'& code': {
whiteSpace: 'pre-wrap',
@@ -85,7 +82,7 @@ export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
<Dialog fullScreen={fullScreen} open={open} onClose={onClose}>
<DialogTitle id="badges-dialog-title">Entity Badges</DialogTitle>
<DialogContent className={classes.content}>
<DialogContent>
{loading ? <Progress /> : null}
{error ? (