diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 6c28c8abbd3..aec32fd9282 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -24,6 +24,7 @@ "jquery": "^3.2.1", "lodash": "^4.17.10", "moment": "^2.22.2", + "papaparse": "^4.6.3", "react": "^16.6.3", "react-color": "^2.17.0", "react-custom-scrollbars": "^4.2.1", @@ -46,6 +47,7 @@ "@types/jquery": "^1.10.35", "@types/lodash": "^4.14.119", "@types/node": "^10.12.18", + "@types/papaparse": "^4.5.9", "@types/react": "^16.7.6", "@types/react-custom-scrollbars": "^4.0.5", "@types/react-test-renderer": "^16.0.3", diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx new file mode 100644 index 00000000000..604eefca7a2 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { storiesOf } from '@storybook/react'; +import TableInputCSV from './TableInputCSV'; +import { action } from '@storybook/addon-actions'; +import { TableData } from '../../types/data'; +import { withCenteredStory } from '../../utils/storybook/withCenteredStory'; + +const TableInputStories = storiesOf('UI/Table/Input', module); + +TableInputStories.addDecorator(withCenteredStory); + +TableInputStories.add('default', () => { + return ( +
+ { + console.log('Table', table, text); + action('Table')(table, text); + }} + /> +
+ ); +}); diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx new file mode 100644 index 00000000000..9e2dfbf80c3 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import renderer from 'react-test-renderer'; +import TableInputCSV from './TableInputCSV'; +import { TableData } from '../../types/data'; + +describe('TableInputCSV', () => { + it('renders correctly', () => { + const tree = renderer + .create( + { + // console.log('Table:', table, 'from:', text); + }} + /> + ) + .toJSON(); + //expect(tree).toMatchSnapshot(); + expect(tree).toBeDefined(); + }); +}); diff --git a/packages/grafana-ui/src/components/Table/TableInputCSV.tsx b/packages/grafana-ui/src/components/Table/TableInputCSV.tsx new file mode 100644 index 00000000000..14bc68550c3 --- /dev/null +++ b/packages/grafana-ui/src/components/Table/TableInputCSV.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import debounce from 'lodash/debounce'; +import { parseCSV, TableParseOptions, TableParseDetails } from '../../utils/processTableData'; +import { TableData } from '../../types/data'; +import { AutoSizer } from 'react-virtualized'; + +interface Props { + options?: TableParseOptions; + text: string; + onTableParsed: (table: TableData, text: string) => void; +} + +interface State { + text: string; + table: TableData; + details: TableParseDetails; +} + +/** + * Expects the container div to have size set and will fill it 100% + */ +class TableInputCSV extends React.PureComponent { + constructor(props: Props) { + super(props); + + // Shoud this happen in onComponentMounted? + const { text, options, onTableParsed } = props; + const details = {}; + const table = parseCSV(text, options, details); + this.state = { + text, + table, + details, + }; + onTableParsed(table, text); + } + + readCSV = debounce(() => { + const details = {}; + const table = parseCSV(this.state.text, this.props.options, details); + this.setState({ table, details }); + }, 150); + + componentDidUpdate(prevProps: Props, prevState: State) { + const { text } = this.state; + if (text !== prevState.text || this.props.options !== prevProps.options) { + this.readCSV(); + } + // If the props text has changed, replace our local version + if (this.props.text !== prevProps.text && this.props.text !== text) { + this.setState({ text: this.props.text }); + } + + if (this.state.table !== prevState.table) { + this.props.onTableParsed(this.state.table, this.state.text); + } + } + + onFooterClicked = (event: any) => { + console.log('Errors', this.state); + const message = this.state.details + .errors!.map(err => { + return err.message; + }) + .join('\n'); + alert('CSV Parsing Errors:\n' + message); + }; + + onTextChange = (event: any) => { + this.setState({ text: event.target.value }); + }; + + render() { + const { table, details } = this.state; + + const hasErrors = details.errors && details.errors.length > 0; + const footerClassNames = hasErrors ? 'gf-table-input-csv-err' : ''; + + return ( + + {({ height, width }) => ( +
+