Browse Source

moved utillities to util

ryan 7 years ago
parent
commit
c7f35f2dcf

+ 5 - 2
packages/grafana-ui/src/components/Table/TableInputCSV.story.tsx

@@ -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>

+ 2 - 1
packages/grafana-ui/src/components/Table/TableInputCSV.test.tsx

@@ -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', () => {

+ 4 - 112
packages/grafana-ui/src/components/Table/TableInputCSV.tsx

@@ -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>
     );

+ 42 - 0
packages/grafana-ui/src/utils/__snapshots__/processTableData.test.ts.snap

@@ -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",
+  },
+}
+`;

+ 10 - 0
packages/grafana-ui/src/utils/processTableData.test.ts

@@ -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();
+    });
+  });
+});

+ 127 - 0
packages/grafana-ui/src/utils/processTableData.ts

@@ -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,
+  };
+}