Surface errors appearing within Flux tables
parent
4565d6b57c
commit
7d7d366684
|
@ -9,6 +9,7 @@
|
||||||
1. [12791](https://github.com/influxdata/influxdb/pull/12791): Use time range for metaqueries in Data Explorer and Cell Editor Overlay
|
1. [12791](https://github.com/influxdata/influxdb/pull/12791): Use time range for metaqueries in Data Explorer and Cell Editor Overlay
|
||||||
1. [12827](https://github.com/influxdata/influxdb/pull/12827): Fix screen tearing bug in Raw Data View
|
1. [12827](https://github.com/influxdata/influxdb/pull/12827): Fix screen tearing bug in Raw Data View
|
||||||
1. [12843](https://github.com/influxdata/influxdb/pull/12843): Add copy to clipboard button to export overlays
|
1. [12843](https://github.com/influxdata/influxdb/pull/12843): Add copy to clipboard button to export overlays
|
||||||
|
1. [12826](https://github.com/influxdata/influxdb/pull/12826): Enable copying error messages to the clipboard from dashboard cells
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,9 @@
|
||||||
top: $ix-marg-b;
|
top: $ix-marg-b;
|
||||||
right: $ix-marg-b;
|
right: $ix-marg-b;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
.empty-graph-error:hover & {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -12,6 +12,7 @@ import {
|
||||||
// Utils
|
// Utils
|
||||||
import {parseResponse} from 'src/shared/parsing/flux/response'
|
import {parseResponse} from 'src/shared/parsing/flux/response'
|
||||||
import {getActiveOrg} from 'src/organizations/selectors'
|
import {getActiveOrg} from 'src/organizations/selectors'
|
||||||
|
import {checkQueryResult} from 'src/shared/utils/checkQueryResult'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {RemoteDataState, FluxTable} from 'src/types'
|
import {RemoteDataState, FluxTable} from 'src/types'
|
||||||
|
@ -137,6 +138,8 @@ class TimeSeries extends Component<Props, State> {
|
||||||
const tables = flatten(results.map(r => parseResponse(r.csv)))
|
const tables = flatten(results.map(r => parseResponse(r.csv)))
|
||||||
const files = results.map(r => r.csv)
|
const files = results.map(r => r.csv)
|
||||||
|
|
||||||
|
files.forEach(checkQueryResult)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
tables,
|
tables,
|
||||||
files,
|
files,
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import {checkQueryResult} from 'src/shared/utils/checkQueryResult'
|
||||||
|
|
||||||
|
describe('checkQueryResult', () => {
|
||||||
|
test('throws an error when the response has an error table', () => {
|
||||||
|
const RESPONSE = `#group,true,true
|
||||||
|
#datatype,string,string
|
||||||
|
#default,,
|
||||||
|
,error,reference
|
||||||
|
,"function references unknown column ""_value""",`
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
checkQueryResult(RESPONSE)
|
||||||
|
}).toThrow('function references unknown column')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not throw an error when the response is valid', () => {
|
||||||
|
const RESPONSE = `#group,false,false,true,true,false,false,true,true,true
|
||||||
|
#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,long,string,string,string
|
||||||
|
#default,_result,,,,,,,,
|
||||||
|
,result,table,_start,_stop,_time,_value,_measurement,host,_field
|
||||||
|
,,0,2019-03-21T18:54:14.113478Z,2019-03-21T19:54:14.113478Z,2019-03-21T18:54:21Z,4780101632,mem,oox4k.local,active
|
||||||
|
,,0,2019-03-21T18:54:14.113478Z,2019-03-21T19:54:14.113478Z,2019-03-21T18:54:31Z,5095436288,mem,oox4k.local,active`
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
checkQueryResult(RESPONSE)
|
||||||
|
}).not.toThrow()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Given Flux query response as a CSV, check if the CSV contains an error table
|
||||||
|
as the first result. If it does, throw the error message contained within
|
||||||
|
that table.
|
||||||
|
|
||||||
|
For example, given the following response:
|
||||||
|
|
||||||
|
#datatype,string,long
|
||||||
|
,error,reference
|
||||||
|
,Failed to parse query,897
|
||||||
|
|
||||||
|
we want to throw an error with the message "Failed to parse query".
|
||||||
|
|
||||||
|
See https://github.com/influxdata/flux/blob/master/docs/SPEC.md#errors.
|
||||||
|
*/
|
||||||
|
export const checkQueryResult = (file: string): void => {
|
||||||
|
// Don't check the whole file, since it could be huge and the error table
|
||||||
|
// will be within the first few lines (if it exists)
|
||||||
|
const fileHead = file.slice(0, findNthIndex(file, '\n', 6))
|
||||||
|
|
||||||
|
const lines = fileHead.split('\n').filter(line => !line.startsWith('#'))
|
||||||
|
|
||||||
|
if (!lines.length || !lines[0].includes('error') || !lines[1]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = lines[0].split(',').map(s => s.trim())
|
||||||
|
const row = lines[1].split(',').map(s => s.trim())
|
||||||
|
const index = header.indexOf('error')
|
||||||
|
|
||||||
|
if (index === -1 || !row[index]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim off extra quotes at start and end of message
|
||||||
|
const errorMessage = row[index].replace(/^"/, '').replace(/"$/, '')
|
||||||
|
|
||||||
|
throw new Error(errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
const findNthIndex = (s: string, c: string, n: number) => {
|
||||||
|
let count = 0
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
while (i < s.length) {
|
||||||
|
if (s[i] == c) {
|
||||||
|
count += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count === n) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import {getActiveOrg} from 'src/organizations/selectors'
|
||||||
import {getVariableAssignments} from 'src/variables/selectors'
|
import {getVariableAssignments} from 'src/variables/selectors'
|
||||||
import {getTimeRangeVars} from 'src/variables/utils/getTimeRangeVars'
|
import {getTimeRangeVars} from 'src/variables/utils/getTimeRangeVars'
|
||||||
import {filterUnusedVars} from 'src/shared/utils/filterUnusedVars'
|
import {filterUnusedVars} from 'src/shared/utils/filterUnusedVars'
|
||||||
|
import {checkQueryResult} from 'src/shared/utils/checkQueryResult'
|
||||||
import {
|
import {
|
||||||
getVariablesForOrg,
|
getVariablesForOrg,
|
||||||
getVariable,
|
getVariable,
|
||||||
|
@ -113,9 +114,10 @@ export const executeQueries = () => async (dispatch, getState: GetState) => {
|
||||||
const results = await Promise.all(pendingResults.map(r => r.promise))
|
const results = await Promise.all(pendingResults.map(r => r.promise))
|
||||||
|
|
||||||
const duration = Date.now() - startTime
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
const files = results.map(r => r.csv)
|
const files = results.map(r => r.csv)
|
||||||
|
|
||||||
|
files.forEach(checkQueryResult)
|
||||||
|
|
||||||
dispatch(setQueryResults(RemoteDataState.Done, files, duration))
|
dispatch(setQueryResults(RemoteDataState.Done, files, duration))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof CancellationError) {
|
if (e instanceof CancellationError) {
|
||||||
|
|
Loading…
Reference in New Issue