badges: move to context menu. support for multiple badges from config. #4457.

Signed-off-by: Andreas Stenius <git@astekk.se>
This commit is contained in:
Andreas Stenius
2021-02-19 21:45:47 +01:00
committed by Fredrik Adelöw
parent 798002ee03
commit b99231c0bc
12 changed files with 275 additions and 32 deletions
+63 -6
View File
@@ -19,6 +19,15 @@ import { createApiRef, ConfigApi, DiscoveryApi } from '@backstage/core';
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
import { entityRoute } from '@backstage/plugin-catalog-react';
// TODO: place interpolate code someplace re-usable...
// import { interpolate } from '@backstage/plugin-badges-backend';
function interpolate(template, params) {
const names = Object.keys(params);
const vals = Object.values(params);
// eslint-disable-next-line no-new-func
return new Function(...names, `return \`${template}\`;`)(...vals);
}
export class BadgesClientApi {
private readonly configApi: ConfigApi;
private readonly discoveryApi: DiscoveryApi;
@@ -28,16 +37,64 @@ export class BadgesClientApi {
this.discoveryApi = options.discoveryApi;
}
async getEntityPoweredByMarkdownCode(entity: Entity): string {
const badge = await this.getEntityPoweredByBadgeURL(entity);
const target = this.getEntityLink(entity);
return `[![Powered by Backstage](${badge})](${target})`;
async getDefaultContext(entity: Entity) {
return {
app: {
title: this.configApi.getString('app.title'),
},
entity,
entity_url: entity && (await this.getEntityLink(entity)),
};
}
async getEntityPoweredByBadgeURL(entity: Entity): string {
async getDefinedBadges(badgeKind: string, context: object) {
const badges = [];
const badgesConfig = this.configApi.getOptional('badges') ?? {};
for (const [badgeId, badge] of Object.entries(badgesConfig)) {
if (!badgeKind || !badge.kind || badgeKind === badge.kind) {
badges.push(await this.renderBadgeInfo(badgeId, badge, context));
}
}
return badges;
}
private async renderBadgeInfo(id, badge, context) {
const info = {
id,
url: context.entity && (await this.getEntityBadgeURL(id, context.entity)),
title: badge.title && this.render(badge.title, context),
description: this.render(badge.description || id, context),
target: badge.target && this.render(badge.target, context),
};
info.markdown = this.getBadgeMarkdownCode(info);
return info;
}
private async getEntityBadgeURL(badgeId: string, entity: Entity): 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}/${badgeId}`;
}
private getBadgeMarkdownCode(badge: object): string {
const title = badge.title && ` "${badge.title}"`;
const img = `![${badge.description}](${badge.url}${title})`;
if (!badge.target) {
return img;
}
return `[${img}](${badge.target})`;
}
private render(template, context) {
try {
return interpolate(template.replace('$$', '$'), context);
} catch (err) {
return `${err} [${template}]`;
}
}
private getEntityLink(entity: Entity): string {
@@ -0,0 +1,101 @@
/*
* 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 { Entity } from '@backstage/catalog-model';
import { CodeSnippet, Progress, useApi } from '@backstage/core';
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Typography,
useMediaQuery,
useTheme,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import Alert from '@material-ui/lab/Alert';
import React from 'react';
import { useAsync } from 'react-use';
import { badgesClientApiRef } from '../BadgesClientApi';
type Props = {
open: boolean;
onClose: () => any;
entity: Entity;
};
const useStyles = makeStyles(theme => ({
codeBlock: {
'& code': {
whiteSpace: 'pre-wrap',
},
},
}));
export const EntityBadgesDialog = ({ open, onClose, entity }: Props) => {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const badgesClientApi = useApi(badgesClientApiRef);
const classes = useStyles();
const { value: badges, loading, error } = useAsync(async () => {
const context = await badgesClientApi.getDefaultContext(entity);
return await badgesClientApi.getDefinedBadges('entity', context);
});
const content = (badges || []).map(
({ id, title, description, url, target, markdown }) => (
<div key={id}>
<DialogContentText>
{title || description}
<br />
<img alt={description} src={url} />
</DialogContentText>
<Typography component="div" className={classes.codeBlock}>
Copy the following snippet of markdown code for the badge:
<CodeSnippet text={markdown} showCopyCodeButton />
</Typography>
<hr />
</div>
),
);
return (
<Dialog fullScreen={fullScreen} open={open} onClose={onClose}>
<DialogTitle id="badges-dialog-title">Entity Badges</DialogTitle>
<DialogContent>
{loading ? <Progress /> : null}
{error ? (
<Alert severity="error" style={{ wordBreak: 'break-word' }}>
{error.toString()}
</Alert>
) : null}
{content}
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary">
Close
</Button>
</DialogActions>
</Dialog>
);
};
+11
View File
@@ -51,3 +51,14 @@ export const EntityBadgesCard = badgesPlugin.provide(
},
}),
);
export const EntityBadgesDialog = badgesPlugin.provide(
createComponentExtension({
component: {
lazy: () =>
import('./components/EntityBadgesDialog').then(
m => m.EntityBadgesDialog,
),
},
}),
);
-21
View File
@@ -1,21 +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.
*/
import { createRouteRef } from '@backstage/core';
export const badgeRouteRef = createRouteRef({
title: 'Powered By Badge',
path: 'badges/:kind/:namespace/:entityname',
});