Merge branch 'master' into feature/yarn-clean

This commit is contained in:
Leonel Mendez Jimenez
2020-04-15 22:13:37 -05:00
committed by GitHub
86 changed files with 1404 additions and 357 deletions
+1 -1
View File
@@ -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.
@@ -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)
@@ -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
+1 -1
View File
@@ -5,5 +5,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.1.1-alpha.3"
"version": "0.1.1-alpha.4"
}
+7 -7
View File
@@ -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",
+2 -2
View File
@@ -24,8 +24,8 @@ describe('App', () => {
value: jest.fn(() => {
return {
matches: true,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
addListener: jest.fn(),
removeListener: jest.fn(),
};
}),
});
+2 -2
View File
@@ -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() {
+3 -4
View File
@@ -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';
+4
View File
@@ -51,6 +51,10 @@ module.exports = {
bundledDependencies: true,
},
],
'@typescript-eslint/no-unused-vars': [
'warn',
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
],
},
overrides: [
{
+1 -1
View File
@@ -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"
@@ -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);
@@ -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"
}
@@ -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"
},
@@ -1 +1 @@
export { default as WelcomePlugin } from 'plugin-welcome';
export { plugin as WelcomePlugin } from 'plugin-welcome';
@@ -1 +1 @@
export { default } from './plugin';
export { plugin } from './plugin';
@@ -1,4 +1,4 @@
import plugin from './plugin';
import { plugin } from './plugin';
describe('welcome', () => {
it('should export plugin', () => {
@@ -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);
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './plugin';
export { plugin } from './plugin';
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import plugin from './plugin';
import { plugin } from './plugin';
describe('{{ id }}', () => {
it('should export plugin', () => {
@@ -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);
-22
View File
@@ -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;
},
};
+5 -4
View File
@@ -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",
@@ -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;
@@ -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('<CircleProgress />', () => {
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);
});
});
@@ -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> = props => {
const classes = useStyles(props);
const theme = useTheme();
const { value, fractional, inverse, unit, max } = {
...defaultProps,
...props,
@@ -89,7 +90,7 @@ const CircleProgress: FC<Props> = props => {
percent={asPercentage}
strokeWidth={12}
trailWidth={12}
strokeColor={getProgressColor(asActual, inverse, max)}
strokeColor={getProgressColor(theme.palette, asActual, inverse, max)}
className={classes.circle}
/>
<div className={classes.overlay}>
@@ -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 = {
@@ -171,7 +171,7 @@ const DataTableRow = pure(({ row, columns, handleRowClick, style }) => {
* @example
* render {
* const data = [
* { id: 'buffalos', amount: 1, status: <Error />, statusValue: 2 }
* { id: 'buffalos', amount: 1, status: <Error />, statusValue: 2 },
* { id: 'milk', amount: 3, status: <Warning />, statusValue: 1 }
* ];
* const columns = [
@@ -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: <StatusError />, statusValue: 2 },
{ id: 'milk', amount: 3, status: <StatusWarning />, statusValue: 1 },
{ id: 'cheese', amount: 8, status: <StatusWarning />, statusValue: 1 },
{ id: 'bread', amount: 2, status: <StatusOK />, 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: <StatusError /> },
];
export const Default = () => (
<div style={containerStyle}>
<SortableTable orderBy="desc" data={data} columns={columns} />
</div>
);
export const WithFooter = () => (
<div style={containerStyle}>
<SortableTable
orderBy="asc"
data={data}
columns={columns}
footerData={footerData}
/>
</div>
);
@@ -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 = () => (
<>
<StatusOK /> Status OK
</>
);
export const statusWarning = () => (
<>
<StatusWarning /> Status Warning
</>
);
export const statusError = () => (
<>
<StatusError /> Status Error
</>
);
export const statusFailed = () => (
<>
<StatusFailed /> Status Failed
</>
);
export const statusPending = () => (
<>
<StatusPending /> Status Pending
</>
);
export const statusRunning = () => (
<>
<StatusRunning /> Status Running
</>
);
export const statusNA = () => (
<>
<StatusNA /> Status NA
</>
);
@@ -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 (
<div className={classes.message}>
<div className={classes.header}>
<ErrorOutlineStyled />
<Typography className={classes.headerText} variant="subtitle1">
{title}
</Typography>
</div>
{message && (
<Typography className={classes.messageText}>{message}</Typography>
)}
{children}
</div>
);
}
}
export default withStyles(styles)(WarningPanel);
@@ -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 = () => (
<WarningPanel
title="Example Warning Title"
message={
<>
This example entity is missing something. If this is unexpected, please
make sure you have set up everything correctly by following{' '}
<Link href="http://example.com">this guide</Link>.
</>
}
/>
);
export const Children = () => (
<WarningPanel title="Example Warning Title">
<Button variant="outlined" color="primary">
Supports custom children - for example this button
</Button>
</WarningPanel>
);
@@ -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('<WarningPanel />', () => {
it('renders without exploding', () => {
const { getByText } = render(
wrapInThemedTestApp(<WarningPanel {...minProps} />),
);
expect(getByText('Mock title')).toBeInTheDocument();
});
it('renders message and children', () => {
const { getByText } = render(
wrapInThemedTestApp(<WarningPanel {...minProps}>children</WarningPanel>),
);
expect(getByText('Some more info')).toBeInTheDocument();
expect(getByText('children')).toBeInTheDocument();
});
});
@@ -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';
+1
View File
@@ -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';
@@ -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: {
@@ -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';
@@ -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 && (
<Link href="https://spotify.stackenterprise.co/a/4412/23">
<span className={classes.notVerified}>
<StatusError style={{ position: 'relative', top: 2 }} /> Squad not
verified!
</span>
</Link>
);
const label = (
<Tooltip
title="This component is not owned by an existing squad. Click the badge to learn how to fix this."
placement="bottom"
>
<span>
<span className={classes.label}>{owner.name}</span>
</span>
</Tooltip>
);
return (
<>
<HeaderLabel
label="Owner"
value={isBadSquad ? label : owner.name}
url={owner.name ? `/org/${owner.name}` : ''}
/>
{notVerified}
</>
);
}
}
export default withStyles(style)(OwnerHeaderLabel);
@@ -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: {
+3 -2
View File
@@ -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<typeof BackstageTheme>(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', {
@@ -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 (
<TabbedCard title="Default Example Header">
<CardTab label="Option 1">some content 1</CardTab>
<CardTab label="Option 2">some content 2</CardTab>
<CardTab label="Option 3">some content 3</CardTab>
<CardTab label="Option 4">some content 4</CardTab>
</TabbedCard>
);
};
const linkInfo = { title: 'Go to XYZ Location', link: '#' };
export const WithFooterLink = () => {
return (
<TabbedCard title="Footer Link Example Header" deepLink={linkInfo}>
<CardTab label="Option 1">some content 1</CardTab>
<CardTab label="Option 2">some content 2</CardTab>
<CardTab label="Option 3">some content 3</CardTab>
<CardTab label="Option 4">some content 4</CardTab>
</TabbedCard>
);
};
export const WithControlledTabValue = () => {
const [selectedTab, setSelectedTab] = useState('one');
const handleChange = (_ev, newSelectedTab) => setSelectedTab(newSelectedTab);
return (
<>
<span>Selected tab is {selectedTab}</span>
<TabbedCard
value={selectedTab}
onChange={handleChange}
title="Controlled Value Example"
>
<CardTab value="one" label="Option 1">
some content 1
</CardTab>
<CardTab value="two" label="Option 2">
some content 2
</CardTab>
<CardTab value="three" label="Option 3">
some content 3
</CardTab>
<CardTab value="four" label="Option 4">
some content 4
</CardTab>
</TabbedCard>
</>
);
};
@@ -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('<TabbedCard />', () => {
it('renders without exploding', () => {
const rendered = render(
wrapInTestApp(
<TabbedCard title={minProps.title}>
<CardTab label="Test 1">Test Content</CardTab>
<CardTab label="Test 2">Test Content</CardTab>
</TabbedCard>,
),
);
expect(rendered.getByText('Some title')).toBeInTheDocument();
});
it('renders a deepLink when prop is set', () => {
const rendered = render(
wrapInTestApp(
<TabbedCard deepLink={minProps.deepLink}>
<CardTab label="Test 1">Test Content</CardTab>
<CardTab label="Test 2">Test Content</CardTab>
</TabbedCard>,
),
);
expect(rendered.getByText('A deepLink title')).toBeInTheDocument();
});
it('switches tabs when clicking', () => {
const rendered = render(
wrapInTestApp(
<TabbedCard>
<CardTab label="Test 1">Test Content 1</CardTab>
<CardTab label="Test 2">Test Content 2</CardTab>
</TabbedCard>,
),
);
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(
<TabbedCard value={selectedTab} onChange={handleTabChange}>
<CardTab value="one" label="Test 1">
Test Content 1
</CardTab>
<CardTab value="two" label="Test 2">
Test Content 2
</CardTab>
</TabbedCard>,
),
);
expect(rendered.getByText('Test Content 1')).toBeInTheDocument();
fireEvent.click(rendered.getByText('Test 2'));
expect(handleTabChange.mock.calls.length).toBe(1);
rendered.rerender(
<TabbedCard value={selectedTab} onChange={handleTabChange}>
<CardTab value="one" label="Test 1">
Test Content 1
</CardTab>
<CardTab value="two" label="Test 2">
Test Content 2
</CardTab>
</TabbedCard>,
);
expect(rendered.getByText('Test Content 2')).toBeInTheDocument();
});
});
@@ -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<TabProps>[];
onChange?: (event: React.ChangeEvent<{}>, value: number | string) => void;
title?: string;
value?: number | string;
deepLink?: BottomLinkProps;
};
const TabbedCard: FC<Props> = ({
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 (
<Card>
<ErrorBoundary slackChannel={slackChannel}>
{title && <BoldHeader title={title} />}
<Tabs
classes={tabsClasses}
value={value || selectedIndex}
onChange={handleChange}
>
{children}
</Tabs>
<Divider />
<CardContent>{selectedTabContent}</CardContent>
{deepLink && <BottomLink {...deepLink} />}
</ErrorBoundary>
</Card>
);
};
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<CardTabProps> = ({ children, ...props }) => {
const classes = useCardTabStyles();
return <Tab disableRipple classes={classes} {...props} />;
};
export { TabbedCard, CardTab };
@@ -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';
+10
View File
@@ -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 => (
<ThemeProvider theme={BackstageTheme}>
<CssBaseline>{story()}</CssBaseline>
</ThemeProvider>
));
+9 -1
View File
@@ -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(
{
+2 -1
View File
@@ -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"
}
+1
View File
@@ -0,0 +1 @@
registry=https://registry.npmjs.org/
+15
View File
@@ -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)
+34
View File
@@ -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"
}
}
+16
View File
@@ -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';
@@ -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';
@@ -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);
}
@@ -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', () => {
@@ -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';
@@ -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([]);
});
});
@@ -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<void>;
export type SyncLogCollector = () => void;
export type LogCollector = AsyncLogCollector | SyncLogCollector;
export type CollectedLogs<T extends LogFuncs> = { [key in T]: string[] };
const allCategories = ['log', 'warn', 'error'];
// Asynchronous log collector with that collects all categories
export function withLogCollector(
callback: AsyncLogCollector,
): Promise<CollectedLogs<LogFuncs>>;
// Synchronous log collector with that collects all categories
export function withLogCollector(
callback: SyncLogCollector,
): CollectedLogs<LogFuncs>;
// Asynchronous log collector with that only collects selected categories
export function withLogCollector<T extends LogFuncs>(
logsToCollect: T[],
callback: AsyncLogCollector,
): Promise<CollectedLogs<T>>;
// Synchronous log collector with that only collects selected categories
export function withLogCollector<T extends LogFuncs>(
logsToCollect: T[],
callback: SyncLogCollector,
): CollectedLogs<T>;
export function withLogCollector(
logsToCollect: LogFuncs[] | LogCollector,
callback?: LogCollector,
): CollectedLogs<LogFuncs> | Promise<CollectedLogs<LogFuncs>> {
const oneArg = !callback;
const actualCallback = (oneArg ? logsToCollect : callback) as LogCollector;
const categories = (oneArg ? allCategories : logsToCollect) as LogFuncs[];
const logs = {
log: new Array<string>(),
warn: new Array<string>(),
error: new Array<string>(),
};
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;
}
}
@@ -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<RenderResult> {
let value: RenderResult;
await act(() => {
value = render(nodes);
});
// @ts-ignore
return value;
}
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src"],
"compilerOptions": {
"baseUrl": "src"
}
}
+4 -3
View File
@@ -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",
+1
View File
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export * from './testUtils';
export * from '@backstage/test-utils-core';
@@ -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 path="/route1">Route 1</Route>
<Route path="/route2">Route 2</Route>
</>,
['/route2'],
),
);
expect(rendered.getByText('Route 2')).toBeInTheDocument();
});
});
@@ -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 (
<MemoryRouter initialEntries={initialRouterEntries}>
<Route component={Wrapper} />
</MemoryRouter>
);
}
export function wrapInThemedTestApp(
component: ReactNode,
initialRouterEntries: string[] = ['/'],
) {
const themed = (
<ThemeProvider theme={BackstageTheme}>{component}</ThemeProvider>
);
return wrapInTestApp(themed, initialRouterEntries);
}
export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => (
<ThemeProvider theme={theme}>{component}</ThemeProvider>
);
+1 -63
View File
@@ -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 (
<MemoryRouter initialEntries={initialRouterEntries}>
<Route component={Wrapper} />
</MemoryRouter>
);
}
export function wrapInThemedTestApp(
component: ReactNode,
initialRouterEntries: string[] = ['/'],
) {
const themed = (
<ThemeProvider theme={BackstageTheme}>{component}</ThemeProvider>
);
return wrapInTestApp(themed, initialRouterEntries);
}
export const wrapInTheme = (component: ReactNode, theme = BackstageTheme) => (
<ThemeProvider theme={theme}>{component}</ThemeProvider>
);
// 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<RenderResult> {
let value: RenderResult;
await act(() => {
value = render(nodes);
});
// @ts-ignore
return value;
}
export * from './appWrappers';
@@ -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;
}
}
+2 -2
View File
@@ -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": {
+3 -1
View File
@@ -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,
+3 -2
View File
@@ -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,
+3 -1
View File
@@ -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,
+1 -1
View File
@@ -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';
+1
View File
@@ -38,6 +38,7 @@ export type BackstageMuiPalette = Theme['palette'] & {
linkHover: string;
link: string;
gold: string;
sidebar: string;
bursts: {
fontColor: string;
slackChannelText: string;
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './plugin';
export { plugin } from './plugin';
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import plugin from './plugin';
import { plugin } from './plugin';
describe('home-page', () => {
it('should export plugin', () => {
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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",
@@ -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<SparklinesProps & { title?: string }> = props => {
const theme = useTheme<typeof BackstageTheme>();
if (!props.data) return null;
return (
<Sparklines width={120} height={30} min={0} max={1} {...props}>
{props.title && <title>{props.title}</title>}
<SparklinesLine color={color(props.data)} />
<SparklinesLine color={color(props.data, theme)} />
</Sparklines>
);
};
+1 -1
View File
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { default } from './plugin';
export { plugin } from './plugin';
export * from './api';
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import plugin from './plugin';
import { plugin } from './plugin';
describe('lighthouse', () => {
it('should export plugin', () => {
+1 -1
View File
@@ -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);
+4 -4
View File
@@ -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",
+1 -1
View File
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { default } from './plugin';
export { plugin } from './plugin';
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import plugin from './plugin';
import { plugin } from './plugin';
describe('welcome', () => {
it('should export plugin', () => {
+1 -1
View File
@@ -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);
+157 -2
View File
@@ -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==