diff --git a/chronograf.go b/chronograf.go index 3faeabe030..b26c3bea0d 100644 --- a/chronograf.go +++ b/chronograf.go @@ -159,7 +159,7 @@ type Range struct { // TemplateValue is a value use to replace a template in an InfluxQL query type TemplateValue struct { Value string `json:"value"` // Value is the specific value used to replace a template in an InfluxQL query - Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant + Type string `json:"type"` // Type can be tagKey, tagValue, fieldKey, csv, measurement, database, constant, influxql Selected bool `json:"selected"` // Selected states that this variable has been picked to use for replacement Key string `json:"key,omitempty"` // Key is the key for the Value if the Template Type is 'map' } @@ -177,7 +177,7 @@ type TemplateID string type Template struct { TemplateVar ID TemplateID `json:"id"` // ID is the unique ID associated with this template - Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, query, measurements, databases, map + Type string `json:"type"` // Type can be fieldKeys, tagKeys, tagValues, CSV, constant, measurements, databases, map, influxql Label string `json:"label"` // Label is a user-facing description of the Template Query *TemplateQuery `json:"query,omitempty"` // Query is used to generate the choices for a template } diff --git a/influx/templates.go b/influx/templates.go index fe6a24a404..86168f7727 100644 --- a/influx/templates.go +++ b/influx/templates.go @@ -72,7 +72,7 @@ func RenderTemplate(query string, t chronograf.TemplateVar, now time.Time) (stri return strings.Replace(q, t.Var, `"`+t.Values[0].Value+`"`, -1), nil case "tagValue", "timeStamp": return strings.Replace(q, t.Var, `'`+t.Values[0].Value+`'`, -1), nil - case "csv", "constant": + case "csv", "constant", "influxql": return strings.Replace(q, t.Var, t.Values[0].Value, -1), nil } diff --git a/server/templates.go b/server/templates.go index e70c1dab96..6f158ac3c3 100644 --- a/server/templates.go +++ b/server/templates.go @@ -15,14 +15,14 @@ func ValidTemplateRequest(template *chronograf.Template) error { switch template.Type { default: return fmt.Errorf("Unknown template type %s", template.Type) - case "query", "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map": + case "constant", "csv", "fieldKeys", "tagKeys", "tagValues", "measurements", "databases", "map", "influxql": } for _, v := range template.Values { switch v.Type { default: return fmt.Errorf("Unknown template variable type %s", v.Type) - case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant": + case "csv", "fieldKey", "tagKey", "tagValue", "measurement", "database", "constant", "influxql": } if template.Type == "map" && v.Key == "" { @@ -30,8 +30,8 @@ func ValidTemplateRequest(template *chronograf.Template) error { } } - if template.Type == "query" && template.Query == nil { - return fmt.Errorf("No query set for template of type 'query'") + if template.Type == "influxql" && template.Query == nil { + return fmt.Errorf("No query set for template of type 'influxql'") } return nil diff --git a/server/templates_test.go b/server/templates_test.go index 83813342bb..cd775acea9 100644 --- a/server/templates_test.go +++ b/server/templates_test.go @@ -57,7 +57,7 @@ func TestValidTemplateRequest(t *testing.T) { name: "No query set", wantErr: true, template: &chronograf.Template{ - Type: "query", + Type: "influxql", }, }, { diff --git a/ui/src/dashboards/actions/index.ts b/ui/src/dashboards/actions/index.ts index 0bbe9bc76e..62419f4ebb 100644 --- a/ui/src/dashboards/actions/index.ts +++ b/ui/src/dashboards/actions/index.ts @@ -5,7 +5,9 @@ import {replace} from 'react-router-redux' import _ from 'lodash' import queryString from 'query-string' +import {proxy} from 'src/utils/queryUrlGenerator' import {isUserAuthorized, EDITOR_ROLE} from 'src/auth/Authorized' +import {parseMetaQuery} from 'src/tempVars/utils/parsing' import { getDashboards as getDashboardsAJAX, @@ -15,7 +17,6 @@ import { updateDashboardCell as updateDashboardCellAJAX, addDashboardCell as addDashboardCellAJAX, deleteDashboardCell as deleteDashboardCellAJAX, - getTempVarValuesBySourceQuery, createDashboard as createDashboardAJAX, } from 'src/dashboards/apis' import {getMe} from 'src/shared/apis/auth' @@ -48,7 +49,6 @@ import { } from 'src/shared/copy/notifications' import {makeQueryForTemplate} from 'src/dashboards/utils/tempVars' -import parsers from 'src/shared/parsing' import {getDeep} from 'src/utils/wrappers' import idNormalizer, {TYPE_ID} from 'src/normalizers/id' @@ -628,25 +628,23 @@ export const hydrateTempVarValuesAsync = ( const dashboard = getState().dashboardUI.dashboards.find( d => d.id === dashboardID ) + const templates: Template[] = dashboard.templates + const queries = templates + .filter( + template => getDeep(template, 'query.influxql', '') !== '' + ) + .map(async template => { + const query = makeQueryForTemplate(template.query) + const response = await proxy({source: source.links.proxy, query}) + const values = parseMetaQuery(query, response.data) - const tempsWithQueries = dashboard.templates.filter( - ({query}) => !!query.influxql - ) - - const asyncQueries = tempsWithQueries.map(({query}) => - getTempVarValuesBySourceQuery(source, { - query: makeQueryForTemplate(query), + return {template, values} }) - ) + const results = await Promise.all(queries) - const results = await Promise.all(asyncQueries) - - results.forEach(({data}, i) => { - const {type, query, id} = tempsWithQueries[i] - const parsed = parsers[type](data, query.tagKey || query.measurement) - const vals = parsed[type] - dispatch(editTemplateVariableValues(+dashboard.id, id, vals)) - }) + for (const {template, values} of results) { + dispatch(editTemplateVariableValues(+dashboard.id, template.id, values)) + } } catch (error) { console.error(error) dispatch(errorThrown(error)) diff --git a/ui/src/dashboards/apis/index.js b/ui/src/dashboards/apis/index.js index 5719a0b05c..ff04e841a1 100644 --- a/ui/src/dashboards/apis/index.js +++ b/ui/src/dashboards/apis/index.js @@ -1,5 +1,4 @@ import AJAX from 'utils/ajax' -import {proxy} from 'utils/queryUrlGenerator' export function getDashboards() { return AJAX({ @@ -98,19 +97,3 @@ export const editTemplateVariables = async templateVariable => { throw error } } - -export const getTempVarValuesBySourceQuery = async (source, templateQuery) => { - const { - query, - db, - // rp, TODO - tempVars, - } = templateQuery - try { - // TODO: add rp as argument to proxy - return await proxy({source: source.links.proxy, query, db, tempVars}) - } catch (error) { - console.error(error) - throw error - } -} diff --git a/ui/src/shared/parsing/showSeries.ts b/ui/src/shared/parsing/showSeries.ts new file mode 100644 index 0000000000..0ebd1436a9 --- /dev/null +++ b/ui/src/shared/parsing/showSeries.ts @@ -0,0 +1,26 @@ +import {getDeep} from 'src/utils/wrappers' + +interface ParseShowSeriesResponse { + errors: string[] + series: string[] +} + +const parseShowSeries = (response): ParseShowSeriesResponse => { + const results = response.results[0] + + if (results.error) { + return {errors: [results.error], series: []} + } + + const seriesValues = getDeep(results, 'series.0.values', []) + + if (!seriesValues.length) { + return {errors: [], series: []} + } + + const series = seriesValues.map(s => s[0]) + + return {series, errors: []} +} + +export default parseShowSeries diff --git a/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx b/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx new file mode 100644 index 0000000000..6fdc421006 --- /dev/null +++ b/ui/src/tempVars/components/MetaQueryTemplateBuilder.tsx @@ -0,0 +1,169 @@ +import React, {PureComponent, ChangeEvent} from 'react' +import _ from 'lodash' + +import {proxy} from 'src/utils/queryUrlGenerator' +import {ErrorHandling} from 'src/shared/decorators/errors' +import TemplateMetaQueryPreview from 'src/tempVars/components/TemplateMetaQueryPreview' +import {parseMetaQuery, isInvalidMetaQuery} from 'src/tempVars/utils/parsing' +import {getDeep} from 'src/utils/wrappers' + +import { + TemplateBuilderProps, + RemoteDataState, + TemplateValueType, +} from 'src/types' + +const DEBOUNCE_DELAY = 750 + +interface State { + metaQueryInput: string // bound to input + metaQuery: string // debounced view of metaQueryInput + metaQueryResults: string[] + metaQueryResultsStatus: RemoteDataState +} + +@ErrorHandling +class CustomMetaQueryTemplateBuilder extends PureComponent< + TemplateBuilderProps, + State +> { + private handleMetaQueryChange: () => void = _.debounce(() => { + const {metaQuery, metaQueryInput} = this.state + + if (metaQuery === metaQueryInput) { + return + } + + this.setState({metaQuery: metaQueryInput}, this.executeQuery) + }, DEBOUNCE_DELAY) + + constructor(props: TemplateBuilderProps) { + super(props) + + const metaQuery = getDeep(props.template, 'query.influxql', '') + + this.state = { + metaQuery, + metaQueryInput: metaQuery, + metaQueryResults: [], + metaQueryResultsStatus: RemoteDataState.NotStarted, + } + } + + public componentDidMount() { + this.executeQuery() + } + + public render() { + const {metaQueryInput} = this.state + + return ( +
+
+ +
+