badges: load default badges from code rather than config. #4457.

Signed-off-by: Andreas Stenius <git@astekk.se>
This commit is contained in:
Andreas Stenius
2021-02-23 13:03:43 +01:00
committed by Fredrik Adelöw
parent ce4897ff37
commit ac17aeb35d
15 changed files with 278 additions and 206 deletions
-27
View File
@@ -1,27 +0,0 @@
/*
* 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.
*/
export interface Config {
// defined with doc in badges-backend
badges?: {
[badgeId: string]: {
/**
* @visibility frontend
*/
kind?: 'entity';
};
};
}
+2 -4
View File
@@ -45,8 +45,6 @@
"msw": "^0.21.2"
},
"files": [
"dist",
"config.d.ts"
],
"configSchema": "config.d.ts"
"dist"
]
}
+13 -47
View File
@@ -15,58 +15,33 @@
*/
import { generatePath } from 'react-router';
import { ConfigApi, DiscoveryApi } from '@backstage/core';
import { DiscoveryApi } from '@backstage/core';
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { entityRoute } from '@backstage/plugin-catalog-react';
import { Badge, BadgesApi, BadgeConfig, BadgeSpec } from './types';
import { BadgesApi, BadgeSpec } from './types';
export class BadgesClient implements BadgesApi {
private readonly configApi: ConfigApi;
private readonly discoveryApi: DiscoveryApi;
constructor(options: { configApi: ConfigApi; discoveryApi: DiscoveryApi }) {
this.configApi = options.configApi;
constructor(options: { discoveryApi: DiscoveryApi }) {
this.discoveryApi = options.discoveryApi;
}
public async getDefinedEntityBadges(entity: Entity): Promise<Badge[]> {
const badges = [];
const badgesConfig = this.configApi.getOptional('badges') ?? {};
const entityBadgeUri = await this.getEntityBadgeUri(entity);
public async getEntityBadgeSpecs(entity: Entity): Promise<BadgeSpec[]> {
const entityBadgeSpecsUrl = await this.getEntityBadgeSpecsUrl(entity);
const specs = (await (
await fetch(entityBadgeSpecsUrl)
).json()) as BadgeSpec[];
for (const [badgeId, badge] of Object.entries(badgesConfig)) {
if (!badge.kind || badge.kind === 'entity') {
const info = await this.getBadgeInfo(entityBadgeUri, badgeId);
if (info) {
badges.push(info);
}
}
}
return badges;
return specs;
}
private async getBadgeInfo(
entityBadgeUri: string,
badgeId: string,
): Promise<Badge> {
const badgeUrl = `${entityBadgeUri}/${badgeId}`;
const spec = (await (
await fetch(`${badgeUrl}?format=json`)
).json()) as BadgeSpec;
return {
id: badgeId,
markdown: this.getBadgeMarkdownCode(badgeUrl, spec.badge),
spec,
url: badgeUrl,
};
}
private async getEntityBadgeUri(entity: Entity): Promise<string> {
private async getEntityBadgeSpecsUrl(entity: Entity): Promise<string> {
const routeParams = this.getEntityRouteParams(entity);
const path = generatePath(entityRoute.path, routeParams);
return `${await this.discoveryApi.getBaseUrl('badges')}/entity/${path}`;
return `${await this.discoveryApi.getBaseUrl(
'badges',
)}/entity/${path}/badge-specs`;
}
private getEntityRouteParams(entity: Entity) {
@@ -77,13 +52,4 @@ export class BadgesClient implements BadgesApi {
name: entity.metadata.name,
};
}
private getBadgeMarkdownCode(badgeUrl: string, badge: BadgeConfig): string {
const title = badge.title ? ` "${badge.title}"` : '';
const img = `![${badge.description || 'badge'}](${badgeUrl}${title})`;
if (!badge.link) {
return img;
}
return `[${img}](${badge.link})`;
}
}
+25 -18
View File
@@ -29,35 +29,42 @@ export type BadgeStyle =
| 'for-the-badge'
| 'social';
// should probably have this in a "badges-common" package
export interface BadgeConfig {
interface BadgeParams {
color?: string;
description?: string;
kind?: 'entity';
label: string;
message: string;
color?: string;
labelColor?: string;
style?: BadgeStyle;
title?: string;
description?: string;
link?: string;
message: string;
style?: BadgeStyle;
}
interface Badge extends BadgeParams {
markdown: string;
}
interface BadgeConfig extends BadgeParams {
id: string;
}
export interface BadgeSpec {
/** The rendered data */
badge: BadgeConfig;
/** The rendered fields, markdown code */
badge: Badge;
/** The configuration data, with placeholders and all */
config: BadgeConfig;
/** The context used when rendering config -> badge */
context: object;
}
export interface Badge {
id: string;
markdown: string;
spec: BadgeSpec;
url: string;
/** The context used when rendering config -> badge */
context: {
// here is more, but only badge_url we care about
badge_url: string;
};
format: 'json'; // or 'svg', but we'll never see that as structured
// data, only as an svg element
}
export interface BadgesApi {
getDefinedEntityBadges(entity: Entity): Promise<Badge[]>;
getEntityBadgeSpecs(entity: Entity): Promise<BadgeSpec[]>;
}
@@ -55,26 +55,32 @@ export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
const { value: badges, loading, error } = useAsync(async () => {
if (open) {
return await badgesApi.getDefinedEntityBadges(entity);
return await badgesApi.getEntityBadgeSpecs(entity);
}
return [];
}, [badgesApi, entity, open]);
const content = (badges || []).map(({ id, markdown, spec, url }) => (
<div key={id}>
<DialogContentText>
{spec.badge.title || spec.badge.description || id}
<br />
<img alt={spec.badge.description} src={url} />
</DialogContentText>
<Typography component="div" className={classes.codeBlock}>
Copy the following snippet of markdown code for the badge:
<CodeSnippet language="markdown" text={markdown} showCopyCodeButton />
</Typography>
<hr />
</div>
));
const content = (badges || []).map(
({
badge: { description, markdown },
config: { id },
context: { badge_url },
}) => (
<div key={id}>
<DialogContentText>
{description}
<br />
<img alt={description} src={badge_url} />
</DialogContentText>
<Typography component="div" className={classes.codeBlock}>
Copy the following snippet of markdown code for the badge:
<CodeSnippet language="markdown" text={markdown} showCopyCodeButton />
</Typography>
<hr />
</div>
),
);
return (
<Dialog fullScreen={fullScreen} open={open} onClose={onClose}>
+2 -4
View File
@@ -17,7 +17,6 @@ import {
createApiFactory,
createComponentExtension,
createPlugin,
configApiRef,
discoveryApiRef,
} from '@backstage/core';
import { badgesApiRef, BadgesClient } from './api';
@@ -27,9 +26,8 @@ export const badgesPlugin = createPlugin({
apis: [
createApiFactory({
api: badgesApiRef,
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
factory: ({ configApi, discoveryApi }) =>
new BadgesClient({ configApi, discoveryApi }),
deps: { discoveryApi: discoveryApiRef },
factory: ({ discoveryApi }) => new BadgesClient({ discoveryApi }),
}),
],
});