Merge branch 'master' into alund/components
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -51,6 +51,10 @@ module.exports = {
|
||||
bundledDependencies: true,
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ vars: 'all', args: 'after-used', ignoreRestSiblings: true },
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
{
|
||||
"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",
|
||||
"create-plugin": "backstage-cli create-plugin",
|
||||
"lint": "lerna run lint"
|
||||
"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"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
@@ -14,10 +21,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,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;
|
||||
},
|
||||
};
|
||||
@@ -43,8 +43,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.3",
|
||||
"@backstage/test-utils": "^0.1.1-alpha.3",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.3",
|
||||
"@backstage/theme": "^0.1.1-alpha.3",
|
||||
"@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';
|
||||
@@ -36,3 +36,4 @@ export { default as SortableTable } from './components/SortableTable';
|
||||
export { default as StructuredMetadataTable } from './components/StructuredMetadataTable';
|
||||
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
|
||||
export * from './components/Status';
|
||||
export { default as WarningPanel } from './components/WarningPanel';
|
||||
|
||||
+4
-4
@@ -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: {
|
||||
|
||||
@@ -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';
|
||||
@@ -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>
|
||||
));
|
||||
@@ -5,7 +5,11 @@ 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,
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
@@ -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)
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@backstage/test-utils-core",
|
||||
"description": "Utilities to test Backstage core",
|
||||
"version": "0.1.1-alpha.3",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
+1
-1
@@ -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,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/theme": "^0.1.1-alpha.3",
|
||||
"@backstage/test-utils-core": "^0.1.1-alpha.3",
|
||||
"@material-ui/core": "^4.9.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -20,10 +20,6 @@ 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[] = ['/'],
|
||||
|
||||
@@ -14,8 +14,5 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { default as Keyboard } from './Keyboard';
|
||||
export { default as mockBreakpoint } from './mockBreakpoint';
|
||||
export * from './appWrappers';
|
||||
export * from './logCollector';
|
||||
export * from './testingLibrary';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -38,6 +38,7 @@ export type BackstageMuiPalette = Theme['palette'] & {
|
||||
linkHover: string;
|
||||
link: string;
|
||||
gold: string;
|
||||
sidebar: string;
|
||||
bursts: {
|
||||
fontColor: string;
|
||||
slackChannelText: string;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
@@ -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==
|
||||
|
||||
Reference in New Issue
Block a user