From 97836e51f189434a18b366cfe7dcd8932ee48a75 Mon Sep 17 00:00:00 2001 From: Andreas Stenius Date: Fri, 12 Mar 2021 09:00:03 +0100 Subject: [PATCH] badges: use new error api. revert css layout tweak for CodeSnippet. add basic frontend test. improve doc. Signed-off-by: Andreas Stenius --- plugins/badges-backend/README.md | 58 ++++++++++++++++-- plugins/badges-backend/package.json | 1 + plugins/badges-backend/src/service/router.ts | 19 +++--- plugins/badges/README.md | 2 +- .../components/EntityBadgesDialog.test.tsx | 61 +++++++++++++++++++ .../src/components/EntityBadgesDialog.tsx | 7 +-- 6 files changed, 130 insertions(+), 18 deletions(-) create mode 100644 plugins/badges/src/components/EntityBadgesDialog.test.tsx diff --git a/plugins/badges-backend/README.md b/plugins/badges-backend/README.md index 22354cb444..59f5fc0445 100644 --- a/plugins/badges-backend/README.md +++ b/plugins/badges-backend/README.md @@ -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 => ({ + : { + 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 diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 5c395bf3ea..fa6f6a4557 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -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", diff --git a/plugins/badges-backend/src/service/router.ts b/plugins/badges-backend/src/service/router.ts index 86caf793d5..797d23f432 100644 --- a/plugins/badges-backend/src/service/router.ts +++ b/plugins/badges-backend/src/service/router.ts @@ -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()); diff --git a/plugins/badges/README.md b/plugins/badges/README.md index 09e00c79fe..bfc01ec63c 100644 --- a/plugins/badges/README.md +++ b/plugins/badges/README.md @@ -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 diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx new file mode 100644 index 0000000000..2491a3462c --- /dev/null +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -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 = { + 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( + + + , + ); + + await expect( + rendered.findByText('testbadge badge'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/plugins/badges/src/components/EntityBadgesDialog.tsx b/plugins/badges/src/components/EntityBadgesDialog.tsx index 9d4661f520..ac0d46f114 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.tsx @@ -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) => { Entity Badges - + {loading ? : null} {error ? (