From bda7cb8b7a32dc535c7b6d0051022f81027c9874 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 5 Feb 2020 15:10:01 +0100 Subject: [PATCH] frontend/core: added plugin route registration api + moved home page to plugin --- frontend/packages/app/package.json | 1 + frontend/packages/app/src/App.test.tsx | 1 - frontend/packages/app/src/App.tsx | 28 ++-------- frontend/packages/core/package.json | 2 +- .../packages/core/src/appApi/AppBuilder.tsx | 53 +++++++++++++++---- frontend/packages/core/src/appApi/api.ts | 6 ++- .../src/{ => appApi}/createPlugin.test.ts | 0 .../packages/core/src/appApi/createPlugin.ts | 10 ++++ frontend/packages/core/src/appApi/types.ts | 37 +++++++++++++ frontend/packages/core/src/createPlugin.ts | 11 ---- frontend/packages/core/src/index.ts | 2 - frontend/packages/core/src/types.ts | 3 -- frontend/packages/plugins/home-page/README.md | 1 + .../packages/plugins/home-page/jest.config.js | 4 ++ .../packages/plugins/home-page/jest.setup.ts | 1 + .../packages/plugins/home-page/package.json | 26 +++++++++ .../src/components/HomePage/HomePage.test.tsx | 10 ++++ .../src/components/HomePage/HomePage.tsx | 23 ++++++++ .../src/components/HomePage/index.ts | 1 + .../packages/plugins/home-page/src/index.ts | 1 + .../plugins/home-page/src/plugin.test.ts | 7 +++ .../packages/plugins/home-page/src/plugin.ts | 9 ++++ frontend/yarn.lock | 5 -- 23 files changed, 182 insertions(+), 60 deletions(-) rename frontend/packages/core/src/{ => appApi}/createPlugin.test.ts (100%) create mode 100644 frontend/packages/core/src/appApi/createPlugin.ts delete mode 100644 frontend/packages/core/src/createPlugin.ts delete mode 100644 frontend/packages/core/src/types.ts create mode 100644 frontend/packages/plugins/home-page/README.md create mode 100644 frontend/packages/plugins/home-page/jest.config.js create mode 100644 frontend/packages/plugins/home-page/jest.setup.ts create mode 100644 frontend/packages/plugins/home-page/package.json create mode 100644 frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx create mode 100644 frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx create mode 100644 frontend/packages/plugins/home-page/src/components/HomePage/index.ts create mode 100644 frontend/packages/plugins/home-page/src/index.ts create mode 100644 frontend/packages/plugins/home-page/src/plugin.test.ts create mode 100644 frontend/packages/plugins/home-page/src/plugin.ts diff --git a/frontend/packages/app/package.json b/frontend/packages/app/package.json index 9082342597..130e7972ad 100644 --- a/frontend/packages/app/package.json +++ b/frontend/packages/app/package.json @@ -5,6 +5,7 @@ "dependencies": { "@backstage/core": "0.0.0", "@backstage/plugin-hello-world": "0.0.0", + "@backstage/plugin-home-page": "0.0.0", "@backstage/plugin-login": "0.0.0", "@react-workspaces/react-scripts": "^3.3.0-alpha-08", "@testing-library/jest-dom": "^4.2.4", diff --git a/frontend/packages/app/src/App.test.tsx b/frontend/packages/app/src/App.test.tsx index b67d9918cb..3dc39e48b7 100644 --- a/frontend/packages/app/src/App.test.tsx +++ b/frontend/packages/app/src/App.test.tsx @@ -6,6 +6,5 @@ describe('App', () => { it('renders learn react link', () => { const rendered = render(); rendered.getByText('This is Backstage!'); - rendered.getByText('…with plugin hello-world:'); }); }); diff --git a/frontend/packages/app/src/App.tsx b/frontend/packages/app/src/App.tsx index a814c94a57..c172c2bd38 100644 --- a/frontend/packages/app/src/App.tsx +++ b/frontend/packages/app/src/App.tsx @@ -1,17 +1,15 @@ import { BackstageTheme, createApp, - EntityLink, Header, InfoCard, Page, theme, } from '@backstage/core'; -import helloWorld, { MyComponent } from '@backstage/plugin-hello-world'; //import PageHeader from './components/PageHeader'; import { LoginComponent } from '@backstage/plugin-login'; +import HomePagePlugin from '@backstage/plugin-home-page'; import { CssBaseline, makeStyles, ThemeProvider } from '@material-ui/core'; -import Typography from '@material-ui/core/Typography'; import React, { FC } from 'react'; import { BrowserRouter as Router, @@ -58,27 +56,6 @@ const useStyles = makeStyles(theme => ({ }, })); -const Home: FC<{}> = () => { - return ( - - - {' '} - …with plugin {helloWorld?.id ?? 'wat'}: - - -
- Go to Login - - Backstage Backend - - - Backstage LB CI/CD - -
-
- ); -}; - const Login: FC<{}> = () => { return ( @@ -111,7 +88,8 @@ const AppShell: FC<{}> = ({ children }) => { const app = createApp(); app.registerEntityKind(...entities); -app.setHomePage(Home); +app.registerPlugin(HomePagePlugin); + const AppComponent = app.build(); const App: FC<{}> = () => { diff --git a/frontend/packages/core/package.json b/frontend/packages/core/package.json index b2155fd61a..d22516afe1 100644 --- a/frontend/packages/core/package.json +++ b/frontend/packages/core/package.json @@ -15,7 +15,7 @@ "@types/react-router-dom": "^5.1.3", "react": "^16.12.0", "react-dom": "^16.12.0", - "react-helmet":"5.2.1", + "react-helmet": "5.2.1", "react-addons-text-content": "0.0.4", "react-router-dom": "^5.1.2", "recompose": "0.30.0" diff --git a/frontend/packages/core/src/appApi/AppBuilder.tsx b/frontend/packages/core/src/appApi/AppBuilder.tsx index f9045bbbfd..ec61b20a86 100644 --- a/frontend/packages/core/src/appApi/AppBuilder.tsx +++ b/frontend/packages/core/src/appApi/AppBuilder.tsx @@ -1,13 +1,10 @@ import React, { ComponentType, FC } from 'react'; import { AppContextProvider } from './AppContext'; import { App, EntityConfig, AppComponentBuilder } from './types'; -import { Route, Switch, useParams } from 'react-router-dom'; +import { Route, Switch, useParams, Redirect } from 'react-router-dom'; import EntityKind from './EntityKind'; import { EntityContextProvider } from './EntityContext'; - -const DefaultHomePage: FC<{}> = () => { - return Hello! I am default home page; -}; +import { BackstagePlugin } from './types'; class AppImpl implements App { constructor(private readonly entities: Map) {} @@ -33,7 +30,7 @@ function builtComponent( export default class AppBuilder { private readonly entities = new Map(); - private homePage: ComponentType = DefaultHomePage; + private readonly plugins = new Map(); registerEntityKind(...entity: EntityKind[]) { for (const e of entity) { @@ -45,14 +42,20 @@ export default class AppBuilder { } } - setHomePage(page: ComponentType<{}>) { - this.homePage = page; + registerPlugin(...plugin: BackstagePlugin[]) { + for (const p of plugin) { + const { id } = p; + if (this.plugins.has(id)) { + throw new Error(`Plugin '${id}' is already registered`); + } + this.plugins.set(id, p); + } } build(): ComponentType<{}> { const app = new AppImpl(this.entities); - const entityRoutes = []; + const entityRoutes = new Array(); for (const { config } of this.entities.values()) { const { kind, pages } = config; @@ -92,13 +95,41 @@ export default class AppBuilder { } } - const routes = [...entityRoutes]; + const pluginRoutes = new Array(); + + for (const plugin of this.plugins.values()) { + plugin.register({ + router: { + registerRoute(path, component, options = {}) { + if (path.startsWith('/entity/')) { + throw new Error( + `Plugin ${plugin.id} tried to register forbidden route ${path}`, + ); + } + pluginRoutes.push( + , + ); + }, + registerRedirect(path, target, options = {}) { + if (path.startsWith('/entity/')) { + throw new Error( + `Plugin ${plugin.id} tried to register forbidden redirect ${path}`, + ); + } + pluginRoutes.push( + , + ); + }, + }, + }); + } + + const routes = [...pluginRoutes, ...entityRoutes]; return () => ( {routes} - 404 Not Found} /> diff --git a/frontend/packages/core/src/appApi/api.ts b/frontend/packages/core/src/appApi/api.ts index 4d657ee606..f3eaa0692b 100644 --- a/frontend/packages/core/src/appApi/api.ts +++ b/frontend/packages/core/src/appApi/api.ts @@ -1,5 +1,5 @@ import AppBuilder from './AppBuilder'; -import { EntityConfig } from './types'; +import { EntityConfig, PluginConfig, BackstagePlugin } from './types'; import EntityKind from './EntityKind'; import OverviewPageBuilder from './OverviewPageBuilder'; import EntityViewBuilder from './EntityViewPageBuilder'; @@ -19,3 +19,7 @@ export function createOverviewPage() { export function createEntityView() { return new EntityViewBuilder(); } + +export function createPlugin(config: PluginConfig): BackstagePlugin { + return { register() {}, ...config }; +} diff --git a/frontend/packages/core/src/createPlugin.test.ts b/frontend/packages/core/src/appApi/createPlugin.test.ts similarity index 100% rename from frontend/packages/core/src/createPlugin.test.ts rename to frontend/packages/core/src/appApi/createPlugin.test.ts diff --git a/frontend/packages/core/src/appApi/createPlugin.ts b/frontend/packages/core/src/appApi/createPlugin.ts new file mode 100644 index 0000000000..cc5f4a6090 --- /dev/null +++ b/frontend/packages/core/src/appApi/createPlugin.ts @@ -0,0 +1,10 @@ +import { BackstagePlugin, PluginConfig } from './types'; + +function createPlugin(config: PluginConfig): BackstagePlugin { + return { + register() {}, + ...config, + }; +} + +export default createPlugin; diff --git a/frontend/packages/core/src/appApi/types.ts b/frontend/packages/core/src/appApi/types.ts index f37848aca0..d33d6d5e40 100644 --- a/frontend/packages/core/src/appApi/types.ts +++ b/frontend/packages/core/src/appApi/types.ts @@ -34,3 +34,40 @@ export type UserApi = { getUser(): Promise; }; + +export type PluginConfig = { + id: string; + register?(hooks: PluginHooks): void; +}; + +export interface BackstagePlugin { + id: string; + register(hooks: PluginHooks): void; +} + +export type PluginHooks = { + router: Router; +}; + +export type RouteOptions = { + // Whether the route path must match exactly, defaults to true. + exact?: boolean; +}; + +export type RedirectOptions = { + // Whether the route path must match exactly, defaults to true. + exact?: boolean; +}; + +export type Router = { + registerRoute( + path: string, + Component: React.ComponentType, + options?: RouteOptions, + ): void; + registerRedirect( + path: string, + target: string, + options?: RedirectOptions, + ): void; +}; diff --git a/frontend/packages/core/src/createPlugin.ts b/frontend/packages/core/src/createPlugin.ts deleted file mode 100644 index 9b1cef8e44..0000000000 --- a/frontend/packages/core/src/createPlugin.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BackstagePlugin } from './types'; - -export type PluginConfig = { - id: string; -}; - -function createPlugin(config: PluginConfig): BackstagePlugin { - return config; -} - -export default createPlugin; diff --git a/frontend/packages/core/src/index.ts b/frontend/packages/core/src/index.ts index 5619dab3e8..2e08a8929e 100644 --- a/frontend/packages/core/src/index.ts +++ b/frontend/packages/core/src/index.ts @@ -1,6 +1,4 @@ export * from './appApi'; -export * from './types'; -export { default as createPlugin } from './createPlugin'; export { default as Page } from '../src/layout/Page'; export { gradients, theme } from '../src/layout/Page'; export { default as Header } from '../src/layout/Header/Header'; diff --git a/frontend/packages/core/src/types.ts b/frontend/packages/core/src/types.ts deleted file mode 100644 index c47102abb6..0000000000 --- a/frontend/packages/core/src/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type BackstagePlugin = { - id: string; -}; diff --git a/frontend/packages/plugins/home-page/README.md b/frontend/packages/plugins/home-page/README.md new file mode 100644 index 0000000000..ecac8aad72 --- /dev/null +++ b/frontend/packages/plugins/home-page/README.md @@ -0,0 +1 @@ +Welcome to your home-page plugin! diff --git a/frontend/packages/plugins/home-page/jest.config.js b/frontend/packages/plugins/home-page/jest.config.js new file mode 100644 index 0000000000..6b28dacb3d --- /dev/null +++ b/frontend/packages/plugins/home-page/jest.config.js @@ -0,0 +1,4 @@ +module.exports = { + ...require('@spotify/web-scripts/config/jest.config.js'), + setupFilesAfterEnv: ['../jest.setup.ts'], +}; diff --git a/frontend/packages/plugins/home-page/jest.setup.ts b/frontend/packages/plugins/home-page/jest.setup.ts new file mode 100644 index 0000000000..666127af39 --- /dev/null +++ b/frontend/packages/plugins/home-page/jest.setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom/extend-expect'; diff --git a/frontend/packages/plugins/home-page/package.json b/frontend/packages/plugins/home-page/package.json new file mode 100644 index 0000000000..bbb77a1ce6 --- /dev/null +++ b/frontend/packages/plugins/home-page/package.json @@ -0,0 +1,26 @@ +{ + "name": "@backstage/plugin-home-page", + "version": "0.0.0", + "main": "src/index.ts", + "main:src": "src/index.ts", + "devDependencies": { + "@backstage/core": "0.0.0", + "@spotify/web-scripts": "^6.0.0", + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "@types/jest": "^24.0.0", + "@types/node": "^12.0.0", + "@types/react": "^16.9.0", + "@types/react-dom": "^16.9.0", + "react": "^16.12.0", + "react-dom": "^16.12.0", + "@material-ui/core": "^4.9.1", + "@material-ui/icons": "^4.9.1" + }, + "scripts": { + "lint": "web-scripts lint", + "test": "web-scripts test" + }, + "license": "Apache-2.0" +} diff --git a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx new file mode 100644 index 0000000000..383bc9a5aa --- /dev/null +++ b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.test.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import HomePage from './HomePage'; + +describe('HomePage', () => { + it('should render', () => { + const rendered = render(); + expect(rendered.baseElement).toBeInTheDocument(); + }); +}); diff --git a/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx new file mode 100644 index 0000000000..d9cb7217a6 --- /dev/null +++ b/frontend/packages/plugins/home-page/src/components/HomePage/HomePage.tsx @@ -0,0 +1,23 @@ +import React, { FC } from 'react'; +import { InfoCard, EntityLink } from '@backstage/core'; +import { Link } from 'react-router-dom'; +import { Typography } from '@material-ui/core'; + +const HomePage: FC<{}> = () => { + return ( + + Welcome to Backstage! +
+ Go to Login + + Backstage Backend + + + Backstage LB CI/CD + +
+
+ ); +}; + +export default HomePage; diff --git a/frontend/packages/plugins/home-page/src/components/HomePage/index.ts b/frontend/packages/plugins/home-page/src/components/HomePage/index.ts new file mode 100644 index 0000000000..bf97a3e7d5 --- /dev/null +++ b/frontend/packages/plugins/home-page/src/components/HomePage/index.ts @@ -0,0 +1 @@ +export { default } from './HomePage'; diff --git a/frontend/packages/plugins/home-page/src/index.ts b/frontend/packages/plugins/home-page/src/index.ts new file mode 100644 index 0000000000..b68aea57f9 --- /dev/null +++ b/frontend/packages/plugins/home-page/src/index.ts @@ -0,0 +1 @@ +export { default } from './plugin'; diff --git a/frontend/packages/plugins/home-page/src/plugin.test.ts b/frontend/packages/plugins/home-page/src/plugin.test.ts new file mode 100644 index 0000000000..12dbe91d5a --- /dev/null +++ b/frontend/packages/plugins/home-page/src/plugin.test.ts @@ -0,0 +1,7 @@ +import plugin from './plugin'; + +describe('home-page', () => { + it('should export plugin', () => { + expect(plugin.id).toBe('home-page'); + }); +}); diff --git a/frontend/packages/plugins/home-page/src/plugin.ts b/frontend/packages/plugins/home-page/src/plugin.ts new file mode 100644 index 0000000000..29d3ec1e6d --- /dev/null +++ b/frontend/packages/plugins/home-page/src/plugin.ts @@ -0,0 +1,9 @@ +import { createPlugin } from '@backstage/core'; +import HomePage from './components/HomePage'; + +export default createPlugin({ + id: 'home-page', + register({ router }) { + router.registerRoute('/', HomePage); + }, +}); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index cd1327d08d..31d4e195f3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -15388,11 +15388,6 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.2: resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinycolor2@1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8" - integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g= - tmp@^0.0.33: version "0.0.33" resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"