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
|
||||
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]
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
|
|
|
@ -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"];
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
Loading…
Reference in New Issue