badges: fix type issues.
Signed-off-by: Andreas Stenius <git@astekk.se>
This commit is contained in:
committed by
Fredrik Adelöw
parent
45e26d7956
commit
ce4897ff37
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user