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 CHANGELOG
pull/10616/head
lukevmorris 2017-03-28 11:02:49 -07:00 committed by GitHub
parent 6c4ceedcfa
commit 48de1a95d3
10 changed files with 50 additions and 37 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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';
} }

View File

@ -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"];

View File

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

View File

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