Persist and render Dashboard Cell groupby queries (#1092)
* Rename selectStatement to buildInfluxQLQuery * Moved `influxql/select` to `utils/influxql` * Replace `buildQuery` with `buildInfluxQLQuery` util function * Retain GROUP BY clause when saving cell query * Revert "Replace `buildQuery` with `buildInfluxQLQuery` util function" This reverts commit d932d99bfa0de54d07be4b42cc13d1b34fbe950b. * Build DashboardCell queries with buildInfluxQLQuery util Retain old LayouRenderer.buildQuery functionality for canned dashboards, and anything else that isn’t using the queryConfig schema. Rename this function to make it clear that it is legacy behavior, and that it should not be a dependency of any new code. * Update CHANGELOGpull/10616/head
parent
6c4ceedcfa
commit
48de1a95d3
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
1. [#1074](https://github.com/influxdata/chronograf/pull/1074): Fix unexpected redirection to create sources page when deleting a source
|
1. [#1074](https://github.com/influxdata/chronograf/pull/1074): Fix unexpected redirection to create sources page when deleting a source
|
||||||
### Features
|
|
||||||
### UI Improvements
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
1. [#1092](https://github.com/influxdata/chronograf/pull/1092): Persist and render Dashboard Cell groupby queries
|
||||||
|
|
||||||
|
### UI Improvements
|
||||||
|
|
||||||
## v1.2.0-beta6 [2017-03-24]
|
## v1.2.0-beta6 [2017-03-24]
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import selectStatement from 'src/data_explorer/utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
import defaultQueryConfig from 'src/utils/defaultQueryConfig';
|
import defaultQueryConfig from 'src/utils/defaultQueryConfig';
|
||||||
|
|
||||||
function mergeConfig(options) {
|
function mergeConfig(options) {
|
||||||
return Object.assign({}, defaultQueryConfig(123), options);
|
return Object.assign({}, defaultQueryConfig(123), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('selectStatement', () => {
|
describe('buildInfluxQLQuery', () => {
|
||||||
let config, timeBounds;
|
let config, timeBounds;
|
||||||
describe('when information is missing', () => {
|
describe('when information is missing', () => {
|
||||||
it('returns a null select statement', () => {
|
it('returns a null select statement', () => {
|
||||||
expect(selectStatement({}, mergeConfig())).to.equal(null);
|
expect(buildInfluxQLQuery({}, mergeConfig())).to.equal(null);
|
||||||
expect(selectStatement({}, mergeConfig({database: 'db1'}))).to.equal(null); // no measurement
|
expect(buildInfluxQLQuery({}, mergeConfig({database: 'db1'}))).to.equal(null); // no measurement
|
||||||
expect(selectStatement({}, mergeConfig({database: 'db1', measurement: 'm1'}))).to.equal(null); // no fields
|
expect(buildInfluxQLQuery({}, mergeConfig({database: 'db1', measurement: 'm1'}))).to.equal(null); // no fields
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ describe('selectStatement', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
expect(selectStatement({}, config)).to.equal('SELECT "f1" FROM "db1".."m1"');
|
expect(buildInfluxQLQuery({}, config)).to.equal('SELECT "f1" FROM "db1".."m1"');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,11 +32,11 @@ describe('selectStatement', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
expect(selectStatement({}, config)).to.equal('SELECT "f1" FROM "db1"."rp1"."m1"');
|
expect(buildInfluxQLQuery({}, config)).to.equal('SELECT "f1" FROM "db1"."rp1"."m1"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds the right query with a time range', () => {
|
it('builds the right query with a time range', () => {
|
||||||
expect(selectStatement(timeBounds, config)).to.equal('SELECT "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 1hr');
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal('SELECT "f1" FROM "db1"."rp1"."m1" WHERE time > now() - 1hr');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ describe('selectStatement', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not quote the star', () => {
|
it('does not quote the star', () => {
|
||||||
expect(selectStatement({}, config)).to.equal('SELECT * FROM "db1"."rp1"."m1"');
|
expect(buildInfluxQLQuery({}, config)).to.equal('SELECT * FROM "db1"."rp1"."m1"');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ describe('selectStatement', () => {
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
const expected = 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m)';
|
const expected = 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m)';
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ describe('selectStatement', () => {
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
const expected = `SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY "t1", "t2"`;
|
const expected = `SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY "t1", "t2"`;
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ describe('selectStatement', () => {
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
const expected = 'SELECT "value" FROM "db1"."rp1"."m0" WHERE time > \'2015-07-23T15:52:24.447Z\' AND time < \'2015-07-24T15:52:24.447Z\'';
|
const expected = 'SELECT "value" FROM "db1"."rp1"."m0" WHERE time > \'2015-07-23T15:52:24.447Z\' AND time < \'2015-07-24T15:52:24.447Z\'';
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ describe('selectStatement', () => {
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
const expected = 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2"';
|
const expected = 'SELECT min("value") AS "min_value" FROM "db1"."rp1"."m0" WHERE time > now() - 12h GROUP BY time(10m), "t1", "t2"';
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,12 +105,12 @@ describe('selectStatement', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds the right query', () => {
|
it('builds the right query', () => {
|
||||||
expect(selectStatement({}, config)).to.equal('SELECT "f0", "f1" FROM "db1"."rp1"."m0"');
|
expect(buildInfluxQLQuery({}, config)).to.equal('SELECT "f0", "f1" FROM "db1"."rp1"."m0"');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('builds the right query with a time range', () => {
|
it('builds the right query with a time range', () => {
|
||||||
const expected = `SELECT "f0", "f1" FROM "db1"."rp1"."m0" WHERE time < '2015-02-24T00:00:00Z'`;
|
const expected = `SELECT "f0", "f1" FROM "db1"."rp1"."m0" WHERE time < '2015-02-24T00:00:00Z'`;
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with multiple tag pairs', () => {
|
describe('with multiple tag pairs', () => {
|
||||||
|
@ -136,7 +136,7 @@ describe('selectStatement', () => {
|
||||||
|
|
||||||
it('correctly uses AND/OR to combine pairs', () => {
|
it('correctly uses AND/OR to combine pairs', () => {
|
||||||
const expected = `SELECT "f0" FROM "db1"."rp1"."m0" WHERE time > now() - 6h AND ("k1"='v1' OR "k1"='v3' OR "k1"='v4') AND "k2"='v2'`;
|
const expected = `SELECT "f0" FROM "db1"."rp1"."m0" WHERE time > now() - 6h AND ("k1"='v1' OR "k1"='v3' OR "k1"='v4') AND "k2"='v2'`;
|
||||||
expect(selectStatement(timeBounds, config)).to.equal(expected);
|
expect(buildInfluxQLQuery(timeBounds, config)).to.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,9 +9,8 @@ import Visualization from 'src/data_explorer/components/Visualization'
|
||||||
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
import OverlayControls from 'src/dashboards/components/OverlayControls'
|
||||||
import * as queryModifiers from 'src/utils/queryTransitions'
|
import * as queryModifiers from 'src/utils/queryTransitions'
|
||||||
|
|
||||||
import {buildSelectStatement} from 'src/data_explorer/utils/influxql/select'
|
|
||||||
|
|
||||||
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
|
||||||
|
import buildInfluxQLQuery from 'utils/influxql'
|
||||||
|
|
||||||
class CellEditorOverlay extends Component {
|
class CellEditorOverlay extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -63,13 +62,13 @@ class CellEditorOverlay extends Component {
|
||||||
|
|
||||||
handleSaveCell() {
|
handleSaveCell() {
|
||||||
const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state
|
const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state
|
||||||
const {cell} = this.props
|
const {cell, timeRange} = this.props
|
||||||
|
|
||||||
const newCell = _.cloneDeep(cell)
|
const newCell = _.cloneDeep(cell)
|
||||||
newCell.name = cellWorkingName
|
newCell.name = cellWorkingName
|
||||||
newCell.type = cellWorkingType
|
newCell.type = cellWorkingType
|
||||||
newCell.queries = queriesWorkingDraft.map((q) => {
|
newCell.queries = queriesWorkingDraft.map((q) => {
|
||||||
const query = q.rawText || buildSelectStatement(q)
|
const query = q.rawText || buildInfluxQLQuery(timeRange, q)
|
||||||
const label = `${q.measurement}.${q.fields[0].field}`
|
const label = `${q.measurement}.${q.fields[0].field}`
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
import selectStatement from '../utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
|
|
||||||
import DatabaseList from './DatabaseList';
|
import DatabaseList from './DatabaseList';
|
||||||
import MeasurementList from './MeasurementList';
|
import MeasurementList from './MeasurementList';
|
||||||
|
@ -89,7 +89,7 @@ const QueryEditor = React.createClass({
|
||||||
|
|
||||||
renderQuery() {
|
renderQuery() {
|
||||||
const {query, timeRange} = this.props;
|
const {query, timeRange} = this.props;
|
||||||
const statement = query.rawText || selectStatement(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
|
const statement = query.rawText || buildInfluxQLQuery(timeRange, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
|
||||||
|
|
||||||
if (!query.rawText) {
|
if (!query.rawText) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
import selectStatement from '../utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
import AutoRefresh from 'shared/components/AutoRefresh';
|
||||||
import LineGraph from 'shared/components/LineGraph';
|
import LineGraph from 'shared/components/LineGraph';
|
||||||
|
@ -80,7 +80,7 @@ const Visualization = React.createClass({
|
||||||
|
|
||||||
const {isGraphInView} = this.state;
|
const {isGraphInView} = this.state;
|
||||||
const statements = queryConfigs.map((query) => {
|
const statements = queryConfigs.map((query) => {
|
||||||
const text = query.rawText || selectStatement(timeRange, query);
|
const text = query.rawText || buildInfluxQLQuery(timeRange, query);
|
||||||
return {text, id: query.id};
|
return {text, id: query.id};
|
||||||
});
|
});
|
||||||
const queries = statements.filter((s) => s.text !== null).map((s) => {
|
const queries = statements.filter((s) => s.text !== null).map((s) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import selectStatement from '../../data_explorer/utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
|
|
||||||
import DatabaseList from '../../data_explorer/components/DatabaseList';
|
import DatabaseList from '../../data_explorer/components/DatabaseList';
|
||||||
import MeasurementList from '../../data_explorer/components/MeasurementList';
|
import MeasurementList from '../../data_explorer/components/MeasurementList';
|
||||||
|
@ -98,7 +98,7 @@ export const DataSection = React.createClass({
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {query, timeRange: {lower}} = this.props;
|
const {query, timeRange: {lower}} = this.props;
|
||||||
const statement = query.rawText || selectStatement({lower}, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
|
const statement = query.rawText || buildInfluxQLQuery({lower}, query) || `SELECT "fields" FROM "db"."rp"."measurement"`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="kapacitor-rule-section">
|
<div className="kapacitor-rule-section">
|
||||||
|
|
|
@ -5,7 +5,7 @@ import RuleHeader from 'src/kapacitor/components/RuleHeader';
|
||||||
import RuleGraph from 'src/kapacitor/components/RuleGraph';
|
import RuleGraph from 'src/kapacitor/components/RuleGraph';
|
||||||
import RuleMessage from 'src/kapacitor/components/RuleMessage';
|
import RuleMessage from 'src/kapacitor/components/RuleMessage';
|
||||||
import {createRule, editRule} from 'src/kapacitor/apis';
|
import {createRule, editRule} from 'src/kapacitor/apis';
|
||||||
import selectStatement from '../../data_explorer/utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
import timeRanges from 'hson!../../shared/data/timeRanges.hson';
|
import timeRanges from 'hson!../../shared/data/timeRanges.hson';
|
||||||
|
|
||||||
export const KapacitorRule = React.createClass({
|
export const KapacitorRule = React.createClass({
|
||||||
|
@ -108,7 +108,7 @@ export const KapacitorRule = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
validationError() {
|
validationError() {
|
||||||
if (!selectStatement({}, this.props.query)) {
|
if (!buildInfluxQLQuery({}, this.props.query)) {
|
||||||
return 'Please select a database, measurement, and field';
|
return 'Please select a database, measurement, and field';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, {PropTypes} from 'react';
|
import React, {PropTypes} from 'react';
|
||||||
import selectStatement from 'src/data_explorer/utils/influxql/select';
|
import buildInfluxQLQuery from 'utils/influxql';
|
||||||
import AutoRefresh from 'shared/components/AutoRefresh';
|
import AutoRefresh from 'shared/components/AutoRefresh';
|
||||||
import LineGraph from 'shared/components/LineGraph';
|
import LineGraph from 'shared/components/LineGraph';
|
||||||
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
const RefreshingLineGraph = AutoRefresh(LineGraph);
|
||||||
|
@ -27,7 +27,7 @@ export const RuleGraph = React.createClass({
|
||||||
renderGraph() {
|
renderGraph() {
|
||||||
const {query, source, timeRange: {lower}, rule} = this.props;
|
const {query, source, timeRange: {lower}, rule} = this.props;
|
||||||
const autoRefreshMs = 30000;
|
const autoRefreshMs = 30000;
|
||||||
const queryText = selectStatement({lower}, query);
|
const queryText = buildInfluxQLQuery({lower}, query);
|
||||||
const queries = [{host: source.links.proxy, text: queryText}];
|
const queries = [{host: source.links.proxy, text: queryText}];
|
||||||
const kapacitorLineColors = ["#4ED8A0"];
|
const kapacitorLineColors = ["#4ED8A0"];
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ import SingleStat from 'shared/components/SingleStat';
|
||||||
import NameableGraph from 'shared/components/NameableGraph';
|
import NameableGraph from 'shared/components/NameableGraph';
|
||||||
import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
|
import ReactGridLayout, {WidthProvider} from 'react-grid-layout';
|
||||||
|
|
||||||
import timeRanges from 'hson!../data/timeRanges.hson';
|
import timeRanges from 'hson!../data/timeRanges.hson'
|
||||||
|
import buildInfluxQLQuery from 'utils/influxql'
|
||||||
|
|
||||||
const GridLayout = WidthProvider(ReactGridLayout);
|
const GridLayout = WidthProvider(ReactGridLayout);
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ export const LayoutRenderer = React.createClass({
|
||||||
shouldNotBeEditable: bool,
|
shouldNotBeEditable: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
buildQuery(q) {
|
buildQueryForOldQuerySchema(q) {
|
||||||
const {timeRange: {lower}, host} = this.props
|
const {timeRange: {lower}, host} = this.props
|
||||||
const {defaultGroupBy} = timeRanges.find((range) => range.lower === lower)
|
const {defaultGroupBy} = timeRanges.find((range) => range.lower === lower)
|
||||||
const {wheres, groupbys} = q
|
const {wheres, groupbys} = q
|
||||||
|
@ -89,13 +90,24 @@ export const LayoutRenderer = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
generateVisualizations() {
|
generateVisualizations() {
|
||||||
const {autoRefresh, source, cells, onEditCell, onRenameCell, onUpdateCell, onDeleteCell, onSummonOverlayTechnologies, shouldNotBeEditable} = this.props;
|
const {autoRefresh, timeRange, source, cells, onEditCell, onRenameCell, onUpdateCell, onDeleteCell, onSummonOverlayTechnologies, shouldNotBeEditable} = this.props;
|
||||||
|
|
||||||
return cells.map((cell) => {
|
return cells.map((cell) => {
|
||||||
const qs = cell.queries.map((query) => {
|
const qs = cell.queries.map((query) => {
|
||||||
|
// TODO: Canned dashboards (and possibly Kubernetes dashboard) use an old query schema,
|
||||||
|
// which does not have enough information for the new `buildInfluxQLQuery` function
|
||||||
|
// to operate on. We will use `buildQueryForOldQuerySchema` until we conform
|
||||||
|
// on a stable query representation.
|
||||||
|
let queryText
|
||||||
|
if (query.queryConfig) {
|
||||||
|
queryText = buildInfluxQLQuery(timeRange, query.queryConfig)
|
||||||
|
} else {
|
||||||
|
queryText = this.buildQueryForOldQuerySchema(query)
|
||||||
|
}
|
||||||
|
|
||||||
return Object.assign({}, query, {
|
return Object.assign({}, query, {
|
||||||
host: source,
|
host: source,
|
||||||
text: this.buildQuery(query),
|
text: queryText,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default function selectStatement(timeBounds, config) {
|
export default function buildInfluxQLQuery(timeBounds, config) {
|
||||||
const {groupBy, tags, areTagsAccepted} = config;
|
const {groupBy, tags, areTagsAccepted} = config;
|
||||||
const {upper, lower} = timeBounds;
|
const {upper, lower} = timeBounds;
|
||||||
|
|
Loading…
Reference in New Issue