Add api for configuring tools in the explore plugin

This commit is contained in:
Oliver Sand
2021-01-28 16:54:59 +01:00
parent 207024e2e0
commit c4c0f82b52
16 changed files with 364 additions and 98 deletions
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+4
View File
@@ -0,0 +1,4 @@
# explore-react
This package provides helpers to the `explore` plugin that can be imported by
any other plugin or app.
+50
View File
@@ -0,0 +1,50 @@
{
"name": "@backstage/plugin-explore-react",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"homepage": "https://backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
"directory": "plugins/explore-react"
},
"keywords": [
"backstage"
],
"scripts": {
"build": "backstage-cli plugin:build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"diff": "backstage-cli plugin:diff",
"clean": "backstage-cli clean",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"start": "backstage-cli plugin:serve"
},
"dependencies": {
"@backstage/core": "^0.5.0"
},
"devDependencies": {
"@backstage/cli": "^0.5.0",
"@backstage/dev-utils": "^0.1.8",
"@backstage/test-utils": "^0.1.6",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
"@types/jest": "^26.0.7",
"@types/node": "^12.0.0",
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
},
"files": [
"dist"
]
}
@@ -14,12 +14,4 @@
* limitations under the License.
*/
export type CardData = {
title: string;
description: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
export * from './tools';
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 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 '@testing-library/jest-dom';
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright 2020 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 { createApiRef } from '@backstage/core';
export const exploreToolsConfigRef = createApiRef<ExploreToolsConfig>({
id: 'plugin.explore.toolsconfig',
description: 'Used to configure tools displayed in the explore plugin',
});
export type ExploreTool = {
title: string;
description: string;
url: string;
image: string;
tags?: string[];
lifecycle?: string;
newsTag?: string;
};
export interface ExploreToolsConfig {
getTools: () => Promise<ExploreTool[]>;
}
+18
View File
@@ -0,0 +1,18 @@
/*
* Copyright 2020 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 { exploreToolsConfigRef } from './api';
export type { ExploreTool, ExploreToolsConfig } from './api';
+1
View File
@@ -33,6 +33,7 @@
"@backstage/catalog-model": "^0.7.0",
"@backstage/core": "^0.5.0",
"@backstage/plugin-catalog": "^0.2.12",
"@backstage/plugin-explore-react": "^0.0.1",
"@backstage/theme": "^0.2.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -43,6 +43,7 @@ export const DomainExplorerContent = () => {
<ContentHeader title="Domains">
<SupportButton>Discover the domains in your ecosystem.</SupportButton>
</ContentHeader>
{loading && <Progress />}
{!loading && (!entities || entities.length === 0) && (
<EmptyState
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { ExploreTool } from '@backstage/plugin-explore-react';
import { BackstageTheme } from '@backstage/theme';
import {
Button,
@@ -27,7 +28,6 @@ import {
} from '@material-ui/core';
import classNames from 'classnames';
import React from 'react';
import { CardData } from './types';
// TODO: Align styling between Domain and ToolCard
@@ -69,7 +69,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
}));
type Props = {
card: CardData;
card: ExploreTool;
objectFit?: 'cover' | 'contain';
};
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { ExploreTool } from '@backstage/plugin-explore-react';
import { BackstageTheme } from '@backstage/theme';
import { makeStyles } from '@material-ui/core';
import React from 'react';
import { ToolCard } from './ToolCard';
import { CardData } from './types';
const useStyles = makeStyles<BackstageTheme>(theme => ({
container: {
@@ -30,7 +30,7 @@ const useStyles = makeStyles<BackstageTheme>(theme => ({
}));
type ToolCardGridProps = {
tools: CardData[];
tools: ExploreTool[];
};
export const ToolCardGrid = ({ tools }: ToolCardGridProps) => {
@@ -38,7 +38,7 @@ export const ToolCardGrid = ({ tools }: ToolCardGridProps) => {
return (
<div className={classes.container}>
{tools.map((card: CardData, ix: any) => (
{tools.map((card: ExploreTool, ix: any) => (
<ToolCard card={card} key={ix} />
))}
</div>
@@ -15,4 +15,3 @@
*/
export { ToolCard } from './ToolCard';
export { ToolCardGrid } from './ToolCardGrid';
export type { CardData } from './types';
@@ -0,0 +1,94 @@
/*
* Copyright 2020 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 { ApiProvider, ApiRegistry } from '@backstage/core';
import {
ExploreTool,
exploreToolsConfigRef,
} from '@backstage/plugin-explore-react';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { render, waitFor } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { ToolExplorerContent } from './ToolExplorerContent';
describe('<ToolExplorerContent />', () => {
const exploreToolsConfigApi: jest.Mocked<typeof exploreToolsConfigRef.T> = {
getTools: jest.fn(),
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ThemeProvider theme={lightTheme}>
<MemoryRouter>
<ApiProvider
apis={ApiRegistry.with(exploreToolsConfigRef, exploreToolsConfigApi)}
>
{children}
</ApiProvider>
</MemoryRouter>
</ThemeProvider>
);
beforeEach(() => {
jest.resetAllMocks();
});
it('renders a grid of tools', async () => {
const tools: ExploreTool[] = [
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
];
exploreToolsConfigApi.getTools.mockResolvedValue(tools);
const { getByText } = render(<ToolExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() => {
expect(getByText('Lighthouse')).toBeInTheDocument();
expect(getByText('Tech Radar')).toBeInTheDocument();
});
});
it('renders empty state', async () => {
exploreToolsConfigApi.getTools.mockResolvedValue([]);
const { getByText } = render(<ToolExplorerContent />, {
wrapper: Wrapper,
});
await waitFor(() =>
expect(getByText('No tools to display')).toBeInTheDocument(),
);
});
});
@@ -13,97 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Content, ContentHeader, SupportButton } from '@backstage/core';
import {
Content,
ContentHeader,
EmptyState,
Progress,
SupportButton,
useApi,
} from '@backstage/core';
import React from 'react';
import { useAsync } from 'react-use';
import { exploreToolsConfigRef } from '../../../../explore-react/src';
import { ToolCardGrid } from '../ToolCard';
// TODO: Provide API to configure the toolsCards
const toolsCards = [
{
title: 'New Relic',
description:
'Observability platform built to help engineers create and monitor their software',
url: '/newrelic',
image: 'https://i.imgur.com/L37ikrX.jpg',
tags: ['newrelic', 'performance', 'monitoring', 'errors', 'alerting'],
},
{
title: 'CircleCI',
description:
'Provides builds overview, detailed build info and retriggering functionality for CircleCI.',
url: '/circleci',
image: 'https://miro.medium.com/max/1200/1*hkTBp22vLAqlIHkrkZHPnw.png',
tags: ['circleci', 'ci', 'dev'],
},
{
title: 'Sentry',
description:
'Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time.',
url: '/sentry',
image: 'https://sentry-brand.storage.googleapis.com/sentry-logo-black.png',
tags: ['sentry', 'monitoring', 'errors'],
},
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
{
title: 'Cost Insights',
description: 'Insights into cloud costs for your organization.',
url: '/cost-insights',
image: 'https://cloud.google.com/images/press/logo-cloud.png',
tags: ['cloud', 'finops'],
},
{
title: 'GraphiQL',
description:
'Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.',
url: '/graphiql',
image:
'https://camo.githubusercontent.com/517398c3fbe0687d3d4dcbe05da82970b882e75a/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f33413061324e314c3346324f304c3377326e316a2f477261706869514c382e706e673f582d436c6f75644170702d56697369746f722d49643d3433363432',
tags: ['graphql', 'dev'],
},
{
title: 'GitOps Clusters',
description:
'Create GitOps-managed clusters with Backstage. Currently supports EKS flavors and profiles like Machine Learning Ops (MLOps)',
url: '/gitops-clusters',
image: 'https://miro.medium.com/max/801/1*R28u8gj-hVdDFISoYqPhrQ.png',
tags: ['gitops', 'dev'],
},
{
title: 'Rollbar',
description:
'Error monitoring and crash reporting for agile development and continuous delivery',
url: '/rollbar',
image:
'https://images.ctfassets.net/cj4mgtttlyx7/4DfiWj9CbuHBi10uWK7JHn/5e94a6c5dbd5d50bdcd8d9e78f88689b/rollbar-seo.png',
tags: ['rollbar', 'monitoring', 'errors'],
},
];
export const ToolExplorerContent = () => {
const exploreToolsConfigApi = useApi(exploreToolsConfigRef);
const { value: tools, loading } = useAsync(async () => {
return await exploreToolsConfigApi.getTools();
}, [exploreToolsConfigApi]);
return (
<Content noPadding>
<ContentHeader title="Tools">
<SupportButton>Discover the tools in your ecosystem.</SupportButton>
</ContentHeader>
<ToolCardGrid tools={toolsCards} />
{loading && <Progress />}
{!loading && (!tools || tools.length === 0) && (
<EmptyState
missing="info"
title="No tools to display"
description={`You haven't added any tools yet.`}
/>
)}
{!loading && tools && <ToolCardGrid tools={tools} />}
</Content>
);
};
+16 -1
View File
@@ -14,11 +14,26 @@
* limitations under the License.
*/
import { createPlugin } from '@backstage/core';
import { createApiFactory, createPlugin } from '@backstage/core';
import { exploreToolsConfigRef } from '@backstage/plugin-explore-react';
import { exploreRouteRef } from './routes';
import { exampleTools } from './util/examples';
export const explorePlugin = createPlugin({
id: 'explore',
apis: [
// Register a default for exploreToolsConfigRef, you may want to override
// the API locally in your app.
createApiFactory({
api: exploreToolsConfigRef,
deps: {},
factory: () => ({
async getTools() {
return exampleTools;
},
}),
}),
],
routes: {
explore: exploreRouteRef,
},
+93
View File
@@ -0,0 +1,93 @@
/*
* Copyright 2020 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 const exampleTools = [
{
title: 'New Relic',
description:
'Observability platform built to help engineers create and monitor their software',
url: '/newrelic',
image: 'https://i.imgur.com/L37ikrX.jpg',
tags: ['newrelic', 'performance', 'monitoring', 'errors', 'alerting'],
},
{
title: 'CircleCI',
description:
'Provides builds overview, detailed build info and retriggering functionality for CircleCI.',
url: '/circleci',
image: 'https://miro.medium.com/max/1200/1*hkTBp22vLAqlIHkrkZHPnw.png',
tags: ['circleci', 'ci', 'dev'],
},
{
title: 'Sentry',
description:
'Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time.',
url: '/sentry',
image: 'https://sentry-brand.storage.googleapis.com/sentry-logo-black.png',
tags: ['sentry', 'monitoring', 'errors'],
},
{
title: 'Lighthouse',
description:
"Google's Lighthouse tool is a great resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your website.",
url: '/lighthouse',
image:
'https://raw.githubusercontent.com/GoogleChrome/lighthouse/8b3d7f052b2e64dd857e741d7395647f487697e7/assets/lighthouse-logo.png',
tags: ['web', 'seo', 'accessibility', 'performance'],
},
{
title: 'Tech Radar',
description:
'Tech Radar is a list of technologies, complemented by an assessment result, called ring assignment.',
url: '/tech-radar',
image:
'https://storage.googleapis.com/wf-blogs-engineering-media/2018/09/fe13bb32-wf-tech-radar-hero-1024x597.png',
tags: ['standards', 'landscape'],
},
{
title: 'Cost Insights',
description: 'Insights into cloud costs for your organization.',
url: '/cost-insights',
image: 'https://cloud.google.com/images/press/logo-cloud.png',
tags: ['cloud', 'finops'],
},
{
title: 'GraphiQL',
description:
'Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.',
url: '/graphiql',
image:
'https://camo.githubusercontent.com/517398c3fbe0687d3d4dcbe05da82970b882e75a/68747470733a2f2f64337676366c703535716a6171632e636c6f756466726f6e742e6e65742f6974656d732f33413061324e314c3346324f304c3377326e316a2f477261706869514c382e706e673f582d436c6f75644170702d56697369746f722d49643d3433363432',
tags: ['graphql', 'dev'],
},
{
title: 'GitOps Clusters',
description:
'Create GitOps-managed clusters with Backstage. Currently supports EKS flavors and profiles like Machine Learning Ops (MLOps)',
url: '/gitops-clusters',
image: 'https://miro.medium.com/max/801/1*R28u8gj-hVdDFISoYqPhrQ.png',
tags: ['gitops', 'dev'],
},
{
title: 'Rollbar',
description:
'Error monitoring and crash reporting for agile development and continuous delivery',
url: '/rollbar',
image:
'https://images.ctfassets.net/cj4mgtttlyx7/4DfiWj9CbuHBi10uWK7JHn/5e94a6c5dbd5d50bdcd8d9e78f88689b/rollbar-seo.png',
tags: ['rollbar', 'monitoring', 'errors'],
},
];