diff --git a/docs/FAQ.md b/docs/FAQ.md index c79d9c1f60..cbe1b8ab2b 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -17,7 +17,7 @@ and a large flora of components. Additional open sourced plugins would be added to the `plugins` directory in this monorepo. -While we encourage using the open soure model, integrators that want to experiment with +While we encourage using the open source model, integrators that want to experiment with Backstage internally may also choose to develop closed source plugins in a manner that suits them best, for example in their respective Backstage source repository. diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md index bb5b24333e..e4fe69af68 100644 --- a/docs/getting-started/development-environment.md +++ b/docs/getting-started/development-environment.md @@ -26,10 +26,12 @@ You can now view example-app in the browser. Run the following commands if you have Docker environment ```bash -$ docker build . -t spotify/backstage +$ yarn docker-build $ docker run --rm -it -p 80:80 spotify/backstage ``` Then open http://localhost/ on your browser. +> See [package.json](/package.json) for other yarn commands/options. + [Back to Docs](README.md) diff --git a/docs/getting-started/structure-of-a-plugin.md b/docs/getting-started/structure-of-a-plugin.md index 2b31221373..361546a1b6 100644 --- a/docs/getting-started/structure-of-a-plugin.md +++ b/docs/getting-started/structure-of-a-plugin.md @@ -54,7 +54,7 @@ export default createPlugin({ }); ``` -This is where the plugin is created and where it hooks into the app by declaring what component should be shown on what url. See reference docs for [createPlugin(coming soon)](http://github.com/spotify/backstage/) or [router(coming soon)](http://github.com/spotify/backstage/). +This is where the plugin is created and where it hooks into the app by declaring what component should be shown on what url. See reference docs for [createPlugin](../reference/createPlugin.md) or [router](../reference/createPlugin-router.md). ## Components diff --git a/lerna.json b/lerna.json index 2cc439d190..1d77db92fb 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.3" + "version": "0.1.1-alpha.4" } diff --git a/packages/app/package.json b/packages/app/package.json index d9e91a122a..fd9aa88323 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,14 +1,14 @@ { "name": "example-app", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "private": true, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/core": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", - "@backstage/plugin-home-page": "^0.1.1-alpha.3", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.3", - "@backstage/plugin-welcome": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.4", + "@backstage/plugin-home-page": "^0.1.1-alpha.4", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.4", + "@backstage/plugin-welcome": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/packages/app/src/App.test.tsx b/packages/app/src/App.test.tsx index 83fcc039a3..79f78e45e0 100644 --- a/packages/app/src/App.test.tsx +++ b/packages/app/src/App.test.tsx @@ -24,8 +24,8 @@ describe('App', () => { value: jest.fn(() => { return { matches: true, - addEventListener: jest.fn(), - removeEventListener: jest.fn(), + addListener: jest.fn(), + removeListener: jest.fn(), }; }), }); diff --git a/packages/app/src/ThemeContext.tsx b/packages/app/src/ThemeContext.tsx index 4ee76d400e..3d5a04f22a 100644 --- a/packages/app/src/ThemeContext.tsx +++ b/packages/app/src/ThemeContext.tsx @@ -41,9 +41,9 @@ export function useThemeType(themeId: string): [string, () => void] { setTheme('auto'); } }; - mql.addEventListener('change', darkListener); + mql.addListener(darkListener); return () => { - mql.removeEventListener('change', darkListener); + mql.removeListener(darkListener); }; }); function toggleTheme() { diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 9f72242c80..919ea0666b 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { default as HomePagePlugin } from '@backstage/plugin-home-page'; -import { default as WelcomePlugin } from '@backstage/plugin-welcome'; -import { default as LighthousePlugin } from '@backstage/plugin-lighthouse'; -export { HomePagePlugin, WelcomePlugin, LighthousePlugin }; +export { plugin as HomePagePlugin } from '@backstage/plugin-home-page'; +export { plugin as WelcomePlugin } from '@backstage/plugin-welcome'; +export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index c169e354d3..983a423a57 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -51,6 +51,10 @@ module.exports = { bundledDependencies: true, }, ], + '@typescript-eslint/no-unused-vars': [ + 'warn', + { vars: 'all', args: 'after-used', ignoreRestSiblings: true }, + ], }, overrides: [ { diff --git a/packages/cli/package.json b/packages/cli/package.json index 40c8b9b266..2a2adba6a8 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "private": false, "publishConfig": { "access": "public" diff --git a/packages/cli/src/commands/create-plugin/createPlugin.ts b/packages/cli/src/commands/create-plugin/createPlugin.ts index 7efb29d270..df8b71f619 100644 --- a/packages/cli/src/commands/create-plugin/createPlugin.ts +++ b/packages/cli/src/commands/create-plugin/createPlugin.ts @@ -122,7 +122,7 @@ export async function addPluginToApp(rootDir: string, pluginName: string) { .split('-') .map(name => capitalize(name)) .join(''); - const pluginImport = `import { default as ${pluginNameCapitalized} } from '${pluginPackage}';`; + const pluginImport = `import { plugin as ${pluginNameCapitalized} } from '${pluginPackage}';`; const pluginExport = `export { ${pluginNameCapitalized} };`; const pluginsFilePath = 'packages/app/src/plugins.ts'; const pluginsFile = resolvePath(rootDir, pluginsFilePath); diff --git a/packages/cli/templates/default-app/package.json.hbs b/packages/cli/templates/default-app/package.json.hbs index 03371f887c..231194a54e 100644 --- a/packages/cli/templates/default-app/package.json.hbs +++ b/packages/cli/templates/default-app/package.json.hbs @@ -1,13 +1,20 @@ { "name": "root", + "version": "1.0.0", "private": true, + "engines": { + "node": ">=12.0.0" + }, "scripts": { - "start": "yarn build && yarn workspace app start", + "start": "yarn workspace app start", + "bundle": "yarn build && yarn workspace app bundle", "build": "lerna run build", - "test": "cross-env CI=true lerna run test -- --coverage", + "test": "yarn build && lerna run test --since origin/master -- --coverage", + "test:all": "yarn build && lerna run test -- --coverage", + "lint": "lerna run lint --since origin/master --", + "lint:all": "lerna run lint --", "create-plugin": "backstage-cli create-plugin", - "clean": "lerna run clean", - "lint": "lerna run lint" + "clean": "lerna run clean" }, "workspaces": { "packages": [ @@ -15,10 +22,8 @@ "plugins/*" ] }, - "version": "1.0.0", "devDependencies": { "@backstage/cli": "^{{version}}", - "cross-env": "^7.0.0", "lerna": "^3.20.2", "prettier": "^1.19.1" } diff --git a/packages/cli/templates/default-app/packages/app/package.json.hbs b/packages/cli/templates/default-app/packages/app/package.json.hbs index 6f24814a71..a5b2b07b96 100644 --- a/packages/cli/templates/default-app/packages/app/package.json.hbs +++ b/packages/cli/templates/default-app/packages/app/package.json.hbs @@ -21,7 +21,7 @@ }, "scripts": { "start": "backstage-cli app:serve", - "build": "backstage-cli app:build", + "bundle": "backstage-cli app:build", "test": "backstage-cli test", "lint": "backstage-cli lint" }, diff --git a/packages/cli/templates/default-app/packages/app/src/plugins.ts b/packages/cli/templates/default-app/packages/app/src/plugins.ts index 6639319bbd..000bd79f3e 100644 --- a/packages/cli/templates/default-app/packages/app/src/plugins.ts +++ b/packages/cli/templates/default-app/packages/app/src/plugins.ts @@ -1 +1 @@ -export { default as WelcomePlugin } from 'plugin-welcome'; +export { plugin as WelcomePlugin } from 'plugin-welcome'; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/index.ts b/packages/cli/templates/default-app/plugins/welcome/src/index.ts index b68aea57f9..99edba26c3 100644 --- a/packages/cli/templates/default-app/plugins/welcome/src/index.ts +++ b/packages/cli/templates/default-app/plugins/welcome/src/index.ts @@ -1 +1 @@ -export { default } from './plugin'; +export { plugin } from './plugin'; diff --git a/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts b/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts index f61dee5690..f5bf8e68c3 100644 --- a/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts +++ b/packages/cli/templates/default-app/plugins/welcome/src/plugin.test.ts @@ -1,4 +1,4 @@ -import plugin from './plugin'; +import { plugin } from './plugin'; describe('welcome', () => { it('should export plugin', () => { diff --git a/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts b/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts index 35ceddd65f..a65fad5348 100644 --- a/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts +++ b/packages/cli/templates/default-app/plugins/welcome/src/plugin.ts @@ -1,7 +1,7 @@ import { createPlugin } from '@backstage/core'; import WelcomePage from './components/WelcomePage'; -export default createPlugin({ +export const plugin = createPlugin({ id: 'welcome', register({ router }) { router.registerRoute('/', WelcomePage); diff --git a/packages/cli/templates/default-plugin/src/index.ts b/packages/cli/templates/default-plugin/src/index.ts index 0b2dc1b524..3a0a0fe2d3 100644 --- a/packages/cli/templates/default-plugin/src/index.ts +++ b/packages/cli/templates/default-plugin/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './plugin'; +export { plugin } from './plugin'; diff --git a/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs index 4e94ca5d50..e34204c9ae 100644 --- a/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs +++ b/packages/cli/templates/default-plugin/src/plugin.test.ts.hbs @@ -14,7 +14,7 @@ * limitations under the License. */ -import plugin from './plugin'; +import { plugin } from './plugin'; describe('{{ id }}', () => { it('should export plugin', () => { diff --git a/packages/cli/templates/default-plugin/src/plugin.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.ts.hbs index bb0b93ca25..61d82fca53 100644 --- a/packages/cli/templates/default-plugin/src/plugin.ts.hbs +++ b/packages/cli/templates/default-plugin/src/plugin.ts.hbs @@ -17,7 +17,7 @@ import { createPlugin } from '@backstage/core'; import ExampleComponent from './components/ExampleComponent'; -export default createPlugin({ +export const plugin = createPlugin({ id: '{{ id }}', register({ router }) { router.registerRoute('/{{ id }}', ExampleComponent); diff --git a/packages/core/.storybook/main.js b/packages/core/.storybook/main.js deleted file mode 100644 index 160ecb3b0c..0000000000 --- a/packages/core/.storybook/main.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - stories: [ - '../src/layout/**/*.stories.tsx', - '../src/components/**/*.stories.tsx', - ], - addons: ['@storybook/addon-actions', '@storybook/addon-links'], - webpackFinal: async config => { - config.module.rules.push({ - test: /\.(ts|tsx)$/, - use: [ - { - loader: require.resolve('ts-loader'), - options: { - transpileOnly: true, - }, - }, - ], - }); - config.resolve.extensions.push('.ts', '.tsx'); - return config; - }, -}; diff --git a/packages/core/package.json b/packages/core/package.json index 014fc0ce33..84158bf151 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "private": false, "publishConfig": { "access": "public" @@ -42,9 +42,10 @@ "recompose": "0.30.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/test-utils": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/test-utils-core": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", + "@storybook/addon-storysource": "^5.3.18", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/packages/core/src/api/apis/ApiProvider.test.tsx b/packages/core/src/api/apis/ApiProvider.test.tsx index 5734899f68..f572907c2f 100644 --- a/packages/core/src/api/apis/ApiProvider.test.tsx +++ b/packages/core/src/api/apis/ApiProvider.test.tsx @@ -19,7 +19,7 @@ import ApiProvider, { useApi, withApis } from './ApiProvider'; import ApiRef from './ApiRef'; import ApiRegistry from './ApiRegistry'; import { render } from '@testing-library/react'; -import { withLogCollector } from '@backstage/test-utils'; +import { withLogCollector } from '@backstage/test-utils-core'; describe('ApiProvider', () => { type Api = () => string; diff --git a/packages/core/src/components/CircleProgress.test.js b/packages/core/src/components/CircleProgress.test.js index 68e8f027c2..4975e00cb8 100644 --- a/packages/core/src/components/CircleProgress.test.js +++ b/packages/core/src/components/CircleProgress.test.js @@ -16,7 +16,6 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { COLORS } from '@backstage/theme'; import { wrapInThemedTestApp } from '@backstage/test-utils'; import CircleProgress, { getProgressColor } from './CircleProgress'; @@ -52,17 +51,22 @@ describe('', () => { getByText('10m'); }); + const ok = '#111'; + const warning = '#222'; + const error = '#333'; + const palette = { status: { ok, warning, error } }; + it('colors the progress correctly', () => { - expect(getProgressColor()).toBe('#ddd'); - expect(getProgressColor(10)).toBe(COLORS.STATUS.ERROR); - expect(getProgressColor(50)).toBe(COLORS.STATUS.WARNING); - expect(getProgressColor(90)).toBe(COLORS.STATUS.OK); + expect(getProgressColor(palette)).toBe('#ddd'); + expect(getProgressColor(palette, 10)).toBe(error); + expect(getProgressColor(palette, 50)).toBe(warning); + expect(getProgressColor(palette, 90)).toBe(ok); }); it('colors the inverse progress correctly', () => { - expect(getProgressColor()).toBe('#ddd'); - expect(getProgressColor(10, true)).toBe(COLORS.STATUS.OK); - expect(getProgressColor(50, true)).toBe(COLORS.STATUS.WARNING); - expect(getProgressColor(90, true)).toBe(COLORS.STATUS.ERROR); + expect(getProgressColor(palette)).toBe('#ddd'); + expect(getProgressColor(palette, 10, true)).toBe(ok); + expect(getProgressColor(palette, 50, true)).toBe(warning); + expect(getProgressColor(palette, 90, true)).toBe(error); }); }); diff --git a/packages/core/src/components/CircleProgress.tsx b/packages/core/src/components/CircleProgress.tsx index d5cd191b3d..4d9c5c3340 100644 --- a/packages/core/src/components/CircleProgress.tsx +++ b/packages/core/src/components/CircleProgress.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core'; -import { BackstageTheme, COLORS } from '@backstage/theme'; +import { makeStyles, useTheme } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; import { Circle } from 'rc-progress'; import React, { FC } from 'react'; @@ -55,7 +55,7 @@ const defaultProps = { max: 100, }; -export function getProgressColor(value, inverse, max) { +export function getProgressColor(palette, value, inverse, max) { if (isNaN(value)) { return '#ddd'; } @@ -64,16 +64,17 @@ export function getProgressColor(value, inverse, max) { const actualValue = inverse ? actualMax - value : value; if (actualValue < actualMax / 3) { - return COLORS.STATUS.ERROR; + return palette.status.error; } else if (actualValue < actualMax * (2 / 3)) { - return COLORS.STATUS.WARNING; + return palette.status.warning; } - return COLORS.STATUS.OK; + return palette.status.ok; } const CircleProgress: FC = props => { const classes = useStyles(props); + const theme = useTheme(); const { value, fractional, inverse, unit, max } = { ...defaultProps, ...props, @@ -89,7 +90,7 @@ const CircleProgress: FC = props => { percent={asPercentage} strokeWidth={12} trailWidth={12} - strokeColor={getProgressColor(asActual, inverse, max)} + strokeColor={getProgressColor(theme.palette, asActual, inverse, max)} className={classes.circle} />
diff --git a/packages/core/src/components/ProgressCard.tsx b/packages/core/src/components/ProgressCard.tsx index 1964f382e9..dc3ee7aa35 100644 --- a/packages/core/src/components/ProgressCard.tsx +++ b/packages/core/src/components/ProgressCard.tsx @@ -16,9 +16,8 @@ import React, { FC } from 'react'; import { makeStyles } from '@material-ui/core'; - import InfoCard from 'layout/InfoCard'; -import { Props as BottomLinkProps } from 'layout/InfoCard/BottomLink'; +import { Props as BottomLinkProps } from '../layout/BottomLink'; import CircleProgress from './CircleProgress'; type Props = { diff --git a/packages/core/src/components/SortableTable.js b/packages/core/src/components/SortableTable.js index e04b844048..ab4e1af2de 100644 --- a/packages/core/src/components/SortableTable.js +++ b/packages/core/src/components/SortableTable.js @@ -171,7 +171,7 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => { * @example * render { * const data = [ - * { id: 'buffalos', amount: 1, status: , statusValue: 2 } + * { id: 'buffalos', amount: 1, status: , statusValue: 2 }, * { id: 'milk', amount: 3, status: , statusValue: 1 } * ]; * const columns = [ diff --git a/packages/core/src/components/SortableTable.stories.tsx b/packages/core/src/components/SortableTable.stories.tsx new file mode 100644 index 0000000000..c534457d53 --- /dev/null +++ b/packages/core/src/components/SortableTable.stories.tsx @@ -0,0 +1,57 @@ +/* + * 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 from 'react'; +import { StatusError, StatusOK, StatusWarning } from './Status'; +import SortableTable from './SortableTable'; + +export default { + title: 'Sortable Table', + component: SortableTable, +}; +const containerStyle = { width: 600, padding: 20 }; + +const data = [ + { id: 'buffalos', amount: 1, status: , statusValue: 2 }, + { id: 'milk', amount: 3, status: , statusValue: 1 }, + { id: 'cheese', amount: 8, status: , statusValue: 1 }, + { id: 'bread', amount: 2, status: , statusValue: 0 }, +]; +const columns = [ + { id: 'id', label: 'ID' }, + { id: 'amount', disablePadding: false, numeric: true, label: 'AMOUNT' }, + { id: 'status', label: 'STATUS', sortValue: row => row.statusValue }, +]; +const footerData = [ + { id: 'total', amount: 4, statusValue: 2, status: }, +]; + +export const Default = () => ( +
+ +
+); + +export const WithFooter = () => ( +
+ +
+); diff --git a/packages/core/src/components/Status/Status.stories.tsx b/packages/core/src/components/Status/Status.stories.tsx new file mode 100644 index 0000000000..91ef53d7fe --- /dev/null +++ b/packages/core/src/components/Status/Status.stories.tsx @@ -0,0 +1,67 @@ +/* + * 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 from 'react'; +import { + StatusError, + StatusFailed, + StatusNA, + StatusOK, + StatusPending, + StatusRunning, + StatusWarning, +} from './Status'; + +export default { + title: 'Status', + component: StatusOK, +}; + +export const statusOK = () => ( + <> + Status OK + +); +export const statusWarning = () => ( + <> + Status Warning + +); +export const statusError = () => ( + <> + Status Error + +); +export const statusFailed = () => ( + <> + Status Failed + +); +export const statusPending = () => ( + <> + Status Pending + +); +export const statusRunning = () => ( + <> + Status Running + +); +export const statusNA = () => ( + <> + Status NA + +); diff --git a/packages/core/src/components/WarningPanel/WarningPanel.js b/packages/core/src/components/WarningPanel/WarningPanel.js new file mode 100644 index 0000000000..679f7511d8 --- /dev/null +++ b/packages/core/src/components/WarningPanel/WarningPanel.js @@ -0,0 +1,80 @@ +/* + * 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Typography, withStyles } from '@material-ui/core'; +import ErrorOutline from '@material-ui/icons/ErrorOutline'; + +const errorOutlineStyles = theme => ({ + root: { + marginRight: theme.spacing(1), + fill: theme.palette.warningText, + }, +}); +const ErrorOutlineStyled = withStyles(errorOutlineStyles)(ErrorOutline); + +const styles = theme => ({ + message: { + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(1.5), + backgroundColor: theme.palette.warningBackground, + color: theme.palette.warningText, + verticalAlign: 'middle', + }, + header: { + display: 'flex', + flexDirection: 'row', + marginBottom: theme.spacing(1), + }, + headerText: { + color: theme.palette.warningText, + }, + messageText: { + color: theme.palette.warningText, + }, +}); + +/** + * WarningPanel. Show a user friendly error message to a user similar to ErrorPanel except that the warning panel + * only shows the warning message to the user + */ +class WarningPanel extends Component { + static propTypes = { + message: PropTypes.node.isRequired, + }; + + render() { + const { classes, title, message, children } = this.props; + return ( +
+
+ + + {title} + +
+ {message && ( + {message} + )} + {children} +
+ ); + } +} + +export default withStyles(styles)(WarningPanel); diff --git a/packages/core/src/components/WarningPanel/WarningPanel.stories.tsx b/packages/core/src/components/WarningPanel/WarningPanel.stories.tsx new file mode 100644 index 0000000000..2fd5395db5 --- /dev/null +++ b/packages/core/src/components/WarningPanel/WarningPanel.stories.tsx @@ -0,0 +1,45 @@ +/* + * 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 from 'react'; +import WarningPanel from '.'; +import { Link, Button } from '@material-ui/core'; + +export default { + title: 'Warning Panel', + component: WarningPanel, +}; + +export const Default = () => ( + + This example entity is missing something. If this is unexpected, please + make sure you have set up everything correctly by following{' '} + this guide. + + } + /> +); + +export const Children = () => ( + + + +); diff --git a/packages/core/src/components/WarningPanel/WarningPanel.test.js b/packages/core/src/components/WarningPanel/WarningPanel.test.js new file mode 100644 index 0000000000..4094c65a1f --- /dev/null +++ b/packages/core/src/components/WarningPanel/WarningPanel.test.js @@ -0,0 +1,40 @@ +/* + * 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 from 'react'; +import { render } from '@testing-library/react'; +import { wrapInThemedTestApp } from '@backstage/test-utils'; + +import WarningPanel from './WarningPanel'; + +const minProps = { title: 'Mock title', message: 'Some more info' }; + +describe('', () => { + it('renders without exploding', () => { + const { getByText } = render( + wrapInThemedTestApp(), + ); + expect(getByText('Mock title')).toBeInTheDocument(); + }); + + it('renders message and children', () => { + const { getByText } = render( + wrapInThemedTestApp(children), + ); + expect(getByText('Some more info')).toBeInTheDocument(); + expect(getByText('children')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/components/WarningPanel/index.js b/packages/core/src/components/WarningPanel/index.js new file mode 100644 index 0000000000..704453fb47 --- /dev/null +++ b/packages/core/src/components/WarningPanel/index.js @@ -0,0 +1,16 @@ +/* + * 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 { default } from './WarningPanel'; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 740a4cc803..9153b9fa3b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,3 +33,4 @@ export { default as SupportButton } from './components/SupportButton'; export { default as SortableTable } from './components/SortableTable'; export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular'; export * from './components/Status'; +export { default as WarningPanel } from './components/WarningPanel'; diff --git a/packages/core/src/layout/InfoCard/BottomLink.test.tsx b/packages/core/src/layout/BottomLink/BottomLink.test.tsx similarity index 100% rename from packages/core/src/layout/InfoCard/BottomLink.test.tsx rename to packages/core/src/layout/BottomLink/BottomLink.test.tsx diff --git a/packages/core/src/layout/InfoCard/BottomLink.tsx b/packages/core/src/layout/BottomLink/BottomLink.tsx similarity index 100% rename from packages/core/src/layout/InfoCard/BottomLink.tsx rename to packages/core/src/layout/BottomLink/BottomLink.tsx index 6721eda0f1..9250685504 100644 --- a/packages/core/src/layout/InfoCard/BottomLink.tsx +++ b/packages/core/src/layout/BottomLink/BottomLink.tsx @@ -14,18 +14,18 @@ * limitations under the License. */ +import React, { FC } from 'react'; import { - Divider, Link, ListItem, ListItemIcon, + Divider, ListItemText, makeStyles, } from '@material-ui/core'; -import Box from '@material-ui/core/Box'; -import grey from '@material-ui/core/colors/grey'; import ArrowIcon from '@material-ui/icons/ArrowForward'; -import React, { FC } from 'react'; +import grey from '@material-ui/core/colors/grey'; +import Box from '@material-ui/core/Box'; const useStyles = makeStyles(theme => ({ root: { diff --git a/packages/core/src/layout/BottomLink/index.ts b/packages/core/src/layout/BottomLink/index.ts new file mode 100644 index 0000000000..5d4845064b --- /dev/null +++ b/packages/core/src/layout/BottomLink/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 { default, Props } from './BottomLink'; diff --git a/packages/core/src/layout/HeaderLabel/OwnerHeaderLabel.js b/packages/core/src/layout/HeaderLabel/OwnerHeaderLabel.js deleted file mode 100644 index abe0ee8b33..0000000000 --- a/packages/core/src/layout/HeaderLabel/OwnerHeaderLabel.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { Tooltip, Link, withStyles } from '@material-ui/core'; - -import { StatusError } from 'components/Status'; -import HeaderLabel from './HeaderLabel'; - -const style = theme => ({ - notVerified: { - color: theme.palette.status.error, - borderRadius: 4, - padding: '3px 6px', - fontSize: '8pt', - opacity: 0.8, - fontWeight: 'bold', - position: 'relative', - top: -4, - backgroundColor: 'pink', - float: 'right', - marginLeft: 14, - }, - label: { float: 'left' }, -}); - -class OwnerHeaderLabel extends Component { - static propTypes = { - owner: PropTypes.object.isRequired, - }; - - render() { - const { owner, classes } = this.props; - const isBadSquad = owner.type !== 'squad'; - - const notVerified = isBadSquad && ( - - - Squad not - verified! - - - ); - const label = ( - - - {owner.name} - - - ); - return ( - <> - - {notVerified} - - ); - } -} - -export default withStyles(style)(OwnerHeaderLabel); diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index caa5f16513..f3d03d29c8 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -25,7 +25,7 @@ import { makeStyles, } from '@material-ui/core'; import ErrorBoundary from 'layout/ErrorBoundary/ErrorBoundary'; -import BottomLink, { Props as BottomLinkProps } from './BottomLink'; +import BottomLink, { Props as BottomLinkProps } from '../BottomLink'; const useStyles = makeStyles(theme => ({ header: { diff --git a/packages/core/src/layout/Sidebar/Bar.tsx b/packages/core/src/layout/Sidebar/Bar.tsx index 23ac14b6a4..4cf3db63ab 100644 --- a/packages/core/src/layout/Sidebar/Bar.tsx +++ b/packages/core/src/layout/Sidebar/Bar.tsx @@ -18,8 +18,9 @@ import { makeStyles } from '@material-ui/core'; import clsx from 'clsx'; import React, { FC, useRef, useState } from 'react'; import { sidebarConfig, SidebarContext } from './config'; +import { BackstageTheme } from '@backstage/theme'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles(theme => ({ root: { zIndex: 1000, position: 'relative', @@ -35,7 +36,7 @@ const useStyles = makeStyles(theme => ({ top: 0, bottom: 0, padding: 0, - background: '#171717', + background: theme.palette.sidebar, overflowX: 'hidden', width: sidebarConfig.drawerWidthClosed, transition: theme.transitions.create('width', { diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx new file mode 100644 index 0000000000..d670b0b908 --- /dev/null +++ b/packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx @@ -0,0 +1,77 @@ +/* + * 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, { useState } from 'react'; +import { TabbedCard, CardTab } from '.'; + +export default { + title: 'Tabbed Card', + component: TabbedCard, +}; + +export const Default = () => { + return ( + + some content 1 + some content 2 + some content 3 + some content 4 + + ); +}; + +const linkInfo = { title: 'Go to XYZ Location', link: '#' }; + +export const WithFooterLink = () => { + return ( + + some content 1 + some content 2 + some content 3 + some content 4 + + ); +}; + +export const WithControlledTabValue = () => { + const [selectedTab, setSelectedTab] = useState('one'); + + const handleChange = (_ev, newSelectedTab) => setSelectedTab(newSelectedTab); + + return ( + <> + Selected tab is {selectedTab} + + + + some content 1 + + + some content 2 + + + some content 3 + + + some content 4 + + + + ); +}; diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx new file mode 100644 index 0000000000..ceeae8d49d --- /dev/null +++ b/packages/core/src/layout/TabbedCard/TabbedCard.test.tsx @@ -0,0 +1,105 @@ +/* + * 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 from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import { wrapInTestApp } from '@backstage/test-utils'; +import { TabbedCard, CardTab } from '.'; + +const minProps = { + title: 'Some title', + deepLink: { + title: 'A deepLink title', + link: '/mocked', + }, +}; + +describe('', () => { + it('renders without exploding', () => { + const rendered = render( + wrapInTestApp( + + Test Content + Test Content + , + ), + ); + expect(rendered.getByText('Some title')).toBeInTheDocument(); + }); + + it('renders a deepLink when prop is set', () => { + const rendered = render( + wrapInTestApp( + + Test Content + Test Content + , + ), + ); + expect(rendered.getByText('A deepLink title')).toBeInTheDocument(); + }); + + it('switches tabs when clicking', () => { + const rendered = render( + wrapInTestApp( + + Test Content 1 + Test Content 2 + , + ), + ); + expect(rendered.getByText('Test Content 1')).toBeInTheDocument(); + + fireEvent.click(rendered.getByText('Test 2')); + expect(rendered.getByText('Test Content 2')).toBeInTheDocument(); + }); + + it('switches tabs when clicking in controlled mode', () => { + let selectedTab = 'one'; + + const handleTabChange = jest.fn( + (_ev, newSelectedTab) => (selectedTab = newSelectedTab), + ); + + const rendered = render( + wrapInTestApp( + + + Test Content 1 + + + Test Content 2 + + , + ), + ); + expect(rendered.getByText('Test Content 1')).toBeInTheDocument(); + + fireEvent.click(rendered.getByText('Test 2')); + expect(handleTabChange.mock.calls.length).toBe(1); + rendered.rerender( + + + Test Content 1 + + + Test Content 2 + + , + ); + expect(rendered.getByText('Test Content 2')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/layout/TabbedCard/TabbedCard.tsx b/packages/core/src/layout/TabbedCard/TabbedCard.tsx new file mode 100644 index 0000000000..bd7443e924 --- /dev/null +++ b/packages/core/src/layout/TabbedCard/TabbedCard.tsx @@ -0,0 +1,127 @@ +/* + * 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, useState, ReactElement, ReactNode } from 'react'; +import { + Card, + CardContent, + CardHeader, + Divider, + withStyles, + makeStyles, + Tabs, + Tab, + TabProps, +} from '@material-ui/core'; +import BottomLink, { Props as BottomLinkProps } from '../BottomLink'; +import ErrorBoundary from '../ErrorBoundary/ErrorBoundary'; + +const useTabsStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(0, 2, 0, 2.5), + minHeight: theme.spacing(3), + }, + indicator: { + backgroundColor: theme.palette.info.main, + height: theme.spacing(0.3), + }, +})); + +const BoldHeader = withStyles(theme => ({ + root: { padding: theme.spacing(2, 2, 2, 2.5), display: 'inline-block' }, + title: { fontWeight: 700 }, + subheader: { paddingTop: theme.spacing(1) }, +}))(CardHeader); + +type Props = { + slackChannel?: string; + children?: ReactElement[]; + onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void; + title?: string; + value?: number | string; + deepLink?: BottomLinkProps; +}; + +const TabbedCard: FC = ({ + slackChannel = '#backstage', + children, + title, + deepLink, + value, + onChange, +}) => { + const tabsClasses = useTabsStyles(); + const [selectedIndex, selectIndex] = useState(0); + + const handleChange = onChange + ? onChange + : (_ev, newSelectedIndex: number) => selectIndex(newSelectedIndex); + + let selectedTabContent: ReactNode; + if (!value) { + React.Children.map(children, (child, index) => { + if (index === selectedIndex) selectedTabContent = child?.props.children; + }); + } else { + React.Children.map(children, child => { + if (child?.props.value === value) + selectedTabContent = child?.props.children; + }); + } + + return ( + + + {title && } + + {children} + + + {selectedTabContent} + {deepLink && } + + + ); +}; + +const useCardTabStyles = makeStyles(theme => ({ + root: { + minWidth: theme.spacing(6), + minHeight: theme.spacing(3), + margin: theme.spacing(0, 2, 0, 0), + padding: theme.spacing(0.5, 0, 0.5, 0), + textTransform: 'none', + }, + selected: { + fontWeight: 'bold', + }, +})); + +type CardTabProps = TabProps & { + children: ReactNode; +}; + +const CardTab: FC = ({ children, ...props }) => { + const classes = useCardTabStyles(); + + return ; +}; + +export { TabbedCard, CardTab }; diff --git a/packages/core/src/layout/TabbedCard/index.ts b/packages/core/src/layout/TabbedCard/index.ts new file mode 100644 index 0000000000..f5019f2451 --- /dev/null +++ b/packages/core/src/layout/TabbedCard/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 * from './TabbedCard'; diff --git a/packages/storybook/.storybook/config.js b/packages/storybook/.storybook/config.js new file mode 100644 index 0000000000..b083bc0e9e --- /dev/null +++ b/packages/storybook/.storybook/config.js @@ -0,0 +1,10 @@ +import React from 'react'; +import { addDecorator } from '@storybook/react'; +import { BackstageTheme } from '@backstage/theme'; +import { CssBaseline, ThemeProvider } from '@material-ui/core'; + +addDecorator(story => ( + + {story()} + +)); diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 55d731c8e6..f086fa0d4a 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -5,8 +5,16 @@ module.exports = { '../../core/src/layout/**/*.stories.tsx', '../../core/src/components/**/*.stories.tsx', ], - addons: ['@storybook/addon-actions', '@storybook/addon-links'], + addons: [ + '@storybook/addon-actions', + '@storybook/addon-links', + '@storybook/addon-storysource', + ], webpackFinal: async config => { + config.resolve.alias = { + ...config.resolve.alias, + '@backstage/theme': path.resolve(__dirname, '../../theme/src'), + }; config.resolve.modules.push(path.resolve(__dirname, '../../core/src')); config.module.rules.push( { diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 4dbf2990fa..8b54b2b8e1 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "description": "Storybook build for core package", "private": true, "scripts": { @@ -15,6 +15,7 @@ "devDependencies": { "@storybook/addon-actions": "^5.3.17", "@storybook/addon-links": "^5.3.17", + "@storybook/addon-storysource": "^5.3.18", "@storybook/addons": "^5.3.17", "@storybook/react": "^5.3.17" } diff --git a/packages/test-utils-core/.npmrc b/packages/test-utils-core/.npmrc new file mode 100644 index 0000000000..214c29d139 --- /dev/null +++ b/packages/test-utils-core/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org/ diff --git a/packages/test-utils-core/README.md b/packages/test-utils-core/README.md new file mode 100644 index 0000000000..b99250857d --- /dev/null +++ b/packages/test-utils-core/README.md @@ -0,0 +1,15 @@ +# @backstage/test-utils-core + +This package provides utilities for testing the Backstage core packages. + +## Installation + +This package should not be used directly, use `@backstage/test-utils` instead. All exports from this +package are re-exported by `@backstage/test-utils`. + +The reason this package exists is to allow the Backstage core packages to use the testing utils exposed in this package. Since `@backstage/test-utils` needs to depend on `@backstage/core`, core is not able to use those test-utils. We put any test-utils that don't need to depend on other Backstage packages in this package, in order for them to be usable by any other `@backstage` packages. + +## Documentation + +- [Backstage Readme](https://github.com/spotify/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/spotify/backstage/blob/master/docs/README.md) diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json new file mode 100644 index 0000000000..569622ed1c --- /dev/null +++ b/packages/test-utils-core/package.json @@ -0,0 +1,34 @@ +{ + "name": "@backstage/test-utils-core", + "description": "Utilities to test Backstage core", + "version": "0.1.1-alpha.4", + "private": false, + "publishConfig": { + "access": "public" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/spotify/backstage", + "directory": "packages/test-utils-core" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "backstage-cli plugin:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test" + }, + "dependencies": { + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@types/jest": "^24.0.0", + "@types/node": "^12.0.0", + "react": "^16.12.0", + "react-dom": "^16.12.0" + } +} diff --git a/packages/test-utils-core/src/index.ts b/packages/test-utils-core/src/index.ts new file mode 100644 index 0000000000..43faf28a4a --- /dev/null +++ b/packages/test-utils-core/src/index.ts @@ -0,0 +1,16 @@ +/* + * 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 * from './testUtils'; diff --git a/packages/test-utils-core/src/setupTests.ts b/packages/test-utils-core/src/setupTests.ts new file mode 100644 index 0000000000..8925258421 --- /dev/null +++ b/packages/test-utils-core/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/extend-expect'; diff --git a/packages/test-utils/src/testUtils/Keyboard.js b/packages/test-utils-core/src/testUtils/Keyboard.js similarity index 99% rename from packages/test-utils/src/testUtils/Keyboard.js rename to packages/test-utils-core/src/testUtils/Keyboard.js index e3225f2342..8f4a48b0b4 100644 --- a/packages/test-utils/src/testUtils/Keyboard.js +++ b/packages/test-utils-core/src/testUtils/Keyboard.js @@ -23,7 +23,7 @@ const codes = { Esc: 27, }; -export default class Keyboard { +export class Keyboard { static async type(target, input) { await new Keyboard(target).type(input); } diff --git a/packages/test-utils/src/testUtils/Keyboard.test.js b/packages/test-utils-core/src/testUtils/Keyboard.test.js similarity index 98% rename from packages/test-utils/src/testUtils/Keyboard.test.js rename to packages/test-utils-core/src/testUtils/Keyboard.test.js index 31e0885dc1..48b8522c0c 100644 --- a/packages/test-utils/src/testUtils/Keyboard.test.js +++ b/packages/test-utils-core/src/testUtils/Keyboard.test.js @@ -15,7 +15,7 @@ */ import React from 'react'; -import Keyboard from './Keyboard'; +import { Keyboard } from './Keyboard'; import { render } from '@testing-library/react'; describe('testUtils.Keyboard', () => { diff --git a/packages/test-utils-core/src/testUtils/index.tsx b/packages/test-utils-core/src/testUtils/index.tsx new file mode 100644 index 0000000000..7558f32819 --- /dev/null +++ b/packages/test-utils-core/src/testUtils/index.tsx @@ -0,0 +1,19 @@ +/* + * 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 * from './Keyboard'; +export * from './logCollector'; +export * from './testingLibrary'; diff --git a/packages/test-utils-core/src/testUtils/logCollector.test.ts b/packages/test-utils-core/src/testUtils/logCollector.test.ts new file mode 100644 index 0000000000..2c1cd16806 --- /dev/null +++ b/packages/test-utils-core/src/testUtils/logCollector.test.ts @@ -0,0 +1,96 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +import { withLogCollector } from './logCollector'; + +describe('logCollector', () => { + it('should collect some logs synchronously', () => { + const logs = withLogCollector(() => { + console.log('a'); + console.warn('b'); + console.error('c'); + console.error('3'); + console.warn('2'); + console.log('1'); + }); + + expect(logs.log).toEqual(['a', '1']); + expect(logs.warn).toEqual(['b', '2']); + expect(logs.error).toEqual(['c', '3']); + }); + + it('should collect some logs asynchrnously', async () => { + const logs = await withLogCollector(async () => { + console.log('a'); + console.warn('b'); + console.error('c'); + console.error('3'); + console.warn('2'); + console.log('1'); + }); + + expect(logs.log).toEqual(['a', '1']); + expect(logs.warn).toEqual(['b', '2']); + expect(logs.error).toEqual(['c', '3']); + }); + + it('should collect specific logs synchronously', () => { + const missedLogs = withLogCollector(() => { + const logs = withLogCollector(['warn', 'log'], () => { + console.log('a'); + console.warn('b'); + console.error('c'); + console.error('3'); + console.warn('2'); + console.log('1'); + }); + + expect(logs.log).toEqual(['a', '1']); + expect(logs.warn).toEqual(['b', '2']); + // @ts-ignore + expect(logs.error).toEqual([]); + }); + + expect(missedLogs.log).toEqual([]); + expect(missedLogs.warn).toEqual([]); + expect(missedLogs.error).toEqual(['c', '3']); + }); + + it('should collect specific logs asynchrnously', async () => { + const missedLogs = await withLogCollector(async () => { + const logs = await withLogCollector(['error'], async () => { + console.log('a'); + console.warn('b'); + console.error('c'); + console.error('3'); + console.warn('2'); + console.log('1'); + }); + + // @ts-ignore + expect(logs.log).toEqual([]); + // @ts-ignore + expect(logs.warn).toEqual([]); + expect(logs.error).toEqual(['c', '3']); + }); + + expect(missedLogs.log).toEqual(['a', '1']); + expect(missedLogs.warn).toEqual(['b', '2']); + expect(missedLogs.error).toEqual([]); + }); +}); diff --git a/packages/test-utils-core/src/testUtils/logCollector.ts b/packages/test-utils-core/src/testUtils/logCollector.ts new file mode 100644 index 0000000000..6938499b8f --- /dev/null +++ b/packages/test-utils-core/src/testUtils/logCollector.ts @@ -0,0 +1,111 @@ +/* + * 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. + */ + +/* eslint-disable no-console */ + +export type LogFuncs = 'log' | 'warn' | 'error'; +export type AsyncLogCollector = () => Promise; +export type SyncLogCollector = () => void; +export type LogCollector = AsyncLogCollector | SyncLogCollector; +export type CollectedLogs = { [key in T]: string[] }; + +const allCategories = ['log', 'warn', 'error']; + +// Asynchronous log collector with that collects all categories +export function withLogCollector( + callback: AsyncLogCollector, +): Promise>; + +// Synchronous log collector with that collects all categories +export function withLogCollector( + callback: SyncLogCollector, +): CollectedLogs; + +// Asynchronous log collector with that only collects selected categories +export function withLogCollector( + logsToCollect: T[], + callback: AsyncLogCollector, +): Promise>; + +// Synchronous log collector with that only collects selected categories +export function withLogCollector( + logsToCollect: T[], + callback: SyncLogCollector, +): CollectedLogs; + +export function withLogCollector( + logsToCollect: LogFuncs[] | LogCollector, + callback?: LogCollector, +): CollectedLogs | Promise> { + const oneArg = !callback; + const actualCallback = (oneArg ? logsToCollect : callback) as LogCollector; + const categories = (oneArg ? allCategories : logsToCollect) as LogFuncs[]; + + const logs = { + log: new Array(), + warn: new Array(), + error: new Array(), + }; + + const origLog = console.log; + const origWarn = console.warn; + const origError = console.error; + + if (categories.includes('log')) { + console.log = (message: string) => { + logs.log.push(message); + }; + } + if (categories.includes('warn')) { + console.warn = (message: string) => { + logs.warn.push(message); + }; + } + if (categories.includes('error')) { + console.error = (message: string) => { + logs.error.push(message); + }; + } + + const restore = () => { + console.log = origLog; + console.warn = origWarn; + console.error = origError; + }; + + try { + const ret = actualCallback(); + + if (!ret || !ret.then) { + restore(); + return logs; + } + + return ret.then( + () => { + restore(); + return logs; + }, + error => { + restore(); + throw error; + }, + ); + } catch (error) { + restore(); + throw error; + } +} diff --git a/packages/test-utils-core/src/testUtils/testingLibrary.ts b/packages/test-utils-core/src/testUtils/testingLibrary.ts new file mode 100644 index 0000000000..cf5168fff9 --- /dev/null +++ b/packages/test-utils-core/src/testUtils/testingLibrary.ts @@ -0,0 +1,35 @@ +/* + * 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 { ReactElement } from 'react'; +import { act } from 'react-dom/test-utils'; +import { render, RenderResult } from '@testing-library/react'; + +// Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async +// act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit +// cleaner, since act doesn't return the result of the evaluated function. +// https://github.com/testing-library/react-testing-library/issues/281 +// https://github.com/facebook/react/pull/14853 +export async function renderWithEffects( + nodes: ReactElement, +): Promise { + let value: RenderResult; + await act(() => { + value = render(nodes); + }); + // @ts-ignore + return value; +} diff --git a/packages/test-utils-core/tsconfig.json b/packages/test-utils-core/tsconfig.json new file mode 100644 index 0000000000..7b73db2f0f --- /dev/null +++ b/packages/test-utils-core/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src"], + "compilerOptions": { + "baseUrl": "src" + } +} diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 68d7088918..9eca38eeb5 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "private": false, "publishConfig": { "access": "public" @@ -25,8 +25,8 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", @@ -39,6 +39,7 @@ "react-router-dom": "^5.1.2" }, "peerDependencies": { + "@backstage/test-utils-core": "^0.1.1-alpha.3", "@backstage/theme": "^0.1.1-alpha.3", "@material-ui/core": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 43faf28a4a..57b7fd6773 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './testUtils'; +export * from '@backstage/test-utils-core'; diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx new file mode 100644 index 0000000000..e2f1b6b7cb --- /dev/null +++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 from 'react'; +import { render } from '@testing-library/react'; +import { wrapInTestApp } from './appWrappers'; +import { Route } from 'react-router'; + +describe('wrapInTestApp', () => { + it('should provide routing', () => { + const rendered = render( + wrapInTestApp( + <> + Route 1 + Route 2 + , + ['/route2'], + ), + ); + expect(rendered.getByText('Route 2')).toBeInTheDocument(); + }); +}); diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx new file mode 100644 index 0000000000..93f92ec537 --- /dev/null +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -0,0 +1,53 @@ +/* + * 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, { ComponentType, ReactNode, FunctionComponent } from 'react'; +import { ThemeProvider } from '@material-ui/core'; +import { MemoryRouter } from 'react-router'; +import { Route } from 'react-router-dom'; +import { BackstageTheme } from '@backstage/theme'; + +export function wrapInTestApp( + Component: ComponentType | ReactNode, + initialRouterEntries: string[] = ['/'], +) { + let Wrapper: ComponentType; + if (Component instanceof Function) { + Wrapper = Component; + } else { + Wrapper = (() => Component) as FunctionComponent; + } + + return ( + + + + ); +} + +export function wrapInThemedTestApp( + component: ReactNode, + initialRouterEntries: string[] = ['/'], +) { + const themed = ( + {component} + ); + return wrapInTestApp(themed, initialRouterEntries); +} + +export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => ( + {component} +); diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx index d9a4389ac8..ea33de2f31 100644 --- a/packages/test-utils/src/testUtils/index.tsx +++ b/packages/test-utils/src/testUtils/index.tsx @@ -14,67 +14,5 @@ * limitations under the License. */ -import React, { - ComponentType, - ReactNode, - FunctionComponent, - ReactElement, -} from 'react'; -import { ThemeProvider } from '@material-ui/core'; -import { act } from 'react-dom/test-utils'; -import { render, RenderResult } from '@testing-library/react'; -import { MemoryRouter } from 'react-router'; -import { Route } from 'react-router-dom'; -import { BackstageTheme } from '@backstage/theme'; - -export { default as Keyboard } from './Keyboard'; export { default as mockBreakpoint } from './mockBreakpoint'; -export * from './logCollector'; - -export function wrapInTestApp( - Component: ComponentType | ReactNode, - initialRouterEntries: string[] = ['/'], -) { - let Wrapper: ComponentType; - if (Component instanceof Function) { - Wrapper = Component; - } else { - Wrapper = (() => Component) as FunctionComponent; - } - - return ( - - - - ); -} - -export function wrapInThemedTestApp( - component: ReactNode, - initialRouterEntries: string[] = ['/'], -) { - const themed = ( - {component} - ); - return wrapInTestApp(themed, initialRouterEntries); -} - -export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => ( - {component} -); - -// Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async -// act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit -// cleaner, since act doesn't return the result of the evaluated function. -// https://github.com/testing-library/react-testing-library/issues/281 -// https://github.com/facebook/react/pull/14853 -export async function renderWithEffects( - nodes: ReactElement, -): Promise { - let value: RenderResult; - await act(() => { - value = render(nodes); - }); - // @ts-ignore - return value; -} +export * from './appWrappers'; diff --git a/packages/test-utils/src/testUtils/logCollector.js b/packages/test-utils/src/testUtils/logCollector.js deleted file mode 100644 index c3ffa525b3..0000000000 --- a/packages/test-utils/src/testUtils/logCollector.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - */ - -/* eslint-disable no-console */ -/* eslint-disable no-param-reassign */ - -// If the callback function is async this one will be too. -export function withLogCollector(logsToCollect, callback) { - if (typeof logsToCollect === 'function') { - callback = logsToCollect; - logsToCollect = ['log', 'warn', 'error']; - } - const logs = { - log: [], - warn: [], - error: [], - }; - - const origLog = console.log; - const origWarn = console.warn; - const origError = console.error; - - if (logsToCollect.includes('log')) { - console.log = message => { - logs.log.push(message); - }; - } - if (logsToCollect.includes('warn')) { - console.warn = message => { - logs.warn.push(message); - }; - } - if (logsToCollect.includes('error')) { - console.error = message => { - logs.error.push(message); - }; - } - - const restore = () => { - console.log = origLog; - console.warn = origWarn; - console.error = origError; - }; - - try { - const ret = callback(); - - if (!ret || !ret.then) { - restore(); - return logs; - } - - return ret.then( - () => { - restore(); - return logs; - }, - error => { - restore(); - throw error; - }, - ); - } catch (error) { - restore(); - throw error; - } -} diff --git a/packages/theme/package.json b/packages/theme/package.json index 09c9ce2089..6504d7dea5 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "private": false, "publishConfig": { "access": "public" @@ -24,7 +24,7 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1" }, "peerDependencies": { diff --git a/packages/theme/src/BackstageTheme.ts b/packages/theme/src/BackstageTheme.ts index 202bdbc08c..4f61437400 100644 --- a/packages/theme/src/BackstageTheme.ts +++ b/packages/theme/src/BackstageTheme.ts @@ -20,10 +20,11 @@ import { blue, yellow } from '@material-ui/core/colors'; import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types'; -export const COLORS = { +const COLORS = { PAGE_BACKGROUND: '#F8F8F8', DEFAULT_PAGE_THEME_COLOR: '#7C3699', DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2', + SIDEBAR_BACKGROUND_COLOR: '#171717', ERROR_BACKGROUND_COLOR: '#FFEBEE', ERROR_TEXT_COLOR: '#CA001B', INFO_TEXT_COLOR: '#004e8a', @@ -86,6 +87,7 @@ const extendedThemeConfig: BackstageMuiThemeOptions = { linkHover: COLORS.LINK_TEXT_HOVER, link: COLORS.LINK_TEXT, gold: yellow.A700, + sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR, }, navigation: { width: 220, diff --git a/packages/theme/src/BackstageThemeDark.ts b/packages/theme/src/BackstageThemeDark.ts index b8b89c8f0a..9952a5eeaa 100644 --- a/packages/theme/src/BackstageThemeDark.ts +++ b/packages/theme/src/BackstageThemeDark.ts @@ -20,11 +20,11 @@ import { blue, yellow } from '@material-ui/core/colors'; import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types'; -export const COLORS = { +const COLORS = { PAGE_BACKGROUND: '#282828', - EFAULT_PAGE_THEME_COLOR: '#232323', DEFAULT_PAGE_THEME_COLOR: '#7C3699', DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2', + SIDEBAR_BACKGROUND_COLOR: '#424242', ERROR_BACKGROUND_COLOR: '#FFEBEE', ERROR_TEXT_COLOR: '#CA001B', INFO_TEXT_COLOR: '#004e8a', @@ -91,6 +91,7 @@ const extendedThemeConfig: BackstageMuiThemeOptions = { linkHover: COLORS.LINK_TEXT_HOVER, link: COLORS.LINK_TEXT, gold: yellow.A700, + sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR, }, navigation: { width: 220, diff --git a/packages/theme/src/BackstageThemeLight.ts b/packages/theme/src/BackstageThemeLight.ts index 14bc942910..a0a063ad24 100644 --- a/packages/theme/src/BackstageThemeLight.ts +++ b/packages/theme/src/BackstageThemeLight.ts @@ -20,10 +20,11 @@ import { blue, yellow } from '@material-ui/core/colors'; import { BackstageMuiTheme, BackstageMuiThemeOptions } from './types'; -export const COLORS = { +const COLORS = { PAGE_BACKGROUND: '#F8F8F8', DEFAULT_PAGE_THEME_COLOR: '#7C3699', DEFAULT_PAGE_THEME_LIGHT_COLOR: '#ECDBF2', + SIDEBAR_BACKGROUND_COLOR: '#171717', ERROR_BACKGROUND_COLOR: '#FFEBEE', ERROR_TEXT_COLOR: '#CA001B', INFO_TEXT_COLOR: '#004e8a', @@ -89,6 +90,7 @@ const extendedThemeConfig: BackstageMuiThemeOptions = { linkHover: COLORS.LINK_TEXT_HOVER, link: COLORS.LINK_TEXT, gold: yellow.A700, + sidebar: COLORS.SIDEBAR_BACKGROUND_COLOR, }, navigation: { width: 220, diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts index 136355dc37..8aa7e30ef5 100644 --- a/packages/theme/src/index.ts +++ b/packages/theme/src/index.ts @@ -15,4 +15,4 @@ */ export { default as BackstageThemeLight } from './BackstageThemeLight'; export { default as BackstageThemeDark } from './BackstageThemeDark'; -export { default as BackstageTheme, COLORS } from './BackstageTheme'; +export { default as BackstageTheme } from './BackstageTheme'; diff --git a/packages/theme/src/types.ts b/packages/theme/src/types.ts index 9a3e93c3a9..cbd3de4efa 100644 --- a/packages/theme/src/types.ts +++ b/packages/theme/src/types.ts @@ -38,6 +38,7 @@ export type BackstageMuiPalette = Theme['palette'] & { linkHover: string; link: string; gold: string; + sidebar: string; bursts: { fontColor: string; slackChannelText: string; diff --git a/plugins/home-page/package.json b/plugins/home-page/package.json index d87851babe..65fb3cf9da 100644 --- a/plugins/home-page/package.json +++ b/plugins/home-page/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-page", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -12,9 +12,9 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/core": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^4.2.4", diff --git a/plugins/home-page/src/index.ts b/plugins/home-page/src/index.ts index 0b2dc1b524..3a0a0fe2d3 100644 --- a/plugins/home-page/src/index.ts +++ b/plugins/home-page/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './plugin'; +export { plugin } from './plugin'; diff --git a/plugins/home-page/src/plugin.test.ts b/plugins/home-page/src/plugin.test.ts index 76c1fb7a6c..aea6b90d2a 100644 --- a/plugins/home-page/src/plugin.test.ts +++ b/plugins/home-page/src/plugin.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import plugin from './plugin'; +import { plugin } from './plugin'; describe('home-page', () => { it('should export plugin', () => { diff --git a/plugins/home-page/src/plugin.ts b/plugins/home-page/src/plugin.ts index c3b4d30fe3..7de760a849 100644 --- a/plugins/home-page/src/plugin.ts +++ b/plugins/home-page/src/plugin.ts @@ -17,7 +17,7 @@ import { createPlugin } from '@backstage/core'; import HomePage from 'components/HomePage'; -export default createPlugin({ +export const plugin = createPlugin({ id: 'home-page', register({ router }) { router.registerRoute('/home', HomePage); diff --git a/plugins/lighthouse/README.md b/plugins/lighthouse/README.md index 29439b9772..d457662377 100644 --- a/plugins/lighthouse/README.md +++ b/plugins/lighthouse/README.md @@ -7,7 +7,7 @@ A frontend for [lighthouse-audit-service](https://github.com/spotify/lighthouse- ### Use cases Google's [Lighthouse](https://developers.google.com/web/tools/lighthouse) auditing tool for websites -is a great open-source resource forbenchmarking and improving the accessibility, performance, SEO, and best practices of your site. +is a great open-source resource for benchmarking and improving the accessibility, performance, SEO, and best practices of your site. At Spotify, we keep track of Lighthouse audit scores over time to look at trends and overall areas for investment. This plugin allows you to generate on-demand Lighthouse audits for websites, and to track the trends for the diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index e53ff85546..442211a38c 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "license": "Apache-2.0", @@ -17,10 +17,10 @@ "react-sparklines": "^1.7.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/core": "^0.1.1-alpha.3", - "@backstage/test-utils": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.4", + "@backstage/test-utils": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", "@material-ui/core": "^4.9.1", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/lighthouse/src/components/CategoryTrendline/index.tsx b/plugins/lighthouse/src/components/CategoryTrendline/index.tsx index 95622ed918..a46aff52e5 100644 --- a/plugins/lighthouse/src/components/CategoryTrendline/index.tsx +++ b/plugins/lighthouse/src/components/CategoryTrendline/index.tsx @@ -15,22 +15,28 @@ */ import React, { FC } from 'react'; import { Sparklines, SparklinesLine, SparklinesProps } from 'react-sparklines'; -import { COLORS } from '@backstage/theme'; +import { useTheme } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; -function color(data: number[]): string | undefined { +function color( + data: number[], + theme: typeof BackstageTheme, +): string | undefined { const lastNum = data[data.length - 1]; if (!lastNum) return undefined; - if (lastNum >= 0.9) return COLORS.STATUS.OK; - if (lastNum >= 0.5) return COLORS.STATUS.WARNING; - return COLORS.STATUS.ERROR; + if (lastNum >= 0.9) return theme.palette.status.ok; + if (lastNum >= 0.5) return theme.palette.status.warning; + return theme.palette.status.error; } const CategoryTrendline: FC = props => { + const theme = useTheme(); + if (!props.data) return null; return ( {props.title && {props.title}} - + ); }; diff --git a/plugins/lighthouse/src/index.ts b/plugins/lighthouse/src/index.ts index 1ef6bc4339..d67bc6a864 100644 --- a/plugins/lighthouse/src/index.ts +++ b/plugins/lighthouse/src/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { default } from './plugin'; +export { plugin } from './plugin'; export * from './api'; diff --git a/plugins/lighthouse/src/plugin.test.ts b/plugins/lighthouse/src/plugin.test.ts index 58ea41bd60..70b1844ec2 100644 --- a/plugins/lighthouse/src/plugin.test.ts +++ b/plugins/lighthouse/src/plugin.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import plugin from './plugin'; +import { plugin } from './plugin'; describe('lighthouse', () => { it('should export plugin', () => { diff --git a/plugins/lighthouse/src/plugin.ts b/plugins/lighthouse/src/plugin.ts index 4d138a8ab5..f8da8d84ed 100644 --- a/plugins/lighthouse/src/plugin.ts +++ b/plugins/lighthouse/src/plugin.ts @@ -19,7 +19,7 @@ import AuditList from './components/AuditList'; import AuditView from './components/AuditView'; import CreateAudit from './components/CreateAudit'; -export default createPlugin({ +export const plugin = createPlugin({ id: 'lighthouse', register({ router }) { router.registerRoute('/lighthouse', AuditList); diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index beab06746e..86cb0e3f45 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.1.1-alpha.3", + "version": "0.1.1-alpha.4", "main": "dist/index.cjs.js", "types": "dist/index.d.ts", "private": true, @@ -12,9 +12,9 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.3", - "@backstage/core": "^0.1.1-alpha.3", - "@backstage/theme": "^0.1.1-alpha.3", + "@backstage/cli": "^0.1.1-alpha.4", + "@backstage/core": "^0.1.1-alpha.4", + "@backstage/theme": "^0.1.1-alpha.4", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", diff --git a/plugins/welcome/src/index.ts b/plugins/welcome/src/index.ts index 0b2dc1b524..3a0a0fe2d3 100644 --- a/plugins/welcome/src/index.ts +++ b/plugins/welcome/src/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { default } from './plugin'; +export { plugin } from './plugin'; diff --git a/plugins/welcome/src/plugin.test.ts b/plugins/welcome/src/plugin.test.ts index f935f03edb..d60c73ec68 100644 --- a/plugins/welcome/src/plugin.test.ts +++ b/plugins/welcome/src/plugin.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import plugin from './plugin'; +import { plugin } from './plugin'; describe('welcome', () => { it('should export plugin', () => { diff --git a/plugins/welcome/src/plugin.ts b/plugins/welcome/src/plugin.ts index 740c6d2da3..fbc1d14256 100644 --- a/plugins/welcome/src/plugin.ts +++ b/plugins/welcome/src/plugin.ts @@ -17,7 +17,7 @@ import { createPlugin } from '@backstage/core'; import WelcomePage from 'components/WelcomePage'; -export default createPlugin({ +export const plugin = createPlugin({ id: 'welcome', register({ router, featureFlags }) { router.registerRoute('/', WelcomePage); diff --git a/yarn.lock b/yarn.lock index acf9de9c97..22ba268f8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2901,6 +2901,25 @@ qs "^6.6.0" ts-dedent "^1.1.0" +"@storybook/addon-storysource@^5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/addon-storysource/-/addon-storysource-5.3.18.tgz#003374d76c0898c3b74aa4cd008f444eb6b63c68" + integrity sha512-uMSu505rbcNErAYngV5raVRQ/eMiOtGNA88WxNzD/8h/0EVpEXlBAewiGzAmooNhCa/3O3iPuwka8Cky6aWPOA== + dependencies: + "@storybook/addons" "5.3.18" + "@storybook/components" "5.3.18" + "@storybook/router" "5.3.18" + "@storybook/source-loader" "5.3.18" + "@storybook/theming" "5.3.18" + core-js "^3.0.1" + estraverse "^4.2.0" + loader-utils "^1.2.3" + prettier "^1.16.4" + prop-types "^15.7.2" + react-syntax-highlighter "^11.0.2" + regenerator-runtime "^0.13.3" + util-deprecate "^1.0.2" + "@storybook/addons@5.3.17", "@storybook/addons@^5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-5.3.17.tgz#8efab65904040b0b8578eedc9a5772dbcbf6fa83" @@ -2914,6 +2933,19 @@ global "^4.3.2" util-deprecate "^1.0.2" +"@storybook/addons@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-5.3.18.tgz#5cbba6407ef7a802041c5ee831473bc3bed61f64" + integrity sha512-ZQjDgTUDFRLvAiBg2d8FgPgghfQ+9uFyXQbtiGlTBLinrPCeQd7J86qiUES0fcGoohCCw0wWKtvB0WF2z1XNDg== + dependencies: + "@storybook/api" "5.3.18" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" + core-js "^3.0.1" + global "^4.3.2" + util-deprecate "^1.0.2" + "@storybook/api@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/api/-/api-5.3.17.tgz#1c0dad3309afef6b0a5585cb59c65824fb4d2721" @@ -2940,6 +2972,32 @@ telejson "^3.2.0" util-deprecate "^1.0.2" +"@storybook/api@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/api/-/api-5.3.18.tgz#95582ab90d947065e0e34ed603650a3630dcbd16" + integrity sha512-QXaccNCARHzPWOuxYndiebGWBZmwiUvRgB9ji0XTJBS3y8K0ZPb5QyuqiKPaEWUj8dBA8rzdDtkW3Yt95Namaw== + dependencies: + "@reach/router" "^1.2.1" + "@storybook/channels" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/core-events" "5.3.18" + "@storybook/csf" "0.0.1" + "@storybook/router" "5.3.18" + "@storybook/theming" "5.3.18" + "@types/reach__router" "^1.2.3" + core-js "^3.0.1" + fast-deep-equal "^2.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + prop-types "^15.6.2" + react "^16.8.3" + semver "^6.0.0" + shallow-equal "^1.1.0" + store2 "^2.7.1" + telejson "^3.2.0" + util-deprecate "^1.0.2" + "@storybook/channel-postmessage@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-5.3.17.tgz#807b6316cd0e52d9f27363d5092ad1cd896b694c" @@ -2958,6 +3016,13 @@ dependencies: core-js "^3.0.1" +"@storybook/channels@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-5.3.18.tgz#490c9eaa8292b0571c0f665052b12addf7c35f21" + integrity sha512-scP/6td/BJSEOgfN+qaYGDf3E793xye7tIw6W+sYqwg+xdMFO39wVXgVZNpQL6sLEwpJZTaPywCjC6p6ksErqQ== + dependencies: + core-js "^3.0.1" + "@storybook/client-api@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-5.3.17.tgz#fc1d247caf267ebcc6ddf957fca7e02ae752d99e" @@ -2988,6 +3053,13 @@ dependencies: core-js "^3.0.1" +"@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" + integrity sha512-RZjxw4uqZX3Yk27IirbB/pQG+wRsQSSRlKqYa8KQ5bSanm4IrcV9VA1OQbuySW9njE+CexAnakQJ/fENdmurNg== + dependencies: + core-js "^3.0.1" + "@storybook/components@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/components/-/components-5.3.17.tgz#287430fc9c5f59b1d3590b50b3c7688355b22639" @@ -3015,6 +3087,33 @@ simplebar-react "^1.0.0-alpha.6" ts-dedent "^1.1.0" +"@storybook/components@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/components/-/components-5.3.18.tgz#528f6ab1660981e948993a04b407a6fad7751589" + integrity sha512-LIN4aVCCDY7klOwtuqQhfYz4tHaMADhXEzZpij+3r8N68Inck6IJ1oo9A9umXQPsTioQi8e6FLobH1im90j/2A== + dependencies: + "@storybook/client-logger" "5.3.18" + "@storybook/theming" "5.3.18" + "@types/react-syntax-highlighter" "11.0.4" + "@types/react-textarea-autosize" "^4.3.3" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + markdown-to-jsx "^6.9.1" + memoizerific "^1.11.3" + polished "^3.3.1" + popper.js "^1.14.7" + prop-types "^15.7.2" + react "^16.8.3" + react-dom "^16.8.3" + react-focus-lock "^2.1.0" + react-helmet-async "^1.0.2" + react-popper-tooltip "^2.8.3" + react-syntax-highlighter "^11.0.2" + react-textarea-autosize "^7.1.0" + simplebar-react "^1.0.0-alpha.6" + ts-dedent "^1.1.0" + "@storybook/core-events@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.17.tgz#698ce0a36c29fe8fa04608f56ccca53aa1d31638" @@ -3022,6 +3121,13 @@ dependencies: core-js "^3.0.1" +"@storybook/core-events@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-5.3.18.tgz#e5d335f8a2c7dd46502b8f505006f1e111b46d49" + integrity sha512-uQ6NYJ5WODXK8DJ7m8y3yUAtWB3n+6XtYztjY+tdkCsLYvTYDXNS+epV+f5Hu9+gB+/Dm+b5Su4jDD+LZB2QWA== + dependencies: + core-js "^3.0.1" + "@storybook/core@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/core/-/core-5.3.17.tgz#abd09dc416f87c7954ef3615bc3f4898c93e2b45" @@ -3162,6 +3268,37 @@ qs "^6.6.0" util-deprecate "^1.0.2" +"@storybook/router@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/router/-/router-5.3.18.tgz#8ab22f1f2f7f957e78baf992030707a62289076e" + integrity sha512-6B2U2C75KTSVaCuYYgcubeJGcCSnwsXuEf50hEd5mGqWgHZfojCtGvB7Ko4X+0h8rEC+eNA4p7YBOhlUv9WNrQ== + dependencies: + "@reach/router" "^1.2.1" + "@storybook/csf" "0.0.1" + "@types/reach__router" "^1.2.3" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + qs "^6.6.0" + util-deprecate "^1.0.2" + +"@storybook/source-loader@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/source-loader/-/source-loader-5.3.18.tgz#39ba28d9664ab8204d6b04ee757772369931e7e5" + integrity sha512-oljmLt3KMu17W9FAaEgocsI6wrloWczS26SVXadzbDbL+8Tu6vTeFJSd6HcY+e4JIIKjuze0kmaVhpi6wwPbZQ== + dependencies: + "@storybook/addons" "5.3.18" + "@storybook/client-logger" "5.3.18" + "@storybook/csf" "0.0.1" + core-js "^3.0.1" + estraverse "^4.2.0" + global "^4.3.2" + loader-utils "^1.2.3" + prettier "^1.16.4" + prop-types "^15.7.2" + regenerator-runtime "^0.13.3" + "@storybook/theming@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.17.tgz#cf6278c4857229c7167faf04d5b2206bc5ee04e1" @@ -3180,6 +3317,24 @@ resolve-from "^5.0.0" ts-dedent "^1.1.0" +"@storybook/theming@5.3.18": + version "5.3.18" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-5.3.18.tgz#35e78de79d9cf8f1248af0dd1c7fa60555761312" + integrity sha512-lfFTeLoYwLMKg96N3gn0umghMdAHgJBGuk2OM8Ll84yWtdl9RGnzfiI1Fl7Cr5k95dCF7drLJlJCao1VxUkFSA== + dependencies: + "@emotion/core" "^10.0.20" + "@emotion/styled" "^10.0.17" + "@storybook/client-logger" "5.3.18" + core-js "^3.0.1" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.19" + global "^4.3.2" + memoizerific "^1.11.3" + polished "^3.3.1" + prop-types "^15.7.2" + resolve-from "^5.0.0" + ts-dedent "^1.1.0" + "@storybook/ui@5.3.17": version "5.3.17" resolved "https://registry.npmjs.org/@storybook/ui/-/ui-5.3.17.tgz#2d47617896a2d928fb79dc8a0e709cee9b57cc50" @@ -7071,7 +7226,7 @@ cyclist@^1.0.1: resolved "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@*, cypress@4.2.0, cypress@^4.2.0: +cypress@*, cypress@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/cypress/-/cypress-4.2.0.tgz#45673fb648b1a77b9a78d73e58b89ed05212d243" integrity sha512-8LdreL91S/QiTCLYLNbIjLL8Ht4fJmu/4HGLxUI20Tc7JSfqEfCmXELrRfuPT0kjosJwJJZacdSji9XSRkPKUw== @@ -15765,7 +15920,7 @@ prepend-http@^1.0.0, prepend-http@^1.0.1: resolved "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@^1.18.2, prettier@^1.19.1: +prettier@^1.16.4, prettier@^1.18.2, prettier@^1.19.1: version "1.19.1" resolved "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==