badges: fix type issues.

Signed-off-by: Andreas Stenius <git@astekk.se>
This commit is contained in:
Andreas Stenius
2021-02-22 19:02:11 +01:00
committed by Fredrik Adelöw
parent 45e26d7956
commit ce4897ff37
11 changed files with 96 additions and 52 deletions
+1
View File
@@ -31,6 +31,7 @@
},
"dependencies": {
"@backstage/backend-common": "^0.5.2",
"@backstage/catalog-model": "^0.7.1",
"@backstage/config": "^0.1.2",
"@types/express": "^4.17.6",
"badge-maker": "^3.3.0",
@@ -15,41 +15,46 @@
*/
import { Logger } from 'winston';
import { makeBadge, ValidationError } from 'badge-maker';
import { BadgeBuilder, BadgeConfig, BadgeOptions } from './types';
import { makeBadge } from 'badge-maker';
import { JsonObject } from '@backstage/config';
import {
BadgeBuilder,
BadgeConfig,
BadgeOptions,
BadgeStyle,
BadgeStyles,
} from './types';
import { interpolate } from '../../utils';
export class DefaultBadgeBuilder implements BadgeBuilder {
constructor(
private readonly logger: Logger,
private readonly config: { [id: string]: BadgeConfig },
private readonly config: JsonObject,
) {}
public async getBadgeConfig(badgeId: string): BadgeConfig {
return (
this.config[badgeId] ||
this.config.default || {
public async getBadgeConfig(badgeId: string): Promise<BadgeConfig> {
return ((this.config[badgeId] as unknown) ||
(this.config.default as unknown) || {
label: 'Unknown badge ID',
message: badgeId,
color: 'red',
}
);
}) as BadgeConfig;
}
public async createBadge(options: BadgeOptions): string {
public async createBadge(options: BadgeOptions): Promise<string> {
const { context, config: badge } = options;
const params = {
label: this.render(badge.label, context),
message: this.render(badge.message, context),
color: badge.color || '#36BAA2',
};
} as BadgeConfig;
if (badge.labelColor) {
params.labelColor = badge.labelColor;
}
if (badge.style) {
params.style = badge.style;
if (BadgeStyles.includes(badge.style as BadgeStyle)) {
params.style = badge.style as BadgeStyle;
}
switch (options.format) {
@@ -90,6 +95,9 @@ export class DefaultBadgeBuilder implements BadgeBuilder {
try {
return interpolate(template.replace(/_{/g, '${'), context);
} catch (err) {
this.logger.info(
`badge template error: ${err}. In template: "${template}"`,
);
return `${err} [${template}]`;
}
}
@@ -14,13 +14,22 @@
* limitations under the License.
*/
export const BadgeStyles = [
'plastic',
'flat',
'flat-square',
'for-the-badge',
'social',
] as const;
export type BadgeStyle = typeof BadgeStyles[number];
export interface BadgeConfig {
kind?: 'entity';
label: string;
message: string;
color?: string;
labelColor?: string;
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social';
style?: BadgeStyle;
title?: string;
description?: string;
link?: string;
@@ -14,12 +14,23 @@
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import express from 'express';
import request from 'supertest';
import { BadgesApi } from '../api';
import * as winston from 'winston';
import {
loadBackendConfig,
SingleHostDiscovery,
} from '@backstage/backend-common';
import { createRouter } from './router';
import {} from '../api/types';
describe('createRouter', () => {});
describe('createRouter', () => {
it('works', async () => {
const logger = winston.createLogger();
const config = await loadBackendConfig({ logger, argv: [] });
const discovery = SingleHostDiscovery.fromConfig(config);
const router = await createRouter({
config,
logger,
discovery,
});
expect(router).toBeDefined();
});
});
+25 -19
View File
@@ -22,11 +22,17 @@ import {
errorHandler,
PluginEndpointDiscovery,
} from '@backstage/backend-common';
import { Config } from '@backstage/config';
import { BadgeBuilder, DefaultBadgeBuilder } from '../lib/BadgeBuilder';
import { Entity } from '@backstage/catalog-model';
import { Config, JsonObject } from '@backstage/config';
import {
BadgeBuilder,
BadgeStyle,
BadgeStyles,
DefaultBadgeBuilder,
} from '../lib/BadgeBuilder';
export interface RouterOptions {
badgeBuilder: BadgeBuilder;
badgeBuilder?: BadgeBuilder;
logger: Logger;
config: Config;
discovery: PluginEndpointDiscovery;
@@ -39,10 +45,13 @@ export async function createRouter(
const logger = options.logger.child({ plugin: 'badges' });
const title = options.config.getString('app.title') || 'Backstage';
const catalogUrl = `${options.config.getString('app.baseUrl')}/catalog`;
const badgesConfig = options.config.getOptional('badges') ?? {};
const badgesConfig = (options.config.getOptional('badges') ??
{}) as JsonObject;
const badgeBuilder =
options.badgeBuilder || new DefaultBadgeBuilder(logger, badgesConfig);
logger.debug(`loading badges`);
router.get('/entity/:namespace/:kind/:name/:badgeId', async (req, res) => {
const { badgeId } = req.params;
let badge = await badgeBuilder.getBadgeConfig(badgeId);
@@ -55,7 +64,7 @@ export async function createRouter(
}
const entityUri = getEntityUri(req.params);
const entity = await getEntity(logger, options.discovery, entityUri);
const entity = await getEntity(options.discovery, entityUri);
if (!entity) {
res.status(400).send(`Unknown entity`);
return;
@@ -67,8 +76,8 @@ export async function createRouter(
format = 'application/json';
}
if (req.query.style) {
badge.style = req.query.style;
if (BadgeStyles.includes(req.query.style as BadgeStyle)) {
badge.style = req.query.style as BadgeStyle;
}
const data = await badgeBuilder.createBadge({
@@ -90,23 +99,20 @@ export async function createRouter(
return router;
}
function getEntityUri(params) {
function getEntityUri(params: JsonObject): string {
const { kind, namespace, name } = params;
return `${kind}/${namespace}/${name}`;
}
async function getEntity(logger, discovery, entityUri) {
async function getEntity(
discovery: PluginEndpointDiscovery,
entityUri: string,
): Promise<Entity> {
const catalogUrl = await discovery.getBaseUrl('catalog');
try {
const entity = (await (
await fetch(`${catalogUrl}/entities/by-name/${entityUri}`)
).json()) as Entity;
const entity = (await (
await fetch(`${catalogUrl}/entities/by-name/${entityUri}`)
).json()) as Entity;
return entity;
} catch (err) {
const msg = `Unable to get entity ${entityUri}, error ${err}`;
logger.info(msg);
return null;
}
return entity;
}
+1 -1
View File
@@ -17,7 +17,7 @@
/**
* adapted from https://stackoverflow.com/a/41015840/444060
*/
export function interpolate(template, params) {
export function interpolate(template: string, params: object): string {
const names = Object.keys(params);
const vals = Object.values(params);
// eslint-disable-next-line no-new-func
-1
View File
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { createDevApp } from '@backstage/dev-utils';
import { badgesPlugin } from '../src/plugin';
+7 -4
View File
@@ -18,7 +18,7 @@ import { generatePath } from 'react-router';
import { ConfigApi, DiscoveryApi } from '@backstage/core';
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { entityRoute } from '@backstage/plugin-catalog-react';
import { BadgesApi, BadgeConfig, BadgeSpec } from './types';
import { Badge, BadgesApi, BadgeConfig, BadgeSpec } from './types';
export class BadgesClient implements BadgesApi {
private readonly configApi: ConfigApi;
@@ -29,7 +29,7 @@ export class BadgesClient implements BadgesApi {
this.discoveryApi = options.discoveryApi;
}
public async getDefinedEntityBadges(entity: Entity): Badge[] {
public async getDefinedEntityBadges(entity: Entity): Promise<Badge[]> {
const badges = [];
const badgesConfig = this.configApi.getOptional('badges') ?? {};
const entityBadgeUri = await this.getEntityBadgeUri(entity);
@@ -46,7 +46,10 @@ export class BadgesClient implements BadgesApi {
return badges;
}
private async getBadgeInfo(entityBadgeUri: string, badgeId: string): Badge {
private async getBadgeInfo(
entityBadgeUri: string,
badgeId: string,
): Promise<Badge> {
const badgeUrl = `${entityBadgeUri}/${badgeId}`;
const spec = (await (
await fetch(`${badgeUrl}?format=json`)
@@ -60,7 +63,7 @@ export class BadgesClient implements BadgesApi {
};
}
private async getEntityBadgeUri(entity: Entity): string {
private async getEntityBadgeUri(entity: Entity): Promise<string> {
const routeParams = this.getEntityRouteParams(entity);
const path = generatePath(entityRoute.path, routeParams);
return `${await this.discoveryApi.getBaseUrl('badges')}/entity/${path}`;
+9 -2
View File
@@ -22,14 +22,21 @@ export const badgesApiRef = createApiRef<BadgesApi>({
description: 'Used to make requests to the badges backend',
});
export type BadgeStyle =
| 'plastic'
| 'flat'
| 'flat-square'
| 'for-the-badge'
| 'social';
// should probably have this in a "badges-common" package
interface BadgeConfig {
export interface BadgeConfig {
kind?: 'entity';
label: string;
message: string;
color?: string;
labelColor?: string;
style?: 'plastic' | 'flat' | 'flat-square' | 'for-the-badge' | 'social';
style?: BadgeStyle;
title?: string;
description?: string;
link?: string;
@@ -39,13 +39,13 @@ type Props = {
entity: Entity;
};
const useStyles = makeStyles(theme => ({
const useStyles = makeStyles({
codeBlock: {
'& code': {
whiteSpace: 'pre-wrap',
},
},
}));
});
export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
const theme = useTheme();
@@ -70,7 +70,7 @@ export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
</DialogContentText>
<Typography component="div" className={classes.codeBlock}>
Copy the following snippet of markdown code for the badge:
<CodeSnippet text={markdown} showCopyCodeButton />
<CodeSnippet language="markdown" text={markdown} showCopyCodeButton />
</Typography>
<hr />
</div>
@@ -35,7 +35,7 @@ const useStyles = makeStyles({
});
type Props = {
onShowBadgesDialog: () => void;
onShowBadgesDialog?: () => void;
onUnregisterEntity: () => void;
};