Merge branch 'master' of github.com:backstage/backstage into entity-labels-card

This commit is contained in:
Brian Fletcher
2023-01-12 14:25:41 +00:00
62 changed files with 1404 additions and 256 deletions
-1
View File
@@ -56,7 +56,6 @@
"@backstage/cli": "workspace:^",
"@backstage/types": "workspace:^",
"@types/supertest": "^2.0.8",
"get-port": "^6.1.2",
"mock-fs": "^5.1.0",
"msw": "^0.49.0",
"node-fetch": "^2.6.7",
@@ -17,18 +17,14 @@
import mockFs from 'mock-fs';
import { resolve as resolvePath } from 'path';
import fetch from 'node-fetch';
import { coreServices } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { appPlugin } from './appPlugin';
import {
databaseFactory,
httpRouterFactory,
rootHttpRouterFactory,
loggerFactory,
rootLoggerFactory,
} from '@backstage/backend-app-api';
import { ConfigReader } from '@backstage/config';
import getPort from 'get-port';
describe('appPlugin', () => {
beforeEach(() => {
@@ -48,23 +44,12 @@ describe('appPlugin', () => {
});
it('boots', async () => {
const port = await getPort();
await startTestBackend({
const { server } = await startTestBackend({
services: [
[
coreServices.config,
new ConfigReader({
backend: {
listen: { port },
database: { client: 'better-sqlite3', connection: ':memory:' },
},
}),
],
loggerFactory(),
rootLoggerFactory(),
databaseFactory(),
httpRouterFactory(),
rootHttpRouterFactory(),
],
features: [
appPlugin({
@@ -75,12 +60,12 @@ describe('appPlugin', () => {
});
await expect(
fetch(`http://localhost:${port}/api/app/derp.html`).then(res =>
fetch(`http://localhost:${server.port()}/api/app/derp.html`).then(res =>
res.text(),
),
).resolves.toBe('winning');
await expect(
fetch(`http://localhost:${port}`).then(res => res.text()),
fetch(`http://localhost:${server.port()}`).then(res => res.text()),
).resolves.toBe('winning');
});
});
@@ -16,6 +16,7 @@
import React, { useState, useEffect } from 'react';
import { CardHeader, Divider, IconButton, makeStyles } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
HeaderIconLinkRow,
IconLinkVerticalProps,
@@ -182,7 +183,11 @@ export const EntityBazaarInfoContent = ({
)}
<CardHeader
title={<p className={classes.wordBreak}>{bazaarProject?.title!}</p>}
title={
<Typography paragraph className={classes.wordBreak}>
{bazaarProject?.title!}
</Typography>
}
action={
<div>
<IconButton
@@ -22,6 +22,7 @@ import {
IconButton,
makeStyles,
} from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
HeaderIconLinkRow,
IconLinkVerticalProps,
@@ -256,9 +257,9 @@ export const HomePageBazaarInfoCard = ({
<CardHeader
title={
<p className={classes.wordBreak}>
<Typography paragraph className={classes.wordBreak}>
{bazaarProject.value?.title || initProject.title}
</p>
</Typography>
}
action={
<div>
@@ -14,18 +14,15 @@
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import {
getVoidLogger,
PluginEndpointDiscovery,
TokenManager,
} from '@backstage/backend-common';
import { coreServices } from '@backstage/backend-plugin-api';
import {
PluginTaskScheduler,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { startTestBackend } from '@backstage/backend-test-utils';
import {
startTestBackend,
mockConfigFactory,
} from '@backstage/backend-test-utils';
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
import { eventsExtensionPoint } from '@backstage/plugin-events-node';
import { Duration } from 'luxon';
@@ -55,22 +52,6 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
return runner;
},
} as unknown as PluginTaskScheduler;
const discovery = jest.fn() as any as PluginEndpointDiscovery;
const tokenManager = jest.fn() as any as TokenManager;
const config = new ConfigReader({
catalog: {
providers: {
bitbucketCloud: {
schedule: {
frequency: 'P1M',
timeout: 'PT3M',
},
workspace: 'test-ws',
},
},
},
});
await startTestBackend({
extensionPoints: [
@@ -78,11 +59,22 @@ describe('bitbucketCloudEntityProviderCatalogModule', () => {
[eventsExtensionPoint, eventsExtensionPointImpl],
],
services: [
[coreServices.config, config],
[coreServices.discovery, discovery],
[coreServices.logger, getVoidLogger()],
mockConfigFactory({
data: {
catalog: {
providers: {
bitbucketCloud: {
schedule: {
frequency: 'P1M',
timeout: 'PT3M',
},
workspace: 'test-ws',
},
},
},
},
}),
[coreServices.scheduler, scheduler],
[coreServices.tokenManager, tokenManager],
],
features: [bitbucketCloudEntityProviderCatalogModule()],
});
@@ -56,8 +56,7 @@
"devDependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-catalog-backend": "workspace:^",
"get-port": "^6.1.2"
"@backstage/plugin-catalog-backend": "workspace:^"
},
"files": [
"alpha",
@@ -14,11 +14,7 @@
* limitations under the License.
*/
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import {
createBackendModule,
coreServices,
} from '@backstage/backend-plugin-api';
import { createBackendModule } from '@backstage/backend-plugin-api';
import { startTestBackend } from '@backstage/backend-test-utils';
import { CatalogClient } from '@backstage/catalog-client';
import { catalogServiceRef } from './catalogService';
@@ -42,9 +38,6 @@ describe('catalogServiceRef', () => {
});
await startTestBackend({
services: [
[coreServices.discovery, {} as unknown as PluginEndpointDiscovery],
],
features: [testModule()],
});
});
@@ -166,7 +166,8 @@ export const FossaCard = (props: { variant?: InfoCardVariants }) => {
spacing={0}
>
<Grid item>
<p
<Typography
paragraph
className={
value.issueCount > 0 || value.dependencyCount === 0
? classes.numberError
@@ -174,16 +175,18 @@ export const FossaCard = (props: { variant?: InfoCardVariants }) => {
}
>
{value.issueCount}
</p>
</Typography>
{value.dependencyCount > 0 && (
<p className={classes.description}>Number of issues</p>
<Typography paragraph className={classes.description}>
Number of issues
</Typography>
)}
{value.dependencyCount === 0 && (
<p className={classes.description}>
<Typography paragraph className={classes.description}>
No Dependencies.
<br />
Please check your FOSSA project settings.
</p>
</Typography>
)}
</Grid>
@@ -68,8 +68,12 @@ const generatedColumns: TableColumn[] = [
title: 'Source',
render: (row: Partial<WorkflowRun>) => (
<Typography variant="body2" noWrap>
<p>{row.source?.branchName}</p>
<p>{row.source?.commit.hash}</p>
<Typography paragraph variant="body2">
{row.source?.branchName}
</Typography>
<Typography paragraph variant="body2">
{row.source?.commit.hash}
</Typography>
</Typography>
),
},
@@ -14,18 +14,19 @@
* limitations under the License.
*/
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { useRandomJoke } from './Context';
export const Content = () => {
const { joke, loading } = useRandomJoke();
if (loading) return <p>Loading...</p>;
if (loading) return <Typography paragraph>Loading...</Typography>;
return (
<div>
<p>{joke.setup}</p>
<p>{joke.punchline}</p>
<Typography paragraph>{joke.setup}</Typography>
<Typography paragraph>{joke.punchline}</Typography>
</div>
);
};
@@ -126,12 +126,12 @@ const generatedColumns: TableColumn[] = [
field: 'lastBuild.source.branchName',
render: (row: Partial<Project>) => (
<>
<p>
<Typography paragraph>
<Link to={row.lastBuild?.source?.url ?? ''}>
{row.lastBuild?.source?.branchName}
</Link>
</p>
<p>{row.lastBuild?.source?.commit?.hash}</p>
</Typography>
<Typography paragraph>{row.lastBuild?.source?.commit?.hash}</Typography>
</>
),
},
@@ -152,7 +152,7 @@ const generatedColumns: TableColumn[] = [
render: (row: Partial<Project>) => {
return (
<>
<p>
<Typography paragraph>
{row.lastBuild?.tests && (
<Link to={row.lastBuild?.tests.testUrl ?? ''}>
{row.lastBuild?.tests.passed} / {row.lastBuild?.tests.total}{' '}
@@ -165,7 +165,7 @@ const generatedColumns: TableColumn[] = [
)}
{!row.lastBuild?.tests && 'n/a'}
</p>
</Typography>
</>
);
},
@@ -16,6 +16,7 @@
import React from 'react';
import { Step, StepLabel, Stepper } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import {
ArgoRolloutCanaryStep,
SetWeightStep,
@@ -49,11 +50,11 @@ const createLabelForStep = (step: ArgoRolloutCanaryStep): React.ReactNode => {
} else if (isAnalysisStep(step)) {
return (
<div>
<p>analysis templates:</p>
<Typography paragraph>analysis templates:</Typography>
{step.analysis.templates.map((t, i) => (
<p key={i}>{`${t.templateName}${
<Typography paragraph key={i}>{`${t.templateName}${
t.clusterScope ? ' (cluster scoped)' : ''
}`}</p>
}`}</Typography>
))}
</div>
);
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,160 @@
# scaffolder-backend-module-sentry
Welcome to the Sentry Module for Scaffolder.
Here you can find all Sentry related features to improve your scaffolder:
## Getting started
You need to configure the action in your backend:
## From your Backstage root directory
```bash
# From your Backstage root directory
yarn add --cwd packages/backend @backstage/plugin-scaffolder-backend-module-sentry
```
Configure the action (you can check
the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to
see all options):
```typescript
const actions = [
createSentryCreateProjectAction({
integrations,
reader: env.reader,
containerRunner,
}),
];
return await createRouter({
containerRunner,
catalogClient,
actions,
logger: env.logger,
config: env.config,
database: env.database,
reader: env.reader,
});
```
You need to define your Sentry API Token in your `app-config.yaml`:
```yaml
scaffolder:
sentry:
token: ${SENTRY_TOKEN}
```
After that you can use the action in your template:
```yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: sentry-demo
title: Sentry template
description: scaffolder sentry app
spec:
owner: backstage/techdocs-core
type: service
parameters:
- title: Fill in some steps
required:
- name
- owner
properties:
name:
title: Name
type: string
description: Unique name of the component
ui:autofocus: true
ui:options:
rows: 5
owner:
title: Owner
type: string
description: Owner of the component
ui:field: OwnerPicker
ui:options:
catalogFilter:
kind: Group
system:
title: System
type: string
description: System of the component
ui:field: EntityPicker
ui:options:
catalogFilter:
kind: System
defaultKind: System
- title: Choose a location
required:
- repoUrl
- dryRun
properties:
repoUrl:
title: Repository Location
type: string
ui:field: RepoUrlPicker
ui:options:
allowedHosts:
- github.com
dryRun:
title: Only perform a dry run, don't publish anything
type: boolean
default: false
steps:
- id: fetch
name: Fetch
action: fetch:template
input:
url: https://github.com/TEMPLATE
values:
name: ${{ parameters.name }}
- id: create-sentry-project
if: ${{ parameters.dryRun !== true }}
name: Create Sentry Project
action: sentry:create-project
input:
organizationSlug: ORG-SLUG
teamSlug: TEAM-SLUG
name: ${{ parameters.name }}
- id: publish
if: ${{ parameters.dryRun !== true }}
name: Publish
action: publish:github
input:
allowedHosts:
- github.com
description: This is ${{ parameters.name }}
repoUrl: ${{ parameters.repoUrl }}
- id: register
if: ${{ parameters.dryRun !== true }}
name: Register
action: catalog:register
input:
repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }}
catalogInfoPath: '/catalog-info.yaml'
- name: Results
if: ${{ parameters.dryRun }}
action: debug:log
input:
listWorkspace: true
output:
links:
- title: Repository
url: ${{ steps['publish'].output.remoteUrl }}
- title: Open in catalog
icon: catalog
entityRef: ${{ steps['register'].output.entityRef }}
```
@@ -0,0 +1,21 @@
## API Report File for "@backstage/plugin-scaffolder-backend-module-sentry"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { Config } from '@backstage/config';
import { TemplateAction } from '@backstage/plugin-scaffolder-backend';
// @public
export function createSentryCreateProjectAction(options: {
config: Config;
}): TemplateAction<{
organizationSlug: string;
teamSlug: string;
name: string;
slug?: string | undefined;
authToken?: string | undefined;
}>;
// (No @packageDocumentation comment for this package)
```
@@ -0,0 +1,48 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-sentry",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "frontend-plugin"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
"@backstage/dev-utils": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^12.1.3",
"@testing-library/user-event": "^14.0.0",
"@types/node": "*",
"cross-fetch": "^3.1.5",
"msw": "^0.49.0"
},
"files": [
"dist"
]
}
@@ -0,0 +1,122 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { createTemplateAction } from '@backstage/plugin-scaffolder-backend';
import { InputError } from '@backstage/errors';
import { Config } from '@backstage/config';
/**
* Creates the `sentry:craete-project` Scaffolder action.
*
* @remarks
*
* See {@link https://backstage.io/docs/features/software-templates/writing-custom-actions}.
*
* @param options - Configuration of the Sentry API.
* @public
*/
export function createSentryCreateProjectAction(options: { config: Config }) {
const { config } = options;
return createTemplateAction<{
organizationSlug: string;
teamSlug: string;
name: string;
slug?: string;
authToken?: string;
}>({
id: 'sentry:project:create',
schema: {
input: {
required: ['organizationSlug', 'teamSlug', 'name'],
type: 'object',
properties: {
organizationSlug: {
title: 'The slug of the organization the team belongs to',
type: 'string',
},
teamSlug: {
title: 'The slug of the team to create a new project for',
type: 'string',
},
name: {
title: 'The name for the new project',
type: 'string',
},
slug: {
title:
'Optional slug for the new project. If not provided a slug is generated from the name',
type: 'string',
},
authToken: {
title:
'authenticate via bearer auth token. Requires scope: project:write',
type: 'string',
},
},
},
},
async handler(ctx) {
const { organizationSlug, teamSlug, name, slug, authToken } = ctx.input;
const body: any = {
name: name,
};
if (slug) {
body.slug = slug;
}
const token = authToken
? authToken
: config.getOptionalString('scaffolder.sentry.token');
if (!token) {
throw new InputError(`No valid sentry token given`);
}
const response = await fetch(
`https://sentry.io/api/0/teams/${organizationSlug}/${teamSlug}/projects/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
},
);
const contentType = response.headers.get('content-type');
if (contentType !== 'application/json') {
throw new InputError(
`Unexpected Sentry Response Type: ${await response.text()}`,
);
}
const code = response.status;
const result = await response.json();
if (code !== 201) {
throw new InputError(`Sentry Response was: ${await result.detail}`);
}
ctx.output('id', result.id);
ctx.output('result', result);
},
});
}
@@ -0,0 +1,17 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { createSentryCreateProjectAction } from './actions/createProject';
@@ -16,6 +16,7 @@
import { DateTime, Interval } from 'luxon';
import humanizeDuration from 'humanize-duration';
import React from 'react';
import Typography from '@material-ui/core/Typography';
export const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
const createdAtTime = DateTime.fromISO(createdAt);
@@ -23,5 +24,9 @@ export const CreatedAtColumn = ({ createdAt }: { createdAt: string }) => {
.toDuration()
.valueOf();
return <p>{humanizeDuration(formatted, { round: true })} ago</p>;
return (
<Typography paragraph>
{humanizeDuration(formatted, { round: true })} ago
</Typography>
);
};
@@ -20,6 +20,7 @@ import useAsync from 'react-use/lib/useAsync';
import { catalogApiRef, EntityRefLink } from '@backstage/plugin-catalog-react';
import { parseEntityRef, UserEntity } from '@backstage/catalog-model';
import Typography from '@material-ui/core/Typography';
export const OwnerEntityColumn = ({ entityRef }: { entityRef?: string }) => {
const catalogApi = useApi(catalogApiRef);
@@ -30,7 +31,7 @@ export const OwnerEntityColumn = ({ entityRef }: { entityRef?: string }) => {
);
if (!entityRef) {
return <p>Unknown</p>;
return <Typography paragraph>Unknown</Typography>;
}
if (loading || error) {
@@ -24,6 +24,7 @@ import {
ListItemSecondaryAction,
ListItemIcon,
} from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
import useAsync from 'react-use/lib/useAsync';
import _unescape from 'lodash/unescape';
@@ -51,11 +52,11 @@ export const Content = (props: StackOverflowQuestionsContentProps) => {
}, []);
if (loading) {
return <p>loading...</p>;
return <Typography paragraph>loading...</Typography>;
}
if (error || !value || !value.length) {
return <p>could not load questions</p>;
return <Typography paragraph>could not load questions</Typography>;
}
const getSecondaryText = (answer_count: Number) =>
@@ -77,11 +77,22 @@ export interface FactRetrieverRegistry {
register(registration: FactRetrieverRegistration): Promise<void>;
}
// @public
export const initializePersistenceContext: (
database: PluginDatabaseManager,
options?: PersistenceContextOptions,
) => Promise<PersistenceContext>;
// @public
export type PersistenceContext = {
techInsightsStore: TechInsightsStore;
};
// @public
export type PersistenceContextOptions = {
logger: Logger;
};
// @public
export interface RouterOptions<
CheckType extends TechInsightCheck,
@@ -122,6 +133,7 @@ export interface TechInsightsOptions<
factRetrievers?: FactRetrieverRegistration[];
// (undocumented)
logger: Logger;
persistenceContext?: PersistenceContext;
// (undocumented)
scheduler: PluginTaskScheduler;
// (undocumented)
+5 -1
View File
@@ -18,12 +18,16 @@ export * from './service/router';
export type { RouterOptions } from './service/router';
export { buildTechInsightsContext } from './service/techInsightsContextBuilder';
export { initializePersistenceContext } from './service/persistence/persistenceContext';
export type {
TechInsightsOptions,
TechInsightsContext,
} from './service/techInsightsContextBuilder';
export type { FactRetrieverEngine } from './service/fact/FactRetrieverEngine';
export type { PersistenceContext } from './service/persistence/persistenceContext';
export type {
PersistenceContext,
PersistenceContextOptions,
} from './service/persistence/persistenceContext';
export { createFactRetrieverRegistration } from './service/fact/createFactRetriever';
export type { FactRetrieverRegistry } from './service/fact/FactRetrieverRegistry';
export type { FactRetrieverRegistrationOptions } from './service/fact/createFactRetriever';
@@ -46,6 +46,11 @@ type RawDbFactSchemaRow = {
entityFilter?: string;
};
/**
* Default TechInsightsDatabase implementation.
*
* @internal
*/
export class TechInsightsDatabase implements TechInsightsStore {
private readonly CHUNK_SIZE = 50;
@@ -36,22 +36,27 @@ export type PersistenceContext = {
techInsightsStore: TechInsightsStore;
};
export type CreateDatabaseOptions = {
/**
* A Container for persistence context initialization options
*
* @public
*/
export type PersistenceContextOptions = {
logger: Logger;
};
const defaultOptions: CreateDatabaseOptions = {
const defaultOptions: PersistenceContextOptions = {
logger: getVoidLogger(),
};
/**
* A factory method to construct persistence context for running implementation.
* A factory function to construct persistence context for running implementation.
*
* @public
*/
export const initializePersistenceContext = async (
database: PluginDatabaseManager,
options: CreateDatabaseOptions = defaultOptions,
options: PersistenceContextOptions = defaultOptions,
): Promise<PersistenceContext> => {
const client = await database.getClient();
@@ -74,6 +74,12 @@ export interface TechInsightsOptions<
*/
factRetrieverRegistry?: FactRetrieverRegistry;
/**
* Optional persistenceContext implementation that replaces the default one.
* This can be used to replace underlying database with a more suitable implementation if needed
*/
persistenceContext?: PersistenceContext;
logger: Logger;
config: Config;
discovery: PluginEndpointDiscovery;
@@ -139,9 +145,11 @@ export const buildTechInsightsContext = async <
const factRetrieverRegistry = buildFactRetrieverRegistry();
const persistenceContext = await initializePersistenceContext(database, {
logger,
});
const persistenceContext =
options.persistenceContext ??
(await initializePersistenceContext(database, {
logger,
}));
const factRetrieverEngine = await DefaultFactRetrieverEngine.create({
scheduler,
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ClassNameMap } from '@material-ui/core/styles/withStyles';
import Typography from '@material-ui/core/Typography/Typography';
import React from 'react';
import { Entry, Ring } from '../../utils/types';
import { RadarLegendLink } from './RadarLegendLink';
@@ -38,7 +39,9 @@ export const RadarLegendRing = ({
<div data-testid="radar-ring" key={ring.id} className={classes.ring}>
<h3 className={classes.ringHeading}>{ring.name}</h3>
{entries.length === 0 ? (
<p className={classes.ringEmpty}>(empty)</p>
<Typography paragraph className={classes.ringEmpty}>
(empty)
</Typography>
) : (
<ol className={classes.ringList}>
{entries.map(entry => (
@@ -23,6 +23,7 @@ import {
Link,
} from '@backstage/core-components';
import { Grid, Input, makeStyles } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import { RadarComponent, TechRadarComponentProps } from './RadarComponent';
@@ -79,7 +80,7 @@ export function RadarPage(props: TechRadarPageProps) {
onChange={e => setSearchText(e.target.value)}
/>
<SupportButton>
<p>
<Typography paragraph>
This is used for visualizing the official guidelines of different
areas of software development such as languages, frameworks,
infrastructure and processes. You can find an explanation for the
@@ -88,7 +89,7 @@ export function RadarPage(props: TechRadarPageProps) {
Zalando Tech Radar
</Link>
.
</p>
</Typography>
</SupportButton>
</ContentHeader>
<Grid container spacing={3} direction="row">
@@ -15,6 +15,7 @@
*/
import { createStyles, makeStyles, useTheme } from '@material-ui/core';
import Typography from '@material-ui/core/Typography';
import React from 'react';
import {
Bar,
@@ -91,7 +92,7 @@ export const BuildTimeline = ({
width,
}: BuildTimelineProps) => {
const theme = useTheme();
if (!targets.length) return <p>No Targets</p>;
if (!targets.length) return <Typography paragraph>No Targets</Typography>;
const data = getTimelineData(targets);