Merge pull request #541 from spotify/alund/components
Port over more internal components
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"classnames": "^2.2.6",
|
||||
"clsx": "^1.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"prop-types": "^15.7.2",
|
||||
"rc-progress": "^2.5.2",
|
||||
"react": "^16.12.0",
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { IconButton, Tooltip, makeStyles } from '@material-ui/core';
|
||||
import CopyIcon from '@material-ui/icons/FileCopy';
|
||||
import { errorApiRef, useApi } from 'api';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
button: {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.highlight,
|
||||
cursor: 'pointer',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* Copy text button with visual feedback in the form of
|
||||
* - a hover color
|
||||
* - click ripple
|
||||
* - Tooltip shown when user has clicked
|
||||
*
|
||||
* Properties:
|
||||
* - text: the text to be copied
|
||||
* - tooltipDelay: Number os ms to show the tooltip, default: 1000ms
|
||||
* - tooltipText: Text to show in the tooltip when user has clicked the button, default: "Text
|
||||
* copied to clipboard"
|
||||
*
|
||||
* Example:
|
||||
* <CopyTextButton text="My text that I want to be copied to the clipboard" />
|
||||
*/
|
||||
const CopyTextButton = ({
|
||||
text,
|
||||
tooltipDelay = 1000,
|
||||
tooltipText = 'Text copied to clipboard',
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const errorApi = useApi(errorApiRef);
|
||||
const inputRef = useRef();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleCopyClick = e => {
|
||||
e.stopPropagation();
|
||||
setOpen(true);
|
||||
|
||||
try {
|
||||
inputRef.current.select();
|
||||
document.execCommand('copy');
|
||||
} catch (error) {
|
||||
errorApi.post(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
style={{ position: 'absolute', top: -9999, left: 9999 }}
|
||||
defaultValue={text}
|
||||
/>
|
||||
<Tooltip
|
||||
id="copy-test-tooltip"
|
||||
title={tooltipText}
|
||||
placement="top"
|
||||
leaveDelay={tooltipDelay}
|
||||
onClose={() => setOpen(false)}
|
||||
open={open}
|
||||
>
|
||||
<IconButton onClick={handleCopyClick} className={classes.button}>
|
||||
<CopyIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CopyTextButton.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
tooltipDelay: PropTypes.number,
|
||||
tooltipText: PropTypes.string,
|
||||
};
|
||||
|
||||
export default CopyTextButton;
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 CopyTextButton from '.';
|
||||
import { ApiProvider, errorApiRef, ApiRegistry } from 'api';
|
||||
|
||||
export default {
|
||||
title: 'CopyTextButton',
|
||||
component: CopyTextButton,
|
||||
decorators: [
|
||||
storyFn => {
|
||||
// TODO: move this to common storybook config, requires core package to be separate from components
|
||||
const registry = ApiRegistry.from([
|
||||
[
|
||||
errorApiRef,
|
||||
{
|
||||
post(error) {
|
||||
// eslint-disable-next-line no-alert
|
||||
window.alert(`Component posted error, ${error}`);
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
return <ApiProvider apis={registry} children={storyFn()} />;
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<CopyTextButton text="The text to copy to clipboard" />
|
||||
);
|
||||
|
||||
export const WithTooltip = () => (
|
||||
<CopyTextButton
|
||||
text="The text to copy to clipboard"
|
||||
tooltipText="Custom tooltip shown on button click"
|
||||
/>
|
||||
);
|
||||
|
||||
export const LongerTooltipDelay = () => (
|
||||
<CopyTextButton
|
||||
text="The text to copy to clipboard"
|
||||
tooltipText="Waiting 3s before removing tooltip"
|
||||
tooltipDelay={3000}
|
||||
/>
|
||||
);
|
||||
@@ -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 from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInThemedTestApp } from '@backstage/test-utils';
|
||||
import CopyTextButton from './CopyTextButton';
|
||||
import { ApiRegistry, errorApiRef, ApiProvider } from 'api';
|
||||
|
||||
jest.mock('popper.js', () => {
|
||||
const PopperJS = jest.requireActual('popper.js');
|
||||
|
||||
return class {
|
||||
static placements = PopperJS.placements;
|
||||
update() {}
|
||||
destroy() {}
|
||||
scheduleUpdate() {}
|
||||
};
|
||||
});
|
||||
|
||||
const props = {
|
||||
text: 'mockText',
|
||||
tooltipDelay: 2,
|
||||
tooltipText: 'mockTooltip',
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[
|
||||
errorApiRef,
|
||||
{
|
||||
post(error) {
|
||||
throw error;
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
describe('<CopyTextButton />', () => {
|
||||
it('renders without exploding', () => {
|
||||
const { getByDisplayValue } = render(
|
||||
wrapInThemedTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<CopyTextButton {...props} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
getByDisplayValue('mockText');
|
||||
});
|
||||
|
||||
it('displays tooltip on click', async () => {
|
||||
document.execCommand = jest.fn();
|
||||
const rendered = render(
|
||||
wrapInThemedTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<CopyTextButton {...props} />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
const button = rendered.getByTitle('mockTooltip');
|
||||
button.click();
|
||||
expect(document.execCommand).toHaveBeenCalled();
|
||||
rendered.getByText('mockTooltip');
|
||||
});
|
||||
});
|
||||
@@ -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 } from './CopyTextButton';
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 HorizontalProgress from './HorizontalProgress';
|
||||
|
||||
const containerStyle = { width: 300 };
|
||||
|
||||
export default {
|
||||
title: 'HorizontalProgress',
|
||||
component: HorizontalProgress,
|
||||
};
|
||||
|
||||
export const Default = () => (
|
||||
<div style={containerStyle}>
|
||||
<HorizontalProgress value={0.8} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const MediumProgress = () => (
|
||||
<div style={containerStyle}>
|
||||
<HorizontalProgress value={0.5} />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const LowProgress = () => (
|
||||
<div style={containerStyle}>
|
||||
<HorizontalProgress value={0.2} />
|
||||
</div>
|
||||
);
|
||||
@@ -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, { FC } from 'react';
|
||||
import { Tooltip, useTheme } from '@material-ui/core';
|
||||
// @ts-ignore
|
||||
import { Line } from 'rc-progress';
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { getProgressColor } from './CircleProgress';
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* Progress value between 0.0 - 1.0.
|
||||
*/
|
||||
value: number;
|
||||
};
|
||||
|
||||
const HorizontalProgress: FC<Props> = ({ value }) => {
|
||||
if (isNaN(value)) {
|
||||
return null;
|
||||
}
|
||||
let percent = Math.round(value * 100 * 100) / 100;
|
||||
if (percent > 100) {
|
||||
percent = 100;
|
||||
}
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
const strokeColor = getProgressColor(theme.palette, percent, false, 100);
|
||||
return (
|
||||
<Tooltip title={`${percent}%`}>
|
||||
<Line
|
||||
percent={percent}
|
||||
strokeWidth={4}
|
||||
trailWidth={4}
|
||||
strokeColor={strokeColor}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default HorizontalProgress;
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
import React, { FC } from 'react';
|
||||
import { makeStyles } from '@material-ui/core';
|
||||
import InfoCard from 'layout/InfoCard';
|
||||
import { Props as BottomLinkProps } from '../layout/BottomLink';
|
||||
import { Props as BottomLinkProps } from '../../layout/BottomLink';
|
||||
import CircleProgress from './CircleProgress';
|
||||
|
||||
type Props = {
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
withStyles,
|
||||
} from '@material-ui/core';
|
||||
|
||||
const tableTitleCellStyles = theme => ({
|
||||
root: {
|
||||
fontWeight: 'bolder',
|
||||
whiteSpace: 'nowrap',
|
||||
paddingRight: theme.spacing(4),
|
||||
border: '0',
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
});
|
||||
|
||||
const tableContentCellStyles = {
|
||||
root: {
|
||||
color: 'rgba(0, 0, 0, 0.6)',
|
||||
border: '0',
|
||||
verticalAlign: 'top',
|
||||
},
|
||||
};
|
||||
|
||||
const listStyles = theme => ({
|
||||
root: {
|
||||
listStyle: 'none',
|
||||
margin: theme.spacing(0, 0, -1, 0),
|
||||
padding: '0',
|
||||
},
|
||||
});
|
||||
|
||||
const listItemStyles = theme => ({
|
||||
root: {
|
||||
padding: theme.spacing(0, 0, 1, 0),
|
||||
},
|
||||
});
|
||||
|
||||
const TitleCell = withStyles(tableTitleCellStyles)(TableCell);
|
||||
const ContentCell = withStyles(tableContentCellStyles)(TableCell);
|
||||
|
||||
export const MetadataTable = ({ dense, children }) => (
|
||||
<Table>
|
||||
{!dense && (
|
||||
<colgroup>
|
||||
<col style={{ width: 'auto' }} />
|
||||
<col style={{ width: '100%' }} />
|
||||
</colgroup>
|
||||
)}
|
||||
<TableBody>{children}</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
export const MetadataTableItem = ({ title, children, ...rest }) => (
|
||||
<TableRow>
|
||||
{title && <TitleCell>{title}</TitleCell>}
|
||||
<ContentCell colSpan={title ? 1 : 2} {...rest}>
|
||||
{children}
|
||||
</ContentCell>
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
export const MetadataList = withStyles(listStyles)(({ classes, children }) => (
|
||||
<ul className={classes.root}>{children}</ul>
|
||||
));
|
||||
|
||||
export const MetadataListItem = withStyles(
|
||||
listItemStyles,
|
||||
)(({ classes, children }) => <li className={classes.root}>{children}</li>);
|
||||
@@ -0,0 +1,62 @@
|
||||
# Structured MetadataTable
|
||||
|
||||
The `Strucuted MetadataTable` staple is a staple component for displaying basic JSON metadata.
|
||||
|
||||
# API
|
||||
|
||||
There is a very lightweight API around this component
|
||||
|
||||
| property | value |
|
||||
| :------- | :---------: |
|
||||
| metadata | object/JSON |
|
||||
| dense | bool |
|
||||
|
||||
## Metadata
|
||||
|
||||
The Metadata property takes in JSON and iterates over it to display the tabled information.
|
||||
|
||||
The component itself only handles the display area, so you can use standard JS to construct an object that fits your desired outcome. No need to configure deeper within the staple.
|
||||
|
||||
```
|
||||
<StructuredMetadataTable metadata={json} />
|
||||
```
|
||||
|
||||
This will step through each of the keys and based on their types display them in a logical way.
|
||||
|
||||
### Primatives
|
||||
|
||||
Any non complex value will be displayed using `{value}` which will just output the value as text.
|
||||
|
||||
### Objects/Maps
|
||||
|
||||
JSON / Maps are displayed in a `<MetadataItemList>` with its values as formatted key/value pairs.
|
||||
|
||||
### Arrays
|
||||
|
||||
Arrays are displayed similarly to objects, its values in a `<MetadataItemList>`.
|
||||
|
||||
### Custom
|
||||
|
||||
If you want to customize the rendering of your value you can just replace it with a React Element.
|
||||
|
||||
```
|
||||
{
|
||||
contact: me@email.com
|
||||
}
|
||||
```
|
||||
|
||||
Would display as <b>contact</b> <span>me@email.com</span>
|
||||
|
||||
but if you wanted this to be a mailto you could inject that react into your map:
|
||||
|
||||
```
|
||||
{
|
||||
contact: <Link email="me@email.com">me@email.com</Link>
|
||||
}
|
||||
```
|
||||
|
||||
Then it would be displayed using the react element.
|
||||
|
||||
# Usage
|
||||
|
||||
For best usage drop this component inside another card. It can be used similarly to the `<MetadataTable>` and exposes the `dense` for when that is necessary.
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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, Fragment } from 'react';
|
||||
import { withStyles } from '@material-ui/core';
|
||||
import startCase from 'lodash/startCase';
|
||||
|
||||
import {
|
||||
MetadataTable,
|
||||
MetadataTableItem,
|
||||
MetadataList,
|
||||
MetadataListItem,
|
||||
} from './MetadataTable';
|
||||
|
||||
const listStyle = {
|
||||
root: {
|
||||
margin: '0 0',
|
||||
listStyleType: 'none',
|
||||
},
|
||||
};
|
||||
|
||||
const nestedListStyle = {
|
||||
root: {
|
||||
...listStyle.root,
|
||||
paddingLeft: '8px',
|
||||
},
|
||||
};
|
||||
|
||||
function renderList(list, options, nested) {
|
||||
const values = list.map((item, index) => (
|
||||
<MetadataListItem key={index}>{toValue(item)}</MetadataListItem>
|
||||
));
|
||||
return nested ? (
|
||||
<StyledNestedList>{values}</StyledNestedList>
|
||||
) : (
|
||||
<StyledList>{values}</StyledList>
|
||||
);
|
||||
}
|
||||
|
||||
function renderMap(map, options, nested) {
|
||||
const values = Object.keys(map).map(key => {
|
||||
const value = toValue(map[key], true);
|
||||
const fmtKey =
|
||||
options && options.titleFormat
|
||||
? options.titleFormat(key)
|
||||
: startCase(key);
|
||||
return (
|
||||
<MetadataListItem key={key}>
|
||||
{`${fmtKey}: `}
|
||||
{value}
|
||||
</MetadataListItem>
|
||||
);
|
||||
});
|
||||
|
||||
return nested ? (
|
||||
<StyledNestedList>{values}</StyledNestedList>
|
||||
) : (
|
||||
<StyledList>{values}</StyledList>
|
||||
);
|
||||
}
|
||||
|
||||
function toValue(value, options, nested) {
|
||||
if (React.isValidElement(value)) {
|
||||
return <Fragment>{value}</Fragment>;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return renderMap(value, options, nested);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return renderList(value, options, nested);
|
||||
}
|
||||
|
||||
return <Fragment>{value}</Fragment>;
|
||||
}
|
||||
|
||||
function mapToItems(info, options) {
|
||||
return Object.keys(info).map(key => (
|
||||
<TableItem key={key} title={key} value={info[key]} options={options} />
|
||||
));
|
||||
}
|
||||
|
||||
// Sub Components
|
||||
const StyledList = withStyles(listStyle)(({ classes, children }) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
));
|
||||
const StyledNestedList = withStyles(
|
||||
nestedListStyle,
|
||||
)(({ classes, children }) => (
|
||||
<MetadataList classes={classes}>{children}</MetadataList>
|
||||
));
|
||||
const ItemValue = ({ value, options }) => (
|
||||
<Fragment>{toValue(value, options)}</Fragment>
|
||||
);
|
||||
const TableItem = ({ title, value, options }) => {
|
||||
return (
|
||||
<MetadataTableItem
|
||||
title={
|
||||
options && options.titleFormat
|
||||
? options.titleFormat(title)
|
||||
: startCase(title)
|
||||
}
|
||||
>
|
||||
<ItemValue value={value} options={options} />
|
||||
</MetadataTableItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default class StructuredMetadataTable extends Component {
|
||||
render() {
|
||||
const { metadata, dense, options } = this.props;
|
||||
const metadataItems = mapToItems(metadata, options || {});
|
||||
|
||||
return <MetadataTable dense={dense}>{metadataItems}</MetadataTable>;
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 InfoCard from '../../layout/InfoCard';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import StructuredMetadataTable from '.';
|
||||
|
||||
const cardContentStyle = { heightX: 200, width: 500 };
|
||||
|
||||
const metadata = {
|
||||
description:
|
||||
'This is a long description of what this is doing (and some additional info too). \n It has new lines and extra text to make it especially annoying to render. But it just ignores them.',
|
||||
something: 'Yes',
|
||||
owner: 'squad',
|
||||
'longer key name': ['v1', 'v2', 'v3'],
|
||||
rules: {
|
||||
'permit missing partitions': 'No',
|
||||
'max partition finish time': '19 hours',
|
||||
Support: {
|
||||
'office hours': 'Contact goalie',
|
||||
'after hours': 'trigger PD alert',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Structured Metadata Table',
|
||||
component: StructuredMetadataTable,
|
||||
};
|
||||
|
||||
const Wrapper = ({ children }) => (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item>{children}</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
export const Default = () => (
|
||||
<Wrapper>
|
||||
<InfoCard title="Structured Metadata Table" subheader="Wrapped in InfoCard">
|
||||
<div style={cardContentStyle}>
|
||||
<StructuredMetadataTable metadata={metadata} />
|
||||
</div>
|
||||
</InfoCard>
|
||||
</Wrapper>
|
||||
);
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 StructuredMetadataTable from './StructuredMetadataTable';
|
||||
import { startCase } from 'lodash';
|
||||
|
||||
describe('<StructuredMetadataTable />', () => {
|
||||
it('renders without exploding', () => {
|
||||
const metadata = { hello: 'world' };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
expect(getByText(metadata.hello)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Item Mappings', () => {
|
||||
it('Iterates over and displays every field in the map', () => {
|
||||
const metadata = { field1: 'one', field2: 'two', field3: 'three' };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
const keys = Object.keys(metadata);
|
||||
keys.forEach(value => {
|
||||
expect(getByText(startCase(value))).toBeInTheDocument();
|
||||
expect(getByText(metadata[value])).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Supports primative value fields', () => {
|
||||
const metadata = { strField: 'my field', intField: 1 };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
|
||||
const keys = Object.keys(metadata);
|
||||
keys.forEach(value => {
|
||||
expect(getByText(startCase(value))).toBeInTheDocument();
|
||||
expect(getByText(metadata[value].toString())).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Supports array fields', () => {
|
||||
const metadata = { arrayField: ['arrVal1', 'arrVal2'] };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
const keys = Object.keys(metadata);
|
||||
keys.forEach(value => {
|
||||
expect(getByText(startCase(value))).toBeInTheDocument();
|
||||
});
|
||||
metadata.arrayField.forEach(value => {
|
||||
expect(getByText(value)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Supports react elements', () => {
|
||||
const metadata = { react: <div id="findMe"> field </div> };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
|
||||
expect(getByText('field')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Supports object elements', () => {
|
||||
const metadata = { config: { a: 1, b: 2 } };
|
||||
const { getByText } = render(
|
||||
<StructuredMetadataTable metadata={metadata} />,
|
||||
);
|
||||
|
||||
const keys = Object.keys(metadata.config);
|
||||
keys.forEach(value => {
|
||||
expect(
|
||||
getByText(startCase(value), { exact: false }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(metadata.config[value].toString(), { exact: false }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 } from './StructuredMetadataTable';
|
||||
@@ -25,12 +25,15 @@ export { default as InfoCard } from './layout/InfoCard';
|
||||
export { default as ErrorBoundary } from './layout/ErrorBoundary';
|
||||
export * from './layout/Sidebar';
|
||||
export { default as HorizontalScrollGrid } from './components/HorizontalScrollGrid';
|
||||
export { default as ProgressCard } from './components/ProgressCard';
|
||||
export { default as CircleProgress } from './components/CircleProgress';
|
||||
export { default as ProgressCard } from './components/ProgressBars/ProgressCard';
|
||||
export { default as CircleProgress } from './components/ProgressBars/CircleProgress';
|
||||
export { default as HorizontalProgress } from './components/ProgressBars/HorizontalProgress';
|
||||
export { default as CopyTextButton } from './components/CopyTextButton';
|
||||
export { default as Progress } from './components/Progress';
|
||||
export { AlphaLabel, BetaLabel } from './components/Lifecycle';
|
||||
export { default as SupportButton } from './components/SupportButton';
|
||||
export { default as SortableTable } from './components/SortableTable';
|
||||
export { default as StructuredMetadataTable } from './components/StructuredMetadataTable';
|
||||
export { default as TrendLine } from './components/TrendLine';
|
||||
export { FeatureCalloutCircular } from './components/FeatureDiscovery/FeatureCalloutCircular';
|
||||
export * from './components/Status';
|
||||
|
||||
Reference in New Issue
Block a user