diff --git a/docs/headline.png b/docs/headline.png index b0f9cb8b5c..83d7b14f21 100644 Binary files a/docs/headline.png and b/docs/headline.png differ diff --git a/packages/app/package.json b/packages/app/package.json index ed2f547ad7..15681d0548 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -12,15 +12,16 @@ "@backstage/plugin-lighthouse": "^0.1.1-alpha.6", "@backstage/plugin-register-component": "^0.1.1-alpha.6", "@backstage/plugin-scaffolder": "^0.1.1-alpha.6", + "@backstage/plugin-sentry": "^0.1.1-alpha.6", "@backstage/plugin-tech-radar": "^0.1.1-alpha.6", "@backstage/plugin-welcome": "^0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", - "@backstage/plugin-sentry": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "prop-types": "^15.7.2", "react": "^16.12.0", "react-dom": "^16.12.0", + "react-hot-loader": "^4.12.21", "react-router-dom": "^5.2.0", "react-use": "^14.2.0", "zen-observable": "^0.8.15" @@ -73,10 +74,10 @@ } }, "/catalog/api": { - "target": "http://localhost:3003", + "target": "http://localhost:7000", "changeOrigin": true, "pathRewrite": { - "^/catalog/api/": "/" + "^/catalog/api/": "/catalog/" } } } diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index b4d01e067b..39ff0432ea 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -20,6 +20,7 @@ import { BrowserRouter as Router } from 'react-router-dom'; import Root from './components/Root'; import * as plugins from './plugins'; import apis from './apis'; +import { hot } from 'react-hot-loader/root'; const app = createApp({ apis, @@ -41,4 +42,4 @@ const App: FC<{}> = () => ( ); -export default App; +export default hot(App); diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index ffd8fdba22..2d6df2e34e 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,8 @@ { "name": "@backstage/catalog-model", "version": "0.1.1-alpha.6", - "main": "dist/index.esm.js", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", "main:src": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,7 +11,7 @@ "access": "public" }, "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts new file mode 100644 index 0000000000..19e4b17352 --- /dev/null +++ b/packages/cli/src/commands/build.ts @@ -0,0 +1,37 @@ +/* + * 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 { buildPackage, Output } from '../lib/packager'; +import { Command } from 'commander'; + +export default async (cmd: Command) => { + let outputs = new Set(); + + const { outputs: outputsStr } = cmd as { outputs?: string }; + if (outputsStr) { + for (const output of outputsStr.split(',') as (keyof typeof Output)[]) { + if (output in Output) { + outputs.add(Output[output]); + } else { + throw new Error(`Unknown output format: ${output}`); + } + } + } else { + outputs = new Set([Output.types, Output.esm, Output.cjs]); + } + + await buildPackage({ outputs }); +}; diff --git a/packages/cli/src/commands/plugin/build.ts b/packages/cli/src/commands/plugin/build.ts index 561d79ed55..7e4e5cd36a 100644 --- a/packages/cli/src/commands/plugin/build.ts +++ b/packages/cli/src/commands/plugin/build.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { buildPackage } from '../../lib/packager'; +import { buildPackage, Output } from '../../lib/packager'; export default async () => { - await buildPackage(); + await buildPackage({ + outputs: new Set([Output.esm, Output.types]), + }); }; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bdd0d3e1f2..8288f776e1 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -78,6 +78,12 @@ const main = (argv: string[]) => { .description('Diff an existing plugin with the creation template') .action(actionHandler(() => require('./commands/plugin/diff'))); + program + .command('build') + .description('Build a package for publishing') + .option('--outputs ', 'List of formats to output [types,cjs,esm]') + .action(actionHandler(() => require('./commands/build'))); + program .command('lint') .option('--fix', 'Attempt to automatically fix violations') diff --git a/packages/cli/src/lib/packager/config.ts b/packages/cli/src/lib/packager/config.ts index df91f8067a..aaecd4a148 100644 --- a/packages/cli/src/lib/packager/config.ts +++ b/packages/cli/src/lib/packager/config.ts @@ -24,11 +24,14 @@ import esbuild from 'rollup-plugin-esbuild'; import imageFiles from 'rollup-plugin-image-files'; import dts from 'rollup-plugin-dts'; import json from '@rollup/plugin-json'; -import { RollupOptions } from 'rollup'; +import { RollupOptions, OutputOptions } from 'rollup'; +import { BuildOptions, Output } from './types'; import { paths } from '../paths'; -export const makeConfigs = async (): Promise => { +export const makeConfigs = async ( + options: BuildOptions, +): Promise => { const typesInput = paths.resolveTargetRoot( 'dist', relativePath(paths.targetRoot, paths.targetDir), @@ -43,13 +46,27 @@ export const makeConfigs = async (): Promise => { ); } - return [ - { - input: 'src/index.ts', - output: { + const configs = new Array(); + + if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) { + const output = new Array(); + + if (options.outputs.has(Output.cjs)) { + output.push({ + file: 'dist/index.cjs.js', + format: 'commonjs', + }); + } + if (options.outputs.has(Output.esm)) { + output.push({ file: 'dist/index.esm.js', format: 'module', - }, + }); + } + + configs.push({ + input: 'src/index.ts', + output, plugins: [ peerDepsExternal({ includeDependencies: true, @@ -68,14 +85,19 @@ export const makeConfigs = async (): Promise => { target: 'es2019', }), ], - }, - { + }); + } + + if (options.outputs.has(Output.types)) { + configs.push({ input: typesInput, output: { file: 'dist/index.d.ts', format: 'es', }, plugins: [dts()], - }, - ]; + }); + } + + return configs; }; diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts index e639599af9..17aae25ec4 100644 --- a/packages/cli/src/lib/packager/index.ts +++ b/packages/cli/src/lib/packager/index.ts @@ -15,3 +15,5 @@ */ export { buildPackage } from './packager'; +export { Output } from './types'; +export type { BuildOptions } from './types'; diff --git a/packages/cli/src/lib/packager/packager.ts b/packages/cli/src/lib/packager/packager.ts index d3e88d8c3d..0c41d0f767 100644 --- a/packages/cli/src/lib/packager/packager.ts +++ b/packages/cli/src/lib/packager/packager.ts @@ -19,6 +19,7 @@ import chalk from 'chalk'; import { relative as relativePath } from 'path'; import { paths } from '../paths'; import { makeConfigs } from './config'; +import { BuildOptions } from './types'; function formatErrorMessage(error: any) { let msg = ''; @@ -80,7 +81,7 @@ async function build(config: RollupOptions) { } } -export const buildPackage = async () => { - const configs = await makeConfigs(); +export const buildPackage = async (options: BuildOptions) => { + const configs = await makeConfigs(options); await Promise.all(configs.map(build)); }; diff --git a/packages/cli/src/lib/packager/types.ts b/packages/cli/src/lib/packager/types.ts new file mode 100644 index 0000000000..853789df59 --- /dev/null +++ b/packages/cli/src/lib/packager/types.ts @@ -0,0 +1,25 @@ +/* + * 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 enum Output { + esm, + cjs, + types, +} + +export type BuildOptions = { + outputs: Set; +}; diff --git a/packages/core-api/package.json b/packages/core-api/package.json index a8c2b939ac..8139f2c54a 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/core/package.json b/packages/core/package.json index 05986a6b1e..818634238b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index fc77298371..ab595ea9a7 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index f629877a65..1ebe1bf072 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 1f7afc9bbb..136b6d6854 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "test": "backstage-cli test", "prepack": "backstage-cli prepack", diff --git a/packages/theme/package.json b/packages/theme/package.json index 1704299e3c..a5d552a660 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -20,7 +20,7 @@ "main:src": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", + "build": "backstage-cli build --outputs types,esm", "lint": "backstage-cli lint", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 0cdc5d2618..5445fffb32 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -17,15 +17,18 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.1.1-alpha.6", - "@backstage/theme": "^0.1.1-alpha.6", "@backstage/catalog-model": "^0.1.1-alpha.6", + "@backstage/core": "^0.1.1-alpha.6", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.6", + "@backstage/plugin-sentry": "^0.1.1-alpha.6", + "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@backstage/plugin-sentry": "^0.1.1-alpha.6", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-use": "^14.2.0" }, "devDependencies": { diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index abba8a7523..4ae4676703 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -43,6 +43,34 @@ export class CatalogClient implements CatalogApi { if (entity) return entity; throw new Error(`'Entity not found: ${name}`); } + + async addLocation(type: string, target: string) { + const response = await fetch( + `${this.apiOrigin}${this.basePath}/locations`, + { + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ type, target }), + }, + ); + + if (response.status !== 201) { + throw new Error(await response.text()); + } + + const { location, entities } = await response.json(); + + if (!location || entities.length === 0) + throw new Error(`Location wasn't added: ${target}`); + + return { + location, + entities, + }; + } + async getLocationByEntity(entity: Entity): Promise { const findLocationIdInEntity = (e: Entity): string | undefined => e.metadata.annotations?.['backstage.io/managed-by-location']; diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 9e8dc5fbac..5bb6a6ca83 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -25,5 +25,8 @@ export const catalogApiRef = createApiRef({ export interface CatalogApi { getEntities(): Promise; getEntityByName(name: string): Promise; + addLocation(type: string, target: string): Promise; getLocationByEntity(entity: Entity): Promise; } + +export type AddLocationResponse = { location: Location; entities: Entity[] }; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx index 9ebeb7f546..53cc4f6c44 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx @@ -20,11 +20,23 @@ import CatalogPage from './CatalogPage'; import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core'; import { wrapInTestApp } from '@backstage/test-utils'; import { catalogApiRef } from '../..'; +import { CatalogApi } from '../../api/types'; +import { Entity } from '@backstage/catalog-model'; const errorApi = { post: () => {} }; -const catalogApi = { - getEntities: () => Promise.resolve([{ kind: '', metadata: {} }]), - getLocationByEntity: () => Promise.resolve({ data: {} }), +const catalogApi: Partial = { + getEntities: () => + Promise.resolve([ + { + metadata: { + name: 'Entity1', + }, + apiVersion: 'backstage.io/v1beta1', + kind: 'Component', + }, + ] as Entity[]), + getLocationByEntity: () => + Promise.resolve({ id: 'id', type: 'github', target: 'url' }), }; describe('CatalogPage', () => { diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index 25d2618a9a..779bcc7693 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -34,6 +34,8 @@ import { } from '../CatalogFilter/CatalogFilter'; import { Button, makeStyles, Typography, Link } from '@material-ui/core'; import { filterGroups, defaultFilter } from '../../data/filters'; +import { Link as RouterLink } from 'react-router-dom'; +import { rootRoute as scaffolderRootRoute } from '@backstage/plugin-scaffolder'; import GitHub from '@material-ui/icons/GitHub'; import { Entity, Location } from '@backstage/catalog-model'; @@ -118,18 +120,23 @@ const CatalogPage: FC<{}> = () => { 👋🏼 - {' '} + Welcome to Backstage, we are happy to have you. Start by checking - out our{' '} + out our getting started - {' '} + page. } /> - All your components diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 015916c3f4..7ff521ee67 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -17,6 +17,8 @@ import React, { FC } from 'react'; import { Component } from '../../data/component'; import { InfoCard, Progress, Table, TableColumn } from '@backstage/core'; import { Typography, Link } from '@material-ui/core'; +import { Link as RouterLink, generatePath } from 'react-router-dom'; +import { entityRoute } from '../../routes'; const columns: TableColumn[] = [ { @@ -24,7 +26,12 @@ const columns: TableColumn[] = [ field: 'name', highlight: true, render: (componentData: any) => ( - {componentData.name} + + {componentData.name} + ), }, { diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index f495bd3146..2b986b59ea 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -18,3 +18,4 @@ export { plugin } from './plugin'; export * from './api/CatalogClient'; export * from './api/types'; export * from './types'; +export * from './routes'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 97e470a065..1eee5efe07 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -17,11 +17,12 @@ import { createPlugin } from '@backstage/core'; import CatalogPage from './components/CatalogPage'; import ComponentPage from './components/ComponentPage/ComponentPage'; +import { rootRoute, entityRoute } from './routes'; export const plugin = createPlugin({ id: 'catalog', register({ router }) { - router.registerRoute('/', CatalogPage); - router.registerRoute('/catalog/:name/', ComponentPage); + router.addRoute(rootRoute, CatalogPage); + router.addRoute(entityRoute, ComponentPage); }, }); diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts new file mode 100644 index 0000000000..6498d412b8 --- /dev/null +++ b/plugins/catalog/src/routes.ts @@ -0,0 +1,30 @@ +/* + * 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 { createRouteRef } from '@backstage/core'; + +const NoIcon = () => null; + +export const rootRoute = createRouteRef({ + icon: NoIcon, + path: '/', + title: 'Catalog', +}); +export const entityRoute = createRouteRef({ + icon: NoIcon, + path: '/catalog/:name/', + title: 'Entity', +}); diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 053af6bb51..83be6fb7db 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -18,6 +18,8 @@ }, "dependencies": { "@backstage/core": "^0.1.1-alpha.6", + "@backstage/catalog-model": "^0.1.1-alpha.6", + "@backstage/plugin-catalog": "^0.1.1-alpha.6", "@backstage/theme": "^0.1.1-alpha.6", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", @@ -25,6 +27,8 @@ "react": "^16.13.1", "react-dom": "^16.13.1", "react-hook-form": "^5.7.2", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-use": "^14.2.0" }, "devDependencies": { diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx index 9203093d48..e8b0c3b76e 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx @@ -15,17 +15,29 @@ */ import React from 'react'; -import { render, fireEvent } from '@testing-library/react'; -import RegisterComponentForm from './RegisterComponentForm'; +import { render, fireEvent, cleanup } from '@testing-library/react'; +import RegisterComponentForm, { Props } from './RegisterComponentForm'; +import { act } from 'react-dom/test-utils'; +const setup = (props?: Partial) => { + return { + rendered: render( + , + ), + }; +}; describe('RegisterComponentForm', () => { + afterEach(() => cleanup()); + it('should initially render a disabled button', async () => { - const rendered = render( - , - ); + const { rendered } = setup(); expect( await rendered.findByText( - 'Enter the full path to the service-info.yaml file in GHE to start tracking your component. It must be in a public repo, on the master branch.', + 'Enter the full path to the service-info.yaml file in GitHub to start tracking your component. It must be in a public repo.', ), ).toBeInTheDocument(); @@ -34,27 +46,21 @@ describe('RegisterComponentForm', () => { }); it('should enable a submit form when data when component url is set ', async () => { - const rendered = render( - , - ); + const { rendered } = setup(); const input = (await rendered.getByRole('textbox')) as HTMLInputElement; - fireEvent.change(input, { - target: { value: 'https://example.com/blob/master/service.yaml' }, + await act(async () => { + // react-hook-form uses `input` event for changes + fireEvent.input(input, { + target: { value: 'https://example.com/blob/master/service.yaml' }, + }); }); - const submit = (await rendered.findByText('Submit')) as HTMLButtonElement; + const submit = (await rendered.getByRole('button')) as HTMLButtonElement; expect(submit.disabled).toBeFalsy(); }); - - it('should hide input on submission ', async () => { - const rendered = render( - , - ); - - expect( - await rendered.findByText( - 'Your component is being registered. Please wait.', - ), - ).toBeInTheDocument(); - }); +}); + +it('should show spinner while submitting', async () => { + const { rendered } = setup({ submitting: true }); + expect(rendered.getByTestId('loading-progress')).toBeInTheDocument(); }); diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx index d279afac25..d9fb88e44b 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx @@ -20,12 +20,11 @@ import { FormControl, FormHelperText, TextField, - Typography, + LinearProgress, } from '@material-ui/core'; import { useForm } from 'react-hook-form'; import { makeStyles } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; -import { Progress } from '@backstage/core'; import { ComponentIdValidators } from '../../util/validate'; const useStyles = makeStyles(theme => ({ @@ -39,57 +38,49 @@ const useStyles = makeStyles(theme => ({ }, })); -type RegisterComponentProps = { - onSubmit: () => any; +export type Props = { + onSubmit: (formData: Record) => Promise; submitting: boolean; }; -const RegisterComponentForm: FC = ({ - onSubmit, - submitting, -}) => { +const RegisterComponentForm: FC = ({ onSubmit, submitting }) => { const { register, handleSubmit, errors, formState } = useForm({ mode: 'onChange', }); const classes = useStyles(); - const hasErrors = !!errors.componentIdInput; + const hasErrors = !!errors.componentLocation; const dirty = formState?.dirty; - if (submitting) { - return ( - <> - - Your component is being registered. Please wait. - - - - ); - } - return ( + + return submitting ? ( + + ) : (
- {errors.componentIdInput && ( + {errors.componentLocation && ( - {errors.componentIdInput.message} + {errors.componentLocation.message} )} diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx index ac2645135e..ad47c3a41b 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx @@ -15,20 +15,46 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; -import mockFetch from 'jest-fetch-mock'; +import { render, cleanup } from '@testing-library/react'; import RegisterComponentPage from './RegisterComponentPage'; import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; +import { errorApiRef, ApiProvider, ApiRegistry } from '@backstage/core'; +import { catalogApiRef } from '@backstage/plugin-catalog'; +import { MemoryRouter } from 'react-router-dom'; +const errorApi = { post: () => {} }; +const catalogApi: jest.Mocked = { + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + addLocation: jest.fn((_a, _b) => new Promise(() => {})), + getEntities: jest.fn(), + getEntityByName: jest.fn(), + getLocationByEntity: jest.fn(), +}; + +const setup = () => ({ + rendered: render( + + + + + + + , + ), +}); describe('RegisterComponentPage', () => { + afterEach(() => cleanup()); + it('should render', () => { - mockFetch.mockResponse(() => new Promise(() => {})); - const rendered = render( - - - , - ); - expect(rendered.getByText('Register Component')).toBeInTheDocument(); + const { rendered } = setup(); + expect( + rendered.getByText('Register existing component'), + ).toBeInTheDocument(); }); }); diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx index 114c37fe4c..27654d2080 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -14,50 +14,107 @@ * limitations under the License. */ -import React, { FC, useEffect, useState } from 'react'; -import { Grid } from '@material-ui/core'; +import React, { FC, useState } from 'react'; +import { Grid, makeStyles } from '@material-ui/core'; import { InfoCard, Page, pageTheme, Content, - ContentHeader, - SupportButton, + useApi, + errorApiRef, + Header, } from '@backstage/core'; import RegisterComponentForm from '../RegisterComponentForm'; +import { catalogApiRef } from '@backstage/plugin-catalog'; +import { useMountedState } from 'react-use'; +import { Entity, Location } from '@backstage/catalog-model'; +import { RegisterComponentResultDialog } from '../RegisterComponentResultDialog'; +const useStyles = makeStyles(theme => ({ + dialogPaper: { + minHeight: 250, + minWidth: 600, + }, + icon: { + width: 20, + marginRight: theme.spacing(1), + }, + contentText: { + paddingBottom: theme.spacing(2), + }, +})); + +const FormStates = { + Idle: 'idle', + Success: 'success', + Submitting: 'submitting', +} as const; + +type ValuesOf = T extends Record ? V : never; const RegisterComponentPage: FC<{}> = () => { - const [isSubmitting, setIsSubmitting] = useState(false); + const classes = useStyles(); + const catalogApi = useApi(catalogApiRef); + const [formState, setFormState] = useState>( + FormStates.Idle, + ); + const isMounted = useMountedState(); - useEffect(() => { - if (isSubmitting) { - setTimeout(() => { - setIsSubmitting(false); - }, 4000); + const errorApi = useApi(errorApiRef); + + const [result, setResult] = useState<{ + data: { + entities: Entity[]; + location: Location; + } | null; + error: null | Error; + }>({ + data: null, + error: null, + }); + + const handleSubmit = async (formData: Record) => { + setFormState(FormStates.Submitting); + const { componentLocation: target } = formData; + try { + const data = await catalogApi.addLocation('github', target); + + if (!isMounted()) return; + + setResult({ error: null, data }); + setFormState(FormStates.Success); + } catch (e) { + errorApi.post(e); + + if (!isMounted()) return; + + setResult({ error: e, data: null }); + setFormState(FormStates.Idle); } - }, [isSubmitting]); - - const onSubmit = () => { - setIsSubmitting(true); }; return ( +
- - Documentation - + {formState === FormStates.Success && ( + setFormState(FormStates.Idle)} + classes={{ paper: classes.dialogPaper }} + /> + )} ); }; diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx new file mode 100644 index 0000000000..d918e72489 --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx @@ -0,0 +1,82 @@ +/* + * 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 React, { ComponentProps } from 'react'; +import { render, cleanup } from '@testing-library/react'; +import { RegisterComponentResultDialog } from './RegisterComponentResultDialog'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import { MemoryRouter } from 'react-router-dom'; +import { Entity } from '@backstage/catalog-model'; + +const setup = ( + props?: Partial>, +) => ({ + rendered: render( + + + {}} + entities={[]} + {...props} + /> + + , + ), +}); +describe('RegisterComponentResultDialog', () => { + afterEach(() => cleanup()); + + it('should render', () => { + const { rendered } = setup(); + expect( + rendered.getByText('Component Registration Result'), + ).toBeInTheDocument(); + }); +}); + +it('should show a list of components if success', async () => { + const { rendered } = setup({ + entities: [ + { + kind: 'Component', + metadata: { + name: 'Component1', + }, + spec: { + type: 'website', + }, + }, + { + kind: 'Component', + metadata: { + name: 'Component2', + }, + spec: { + type: 'service', + }, + }, + ] as Entity[], + }); + + expect( + rendered.getByText( + 'The following components have been succefully created:', + ), + ).toBeInTheDocument(); + expect(rendered.getByText('Component1')).toBeInTheDocument(); + expect(rendered.getByText('Component2')).toBeInTheDocument(); +}); diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx new file mode 100644 index 0000000000..4d158526a0 --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx @@ -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. + */ + +import React, { FC } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + List, + ListItem, + Link, + Divider, + DialogActions, + Button, +} from '@material-ui/core'; +import { Entity } from '@backstage/catalog-model'; +import { StructuredMetadataTable } from '@backstage/core'; +import { generatePath } from 'react-router'; +import { + entityRoute, + rootRoute as catalogRootRoute, +} from '@backstage/plugin-catalog'; +import { Link as RouterLink } from 'react-router-dom'; + +type Props = { + onClose: () => void; + classes?: Record; + entities: Entity[]; +}; + +export const RegisterComponentResultDialog: FC = ({ + onClose, + classes, + entities, +}) => ( + + Component Registration Result + + + The following components have been succefully created: + + + {entities.map((entity: any, index: number) => ( + + + + {generatePath(entityRoute.path, { + name: entity.metadata.name, + })} + + ), + }} + /> + + {index < entities.length - 1 && } + + ))} + + + + + + +); diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/index.ts b/plugins/register-component/src/components/RegisterComponentResultDialog/index.ts new file mode 100644 index 0000000000..7ed25f3c20 --- /dev/null +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/index.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. + */ + +export { RegisterComponentResultDialog } from './RegisterComponentResultDialog'; diff --git a/plugins/register-component/src/index.ts b/plugins/register-component/src/index.ts index 3a0a0fe2d3..5b20cb0158 100644 --- a/plugins/register-component/src/index.ts +++ b/plugins/register-component/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { plugin, rootRoute } from './plugin'; diff --git a/plugins/register-component/src/plugin.ts b/plugins/register-component/src/plugin.ts index f32e9dc84f..9c73688a70 100644 --- a/plugins/register-component/src/plugin.ts +++ b/plugins/register-component/src/plugin.ts @@ -14,12 +14,18 @@ * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { createPlugin, createRouteRef } from '@backstage/core'; import RegisterComponentPage from './components/RegisterComponentPage'; +export const rootRoute = createRouteRef({ + icon: () => null, + path: '/register-component', + title: 'Register component', +}); + export const plugin = createPlugin({ id: 'register-component', register({ router }) { - router.registerRoute('/register-component', RegisterComponentPage); + router.addRoute(rootRoute, RegisterComponentPage); }, }); diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 7f453fde48..ad44e06986 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -24,6 +24,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router-dom": "^5.2.0", "react-use": "^14.2.0" }, "devDependencies": { diff --git a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx index e6fed50cef..5d976a20b2 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/index.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/index.tsx @@ -24,7 +24,8 @@ import { Page, pageTheme, } from '@backstage/core'; -import { Typography, Link } from '@material-ui/core'; +import { Typography, Link, Button } from '@material-ui/core'; +import { Link as RouterLink } from 'react-router-dom'; // TODO(blam): Connect to backend const STATIC_DATA = [ @@ -49,7 +50,16 @@ const ScaffolderPage: React.FC<{}> = () => { subtitle="Create new software components using standard templates" /> - + + + NOTE! This feature is WIP. You can follow progress{' '} diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index 3a0a0fe2d3..5b20cb0158 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { plugin } from './plugin'; +export { plugin, rootRoute } from './plugin'; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 31bdab2f84..b2cb305e57 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -14,12 +14,18 @@ * limitations under the License. */ -import { createPlugin } from '@backstage/core'; +import { createPlugin, createRouteRef } from '@backstage/core'; import ScaffolderPage from './components/ScaffolderPage'; +export const rootRoute = createRouteRef({ + icon: () => null, + path: '/create', + title: 'Create entity', +}); + export const plugin = createPlugin({ id: 'scaffolder', register({ router }) { - router.registerRoute('/create', ScaffolderPage); + router.addRoute(rootRoute, ScaffolderPage); }, }); diff --git a/yarn.lock b/yarn.lock index 4f6e9ab06e..b3f5b701c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2533,16 +2533,16 @@ read-pkg-up "^7.0.1" "@storybook/addon-actions@^5.3.17": - version "5.3.18" - resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-5.3.18.tgz#e3e3b1475cebc9bdd2d563822fba9ac662b2601a" - integrity sha512-jdBVCcfyWin274Lkwg5cL+1fJ651NCuIWxuJVsmHQtIl2xTjf2MyoMoKQZNdt4xtE+W9w+rS4bYt04elrizThg== + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-5.3.19.tgz#50548fa6e84bc79ad95233ce23ade4878fc7cfac" + integrity sha512-gXF29FFUgYlUoFf1DcVCmH1chg2ElaHWMmCi5h7aZe+g6fXBQw0UtEdJnYLMOqZCIiWoZyuf1ETD0RbNHPhRIw== dependencies: - "@storybook/addons" "5.3.18" - "@storybook/api" "5.3.18" - "@storybook/client-api" "5.3.18" - "@storybook/components" "5.3.18" - "@storybook/core-events" "5.3.18" - "@storybook/theming" "5.3.18" + "@storybook/addons" "5.3.19" + "@storybook/api" "5.3.19" + "@storybook/client-api" "5.3.19" + "@storybook/components" "5.3.19" + "@storybook/core-events" "5.3.19" + "@storybook/theming" "5.3.19" core-js "^3.0.1" fast-deep-equal "^2.0.1" global "^4.3.2" @@ -2676,6 +2676,17 @@ global "^4.3.2" telejson "^3.2.0" +"@storybook/channel-postmessage@5.3.19": + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-5.3.19.tgz#ef9fe974c2a529d89ce342ff7acf5cc22805bae9" + integrity sha512-Iq0f4NPHR0UVVFCWt0cI7Myadk4/SATXYJPT6sv95KhnLjKEeYw571WBlThfp8a9FM80887xG+eIRe93c8dleA== + dependencies: + "@storybook/channels" "5.3.19" + "@storybook/client-logger" "5.3.19" + core-js "^3.0.1" + global "^4.3.2" + telejson "^3.2.0" + "@storybook/channels@5.3.18": version "5.3.18" resolved "https://registry.npmjs.org/@storybook/channels/-/channels-5.3.18.tgz#490c9eaa8292b0571c0f665052b12addf7c35f21" @@ -2713,6 +2724,29 @@ ts-dedent "^1.1.0" util-deprecate "^1.0.2" +"@storybook/client-api@5.3.19": + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-5.3.19.tgz#7a5630bb8fffb92742b1773881e9004ee7fdf8e0" + integrity sha512-Dh8ZLrLH91j9Fa28Gmp0KFUvvgK348aNMrDNAUdj4m4witz/BWQ2pxz6qq9/xFVErk/GanVC05kazGElqgYCRQ== + dependencies: + "@storybook/addons" "5.3.19" + "@storybook/channel-postmessage" "5.3.19" + "@storybook/channels" "5.3.19" + "@storybook/client-logger" "5.3.19" + "@storybook/core-events" "5.3.19" + "@storybook/csf" "0.0.1" + "@types/webpack-env" "^1.15.0" + core-js "^3.0.1" + eventemitter3 "^4.0.0" + global "^4.3.2" + is-plain-object "^3.0.0" + lodash "^4.17.15" + memoizerific "^1.11.3" + qs "^6.6.0" + stable "^0.1.8" + ts-dedent "^1.1.0" + util-deprecate "^1.0.2" + "@storybook/client-logger@5.3.18": version "5.3.18" resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-5.3.18.tgz#27c9d09d788965db0164be6e168bc3f03adbf88f" @@ -2795,26 +2829,26 @@ dependencies: core-js "^3.0.1" -"@storybook/core@5.3.18": - version "5.3.18" - resolved "https://registry.npmjs.org/@storybook/core/-/core-5.3.18.tgz#3f3c0498275826c1cc4368aba203ac17a6ae5c9c" - integrity sha512-XQb/UQb+Ohuaw0GhKKYzvmuuh5Tit93f2cLZD9QCSWUPvDGmLG5g91Y9NbUr4Ap3mANT3NksMNhkAV0GxExEkg== +"@storybook/core@5.3.19": + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/core/-/core-5.3.19.tgz#1e61f35c5148343a0c580f5d5efb77f3b4243a30" + integrity sha512-4EYzglqb1iD6x9gxtAYpRGwGP6qJGiU2UW4GiYrErEmeu6y6tkyaqW5AwGlIo9+6jAfwD0HjaK8afvjKTtmmMQ== dependencies: "@babel/plugin-proposal-class-properties" "^7.7.0" "@babel/plugin-proposal-object-rest-spread" "^7.6.2" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-env" "^7.4.5" - "@storybook/addons" "5.3.18" - "@storybook/channel-postmessage" "5.3.18" - "@storybook/client-api" "5.3.18" - "@storybook/client-logger" "5.3.18" - "@storybook/core-events" "5.3.18" + "@storybook/addons" "5.3.19" + "@storybook/channel-postmessage" "5.3.19" + "@storybook/client-api" "5.3.19" + "@storybook/client-logger" "5.3.19" + "@storybook/core-events" "5.3.19" "@storybook/csf" "0.0.1" - "@storybook/node-logger" "5.3.18" - "@storybook/router" "5.3.18" - "@storybook/theming" "5.3.18" - "@storybook/ui" "5.3.18" + "@storybook/node-logger" "5.3.19" + "@storybook/router" "5.3.19" + "@storybook/theming" "5.3.19" + "@storybook/ui" "5.3.19" airbnb-js-shims "^2.2.1" ansi-to-html "^0.6.11" autoprefixer "^9.7.2" @@ -2881,10 +2915,10 @@ dependencies: lodash "^4.17.15" -"@storybook/node-logger@5.3.18": - version "5.3.18" - resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-5.3.18.tgz#ee278acb8b6f10d456a24c0ff6d59818a0c3ad94" - integrity sha512-Go/hdtaPTtjgJP+GYk8VXcOmecrdG7cXm0yyTlatd6s8xXI0txHme1/0MOZmEPows1Ec7KAQ20+NnaCGUPZUUg== +"@storybook/node-logger@5.3.19": + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/node-logger/-/node-logger-5.3.19.tgz#c414e4d3781aeb06298715220012f552a36dff29" + integrity sha512-hKshig/u5Nj9fWy0OsyU04yqCxr0A9pydOHIassr4fpLAaePIN2YvqCqE2V+TxQHjZUnowSSIhbXrGt0DI5q2A== dependencies: "@types/npmlog" "^4.1.2" chalk "^3.0.0" @@ -2894,16 +2928,16 @@ regenerator-runtime "^0.13.3" "@storybook/react@^5.3.17": - version "5.3.18" - resolved "https://registry.npmjs.org/@storybook/react/-/react-5.3.18.tgz#c057b680924e188d44149c3d67dd31aead88b28a" - integrity sha512-6yNg+phcrEqEjC2NOiu0mJuxbTwX7yzbkcusIn0S7N/KTXNO7CGvYjAkdjfw0gTLjfuVDZIjDQfoosslvfsj3w== + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/react/-/react-5.3.19.tgz#ad7e7a5538399e2794cdb5a1b844a2b77c10bd09" + integrity sha512-OBRUqol3YLQi/qE55x2pWkv4YpaAmmfj6/Km+7agx+og+oNQl0nnlXy7r27X/4j3ERczzURa5pJHtSjwiNaJNw== dependencies: "@babel/plugin-transform-react-constant-elements" "^7.6.3" "@babel/preset-flow" "^7.0.0" "@babel/preset-react" "^7.0.0" - "@storybook/addons" "5.3.18" - "@storybook/core" "5.3.18" - "@storybook/node-logger" "5.3.18" + "@storybook/addons" "5.3.19" + "@storybook/core" "5.3.19" + "@storybook/node-logger" "5.3.19" "@svgr/webpack" "^4.0.3" "@types/webpack-env" "^1.15.0" babel-plugin-add-react-displayname "^0.0.5" @@ -3002,20 +3036,20 @@ resolve-from "^5.0.0" ts-dedent "^1.1.0" -"@storybook/ui@5.3.18": - version "5.3.18" - resolved "https://registry.npmjs.org/@storybook/ui/-/ui-5.3.18.tgz#c66f6d94a3c50bb706f4d5b1d5592439110f16f0" - integrity sha512-xyXK53fNe9lkGPmXf3Nk+n0gz9gOgXI+fDxetyDLpX79k3DIN/jCKEnv45vXof7OQ45mTmyBvUNTKrNLqKTt5Q== +"@storybook/ui@5.3.19": + version "5.3.19" + resolved "https://registry.npmjs.org/@storybook/ui/-/ui-5.3.19.tgz#ac03b67320044a3892ee784111d4436b61874332" + integrity sha512-r0VxdWab49nm5tzwvveVDnsHIZHMR76veYOu/NHKDUZ5hnQl1LMG1YyMCFFa7KiwD/OrZxRWr6/Ma7ep9kR4Gw== dependencies: "@emotion/core" "^10.0.20" - "@storybook/addons" "5.3.18" - "@storybook/api" "5.3.18" - "@storybook/channels" "5.3.18" - "@storybook/client-logger" "5.3.18" - "@storybook/components" "5.3.18" - "@storybook/core-events" "5.3.18" - "@storybook/router" "5.3.18" - "@storybook/theming" "5.3.18" + "@storybook/addons" "5.3.19" + "@storybook/api" "5.3.19" + "@storybook/channels" "5.3.19" + "@storybook/client-logger" "5.3.19" + "@storybook/components" "5.3.19" + "@storybook/core-events" "5.3.19" + "@storybook/router" "5.3.19" + "@storybook/theming" "5.3.19" copy-to-clipboard "^3.0.8" core-js "^3.0.1" core-js-pure "^3.0.1" @@ -3024,7 +3058,7 @@ fuse.js "^3.4.6" global "^4.3.2" lodash "^4.17.15" - markdown-to-jsx "^6.9.3" + markdown-to-jsx "^6.11.4" memoizerific "^1.11.3" polished "^3.3.1" prop-types "^15.7.2" @@ -3292,9 +3326,9 @@ pretty-format "^25.1.0" "@testing-library/jest-dom@^5.7.0": - version "5.7.0" - resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.7.0.tgz#b2e2acb4c088a293d52ba2cd1674b526282a2f87" - integrity sha512-ZV0OtBXmTDEDxrIbqJXiOcXCZ6aIMpmDlmfHj0hGNsSuQ/nX0qPAs9HWmCzXvPfTrhufTiH2nJLvDJu/LgHzwQ== + version "5.9.0" + resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.9.0.tgz#86464c66cbe75e632b8adb636f539bfd0efc2c9c" + integrity sha512-uZ68dyILuM2VL13lGz4ehFEAgxzvLKRu8wQxyAZfejWnyMhmipJ60w4eG81NQikJHBfaYXx+Or8EaPQTDwGfPA== dependencies: "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.0.2" @@ -3618,9 +3652,9 @@ "@types/uglify-js" "*" "@types/html-webpack-plugin@*", "@types/html-webpack-plugin@^3.2.2": - version "3.2.2" - resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.2.tgz#f552121f3c0a3972dda9a425de1e0029069b2907" - integrity sha512-KsL5cHtNWhOQF9Cu+Dpn7GemzQRxdKhe1/LgZUSku33B5L4Cx2/p3DX6YbeRNOoI552MNbB/VNbCDNEYU//iAw== + version "3.2.3" + resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.3.tgz#865323e30e82560c0ca898dbf9f6f9d1c541cd7f" + integrity sha512-Y7dsVhTn75IaD4lMIY02UP1L8e0ou8KQu8DKPJAegEFKdJR28/8ejayDG8ykfR0DtYCx3dCEHIkdpN8AOB6txQ== dependencies: "@types/html-minifier" "*" "@types/tapable" "*" @@ -4570,7 +4604,7 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" -ansi-colors@^3.0.0: +ansi-colors@^3.0.0, ansi-colors@^3.2.1: version "3.2.4" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== @@ -4580,7 +4614,7 @@ ansi-escapes@^3.0.0, ansi-escapes@^3.2.0: resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== -ansi-escapes@^4.2.1: +ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.1" resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== @@ -4937,6 +4971,11 @@ astral-regex@^1.0.0: resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -6128,6 +6167,14 @@ cli-table3@0.5.1: optionalDependencies: colors "^1.1.2" +cli-truncate@2.1.0, cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== + dependencies: + slice-ansi "^3.0.0" + string-width "^4.2.0" + cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -7942,6 +7989,13 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.5.0" tapable "^1.0.0" +enquirer@^2.3.5: + version "2.3.5" + resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.5.tgz#3ab2b838df0a9d8ab9e7dff235b0e8712ef92381" + integrity sha512-BNT1C08P9XD0vNg3J475yIUG+mVdp9T6towYFHUv897X0KoHBjB1shyrNmhmtHWKP17iSWgo7Gqh7BBuzLZMSA== + dependencies: + ansi-colors "^3.2.1" + entities@^1.1.1, entities@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -8425,7 +8479,7 @@ execa@1.0.0, execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@3.4.0, execa@^3.4.0: +execa@3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== @@ -8441,10 +8495,10 @@ execa@3.4.0, execa@^3.4.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/execa/-/execa-4.0.0.tgz#7f37d6ec17f09e6b8fc53288611695b6d12b9daf" - integrity sha512-JbDUxwV3BoT5ZVXQrSVbAiaXhXUkIwvbhPIwZ0N13kX+5yCzOhUNdocxB/UQRuYOHRYYwAxKYwJYc0T4D12pDA== +execa@^4.0.0, execa@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz#ad87fb7b2d9d564f70d2b62d511bee41d5cbb240" + integrity sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q== dependencies: cross-spawn "^7.0.0" get-stream "^5.0.0" @@ -8764,7 +8818,7 @@ figures@^2.0.0: dependencies: escape-string-regexp "^1.0.5" -figures@^3.0.0: +figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -12100,18 +12154,20 @@ linkify-it@^2.0.0: uc.micro "^1.0.1" lint-staged@^10.1.0: - version "10.1.0" - resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-10.1.0.tgz#18785bb005d5ed404f1c1db6563e082f7a7baac2" - integrity sha512-WzZ/T+O/aEaaT679sMgI4JqK5mnG69V5KQSouzVsShzZ8wGWte39HT3z61LsxjVNeCf8m/ChhvWJa2wTiQLy5A== + version "10.2.9" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-10.2.9.tgz#6013ecfa80829cd422446b545fd30a96bca3098c" + integrity sha512-ziRAuXEqvJLSXg43ezBpHxRW8FOJCXISaXU//BWrxRrp5cBdRkIx7g5IsB3OI45xYGE0S6cOacfekSjDyDKF2g== dependencies: - chalk "^3.0.0" - commander "^4.0.1" + chalk "^4.0.0" + cli-truncate "2.1.0" + commander "^5.1.0" cosmiconfig "^6.0.0" debug "^4.1.1" dedent "^0.7.0" - execa "^3.4.0" - listr "^0.14.3" - log-symbols "^3.0.0" + enquirer "^2.3.5" + execa "^4.0.1" + listr2 "^2.1.0" + log-symbols "^4.0.0" micromatch "^4.0.2" normalize-path "^3.0.0" please-upgrade-node "^3.2.0" @@ -12147,7 +12203,21 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@0.14.3, listr@^0.14.3: +listr2@^2.1.0: + version "2.1.3" + resolved "https://registry.npmjs.org/listr2/-/listr2-2.1.3.tgz#f527e197de12ad8c488c566921fa2da34cbc67f6" + integrity sha512-6oy3QhrZAlJGrG8oPcRp1hix1zUpb5AvyvZ5je979HCyf48tIj3Hn1TG5+rfyhz30t7HfySH/OIaVbwrI2kruA== + dependencies: + chalk "^4.0.0" + cli-truncate "^2.1.0" + figures "^3.2.0" + indent-string "^4.0.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.5.5" + through "^2.3.8" + +listr@0.14.3: version "0.14.3" resolved "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -12363,6 +12433,13 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== + dependencies: + chalk "^4.0.0" + log-update@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" @@ -12372,6 +12449,16 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== + dependencies: + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" + logform@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz#957155ebeb67a13164069825ce67ddb5bb2dd360" @@ -12577,7 +12664,7 @@ markdown-to-jsx@^6.11.4: prop-types "^15.6.2" unquote "^1.1.0" -markdown-to-jsx@^6.9.1, markdown-to-jsx@^6.9.3: +markdown-to-jsx@^6.9.1: version "6.11.0" resolved "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-6.11.0.tgz#a2e3f2bc781c3402d8bb0f8e0a12a186474623b0" integrity sha512-RH7LCJQ4RFmPqVeZEesKaO1biRzB/k4utoofmTCp3Eiw6D7qfvK8fzZq/2bjEJAtVkfPrM5SMt5APGf2rnaKMg== @@ -13869,6 +13956,13 @@ p-map@^3.0.0: dependencies: aggregate-error "^3.0.0" +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-pipe@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz#4b1a11399a11520a67790ee5a0c1d5881d6befe9" @@ -16387,7 +16481,7 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rxjs@^6.3.3: +rxjs@^6.3.3, rxjs@^6.5.5: version "6.5.5" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== @@ -16789,6 +16883,24 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + slide@^1.1.6: version "1.1.6" resolved "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -17854,7 +17966,7 @@ through2@^3.0.0: dependencies: readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@~2.3, through@~2.3.1: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=