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/ColorPicker/ColorInput.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx index 9b5f5b98432..c67407f2e44 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorInput.tsx @@ -39,7 +39,7 @@ class ColorInput extends React.PureComponent { this.props.onChange(color); }; - handleChange = (event: React.SyntheticEvent) => { + onChange = (event: React.SyntheticEvent) => { const newColor = tinycolor(event.currentTarget.value); this.setState({ @@ -51,7 +51,7 @@ class ColorInput extends React.PureComponent { } }; - handleBlur = () => { + onBlur = () => { const newColor = tinycolor(this.state.value); if (!newColor.isValid()) { @@ -84,7 +84,7 @@ class ColorInput extends React.PureComponent { flexGrow: 1, }} > - + ); diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx index 183cbfed67f..5a6ddcd01b9 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPicker.tsx @@ -15,7 +15,7 @@ export const colorPickerFactory = ( static displayName = displayName; pickerTriggerRef = createRef(); - handleColorChange = (color: string) => { + onColorChange = (color: string) => { const { onColorChange, onChange } = this.props; const changeHandler = (onColorChange || onChange) as ColorPickerChangeHandler; @@ -25,7 +25,7 @@ export const colorPickerFactory = ( render() { const popoverElement = React.createElement(popover, { ...this.props, - onChange: this.handleColorChange, + onChange: this.onColorChange, }); const { theme, children } = this.props; diff --git a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx index ce9ca5130d4..674a283ddb9 100644 --- a/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx +++ b/packages/grafana-ui/src/components/ColorPicker/ColorPickerPopover.tsx @@ -60,7 +60,7 @@ export class ColorPickerPopover extends React changeHandler(getColorFromHexRgbOrName(color, theme.type)); }; - handleTabChange = (tab: PickerType | keyof T) => { + onTabChange = (tab: PickerType | keyof T) => { return () => this.setState({ activePicker: tab }); }; @@ -104,7 +104,7 @@ export class ColorPickerPopover extends React <> {Object.keys(customPickers).map(key => { return ( -
+
{customPickers[key].name}
); @@ -119,10 +119,10 @@ export class ColorPickerPopover extends React return (
-
+
Colors
-
+
Custom
{this.renderCustomPickerTabs()} 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 }) => ( +
+