moved utillities to util

pull/15886/head
ryan 6 years ago
parent 3bd26df7de
commit c7f35f2dcf
  1. 7
      packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx
  2. 3
      packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx
  3. 116
      packages/grafana-ui/src/components/Table/TableInputCSV.tsx
  4. 42
      packages/grafana-ui/src/utils/__snapshots__/processTableData.test.ts.snap
  5. 10
      packages/grafana-ui/src/utils/processTableData.test.ts
  6. 127
      packages/grafana-ui/src/utils/processTableData.ts

@ -1,7 +1,9 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import TableInputCSV, { ParseResults } from './TableInputCSV';
import TableInputCSV from './TableInputCSV';
import { action } from '@storybook/addon-actions';
import { ParseResults } from '../../utils/processTableData';
const TableInputStories = storiesOf('UI/Table/Input', module);
@ -12,7 +14,8 @@ TableInputStories.add('default', () => {
width={'90%'}
height={'90vh'}
onTableParsed={(results: ParseResults) => {
console.log('GOT', results);
console.log('Table Results', results);
action('Parsed')(results);
}}
/>
</div>

@ -1,7 +1,8 @@
import React from 'react';
import renderer from 'react-test-renderer';
import TableInputCSV, { ParseResults } from './TableInputCSV';
import TableInputCSV from './TableInputCSV';
import { ParseResults } from '../../utils/processTableData';
describe('TableInputCSV', () => {
it('renders correctly', () => {

@ -1,108 +1,6 @@
import React from 'react';
import debounce from 'lodash/debounce';
import Papa, { ParseError, ParseMeta } from 'papaparse';
import { TableData, Column } from '../../types/data';
// Subset of all parse options
export interface ParseConfig {
delimiter?: string; // default: ","
newline?: string; // default: "\r\n"
quoteChar?: string; // default: '"'
encoding?: string; // default: ""
comments?: boolean | string; // default: false
}
export interface ParseResults {
table: TableData;
meta: ParseMeta;
errors: ParseError[];
}
// This mutates the table structure!
export function checkAndFix(table: TableData): number {
let cols = table.columns.length;
let different = 0;
table.rows.forEach(row => {
if (cols !== row.length) {
different++;
cols = Math.max(cols, row.length);
}
});
if (different > 0) {
if (cols !== table.columns.length) {
const diff = cols - table.columns.length;
for (let i = 0; i < diff; i++) {
table.columns.push({
text: 'Column ' + table.columns.length,
});
}
}
table.rows.forEach(row => {
const diff = cols - row.length;
for (let i = 0; i < diff; i++) {
row.push(null);
}
});
}
return different;
}
export function parseCSV(text: string, config?: ParseConfig): ParseResults {
const results = Papa.parse(text, { ...config, dynamicTyping: true, skipEmptyLines: true });
const { data, meta, errors } = results;
if (!data || data.length < 1) {
if (!text) {
errors.length = 0; // clear other errors
}
errors.push({
type: 'warning', // A generalization of the error
message: 'Empty Data',
code: 'empty',
row: 0,
});
return {
table: {
columns: [],
rows: [],
type: 'table',
columnMap: {},
} as TableData,
meta,
errors,
};
}
const first = results.data.shift();
const table = {
columns: first.map((v: any, index: number) => {
if (!v) {
v = 'Column ' + (index + 1);
}
return {
text: v.toString().trim(),
} as Column;
}),
rows: results.data,
type: 'table',
columnMap: {},
} as TableData;
const changed = checkAndFix(table);
if (changed > 0) {
errors.push({
type: 'warning', // A generalization of the error
message: 'not all rows have the same width. Changed:' + changed,
code: 'width',
row: 0,
});
}
return {
table,
meta,
errors,
};
}
import { ParseConfig, ParseResults, parseCSV } from '../../utils/processTableData';
interface Props {
config?: ParseConfig;
@ -147,20 +45,14 @@ class TableInputCSV extends React.PureComponent<Props, State> {
const { width, height } = this.props;
const { table, errors } = this.state.results;
let clazz = 'fa fa-check-circle';
errors.forEach(error => {
if (error.type === 'warning') {
clazz = 'fa fa-exclamation-triangle';
} else {
clazz = 'fa fa-times-circle';
}
});
const hasErrors = errors.length > 0;
return (
<div className="gf-table-input-csv" style={{ width, height }}>
<textarea placeholder="Enter CSV here..." value={this.state.text} onChange={this.handleChange} />
<footer>
Rows:{table.rows.length}, Columns:{table.columns.length} <i className={clazz} />
Rows:{table.rows.length}, Columns:{table.columns.length}
{hasErrors ? <i className="fa fa-exclamation-triangle" /> : <i className="fa fa-check-circle" />}
</footer>
</div>
);

@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`processTableData basic processing should read header and two rows 1`] = `
Object {
"errors": Array [],
"meta": Object {
"aborted": false,
"cursor": 17,
"delimiter": ",",
"linebreak": "
",
"truncated": false,
},
"table": Object {
"columnMap": Object {},
"columns": Array [
Object {
"text": "a",
},
Object {
"text": "b",
},
Object {
"text": "c",
},
],
"rows": Array [
Array [
1,
2,
3,
],
Array [
4,
5,
6,
],
],
"type": "table",
},
}
`;

@ -0,0 +1,10 @@
import { parseCSV } from './processTableData';
describe('processTableData', () => {
describe('basic processing', () => {
it('should read header and two rows', () => {
const simpleCSV = 'a,b,c\n1,2,3\n4,5,6';
expect(parseCSV(simpleCSV)).toMatchSnapshot();
});
});
});

@ -0,0 +1,127 @@
import { TableData, Column } from '../types/index';
import Papa, { ParseError, ParseMeta } from 'papaparse';
// Subset of all parse options
export interface ParseConfig {
headerIsFirstLine?: boolean; // Not a papa-parse option
delimiter?: string; // default: ","
newline?: string; // default: "\r\n"
quoteChar?: string; // default: '"'
encoding?: string; // default: ""
comments?: boolean | string; // default: false
}
export interface ParseResults {
table: TableData;
meta: ParseMeta;
errors: ParseError[];
}
/**
* This makes sure the header and all rows have equal length.
*
* @param table (immutable)
* @returns a new table that has equal length rows, or the same
* table if no changes were needed
*/
export function matchRowSizes(table: TableData): TableData {
const { rows } = table;
let { columns } = table;
let sameSize = true;
let size = columns.length;
rows.forEach(row => {
if (size !== row.length) {
sameSize = false;
size = Math.max(size, row.length);
}
});
if (sameSize) {
return table;
}
// Pad Columns
if (size !== columns.length) {
const diff = size - columns.length;
columns = [...columns];
for (let i = 0; i < diff; i++) {
columns.push({
text: 'Column ' + (columns.length + 1),
});
}
}
// Pad Rows
const fixedRows: any[] = [];
rows.forEach(row => {
const diff = size - row.length;
if (diff > 0) {
row = [...row];
for (let i = 0; i < diff; i++) {
row.push(null);
}
}
fixedRows.push(row);
});
return {
columns,
rows: fixedRows,
type: table.type,
columnMap: table.columnMap,
};
}
function makeColumns(values: any[]): Column[] {
return values.map((value, index) => {
if (!value) {
value = 'Column ' + (index + 1);
}
return {
text: value.toString().trim(),
};
});
}
export function parseCSV(text: string, config?: ParseConfig): ParseResults {
const results = Papa.parse(text, { ...config, dynamicTyping: true, skipEmptyLines: true });
const { data, meta, errors } = results;
if (!data || data.length < 1) {
if (!text) {
// Show a more reasonable warning on empty input text
errors.length = 0;
errors.push({
code: 'empty',
message: 'Empty input text',
type: 'warning',
row: 0,
});
}
return {
table: {
columns: [],
rows: [],
type: 'table',
columnMap: {},
},
meta,
errors,
};
}
// Assume the first line is the header unless the config says its not
const headerIsNotFirstLine = config && config.headerIsFirstLine === false;
const header = headerIsNotFirstLine ? [] : results.data.shift();
return {
table: matchRowSizes({
columns: makeColumns(header),
rows: results.data,
type: 'table',
columnMap: {},
}),
meta,
errors,
};
}
Loading…
Cancel
Save