diff --git a/ui/package.json b/ui/package.json index aee04c93c..37aa0ce68 100644 --- a/ui/package.json +++ b/ui/package.json @@ -41,6 +41,7 @@ "@types/jest": "^22.1.4", "@types/lodash": "^4.14.104", "@types/node": "^9.4.6", + "@types/papaparse": "^4.1.34", "@types/prop-types": "^15.5.2", "@types/react": "^16.0.38", "@types/react-dnd": "^2.0.36", @@ -133,6 +134,7 @@ "lodash": "^4.3.0", "moment": "^2.13.0", "nano-date": "^2.0.1", + "papaparse": "^4.4.0", "prop-types": "^15.6.1", "query-string": "^5.0.0", "react": "^16.3.1", @@ -157,4 +159,4 @@ "rome": "^2.1.22", "uuid": "^3.2.1" } -} \ No newline at end of file +} diff --git a/ui/src/shared/parsing/ifql.ts b/ui/src/shared/parsing/ifql.ts index c72960cfa..230315450 100644 --- a/ui/src/shared/parsing/ifql.ts +++ b/ui/src/shared/parsing/ifql.ts @@ -1,3 +1,34 @@ -export const parseTables = resp => { - return resp.split('\n\n') +import Papa from 'papaparse' +import _ from 'lodash' + +interface ScriptResult { + name: string + data: string[][] + metadata: string[][] +} + +export const parseResults = (resp: string): ScriptResult[] => { + return resp.split('\n\n').map(parseResult) +} + +export const parseResult = (raw: string, index: number): ScriptResult => { + const lines = raw.split('\n') + const rawMetadata: string = lines + .filter(line => line.startsWith('#')) + .map(line => line.slice(1)) + .join('\n') + const rawData: string = lines.filter(line => !line.startsWith('#')).join('\n') + + const metadata = Papa.parse(rawMetadata).data + const data = Papa.parse(rawData).data + + const headerRow = _.get(data, '0', []) + const measurementHeaderIndex = headerRow.findIndex(v => v === '_measurement') + const name = _.get(data, `1.${measurementHeaderIndex}`, `Result ${index}`) + + return { + name, + data, + metadata, + } } diff --git a/ui/test/shared/parsing/constants.js b/ui/test/shared/parsing/constants.ts similarity index 98% rename from ui/test/shared/parsing/constants.js rename to ui/test/shared/parsing/constants.ts index 3032c8517..f8c6b8e21 100644 --- a/ui/test/shared/parsing/constants.js +++ b/ui/test/shared/parsing/constants.ts @@ -1358,7 +1358,66 @@ export const rule = { } // prettier-ignore -export const FROM_LAST_RESPONSE = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +export const RESPONSE_METADATA = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string +#partition,false,false,false,false,false,false,true,true,true,true +#default,_result,,,,,,,,, +,result,table,_start,_stop,_time,_value,_field,_measurement,cpu,host +,,0,2018-05-23T17:42:29.536834648Z,2018-05-23T17:43:29.536834648Z,2018-05-23T17:42:29.654Z,0,usage_guest,cpu,cpu-total,WattsInfluxDB +` + +export const RESPONSE_NO_METADATA = `,result,table,_start,_stop,_time,_value,_field,_measurement,cpu,host +,,0,2018-05-23T17:42:29.536834648Z,2018-05-23T17:43:29.536834648Z,2018-05-23T17:42:29.654Z,0,usage_guest,cpu,cpu-total,WattsInfluxDB +` + +export const RESPONSE_NO_MEASUREMENT = `,result,table,_start,_stop,_time,_value,_field,cpu,host +,,0,2018-05-23T17:42:29.536834648Z,2018-05-23T17:43:29.536834648Z,2018-05-23T17:42:29.654Z,0,usage_guest,cpu-total,WattsInfluxDB` + +export const EXPECTED_COLUMNS = [ + '', + 'result', + 'table', + '_start', + '_stop', + '_time', + '_value', + '_field', + '_measurement', + 'cpu', + 'host', +] + +export const EXPECTED_METADATA = [ + [ + 'datatype', + 'string', + 'long', + 'dateTime:RFC3339', + 'dateTime:RFC3339', + 'dateTime:RFC3339', + 'double', + 'string', + 'string', + 'string', + 'string', + ], + [ + 'partition', + 'false', + 'false', + 'false', + 'false', + 'false', + 'false', + 'true', + 'true', + 'true', + 'true', + ], + ['default', '_result', '', '', '', '', '', '', '', '', ''], +] + +// prettier-ignore +export const LARGE_RESPONSE = `#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string,string #partition,false,false,false,false,false,false,true,true,true,true #default,_result,,,,,,,,, ,result,table,_start,_stop,_time,_value,_field,_measurement,cpu,host diff --git a/ui/test/shared/parsing/ifql.test.ts b/ui/test/shared/parsing/ifql.test.ts index 59cb8e8bf..775dc5646 100644 --- a/ui/test/shared/parsing/ifql.test.ts +++ b/ui/test/shared/parsing/ifql.test.ts @@ -1,10 +1,52 @@ -import {parseTables} from 'src/shared/parsing/ifql' -import {FROM_LAST_RESPONSE} from 'test/shared/parsing/constants' +import {parseResults} from 'src/shared/parsing/ifql' +import { + RESPONSE_NO_METADATA, + RESPONSE_METADATA, + RESPONSE_NO_MEASUREMENT, + LARGE_RESPONSE, + EXPECTED_METADATA, + EXPECTED_COLUMNS, +} from 'test/shared/parsing/constants' describe('IFQL response parser', () => { - it('parseTables', () => { - const result = parseTables(FROM_LAST_RESPONSE) - + it('parseResults into the right number of tables', () => { + const result = parseResults(LARGE_RESPONSE) expect(result).toHaveLength(47) }) + + describe('headers', () => { + it('can parse headers when no metadata is present', () => { + const actual = parseResults(RESPONSE_NO_METADATA)[0].data[0] + + expect(actual).toEqual(EXPECTED_COLUMNS) + }) + + it('can parse headers when metadata is present', () => { + const actual = parseResults(RESPONSE_METADATA)[0].data[0] + + expect(actual).toEqual(EXPECTED_COLUMNS) + }) + + it('returns the approriate metadata', () => { + const actual = parseResults(RESPONSE_METADATA)[0].metadata + + expect(actual).toEqual(EXPECTED_METADATA) + }) + }) + + describe('name', () => { + it('uses the measurement as a name when present', () => { + const actual = parseResults(RESPONSE_METADATA)[0].name + const expected = 'cpu' + + expect(actual).toBe(expected) + }) + + it('uses the index as a name if a measurement column is not present', () => { + const actual = parseResults(RESPONSE_NO_MEASUREMENT)[0].name + const expected = 'Result 0' + + expect(actual).toBe(expected) + }) + }) }) diff --git a/ui/yarn.lock b/ui/yarn.lock index f415ebb9f..6c9e96b60 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -65,6 +65,10 @@ version "9.6.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2" +"@types/papaparse@^4.1.34": + version "4.1.34" + resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-4.1.34.tgz#893628fbb70243313e46d1b962989c023184783b" + "@types/prop-types@*", "@types/prop-types@^15.5.2": version "15.5.2" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.2.tgz#3c6b8dceb2906cc87fe4358e809f9d20c8d59be1" @@ -6297,6 +6301,10 @@ pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" +papaparse@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-4.4.0.tgz#6bcdbda80873e00cfb0bdcd7a4571c72a9a40168" + parallel-transform@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06"