From 5938544c46f3e7bcb371eabb2b0e7275a1baecb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20=C3=85lund?= Date: Mon, 13 Apr 2020 14:52:24 +0200 Subject: [PATCH] Port over StructuredMetadataTable --- packages/core/package.json | 1 + .../StructuredMetadataTable/MetadataTable.js | 88 ++++++++++++ .../StructuredMetadataTable/README.md | 62 +++++++++ .../StructuredMetadataTable.js | 130 ++++++++++++++++++ .../StructuredMetadataTable.stories.tsx | 58 ++++++++ .../StructuredMetadataTable.test.js | 98 +++++++++++++ .../StructuredMetadataTable/index.js | 17 +++ yarn.lock | 2 +- 8 files changed, 455 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/components/StructuredMetadataTable/MetadataTable.js create mode 100644 packages/core/src/components/StructuredMetadataTable/README.md create mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.js create mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx create mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.js create mode 100644 packages/core/src/components/StructuredMetadataTable/index.js diff --git a/packages/core/package.json b/packages/core/package.json index 3dc8150f83..a41f82a0fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,6 +31,7 @@ "@types/node": "^12.0.0", "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", diff --git a/packages/core/src/components/StructuredMetadataTable/MetadataTable.js b/packages/core/src/components/StructuredMetadataTable/MetadataTable.js new file mode 100644 index 0000000000..dce6b63946 --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/MetadataTable.js @@ -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 }) => ( + + {!dense && ( + + + + + )} + {children} +
+); + +export const MetadataTableItem = ({ title, children, ...rest }) => ( + + {title && {title}} + + {children} + + +); + +export const MetadataList = withStyles(listStyles)(({ classes, children }) => ( + +)); + +export const MetadataListItem = withStyles( + listItemStyles, +)(({ classes, children }) =>
  • {children}
  • ); diff --git a/packages/core/src/components/StructuredMetadataTable/README.md b/packages/core/src/components/StructuredMetadataTable/README.md new file mode 100644 index 0000000000..452d32490f --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/README.md @@ -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. + +``` + +``` + +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 `` with its values as formatted key/value pairs. + +### Arrays + +Arrays are displayed similarly to objects, its values in a ``. + +### 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 contact me@email.com + +but if you wanted this to be a mailto you could inject that react into your map: + +``` +{ + contact: me@email.com +} +``` + +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 `` and exposes the `dense` for when that is necessary. diff --git a/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.js b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.js new file mode 100644 index 0000000000..60516a56f1 --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.js @@ -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) => ( + {toValue(item)} + )); + return nested ? ( + {values} + ) : ( + {values} + ); +} + +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 ( + + {`${fmtKey}: `} + {value} + + ); + }); + + return nested ? ( + {values} + ) : ( + {values} + ); +} + +function toValue(value, options, nested) { + if (React.isValidElement(value)) { + return {value}; + } + + if (typeof value === 'object' && !Array.isArray(value)) { + return renderMap(value, options, nested); + } + + if (Array.isArray(value)) { + return renderList(value, options, nested); + } + + return {value}; +} + +function mapToItems(info, options) { + return Object.keys(info).map(key => ( + + )); +} + +// Sub Components +const StyledList = withStyles(listStyle)(({ classes, children }) => ( + {children} +)); +const StyledNestedList = withStyles( + nestedListStyle, +)(({ classes, children }) => ( + {children} +)); +const ItemValue = ({ value, options }) => ( + {toValue(value, options)} +); +const TableItem = ({ title, value, options }) => { + return ( + + + + ); +}; + +export default class StructuredMetadataTable extends Component { + render() { + const { metadata, dense, options } = this.props; + const metadataItems = mapToItems(metadata, options || {}); + + return {metadataItems}; + } +} diff --git a/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx new file mode 100644 index 0000000000..5a04520dd1 --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx @@ -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 }) => ( + + {children} + +); + +export const Default = () => ( + + +
    + +
    +
    +
    +); diff --git a/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.js b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.js new file mode 100644 index 0000000000..b9aa962969 --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.js @@ -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('', () => { + it('renders without exploding', () => { + const metadata = { hello: 'world' }; + const { getByText } = render( + , + ); + 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( + , + ); + 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( + , + ); + + 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( + , + ); + 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:
    field
    }; + const { getByText } = render( + , + ); + + expect(getByText('field')).toBeInTheDocument(); + }); + + it('Supports object elements', () => { + const metadata = { config: { a: 1, b: 2 } }; + const { getByText } = render( + , + ); + + 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(); + }); + }); + }); +}); diff --git a/packages/core/src/components/StructuredMetadataTable/index.js b/packages/core/src/components/StructuredMetadataTable/index.js new file mode 100644 index 0000000000..588f5ef170 --- /dev/null +++ b/packages/core/src/components/StructuredMetadataTable/index.js @@ -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'; diff --git a/yarn.lock b/yarn.lock index acf9de9c97..03f14af1bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7071,7 +7071,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==