diff --git a/plugins/explore-react/.eslintrc.js b/plugins/explore-react/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/explore-react/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/explore-react/README.md b/plugins/explore-react/README.md new file mode 100644 index 0000000000..4a881955df --- /dev/null +++ b/plugins/explore-react/README.md @@ -0,0 +1,4 @@ +# explore-react + +This package provides helpers to the `explore` plugin that can be imported by +any other plugin or app. diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json new file mode 100644 index 0000000000..ac8498501e --- /dev/null +++ b/plugins/explore-react/package.json @@ -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" + ] +} diff --git a/plugins/explore/src/components/ToolCard/types.ts b/plugins/explore-react/src/index.ts similarity index 78% rename from plugins/explore/src/components/ToolCard/types.ts rename to plugins/explore-react/src/index.ts index 183dbf3963..d46f5d4f27 100644 --- a/plugins/explore/src/components/ToolCard/types.ts +++ b/plugins/explore-react/src/index.ts @@ -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'; diff --git a/plugins/explore-react/src/setupTests.ts b/plugins/explore-react/src/setupTests.ts new file mode 100644 index 0000000000..825bcd4115 --- /dev/null +++ b/plugins/explore-react/src/setupTests.ts @@ -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'; diff --git a/plugins/explore-react/src/tools/api.ts b/plugins/explore-react/src/tools/api.ts new file mode 100644 index 0000000000..34096e8cde --- /dev/null +++ b/plugins/explore-react/src/tools/api.ts @@ -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({ + 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; +} diff --git a/plugins/explore-react/src/tools/index.ts b/plugins/explore-react/src/tools/index.ts new file mode 100644 index 0000000000..7f727881ca --- /dev/null +++ b/plugins/explore-react/src/tools/index.ts @@ -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'; diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 418370d70d..19a865d765 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -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", diff --git a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx index b2f225c458..44478e5b71 100644 --- a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx +++ b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.tsx @@ -43,6 +43,7 @@ export const DomainExplorerContent = () => { Discover the domains in your ecosystem. + {loading && } {!loading && (!entities || entities.length === 0) && ( (theme => ({ })); type Props = { - card: CardData; + card: ExploreTool; objectFit?: 'cover' | 'contain'; }; diff --git a/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx b/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx index 356bf4a5c5..5b6fbd1834 100644 --- a/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx +++ b/plugins/explore/src/components/ToolCard/ToolCardGrid.tsx @@ -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(theme => ({ container: { @@ -30,7 +30,7 @@ const useStyles = makeStyles(theme => ({ })); type ToolCardGridProps = { - tools: CardData[]; + tools: ExploreTool[]; }; export const ToolCardGrid = ({ tools }: ToolCardGridProps) => { @@ -38,7 +38,7 @@ export const ToolCardGrid = ({ tools }: ToolCardGridProps) => { return (
- {tools.map((card: CardData, ix: any) => ( + {tools.map((card: ExploreTool, ix: any) => ( ))}
diff --git a/plugins/explore/src/components/ToolCard/index.ts b/plugins/explore/src/components/ToolCard/index.ts index 6ce62fddaa..84599aa163 100644 --- a/plugins/explore/src/components/ToolCard/index.ts +++ b/plugins/explore/src/components/ToolCard/index.ts @@ -15,4 +15,3 @@ */ export { ToolCard } from './ToolCard'; export { ToolCardGrid } from './ToolCardGrid'; -export type { CardData } from './types'; diff --git a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx new file mode 100644 index 0000000000..b0e1330ff3 --- /dev/null +++ b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx @@ -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('', () => { + const exploreToolsConfigApi: jest.Mocked = { + getTools: jest.fn(), + }; + + const Wrapper = ({ children }: { children?: React.ReactNode }) => ( + + + + {children} + + + + ); + + 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(, { + wrapper: Wrapper, + }); + + await waitFor(() => { + expect(getByText('Lighthouse')).toBeInTheDocument(); + expect(getByText('Tech Radar')).toBeInTheDocument(); + }); + }); + + it('renders empty state', async () => { + exploreToolsConfigApi.getTools.mockResolvedValue([]); + + const { getByText } = render(, { + wrapper: Wrapper, + }); + + await waitFor(() => + expect(getByText('No tools to display')).toBeInTheDocument(), + ); + }); +}); diff --git a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx index c6e1986ea2..5e45102e65 100644 --- a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx +++ b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.tsx @@ -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 ( Discover the tools in your ecosystem. - + + {loading && } + {!loading && (!tools || tools.length === 0) && ( + + )} + {!loading && tools && } ); }; diff --git a/plugins/explore/src/plugin.ts b/plugins/explore/src/plugin.ts index c5346607c9..27573eaeb9 100644 --- a/plugins/explore/src/plugin.ts +++ b/plugins/explore/src/plugin.ts @@ -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, }, diff --git a/plugins/explore/src/util/examples.ts b/plugins/explore/src/util/examples.ts new file mode 100644 index 0000000000..d0a4cdd1f4 --- /dev/null +++ b/plugins/explore/src/util/examples.ts @@ -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'], + }, +];