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

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';
function mergeConfig(options) {
return Object.assign({}, defaultQueryConfig(123), options);
}
describe('selectStatement', () => {
describe('buildInfluxQLQuery', () => {
let config, timeBounds;
describe('when information is missing', () => {
it('returns a null select statement', () => {
expect(selectStatement({}, mergeConfig())).to.equal(null);
expect(selectStatement({}, mergeConfig({database: 'db1'}))).to.equal(null); // no measurement
expect(selectStatement({}, mergeConfig({database: 'db1', measurement: 'm1'}))).to.equal(null); // no fields
expect(buildInfluxQLQuery({}, mergeConfig())).to.equal(null);
expect(buildInfluxQLQuery({}, mergeConfig({database: 'db1'}))).to.equal(null); // no measurement
expect(buildInfluxQLQuery({}, mergeConfig({database: 'db1', measurement: 'm1'}))).to.equal(null); // no fields
});
});
@ -21,7 +21,7 @@ describe('selectStatement', () => {
});
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
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', () => {
@ -136,7 +136,7 @@ describe('selectStatement', () => {
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'`;
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 * as queryModifiers from 'src/utils/queryTransitions'
import {buildSelectStatement} from 'src/data_explorer/utils/influxql/select'
import defaultQueryConfig from 'src/utils/defaultQueryConfig'
import buildInfluxQLQuery from 'utils/influxql'
class CellEditorOverlay extends Component {
constructor(props) {
@ -63,13 +62,13 @@ class CellEditorOverlay extends Component {
handleSaveCell() {
const {queriesWorkingDraft, cellWorkingType, cellWorkingName} = this.state
const {cell} = this.props
const {cell, timeRange} = this.props
const newCell = _.cloneDeep(cell)
newCell.name = cellWorkingName
newCell.type = cellWorkingType
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}`
return {

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react';
import selectStatement from '../utils/influxql/select';
import buildInfluxQLQuery from 'utils/influxql';
import DatabaseList from './DatabaseList';
import MeasurementList from './MeasurementList';
@ -89,7 +89,7 @@ const QueryEditor = React.createClass({
renderQuery() {
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) {
return (

View File

@ -1,5 +1,5 @@
import React, {PropTypes} from 'react';
import selectStatement from '../utils/influxql/select';
import buildInfluxQLQuery from 'utils/influxql';
import classNames from 'classnames';
import AutoRefresh from 'shared/components/AutoRefresh';
import LineGraph from 'shared/components/LineGraph';
@ -80,7 +80,7 @@ const Visualization = React.createClass({
const {isGraphInView} = this.state;
const statements = queryConfigs.map((query) => {
const text = query.rawText || selectStatement(timeRange, query);
const text = query.rawText || buildInfluxQLQuery(timeRange, query);
return {text, id: query.id};
});
const queries = statements.filter((s) => s.text !== null).map((s) => {

View File

@ -1,7 +1,7 @@
import React, {PropTypes} from 'react';
import classNames from 'classnames';
import _ from 'lodash';
import selectStatement from '../../data_explorer/utils/influxql/select';
import buildInfluxQLQuery from 'utils/influxql';
import DatabaseList from '../../data_explorer/components/DatabaseList';
import MeasurementList from '../../data_explorer/components/MeasurementList';
@ -98,7 +98,7 @@ export const DataSection = React.createClass({
render() {
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 (
<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 RuleMessage from 'src/kapacitor/components/RuleMessage';
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';
export const KapacitorRule = React.createClass({
@ -108,7 +108,7 @@ export const KapacitorRule = React.createClass({
},
validationError() {
if (!selectStatement({}, this.props.query)) {
if (!buildInfluxQLQuery({}, this.props.query)) {
return 'Please select a database, measurement, and field';
}

View File

@ -1,5 +1,5 @@
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 LineGraph from 'shared/components/LineGraph';
const RefreshingLineGraph = AutoRefresh(LineGraph);
@ -27,7 +27,7 @@ export const RuleGraph = React.createClass({
renderGraph() {
const {query, source, timeRange: {lower}, rule} = this.props;
const autoRefreshMs = 30000;
const queryText = selectStatement({lower}, query);
const queryText = buildInfluxQLQuery({lower}, query);
const queries = [{host: source.links.proxy, text: queryText}];
const kapacitorLineColors = ["#4ED8A0"];

View File

@ -5,7 +5,8 @@ import SingleStat from 'shared/components/SingleStat';
import NameableGraph from 'shared/components/NameableGraph';
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);
@ -56,7 +57,7 @@ export const LayoutRenderer = React.createClass({
shouldNotBeEditable: bool,
},
buildQuery(q) {
buildQueryForOldQuerySchema(q) {
const {timeRange: {lower}, host} = this.props
const {defaultGroupBy} = timeRanges.find((range) => range.lower === lower)
const {wheres, groupbys} = q
@ -89,13 +90,24 @@ export const LayoutRenderer = React.createClass({
},
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) => {
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, {
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 {upper, lower} = timeBounds;