diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c2b230a6..45642216c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ 1. [#3279](https://github.com/influxdata/chronograf/pull/3279): Change color when value is equal to or greater than threshold value 1. [#3281](https://github.com/influxdata/chronograf/pull/3281): Fix base path for kapacitor logs 1. [#3284](https://github.com/influxdata/chronograf/pull/3284): Fix logout when using basepath & simplify basepath usage (deprecates `PREFIX_ROUTES`) +1. [#3345](https://github.com/influxdata/chronograf/pull/3345): Fix auto not showing in the group by dropdown and explorer getting disconnected ## v1.4.4.1 [2018-04-16] diff --git a/ui/mocks/dummy.ts b/ui/mocks/dummy.ts index 869313eb4..e62a2a519 100644 --- a/ui/mocks/dummy.ts +++ b/ui/mocks/dummy.ts @@ -63,3 +63,49 @@ export const updateKapacitorBody = { proxy: '/chronograf/v1/sources/47/kapacitors/1/proxy', }, } + +export const queryConfig = { + queries: [ + { + id: '60842c85-8bc7-4180-a844-b974e47a98cd', + query: + 'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)', + queryConfig: { + id: '60842c85-8bc7-4180-a844-b974e47a98cd', + database: 'telegraf', + measurement: 'cpu', + retentionPolicy: 'autogen', + fields: [ + { + value: 'mean', + type: 'func', + alias: '', + args: [{value: 'usage_idle', type: 'field', alias: ''}], + }, + { + value: 'mean', + type: 'func', + alias: 'mean_usage_user', + args: [{value: 'usage_user', type: 'field', alias: ''}], + }, + ], + tags: {}, + groupBy: {time: 'auto', tags: []}, + areTagsAccepted: false, + fill: 'null', + rawText: + 'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)', + range: null, + shifts: [], + }, + queryTemplated: + 'SELECT mean("usage_idle"), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)', + tempVars: [ + { + tempVar: ':fields:', + values: [{value: 'usage_idle', type: 'fieldKey', selected: true}], + }, + ], + }, + ], +} diff --git a/ui/mocks/shared/apis/index.ts b/ui/mocks/shared/apis/index.ts index a3c6e233c..7257d4754 100644 --- a/ui/mocks/shared/apis/index.ts +++ b/ui/mocks/shared/apis/index.ts @@ -1,7 +1,10 @@ -import {kapacitor} from 'mocks/dummy' +import {kapacitor, queryConfig} from 'mocks/dummy' export const getKapacitor = jest.fn(() => Promise.resolve(kapacitor)) export const getActiveKapacitor = jest.fn(() => Promise.resolve(kapacitor)) export const createKapacitor = jest.fn(() => Promise.resolve({data: kapacitor})) export const updateKapacitor = jest.fn(() => Promise.resolve({data: kapacitor})) export const pingKapacitor = jest.fn(() => Promise.resolve()) +export const getQueryConfigAndStatus = jest.fn(() => + Promise.resolve({data: queryConfig}) +) diff --git a/ui/src/dashboards/components/CellEditorOverlay.tsx b/ui/src/dashboards/components/CellEditorOverlay.tsx index 161747421..f7f858911 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.tsx +++ b/ui/src/dashboards/components/CellEditorOverlay.tsx @@ -3,6 +3,11 @@ import React, {Component} from 'react' import _ from 'lodash' import uuid from 'uuid' +import { + CellEditorOverlayActions, + CellEditorOverlayActionsFunc, +} from 'src/types/dashboard' + import ResizeContainer from 'src/shared/components/ResizeContainer' import QueryMaker from 'src/dashboards/components/QueryMaker' import Visualization from 'src/dashboards/components/Visualization' @@ -14,7 +19,7 @@ import * as queryModifiers from 'src/utils/queryTransitions' import defaultQueryConfig from 'src/utils/defaultQueryConfig' import {buildQuery} from 'src/utils/influxql' -import {getQueryConfig} from 'src/shared/apis' +import {getQueryConfigAndStatus} from 'src/shared/apis' import {IS_STATIC_LEGEND} from 'src/shared/constants' import {ColorString, ColorNumber} from 'src/types/colors' import {nextSource} from 'src/dashboards/utils/sources' @@ -25,11 +30,21 @@ import { } from 'src/dashboards/constants' import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames' import {MINIMUM_HEIGHTS, INITIAL_HEIGHTS} from 'src/data_explorer/constants' -import {AUTO_GROUP_BY} from 'src/shared/constants' +import { + AUTO_GROUP_BY, + PREDEFINED_TEMP_VARS, + TEMP_VAR_DASHBOARD_TIME, +} from 'src/shared/constants' import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' -import {TimeRange, Source, Query} from 'src/types' -import {Status} from 'src/types/query' -import {Cell, CellQuery, Legend} from 'src/types/dashboard' +import { + TimeRange, + Source, + QueryConfig, + Cell, + CellQuery, + Legend, + Status, +} from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' const staticLegend: Legend = { @@ -65,15 +80,15 @@ interface Props { } interface State { - queriesWorkingDraft: Query[] + queriesWorkingDraft: QueryConfig[] activeQueryIndex: number isDisplayOptionsTabActive: boolean isStaticLegend: boolean } -const createWorkingDraft = (source: string, query: CellQuery): Query => { +const createWorkingDraft = (source: string, query: CellQuery): QueryConfig => { const {queryConfig} = query - const draft: Query = { + const draft: QueryConfig = { ...queryConfig, id: uuid.v4(), source, @@ -84,8 +99,8 @@ const createWorkingDraft = (source: string, query: CellQuery): Query => { const createWorkingDrafts = ( source: string, - queries: CellQuery[] = [] -): Query[] => + queries: CellQuery[] +): QueryConfig[] => _.cloneDeep( queries.map((query: CellQuery) => createWorkingDraft(source, query)) ) @@ -142,7 +157,9 @@ class CellEditorOverlay extends Component { } public componentDidMount() { - this.overlayRef.focus() + if (this.overlayRef) { + this.overlayRef.focus() + } } public render() { @@ -228,7 +245,10 @@ class CellEditorOverlay extends Component { this.overlayRef = r } - private queryStateReducer = queryModifier => (queryID, ...payload) => { + private queryStateReducer = (queryModifier): CellEditorOverlayActionsFunc => ( + queryID: string, + ...payload: any[] + ) => { const {queriesWorkingDraft} = this.state const query = queriesWorkingDraft.find(q => q.id === queryID) @@ -270,7 +290,7 @@ class CellEditorOverlay extends Component { const {cell, thresholdsListColors, gaugeColors, lineColors} = this.props const queries = queriesWorkingDraft.map(q => { - const timeRange = q.range || {upper: null, lower: ':dashboardTime:'} + const timeRange = q.range || {upper: null, lower: TEMP_VAR_DASHBOARD_TIME} return { queryConfig: q, @@ -317,20 +337,91 @@ class CellEditorOverlay extends Component { private getActiveQuery = () => { const {queriesWorkingDraft, activeQueryIndex} = this.state + const activeQuery = _.get( + queriesWorkingDraft, + activeQueryIndex, + queriesWorkingDraft[0] + ) - return _.get(queriesWorkingDraft, activeQueryIndex, queriesWorkingDraft[0]) + const queryText = _.get(activeQuery, 'rawText', '') + const userDefinedTempVarsInQuery = this.findUserDefinedTempVarsInQuery( + queryText, + this.props.templates + ) + + if (!!userDefinedTempVarsInQuery.length) { + activeQuery.isQuerySupportedByExplorer = false + } + + return activeQuery } - private handleEditRawText = async (url, id, text) => { - const templates = removeUnselectedTemplateValues(this.props.templates) - - // use this as the handler passed into fetchTimeSeries to update a query status - try { - const {data} = await getQueryConfig(url, [{query: text, id}], templates) - const config = data.queries.find(q => q.id === id) - const nextQueries = this.state.queriesWorkingDraft.map( - q => (q.id === id ? {...config.queryConfig, source: q.source} : q) + private findUserDefinedTempVarsInQuery = ( + query: string, + templates: Template[] + ): Template[] => { + return templates.filter((temp: Template) => { + if (!query) { + return false + } + const isPredefinedTempVar: boolean = !!PREDEFINED_TEMP_VARS.find( + t => t === temp.tempVar ) + if (!isPredefinedTempVar) { + return query.includes(temp.tempVar) + } + return false + }) + } + + // The schema explorer is not built to handle user defined template variables + // in the query in a clear manner. If they are being used, we indicate that in + // the query config in order to disable the fields column down stream because + // at this point the query string is disconnected from the schema explorer. + private handleEditRawText = async ( + url: string, + id: string, + text: string + ): Promise => { + const userDefinedTempVarsInQuery = this.findUserDefinedTempVarsInQuery( + text, + this.props.templates + ) + + const isUsingUserDefinedTempVars: boolean = !!userDefinedTempVarsInQuery.length + + try { + const selectedTempVars: Template[] = isUsingUserDefinedTempVars + ? removeUnselectedTemplateValues(userDefinedTempVarsInQuery) + : [] + + const {data} = await getQueryConfigAndStatus( + url, + [{query: text, id}], + selectedTempVars + ) + + const config = data.queries.find(q => q.id === id) + const nextQueries: QueryConfig[] = this.state.queriesWorkingDraft.map( + (q: QueryConfig) => { + if (q.id === id) { + const isQuerySupportedByExplorer = !isUsingUserDefinedTempVars + + if (isUsingUserDefinedTempVars) { + return {...q, rawText: text, isQuerySupportedByExplorer} + } + + return { + ...config.queryConfig, + source: q.source, + isQuerySupportedByExplorer, + } + } + + return q + } + ) + this.setState({queriesWorkingDraft: nextQueries}) } catch (error) { console.error(error) @@ -389,17 +480,32 @@ class CellEditorOverlay extends Component { const {queriesWorkingDraft} = this.state return queriesWorkingDraft.every( - (query: Query) => + (query: QueryConfig) => (!!query.measurement && !!query.database && !!query.fields.length) || !!query.rawText ) } - private get queryActions() { - return { - editRawTextAsync: this.handleEditRawText, - ..._.mapValues(queryModifiers, this.queryStateReducer), + private get queryActions(): CellEditorOverlayActions { + const original = { + editRawTextAsync: () => Promise.resolve(), + ...queryModifiers, } + const mapped = _.reduce( + original, + (acc, v, k) => { + acc[k] = this.queryStateReducer(v) + return acc + }, + original + ) + + const result = { + ...mapped, + editRawTextAsync: this.handleEditRawText, + } + + return result } private get sourceLink(): string { diff --git a/ui/src/dashboards/components/QueryMaker.js b/ui/src/dashboards/components/QueryMaker.tsx similarity index 50% rename from ui/src/dashboards/components/QueryMaker.js rename to ui/src/dashboards/components/QueryMaker.tsx index 0658a4df4..64ada3487 100644 --- a/ui/src/dashboards/components/QueryMaker.js +++ b/ui/src/dashboards/components/QueryMaker.tsx @@ -1,20 +1,44 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' +import _ from 'lodash' + +import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types' +import {CellEditorOverlayActions} from 'src/types/dashboard' import EmptyQuery from 'src/shared/components/EmptyQuery' import QueryTabList from 'src/shared/components/QueryTabList' import QueryTextArea from 'src/dashboards/components/QueryTextArea' import SchemaExplorer from 'src/shared/components/SchemaExplorer' -import {buildQuery} from 'utils/influxql' -import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' +import {buildQuery} from 'src/utils/influxql' +import {TYPE_QUERY_CONFIG, TEMPLATE_RANGE} from 'src/dashboards/constants' -const TEMPLATE_RANGE = {upper: null, lower: ':dashboardTime:'} -const rawTextBinder = (links, id, action) => text => - action(links.queries, id, text) -const buildText = q => +const rawTextBinder = ( + links: SourceLinks, + id: string, + action: (linksQueries: string, id: string, text: string) => void +) => (text: string) => action(links.queries, id, text) + +const buildText = (q: QueryConfig): string => q.rawText || buildQuery(TYPE_QUERY_CONFIG, q.range || TEMPLATE_RANGE, q) || '' -const QueryMaker = ({ +interface Template { + tempVar: string +} + +interface Props { + source: Source + queries: QueryConfig[] + timeRange: TimeRange + actions: CellEditorOverlayActions + setActiveQueryIndex: (index: number) => void + onDeleteQuery: (index: number) => void + activeQueryIndex: number + activeQuery: QueryConfig + onAddQuery: () => void + templates: Template[] + initialGroupByTime: string +} + +const QueryMaker: SFC = ({ source, actions, queries, @@ -52,8 +76,12 @@ const QueryMaker = ({ source={source} actions={actions} query={activeQuery} - onAddQuery={onAddQuery} initialGroupByTime={initialGroupByTime} + isQuerySupportedByExplorer={_.get( + activeQuery, + 'isQuerySupportedByExplorer', + true + )} /> ) : ( @@ -62,43 +90,4 @@ const QueryMaker = ({ ) -const {arrayOf, func, number, shape, string} = PropTypes - -QueryMaker.propTypes = { - source: shape({ - links: shape({ - queries: string.isRequired, - }).isRequired, - }).isRequired, - queries: arrayOf(shape({})).isRequired, - timeRange: shape({ - upper: string, - lower: string, - }).isRequired, - actions: shape({ - chooseNamespace: func.isRequired, - chooseMeasurement: func.isRequired, - chooseTag: func.isRequired, - groupByTag: func.isRequired, - toggleField: func.isRequired, - groupByTime: func.isRequired, - toggleTagAcceptance: func.isRequired, - fill: func, - applyFuncsToField: func.isRequired, - editRawTextAsync: func.isRequired, - addInitialField: func.isRequired, - }).isRequired, - setActiveQueryIndex: func.isRequired, - onDeleteQuery: func.isRequired, - activeQueryIndex: number, - activeQuery: shape({}), - onAddQuery: func.isRequired, - templates: arrayOf( - shape({ - tempVar: string.isRequired, - }) - ).isRequired, - initialGroupByTime: string.isRequired, -} - export default QueryMaker diff --git a/ui/src/dashboards/constants/index.js b/ui/src/dashboards/constants/index.js index 94816f26b..a42bcdae4 100644 --- a/ui/src/dashboards/constants/index.js +++ b/ui/src/dashboards/constants/index.js @@ -3,6 +3,7 @@ import { DEFAULT_FIX_FIRST_COLUMN, } from 'src/shared/constants/tableGraph' import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph' +import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' export const UNTITLED_CELL_LINE = 'Untitled Line Graph' export const UNTITLED_CELL_STACKED = 'Untitled Stacked Gracph' @@ -151,3 +152,4 @@ export const TYPE_QUERY_CONFIG = 'queryConfig' export const TYPE_SHIFTED = 'shifted queryConfig' export const TYPE_IFQL = 'ifql' export const DASHBOARD_NAME_MAX_LENGTH = 50 +export const TEMPLATE_RANGE = {upper: null, lower: TEMP_VAR_DASHBOARD_TIME} diff --git a/ui/src/dashboards/containers/DashboardPage.js b/ui/src/dashboards/containers/DashboardPage.js index e8153319b..aee57c118 100644 --- a/ui/src/dashboards/containers/DashboardPage.js +++ b/ui/src/dashboards/containers/DashboardPage.js @@ -36,7 +36,12 @@ import { templateControlBarVisibilityToggled as templateControlBarVisibilityToggledAction, } from 'shared/actions/app' import {presentationButtonDispatcher} from 'shared/dispatchers' -import {interval, DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants' +import { + interval, + DASHBOARD_LAYOUT_ROW_HEIGHT, + TEMP_VAR_DASHBOARD_TIME, + TEMP_VAR_UPPER_DASHBOARD_TIME, +} from 'shared/constants' import {notifyDashboardNotFound} from 'shared/copy/notifications' import {colorsStringSchema, colorsNumberSchema} from 'shared/schemas' import {ErrorHandling} from 'src/shared/decorators/errors' @@ -321,7 +326,7 @@ class DashboardPage extends Component { const dashboardTime = { id: 'dashtime', - tempVar: ':dashboardTime:', + tempVar: TEMP_VAR_DASHBOARD_TIME, type: lowerType, values: [ { @@ -334,7 +339,7 @@ class DashboardPage extends Component { const upperDashboardTime = { id: 'upperdashtime', - tempVar: ':upperDashboardTime:', + tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME, type: upperType, values: [ { diff --git a/ui/src/dashboards/utils/sources.ts b/ui/src/dashboards/utils/sources.ts index fe3a1cadc..baec109d1 100644 --- a/ui/src/dashboards/utils/sources.ts +++ b/ui/src/dashboards/utils/sources.ts @@ -1,6 +1,9 @@ -import {Query} from 'src/types' +import {QueryConfig} from 'src/types' -export const nextSource = (prevQuery: Query, nextQuery: Query): string => { +export const nextSource = ( + prevQuery: QueryConfig, + nextQuery: QueryConfig +): string => { if (nextQuery.source) { return nextQuery.source } diff --git a/ui/src/data_explorer/actions/view/index.js b/ui/src/data_explorer/actions/view/index.js index 8e6f1a46a..a0071e9fe 100644 --- a/ui/src/data_explorer/actions/view/index.js +++ b/ui/src/data_explorer/actions/view/index.js @@ -1,6 +1,6 @@ import uuid from 'uuid' -import {getQueryConfig} from 'shared/apis' +import {getQueryConfigAndStatus} from 'shared/apis' import {errorThrown} from 'shared/actions/errors' @@ -158,7 +158,7 @@ export const timeShift = (queryID, shift) => ({ // Async actions export const editRawTextAsync = (url, id, text) => async dispatch => { try { - const {data} = await getQueryConfig(url, [{query: text, id}]) + const {data} = await getQueryConfigAndStatus(url, [{query: text, id}]) const config = data.queries.find(q => q.id === id) dispatch(updateQueryConfig(config.queryConfig)) } catch (error) { diff --git a/ui/src/data_explorer/components/FieldListItem.js b/ui/src/data_explorer/components/FieldListItem.tsx similarity index 58% rename from ui/src/data_explorer/components/FieldListItem.js rename to ui/src/data_explorer/components/FieldListItem.tsx index bfe67dd70..59f3a9ceb 100644 --- a/ui/src/data_explorer/components/FieldListItem.js +++ b/ui/src/data_explorer/components/FieldListItem.tsx @@ -1,14 +1,45 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent, MouseEvent} from 'react' import classnames from 'classnames' import _ from 'lodash' -import FunctionSelector from 'shared/components/FunctionSelector' -import {firstFieldName} from 'shared/reducers/helpers/fields' +import FunctionSelector from 'src/shared/components/FunctionSelector' +import {firstFieldName} from 'src/shared/reducers/helpers/fields' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Field { + type: string + value: string +} + +interface FuncArg { + value: string + type: string +} + +interface FieldFunc extends Field { + args: FuncArg[] +} + +interface ApplyFuncsToFieldArgs { + field: Field + funcs: FuncArg[] +} +interface Props { + fieldFuncs: FieldFunc[] + isSelected: boolean + onToggleField: (field: Field) => void + onApplyFuncsToField: (args: ApplyFuncsToFieldArgs) => void + isKapacitorRule: boolean + funcs: string[] + isDisabled: boolean +} + +interface State { + isOpen: boolean +} + @ErrorHandling -class FieldListItem extends Component { +class FieldListItem extends PureComponent { constructor(props) { super(props) this.state = { @@ -16,55 +47,10 @@ class FieldListItem extends Component { } } - toggleFunctionsMenu = e => { - if (e) { - e.stopPropagation() - } - this.setState({isOpen: !this.state.isOpen}) - } - - close = () => { - this.setState({isOpen: false}) - } - - handleToggleField = () => { - const {onToggleField} = this.props - const value = this._getFieldName() - - onToggleField({value, type: 'field'}) - this.close() - } - - handleApplyFunctions = selectedFuncs => { - const {onApplyFuncsToField} = this.props - const fieldName = this._getFieldName() - const field = {value: fieldName, type: 'field'} - - onApplyFuncsToField({ - field, - funcs: selectedFuncs.map(this._makeFunc), - }) - this.close() - } - - _makeFunc = value => ({ - value, - type: 'func', - }) - - _getFieldName = () => { - const {fieldFuncs} = this.props - const fieldFunc = _.head(fieldFuncs) - - return _.get(fieldFunc, 'type') === 'field' - ? _.get(fieldFunc, 'value') - : firstFieldName(_.get(fieldFunc, 'args')) - } - - render() { - const {isKapacitorRule, isSelected, funcs} = this.props + public render() { + const {isKapacitorRule, isSelected, funcs, isDisabled} = this.props const {isOpen} = this.state - const fieldName = this._getFieldName() + const fieldName = this.getFieldName() let fieldFuncsLabel const num = funcs.length @@ -84,6 +70,7 @@ class FieldListItem extends Component {
) } -} -const {string, shape, func, arrayOf, bool} = PropTypes + private toggleFunctionsMenu = (e: MouseEvent) => { + e.stopPropagation() + const {isDisabled} = this.props + if (isDisabled) { + return + } -FieldListItem.propTypes = { - fieldFuncs: arrayOf( - shape({ - type: string.isRequired, - value: string.isRequired, - alias: string, - args: arrayOf( - shape({ - type: string.isRequired, - value: string.isRequired, - }) - ), + this.setState({isOpen: !this.state.isOpen}) + } + + private close = (): void => { + this.setState({isOpen: false}) + } + + private handleToggleField = (): void => { + const {onToggleField} = this.props + const value = this.getFieldName() + + onToggleField({value, type: 'field'}) + this.close() + } + + private handleApplyFunctions = (selectedFuncs: string[]) => { + const {onApplyFuncsToField} = this.props + const fieldName = this.getFieldName() + const field: Field = {value: fieldName, type: 'field'} + + onApplyFuncsToField({ + field, + funcs: selectedFuncs.map(val => this.makeFuncArg(val)), }) - ).isRequired, - isSelected: bool.isRequired, - onToggleField: func.isRequired, - onApplyFuncsToField: func.isRequired, - isKapacitorRule: bool.isRequired, - funcs: arrayOf(string.isRequired).isRequired, + this.close() + } + + private makeFuncArg = (value: string): FuncArg => ({ + value, + type: 'func', + }) + + private getFieldName = (): string => { + const {fieldFuncs} = this.props + const fieldFunc = _.head(fieldFuncs) + + return _.get(fieldFunc, 'type') === 'field' + ? _.get(fieldFunc, 'value') + : firstFieldName(_.get(fieldFunc, 'args')) + } } + export default FieldListItem diff --git a/ui/src/data_explorer/components/GroupByTimeDropdown.js b/ui/src/data_explorer/components/GroupByTimeDropdown.tsx similarity index 56% rename from ui/src/data_explorer/components/GroupByTimeDropdown.js rename to ui/src/data_explorer/components/GroupByTimeDropdown.tsx index 3bc0614de..5664be74b 100644 --- a/ui/src/data_explorer/components/GroupByTimeDropdown.js +++ b/ui/src/data_explorer/components/GroupByTimeDropdown.tsx @@ -1,24 +1,40 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import {withRouter} from 'react-router' +import {Location} from 'history' import groupByTimeOptions from 'src/data_explorer/data/groupByTimes' -import Dropdown from 'shared/components/Dropdown' +import Dropdown from 'src/shared/components/Dropdown' -import {AUTO_GROUP_BY} from 'shared/constants' +import {AUTO_GROUP_BY} from 'src/shared/constants' +import {GroupBy} from 'src/types' -const isInRuleBuilder = pathname => pathname.includes('alert-rules') +interface GroupByTimeOption { + defaultTimeBound: string + seconds: number + menuOption: string +} -const getOptions = pathname => +interface Props { + location?: Location + selected: string + onChooseGroupByTime: (groupBy: GroupBy) => void + isDisabled: boolean +} + +const isInRuleBuilder = (pathname: string): boolean => + pathname.includes('alert-rules') + +const getOptions = (pathname: string): GroupByTimeOption[] => isInRuleBuilder(pathname) ? groupByTimeOptions.filter(({menuOption}) => menuOption !== AUTO_GROUP_BY) : groupByTimeOptions -const GroupByTimeDropdown = ({ +const GroupByTimeDropdown: SFC = ({ selected, onChooseGroupByTime, location: {pathname}, + isDisabled, }) => (
@@ -32,18 +48,9 @@ const GroupByTimeDropdown = ({ }))} onChoose={onChooseGroupByTime} selected={selected || 'Time'} + disabled={isDisabled} />
) -const {func, string, shape} = PropTypes - -GroupByTimeDropdown.propTypes = { - location: shape({ - pathname: string.isRequired, - }).isRequired, - selected: string, - onChooseGroupByTime: func.isRequired, -} - export default withRouter(GroupByTimeDropdown) diff --git a/ui/src/data_explorer/components/QueryMaker.tsx b/ui/src/data_explorer/components/QueryMaker.tsx index decdb8c1c..e81d3182f 100644 --- a/ui/src/data_explorer/components/QueryMaker.tsx +++ b/ui/src/data_explorer/components/QueryMaker.tsx @@ -2,7 +2,7 @@ import React, {PureComponent} from 'react' import QueryEditor from './QueryEditor' import SchemaExplorer from 'src/shared/components/SchemaExplorer' -import {Source, Query} from 'src/types' +import {Source, QueryConfig} from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' const rawTextBinder = (links, id, action) => text => @@ -12,7 +12,7 @@ interface Props { source: Source rawText: string actions: any - activeQuery: Query + activeQuery: QueryConfig initialGroupByTime: string } diff --git a/ui/src/data_explorer/containers/DataExplorer.tsx b/ui/src/data_explorer/containers/DataExplorer.tsx index 9b6e59d02..e3581e86a 100644 --- a/ui/src/data_explorer/containers/DataExplorer.tsx +++ b/ui/src/data_explorer/containers/DataExplorer.tsx @@ -26,12 +26,12 @@ import {writeLineProtocolAsync} from 'src/data_explorer/actions/view/write' import {buildRawText} from 'src/utils/influxql' import defaultQueryConfig from 'src/utils/defaultQueryConfig' -import {Source, Query, TimeRange} from 'src/types' +import {Source, QueryConfig, TimeRange} from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { source: Source - queryConfigs: Query[] + queryConfigs: QueryConfig[] queryConfigActions: any // TODO: actually type these autoRefresh: number handleChooseAutoRefresh: () => void @@ -169,7 +169,7 @@ export class DataExplorer extends PureComponent { return _.get(this.props.queryConfigs, ['0', 'database'], null) } - private get activeQuery(): Query { + private get activeQuery(): QueryConfig { const {queryConfigs} = this.props if (queryConfigs.length === 0) { diff --git a/ui/src/data_explorer/data/groupByTimes.js b/ui/src/data_explorer/data/groupByTimes.js index 7f6d891aa..694d0b6e1 100644 --- a/ui/src/data_explorer/data/groupByTimes.js +++ b/ui/src/data_explorer/data/groupByTimes.js @@ -1,5 +1,7 @@ +import {TEMP_VAR_INTERVAL} from 'src/shared/constants' + const groupByTimes = [ - {defaultTimeBound: ':interval:', seconds: 604800, menuOption: 'auto'}, + {defaultTimeBound: TEMP_VAR_INTERVAL, seconds: 604800, menuOption: 'auto'}, {defaultTimeBound: 'now() - 5m', seconds: 10, menuOption: '10s'}, {defaultTimeBound: 'now() - 15m', seconds: 60, menuOption: '1m'}, {defaultTimeBound: 'now() - 1h', seconds: 300, menuOption: '5m'}, diff --git a/ui/src/shared/apis/index.js b/ui/src/shared/apis/index.js index 3620097f8..d0b3697a3 100644 --- a/ui/src/shared/apis/index.js +++ b/ui/src/shared/apis/index.js @@ -231,7 +231,7 @@ export function kapacitorProxy(kapacitor, method, path, body) { }) } -export const getQueryConfig = (url, queries, tempVars) => +export const getQueryConfigAndStatus = (url, queries, tempVars) => AJAX({ url, method: 'POST', diff --git a/ui/src/shared/components/AutoRefresh.tsx b/ui/src/shared/components/AutoRefresh.tsx index b1df696c0..c02f80d15 100644 --- a/ui/src/shared/components/AutoRefresh.tsx +++ b/ui/src/shared/components/AutoRefresh.tsx @@ -1,7 +1,7 @@ import React, {Component, ComponentClass} from 'react' import _ from 'lodash' -import {getQueryConfig} from 'src/shared/apis' +import {getQueryConfigAndStatus} from 'src/shared/apis' import {fetchTimeSeries} from 'src/shared/apis/query' import {DEFAULT_TIME_SERIES} from 'src/shared/constants/series' import {TimeSeriesServerResponse, TimeSeriesResponse} from 'src/types/series' @@ -265,7 +265,11 @@ const AutoRefresh = ( const host = _.isArray(q.host) ? q.host[0] : q.host const url = host.replace('proxy', 'queries') const text = q.text - const {data} = await getQueryConfig(url, [{query: text}], templates) + const {data} = await getQueryConfigAndStatus( + url, + [{query: text}], + templates + ) return data.queries[0].queryAST }) ) diff --git a/ui/src/shared/components/CustomTimeIndicator.js b/ui/src/shared/components/CustomTimeIndicator.js index fbbee76ee..92609fe71 100644 --- a/ui/src/shared/components/CustomTimeIndicator.js +++ b/ui/src/shared/components/CustomTimeIndicator.js @@ -2,8 +2,10 @@ import React from 'react' import PropTypes from 'prop-types' import _ from 'lodash' +import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' + const CustomTimeIndicator = ({queries}) => { - const q = queries.find(({query}) => !query.includes(':dashboardTime:')) + const q = queries.find(({query}) => !query.includes(TEMP_VAR_DASHBOARD_TIME)) const customLower = _.get(q, ['queryConfig', 'range', 'lower'], null) const customUpper = _.get(q, ['queryConfig', 'range', 'upper'], null) diff --git a/ui/src/shared/components/DatabaseList.tsx b/ui/src/shared/components/DatabaseList.tsx index 897f98fad..032fcb153 100644 --- a/ui/src/shared/components/DatabaseList.tsx +++ b/ui/src/shared/components/DatabaseList.tsx @@ -3,7 +3,7 @@ import React, {PureComponent} from 'react' import _ from 'lodash' -import {Query, Source} from 'src/types' +import {QueryConfig, Source} from 'src/types' import {Namespace} from 'src/types/query' import {showDatabases, showRetentionPolicies} from 'src/shared/apis/metaQuery' @@ -15,7 +15,7 @@ import FancyScrollbar from 'src/shared/components/FancyScrollbar' import {ErrorHandling} from 'src/shared/decorators/errors' interface DatabaseListProps { - query: Query + query: QueryConfig querySource: Source onChooseNamespace: (namespace: Namespace) => void source: Source @@ -102,7 +102,7 @@ class DatabaseList extends PureComponent { return () => this.props.onChooseNamespace(namespace) } - public isActive(query: Query, {database, retentionPolicy}: Namespace) { + public isActive(query: QueryConfig, {database, retentionPolicy}: Namespace) { return ( database === query.database && retentionPolicy === query.retentionPolicy ) diff --git a/ui/src/shared/components/FieldList.js b/ui/src/shared/components/FieldList.tsx similarity index 65% rename from ui/src/shared/components/FieldList.js rename to ui/src/shared/components/FieldList.tsx index b7fc0e107..81ce2df78 100644 --- a/ui/src/shared/components/FieldList.js +++ b/ui/src/shared/components/FieldList.tsx @@ -1,23 +1,83 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' import _ from 'lodash' -import QueryOptions from 'shared/components/QueryOptions' -import FieldListItem from 'src/data_explorer/components/FieldListItem' -import FancyScrollbar from 'shared/components/FancyScrollbar' +import {QueryConfig, GroupBy, Source, TimeShift} from 'src/types' -import {showFieldKeys} from 'shared/apis/metaQuery' -import showFieldKeysParser from 'shared/parsing/showFieldKeys' +import QueryOptions from 'src/shared/components/QueryOptions' +import FieldListItem from 'src/data_explorer/components/FieldListItem' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' + +import {showFieldKeys} from 'src/shared/apis/metaQuery' +import showFieldKeysParser from 'src/shared/parsing/showFieldKeys' import { functionNames, numFunctions, getFieldsWithName, getFuncsByFieldName, -} from 'shared/reducers/helpers/fields' +} from 'src/shared/reducers/helpers/fields' import {ErrorHandling} from 'src/shared/decorators/errors' +interface GroupByOption extends GroupBy { + menuOption: string +} + +interface TimeShiftOption extends TimeShift { + text: string +} +interface Links { + proxy: string +} + +interface Field { + type: string + value: string +} + +interface FieldFunc extends Field { + args: FuncArg[] +} +interface FuncArg { + value: string + type: string +} + +interface ApplyFuncsToFieldArgs { + field: Field + funcs: FuncArg[] +} + +interface Props { + query: QueryConfig + onTimeShift: (shift: TimeShiftOption) => void + onToggleField: (field: Field) => void + onGroupByTime: (groupByOption: string) => void + onFill: (fill: string) => void + applyFuncsToField: (field: ApplyFuncsToFieldArgs, groupBy: GroupBy) => void + isKapacitorRule: boolean + querySource: { + links: Links + } + removeFuncs: (fields: Field[]) => void + addInitialField: (field: Field, groupBy: GroupBy) => void + initialGroupByTime: string | null + isQuerySupportedByExplorer: boolean +} + +interface State { + fields: Field[] +} + +interface Context { + source: Source +} @ErrorHandling -class FieldList extends Component { +class FieldList extends PureComponent { + public static context: Context + public static defaultProps: Partial = { + isKapacitorRule: false, + initialGroupByTime: null, + } + constructor(props) { super(props) this.state = { @@ -25,16 +85,16 @@ class FieldList extends Component { } } - componentDidMount() { + public componentDidMount() { const {database, measurement} = this.props.query if (!database || !measurement) { return } - this._getFields() + this.getFields() } - componentDidUpdate(prevProps) { + public componentDidUpdate(prevProps) { const {querySource, query} = this.props const {database, measurement, retentionPolicy} = query const { @@ -55,26 +115,100 @@ class FieldList extends Component { return } - this._getFields() + this.getFields() } - handleGroupByTime = groupBy => { + public render() { + const { + query: {database, measurement, fields = [], groupBy, fill, shifts}, + isQuerySupportedByExplorer, + isKapacitorRule, + } = this.props + + const hasAggregates = numFunctions(fields) > 0 + const noDBorMeas = !database || !measurement + + return ( +
+
+ Fields + {hasAggregates ? ( + + ) : null} +
+ {noDBorMeas ? ( +
+ + No Measurement selected + +
+ ) : ( +
+ + {this.state.fields.map((fieldFunc, i) => { + const selectedFields = getFieldsWithName( + fieldFunc.value, + fields + ) + + const funcs: FieldFunc[] = getFuncsByFieldName( + fieldFunc.value, + fields + ) + const fieldFuncs = selectedFields.length + ? selectedFields + : [fieldFunc] + + return ( + + ) + })} + +
+ )} +
+ ) + } + + private handleGroupByTime = (groupBy: GroupByOption): void => { this.props.onGroupByTime(groupBy.menuOption) } - handleFill = fill => { + private handleFill = (fill: string): void => { this.props.onFill(fill) } - handleToggleField = field => { + private handleToggleField = (field: Field) => { const { query, onToggleField, addInitialField, initialGroupByTime: time, isKapacitorRule, + isQuerySupportedByExplorer, } = this.props const {fields, groupBy} = query + if (!isQuerySupportedByExplorer) { + return + } const initialGroupBy = {...groupBy, time} if (!_.size(fields)) { @@ -86,7 +220,7 @@ class FieldList extends Component { onToggleField(field) } - handleApplyFuncs = fieldFunc => { + private handleApplyFuncs = (fieldFunc: ApplyFuncsToFieldArgs): void => { const { query, removeFuncs, @@ -109,11 +243,11 @@ class FieldList extends Component { applyFuncsToField(fieldFunc, groupBy) } - handleTimeShift = shift => { + private handleTimeShift = (shift: TimeShiftOption): void => { this.props.onTimeShift(shift) } - _getFields = () => { + private getFields = (): void => { const {database, measurement, retentionPolicy} = this.props.query const {source} = this.context const {querySource} = this.props @@ -137,114 +271,6 @@ class FieldList extends Component { }) }) } - - render() { - const { - query: {database, measurement, fields = [], groupBy, fill, shifts}, - isKapacitorRule, - } = this.props - - const hasAggregates = numFunctions(fields) > 0 - const noDBorMeas = !database || !measurement - - return ( -
-
- Fields - {hasAggregates ? ( - - ) : null} -
- {noDBorMeas ? ( -
- - No Measurement selected - -
- ) : ( -
- - {this.state.fields.map((fieldFunc, i) => { - const selectedFields = getFieldsWithName( - fieldFunc.value, - fields - ) - - const funcs = getFuncsByFieldName(fieldFunc.value, fields) - const fieldFuncs = selectedFields.length - ? selectedFields - : [fieldFunc] - - return ( - - ) - })} - -
- )} -
- ) - } -} - -const {arrayOf, bool, func, shape, string} = PropTypes - -FieldList.defaultProps = { - isKapacitorRule: false, - initialGroupByTime: null, -} - -FieldList.contextTypes = { - source: shape({ - links: shape({ - proxy: string.isRequired, - }).isRequired, - }).isRequired, -} - -FieldList.propTypes = { - query: shape({ - database: string, - retentionPolicy: string, - measurement: string, - shifts: arrayOf( - shape({ - label: string, - unit: string, - quantity: string, - }) - ), - }).isRequired, - onTimeShift: func, - onToggleField: func.isRequired, - onGroupByTime: func.isRequired, - onFill: func, - applyFuncsToField: func.isRequired, - isKapacitorRule: bool, - querySource: shape({ - links: shape({ - proxy: string.isRequired, - }).isRequired, - }), - removeFuncs: func.isRequired, - addInitialField: func, - initialGroupByTime: string, } export default FieldList diff --git a/ui/src/shared/components/FillQuery.js b/ui/src/shared/components/FillQuery.tsx similarity index 66% rename from ui/src/shared/components/FillQuery.js rename to ui/src/shared/components/FillQuery.tsx index 7a1385636..022f3b332 100644 --- a/ui/src/shared/components/FillQuery.js +++ b/ui/src/shared/components/FillQuery.tsx @@ -1,18 +1,48 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import Dropdown from 'shared/components/Dropdown' +import React, { + PureComponent, + FocusEvent, + ChangeEvent, + KeyboardEvent, +} from 'react' +import Dropdown from 'src/shared/components/Dropdown' -import {NULL_STRING, NUMBER} from 'shared/constants/queryFillOptions' +import {NULL_STRING, NUMBER} from 'src/shared/constants/queryFillOptions' -import queryFills from 'shared/data/queryFills' +import queryFills from 'src/shared/data/queryFills' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + onChooseFill: (text: string) => void + value: string + size?: string + theme?: string + isDisabled?: boolean +} + +interface Item { + type: string + text: string +} +interface State { + selected: Item + currentNumberValue: string + resetNumberValue: string +} + @ErrorHandling -class FillQuery extends Component { +class FillQuery extends PureComponent { + public static defaultProps: Partial = { + size: 'sm', + theme: 'blue', + value: NULL_STRING, + } + + private numberInput: HTMLElement + constructor(props) { super(props) - const isNumberValue = !isNaN(Number(props.value)) + const isNumberValue: boolean = !isNaN(Number(props.value)) this.state = isNumberValue ? { @@ -27,66 +57,8 @@ class FillQuery extends Component { } } - handleDropdown = item => { - if (item.text === NUMBER) { - this.setState({selected: item}, () => { - this.numberInput.focus() - }) - } else { - this.setState({selected: item}, () => { - this.props.onChooseFill(item.text) - }) - } - } - - handleInputBlur = e => { - const nextNumberValue = e.target.value - ? e.target.value - : this.state.resetNumberValue || '0' - - this.setState({ - currentNumberValue: nextNumberValue, - resetNumberValue: nextNumberValue, - }) - - this.props.onChooseFill(nextNumberValue) - } - - handleInputChange = e => { - const currentNumberValue = e.target.value - - this.setState({currentNumberValue}) - } - - handleKeyDown = e => { - if (e.key === 'Enter') { - this.numberInput.blur() - } - } - - handleKeyUp = e => { - if (e.key === 'Escape') { - this.setState({currentNumberValue: this.state.resetNumberValue}, () => { - this.numberInput.blur() - }) - } - } - - getColor = theme => { - switch (theme) { - case 'BLUE': - return 'plutonium' - case 'GREEN': - return 'malachite' - case 'PURPLE': - return 'astronaut' - default: - return 'plutonium' - } - } - - render() { - const {size, theme} = this.props + public render() { + const {size, theme, isDisabled} = this.props const {selected, currentNumberValue} = this.state return ( @@ -114,26 +86,70 @@ class FillQuery extends Component { buttonColor="btn-info" menuClass={`dropdown-${this.getColor(theme)}`} onChoose={this.handleDropdown} + disabled={isDisabled} />
) } -} -const {func, string} = PropTypes + private handleDropdown = (item: Item): void => { + if (item.text === NUMBER) { + this.setState({selected: item}, () => { + this.numberInput.focus() + }) + } else { + this.setState({selected: item}, () => { + this.props.onChooseFill(item.text) + }) + } + } -FillQuery.defaultProps = { - size: 'sm', - theme: 'blue', - value: NULL_STRING, -} + private handleInputBlur = (e: FocusEvent): void => { + const nextNumberValue = e.target.value + ? e.target.value + : this.state.resetNumberValue || '0' -FillQuery.propTypes = { - onChooseFill: func.isRequired, - value: string, - size: string, - theme: string, + this.setState({ + currentNumberValue: nextNumberValue, + resetNumberValue: nextNumberValue, + }) + + this.props.onChooseFill(nextNumberValue) + } + + private handleInputChange = (e: ChangeEvent): void => { + const currentNumberValue = e.target.value + + this.setState({currentNumberValue}) + } + + private handleKeyDown = (e: KeyboardEvent): void => { + if (e.key === 'Enter') { + this.numberInput.blur() + } + } + + private handleKeyUp = (e: KeyboardEvent): void => { + if (e.key === 'Escape') { + this.setState({currentNumberValue: this.state.resetNumberValue}, () => { + this.numberInput.blur() + }) + } + } + + private getColor = (theme: string): string => { + switch (theme) { + case 'BLUE': + return 'plutonium' + case 'GREEN': + return 'malachite' + case 'PURPLE': + return 'astronaut' + default: + return 'plutonium' + } + } } export default FillQuery diff --git a/ui/src/shared/components/MeasurementList.tsx b/ui/src/shared/components/MeasurementList.tsx index e2d63cbf7..4b2275e5a 100644 --- a/ui/src/shared/components/MeasurementList.tsx +++ b/ui/src/shared/components/MeasurementList.tsx @@ -6,7 +6,7 @@ import _ from 'lodash' import {showMeasurements} from 'src/shared/apis/metaQuery' import showMeasurementsParser from 'src/shared/parsing/showMeasurements' -import {Query, Source} from 'src/types' +import {QueryConfig, Source} from 'src/types' import FancyScrollbar from 'src/shared/components/FancyScrollbar' import MeasurementListFilter from 'src/shared/components/MeasurementListFilter' @@ -14,11 +14,12 @@ import MeasurementListItem from 'src/shared/components/MeasurementListItem' import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { - query: Query + query: QueryConfig querySource: Source onChooseTag: () => void onGroupByTag: () => void onToggleTagAcceptance: () => void + isQuerySupportedByExplorer: boolean onChooseMeasurement: (measurement: string) => void } @@ -117,7 +118,13 @@ class MeasurementList extends PureComponent { } public render() { - const {query, querySource, onChooseTag, onGroupByTag} = this.props + const { + query, + querySource, + onChooseTag, + onGroupByTag, + isQuerySupportedByExplorer, + } = this.props const {database, areTagsAccepted} = query const {filtered} = this.state @@ -147,6 +154,7 @@ class MeasurementList extends PureComponent { areTagsAccepted={areTagsAccepted} onAcceptReject={this.handleAcceptReject} isActive={measurement === query.measurement} + isQuerySupportedByExplorer={isQuerySupportedByExplorer} numTagsActive={Object.keys(query.tags).length} onChooseMeasurement={this.handleChoosemeasurement} /> diff --git a/ui/src/shared/components/MeasurementListItem.tsx b/ui/src/shared/components/MeasurementListItem.tsx index 3c410e0c1..ee1e3a023 100644 --- a/ui/src/shared/components/MeasurementListItem.tsx +++ b/ui/src/shared/components/MeasurementListItem.tsx @@ -38,6 +38,7 @@ interface Props { onChooseTag: () => void onGroupByTag: () => void onAcceptReject: () => void + isQuerySupportedByExplorer: boolean onChooseMeasurement: (measurement: string) => () => void } @@ -62,6 +63,7 @@ class MeasurementListItem extends PureComponent { onGroupByTag, numTagsActive, areTagsAccepted, + isQuerySupportedByExplorer, } = this.props return ( @@ -80,6 +82,7 @@ class MeasurementListItem extends PureComponent {
@@ -96,6 +99,7 @@ class MeasurementListItem extends PureComponent { querySource={querySource} onChooseTag={onChooseTag} onGroupByTag={onGroupByTag} + isQuerySupportedByExplorer={isQuerySupportedByExplorer} /> )}
@@ -105,6 +109,11 @@ class MeasurementListItem extends PureComponent { private handleAcceptReject = (e: MouseEvent) => { e.stopPropagation() + const {isQuerySupportedByExplorer} = this.props + if (!isQuerySupportedByExplorer) { + return + } + const {onAcceptReject} = this.props onAcceptReject() } diff --git a/ui/src/shared/components/QueryOptions.js b/ui/src/shared/components/QueryOptions.tsx similarity index 50% rename from ui/src/shared/components/QueryOptions.js rename to ui/src/shared/components/QueryOptions.tsx index 3dd4e8a05..75e47e45d 100644 --- a/ui/src/shared/components/QueryOptions.js +++ b/ui/src/shared/components/QueryOptions.tsx @@ -1,10 +1,23 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' + +import {GroupBy, TimeShift} from 'src/types' + import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown' import TimeShiftDropdown from 'src/shared/components/TimeShiftDropdown' -import FillQuery from 'shared/components/FillQuery' +import FillQuery from 'src/shared/components/FillQuery' -const QueryOptions = ({ +interface Props { + fill: string + onFill: (fill: string) => void + groupBy: GroupBy + shift: TimeShift + onGroupByTime: (groupBy: GroupBy) => void + isKapacitorRule: boolean + onTimeShift: (shift: TimeShift) => void + isDisabled: boolean +} + +const QueryOptions: SFC = ({ fill, shift, onFill, @@ -12,36 +25,25 @@ const QueryOptions = ({ onTimeShift, onGroupByTime, isKapacitorRule, + isDisabled, }) => (
{isKapacitorRule ? null : ( )} - {isKapacitorRule ? null : } + {isKapacitorRule ? null : ( + + )}
) -const {bool, func, shape, string} = PropTypes - -QueryOptions.propTypes = { - fill: string, - onFill: func.isRequired, - groupBy: shape({ - time: string, - }).isRequired, - shift: shape({ - label: string, - }), - onGroupByTime: func.isRequired, - isKapacitorRule: bool.isRequired, - onTimeShift: func.isRequired, -} - export default QueryOptions diff --git a/ui/src/shared/components/SchemaExplorer.js b/ui/src/shared/components/SchemaExplorer.js index 4919716ee..2dc4111a1 100644 --- a/ui/src/shared/components/SchemaExplorer.js +++ b/ui/src/shared/components/SchemaExplorer.js @@ -9,7 +9,6 @@ const actionBinder = (id, action) => (...args) => action(id, ...args) const SchemaExplorer = ({ query, - query: {id}, source, initialGroupByTime, actions: { @@ -26,39 +25,50 @@ const SchemaExplorer = ({ applyFuncsToField, toggleTagAcceptance, }, -}) => ( -
- - - -
-) + isQuerySupportedByExplorer = true, +}) => { + const {id} = query -const {func, shape, string} = PropTypes + return ( +
+ + + +
+ ) +} + +const {bool, func, shape, string} = PropTypes + +SchemaExplorer.defaultProps = { + isQuerySupportedByExplorer: true, +} SchemaExplorer.propTypes = { query: shape({ @@ -80,6 +90,7 @@ SchemaExplorer.propTypes = { }).isRequired, source: shape({}), initialGroupByTime: string.isRequired, + isQuerySupportedByExplorer: bool, } export default SchemaExplorer diff --git a/ui/src/shared/components/TagList.tsx b/ui/src/shared/components/TagList.tsx index a4e990504..f5c4bd314 100644 --- a/ui/src/shared/components/TagList.tsx +++ b/ui/src/shared/components/TagList.tsx @@ -40,6 +40,7 @@ interface Props { querySource: Source onChooseTag: () => void onGroupByTag: () => void + isQuerySupportedByExplorer: boolean } interface State { @@ -129,7 +130,12 @@ class TagList extends PureComponent { } public render() { - const {query, onChooseTag, onGroupByTag} = this.props + const { + query, + onChooseTag, + onGroupByTag, + isQuerySupportedByExplorer, + } = this.props return (
@@ -142,6 +148,7 @@ class TagList extends PureComponent { onGroupByTag={onGroupByTag} selectedTagValues={query.tags[tagKey] || []} isUsingGroupBy={query.groupBy.tags.indexOf(tagKey) > -1} + isQuerySupportedByExplorer={isQuerySupportedByExplorer} /> ))}
diff --git a/ui/src/shared/components/TagListItem.tsx b/ui/src/shared/components/TagListItem.tsx index 0ba7c1fb6..38d20281e 100644 --- a/ui/src/shared/components/TagListItem.tsx +++ b/ui/src/shared/components/TagListItem.tsx @@ -14,6 +14,7 @@ interface Props { selectedTagValues: string[] isUsingGroupBy?: boolean onChooseTag: (tag: Tag) => void + isQuerySupportedByExplorer: boolean onGroupByTag: (tagKey: string) => void } @@ -36,10 +37,16 @@ class TagListItem extends PureComponent { this.handleGroupBy = this.handleGroupBy.bind(this) this.handleClickKey = this.handleClickKey.bind(this) this.handleFilterText = this.handleFilterText.bind(this) + this.handleInputClick = this.handleInputClick.bind(this) } public handleChoose(tagValue: string, e: MouseEvent) { e.stopPropagation() + + const {isQuerySupportedByExplorer} = this.props + if (!isQuerySupportedByExplorer) { + return + } this.props.onChooseTag({key: this.props.tagKey, value: tagValue}) } @@ -67,7 +74,11 @@ class TagListItem extends PureComponent { } public handleGroupBy(e) { + const {isQuerySupportedByExplorer} = this.props e.stopPropagation() + if (!isQuerySupportedByExplorer) { + return + } this.props.onGroupByTag(this.props.tagKey) } @@ -76,7 +87,11 @@ class TagListItem extends PureComponent { } public renderTagValues() { - const {tagValues, selectedTagValues} = this.props + const { + tagValues, + selectedTagValues, + isQuerySupportedByExplorer, + } = this.props if (!tagValues || !tagValues.length) { return
no tag values
} @@ -103,6 +118,7 @@ class TagListItem extends PureComponent { {filtered.map(v => { const cx = classnames('query-builder--list-item', { active: selectedTagValues.indexOf(v) > -1, + disabled: !isQuerySupportedByExplorer, }) return (
{ } public render() { - const {tagKey, tagValues, isUsingGroupBy} = this.props + const { + tagKey, + tagValues, + isUsingGroupBy, + isQuerySupportedByExplorer, + } = this.props const {isOpen} = this.state const tagItemLabel = `${tagKey} — ${tagValues.length}` @@ -142,6 +163,7 @@ class TagListItem extends PureComponent { className={classnames('btn btn-xs group-by-tag', { 'btn-default': !isUsingGroupBy, 'btn-primary': isUsingGroupBy, + disabled: !isQuerySupportedByExplorer, })} onClick={this.handleGroupBy} > diff --git a/ui/src/shared/components/TimeShiftDropdown.js b/ui/src/shared/components/TimeShiftDropdown.js deleted file mode 100644 index d4161186b..000000000 --- a/ui/src/shared/components/TimeShiftDropdown.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import Dropdown from 'shared/components/Dropdown' -import {TIME_SHIFTS} from 'shared/constants/timeShift' - -const TimeShiftDropdown = ({selected, onChooseTimeShift}) => ( -
- - -
-) - -const {func, string} = PropTypes - -TimeShiftDropdown.propTypes = { - selected: string, - onChooseTimeShift: func.isRequired, -} - -export default TimeShiftDropdown diff --git a/ui/src/shared/components/TimeShiftDropdown.tsx b/ui/src/shared/components/TimeShiftDropdown.tsx new file mode 100644 index 000000000..595bc833b --- /dev/null +++ b/ui/src/shared/components/TimeShiftDropdown.tsx @@ -0,0 +1,31 @@ +import React, {SFC} from 'react' + +import Dropdown from 'src/shared/components/Dropdown' +import {TIME_SHIFTS} from 'src/shared/constants/timeShift' +import {TimeShift} from 'src/types' + +interface Props { + selected: string + onChooseTimeShift: (timeShift: TimeShift) => void + isDisabled: boolean +} + +const TimeShiftDropdown: SFC = ({ + selected, + onChooseTimeShift, + isDisabled, +}) => ( +
+ + +
+) + +export default TimeShiftDropdown diff --git a/ui/src/shared/constants/index.tsx b/ui/src/shared/constants/index.tsx index ff303623f..26acce1c7 100644 --- a/ui/src/shared/constants/index.tsx +++ b/ui/src/shared/constants/index.tsx @@ -410,6 +410,13 @@ export const VIS_VIEWS = [GRAPH, TABLE] // InfluxQL Macros export const TEMP_VAR_INTERVAL = ':interval:' +export const TEMP_VAR_DASHBOARD_TIME = ':dashboardTime:' +export const TEMP_VAR_UPPER_DASHBOARD_TIME = ':upperDashboardTime:' +export const PREDEFINED_TEMP_VARS = [ + TEMP_VAR_INTERVAL, + TEMP_VAR_DASHBOARD_TIME, + TEMP_VAR_UPPER_DASHBOARD_TIME, +] export const INITIAL_GROUP_BY_TIME = '10s' export const AUTO_GROUP_BY = 'auto' @@ -443,7 +450,7 @@ export const intervalValuesPoints = [ export const interval = { id: 'interval', type: 'autoGroupBy', - tempVar: ':interval:', + tempVar: TEMP_VAR_INTERVAL, label: 'automatically determine the best group by time', values: intervalValuesPoints, } diff --git a/ui/src/shared/copy/notifications.js b/ui/src/shared/copy/notifications.js index 7e5ff396a..e175a152c 100644 --- a/ui/src/shared/copy/notifications.js +++ b/ui/src/shared/copy/notifications.js @@ -438,6 +438,13 @@ export const notifyCellDeleted = name => ({ message: `Deleted "${name}" from dashboard.`, }) +export const notifyBuilderDisabled = () => ({ + type: 'info', + icon: 'graphline', + duration: 7500, + message: `Your query contains a user-defined Template Variable. The Schema Explorer cannot render the query and is disabled.`, +}) + // Rule Builder Notifications // ---------------------------------------------------------------------------- export const notifyAlertRuleCreated = () => ({ diff --git a/ui/src/shared/reducers/helpers/fields.js b/ui/src/shared/reducers/helpers/fields.js index 9cd5bfc8a..9ff777189 100644 --- a/ui/src/shared/reducers/helpers/fields.js +++ b/ui/src/shared/reducers/helpers/fields.js @@ -42,6 +42,12 @@ export const firstFieldName = fields => _.head(fieldNamesDeep(fields)) export const hasField = (fieldName, fields) => fieldNamesDeep(fields).some(f => f === fieldName) +/** + * getAllFields and funcs with fieldName + * @param {string} fieldName + * @param {FieldFunc[]} fields + * @returns {FieldFunc[]} + */ // getAllFields and funcs with fieldName export const getFieldsWithName = (fieldName, fields) => getFieldsDeep(fields).filter(f => f.value === fieldName) diff --git a/ui/src/status/containers/StatusPage.js b/ui/src/status/containers/StatusPage.js index 19bd421c8..157e7a658 100644 --- a/ui/src/status/containers/StatusPage.js +++ b/ui/src/status/containers/StatusPage.js @@ -8,6 +8,10 @@ import LayoutRenderer from 'shared/components/LayoutRenderer' import {fixtureStatusPageCells} from 'src/status/fixtures' import {ErrorHandling} from 'src/shared/decorators/errors' +import { + TEMP_VAR_DASHBOARD_TIME, + TEMP_VAR_UPPER_DASHBOARD_TIME, +} from 'src/shared/constants' @ErrorHandling class StatusPage extends Component { @@ -25,7 +29,7 @@ class StatusPage extends Component { const dashboardTime = { id: 'dashtime', - tempVar: ':dashboardTime:', + tempVar: TEMP_VAR_DASHBOARD_TIME, type: 'constant', values: [ { @@ -38,7 +42,7 @@ class StatusPage extends Component { const upperDashboardTime = { id: 'upperdashtime', - tempVar: ':upperDashboardTime:', + tempVar: TEMP_VAR_UPPER_DASHBOARD_TIME, type: 'constant', values: [ { diff --git a/ui/src/status/fixtures.js b/ui/src/status/fixtures.js index 3e96f036f..67bdcbf92 100644 --- a/ui/src/status/fixtures.js +++ b/ui/src/status/fixtures.js @@ -1,4 +1,5 @@ import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' +import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' export const fixtureStatusPageCells = [ { @@ -13,8 +14,7 @@ export const fixtureStatusPageCells = [ colors: DEFAULT_LINE_COLORS, queries: [ { - query: - 'SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > :dashboardTime: GROUP BY time(1d)', + query: `SELECT count("value") AS "count_value" FROM "chronograf"."autogen"."alerts" WHERE time > ${TEMP_VAR_DASHBOARD_TIME} GROUP BY time(1d)`, label: 'Events', queryConfig: { database: 'chronograf', diff --git a/ui/src/style/components/flip-toggle.scss b/ui/src/style/components/flip-toggle.scss index 8290f1e79..8ad1a5a0e 100644 --- a/ui/src/style/components/flip-toggle.scss +++ b/ui/src/style/components/flip-toggle.scss @@ -1,8 +1,8 @@ /* - Flip Toggle - ------------------------------------------------------------- - Toggles between 2 options using a 3D transition - Aesthetic and space conservative + Flip Toggle + ------------------------------------------------------------------------------ + Toggles between 2 options using a 3D transition + Aesthetic and space conservative */ $flip-toggle-text: $g11-sidewalk; @@ -62,4 +62,20 @@ $flip-toggle-size: 28px; /* Flip Animation happens on class toggle */ .flip-toggle.flipped .flip-toggle--container { transform: rotateY(180deg); +} + +/* + Disabled State + ------------------------------------------------------------------------------ +*/ + +.flip-toggle.disabled, +.flip-toggle.disabled:hover { + .flip-toggle--front, + .flip-toggle--back { + cursor: not-allowed; + background-color: $g3-castle; + color: $g9-mountain; + border-color: $g5-pepper; + } } \ No newline at end of file diff --git a/ui/src/style/components/query-builder.scss b/ui/src/style/components/query-builder.scss index 32057c9ce..d1b1e5952 100644 --- a/ui/src/style/components/query-builder.scss +++ b/ui/src/style/components/query-builder.scss @@ -98,6 +98,20 @@ white-space: nowrap; margin-right: 8px; } + /* Disabled State */ + &.disabled { + font-style: italic; + color: $query-builder--list-item-text-disabled; + + &:hover { + cursor: default; + background-color: $query-builder--list-item-bg; + } + + &.active { + background-color: $query-builder--list-item-bg-active; + } + } } /* Filter Element */ .query-builder--filter { @@ -156,6 +170,10 @@ border-radius: 50%; transition: transform 0.25s ease, opacity 0.25s ease; } + + .disabled &:after { + background-color: $g5-pepper; + } } .query-builder--list-item.active .query-builder--checkbox:after { opacity: 1; diff --git a/ui/src/style/components/query-maker.scss b/ui/src/style/components/query-maker.scss index 27b5637d6..2fd034032 100644 --- a/ui/src/style/components/query-maker.scss +++ b/ui/src/style/components/query-maker.scss @@ -63,6 +63,7 @@ $query-builder--list-item-bg-hover: $g4-onyx; $query-builder--list-item-text-hover: $g15-platinum; $query-builder--list-item-bg-active: $g4-onyx; $query-builder--list-item-text-active: $g18-cloud; +$query-builder--list-item-text-disabled: $g9-mountain; $query-builder--sub-list-gutter: 24px; $query-builder--sub-list-bg: $query-builder--list-item-bg-active; diff --git a/ui/src/types/dashboard.ts b/ui/src/types/dashboard.ts index fd1a0965a..2764f4fe7 100644 --- a/ui/src/types/dashboard.ts +++ b/ui/src/types/dashboard.ts @@ -1,5 +1,6 @@ -import {Query} from 'src/types' +import {QueryConfig} from 'src/types' import {ColorString} from 'src/types/colors' + interface Axis { bounds: [string, string] label: string @@ -9,18 +10,18 @@ interface Axis { scale: string } -interface Axes { +export interface Axes { x: Axis y: Axis } -interface FieldName { +export interface FieldName { internalName: string displayName: string visible: boolean } -interface TableOptions { +export interface TableOptions { verticalTimeAxis: boolean sortBy: FieldName wrapping: string @@ -33,7 +34,7 @@ interface CellLinks { export interface CellQuery { query: string - queryConfig: Query + queryConfig: QueryConfig } export interface Legend { @@ -41,7 +42,7 @@ export interface Legend { orientation?: string } -interface DecimalPlaces { +export interface DecimalPlaces { isEnforced: boolean digits: number } @@ -75,3 +76,21 @@ export interface Template { tempVar: string values: TemplateValue[] } + +export type CellEditorOverlayActionsFunc = (id: string, ...args: any[]) => any + +export interface CellEditorOverlayActions { + chooseNamespace: (id: string) => void + chooseMeasurement: (id: string) => void + applyFuncsToField: (id: string) => void + chooseTag: (id: string) => void + groupByTag: (id: string) => void + toggleField: (id: string) => void + groupByTime: (id: string) => void + toggleTagAcceptance: (id: string) => void + fill: (id: string) => void + editRawTextAsync: (url: string, id: string, text: string) => Promise + addInitialField: (id: string) => void + removeFuncs: (id: string) => void + timeShift: (id: string) => void +} diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 43c1d24d1..b36075edf 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -1,8 +1,17 @@ import {AuthLinks, Organization, Role, User, Me} from './auth' -import {Query, QueryConfig, TimeRange} from './query' +import {Template, Cell, CellQuery, Legend} from './dashboard' +import { + GroupBy, + QueryConfig, + Status, + TimeRange, + TimeShift, + Field, +} from './query' import {AlertRule, Kapacitor, Task} from './kapacitor' -import {Source} from './sources' +import {Source, SourceLinks} from './sources' import {DropdownAction, DropdownItem} from './shared' +import {Notification} from 'src/kapacitor/components/AlertOutputs' export { Me, @@ -10,13 +19,22 @@ export { Role, User, Organization, + Template, + Cell, + CellQuery, + Legend, + Status, + QueryConfig, + TimeShift, + Field, + GroupBy, AlertRule, Kapacitor, - Query, - QueryConfig, Source, + SourceLinks, DropdownAction, DropdownItem, TimeRange, Task, + Notification, } diff --git a/ui/src/types/query.ts b/ui/src/types/query.ts index d3fee8b31..45cb28bf2 100644 --- a/ui/src/types/query.ts +++ b/ui/src/types/query.ts @@ -1,5 +1,5 @@ -export interface Query { - id: QueryID +export interface QueryConfig { + id?: string database: string measurement: string retentionPolicy: string @@ -7,12 +7,13 @@ export interface Query { tags: Tags groupBy: GroupBy areTagsAccepted: boolean - rawText: string | null + rawText: string range?: DurationRange | null source?: string fill?: string status?: Status shifts: TimeShift[] + isQuerySupportedByExplorer?: boolean // doesn't come from server -- is set in CellEditorOverlay } export interface Field { @@ -22,8 +23,6 @@ export interface Field { args?: Args[] } -export type QueryID = string - export interface Args { value: string type: string @@ -69,18 +68,3 @@ export interface TimeShift { unit: string quantity: string } - -export interface QueryConfig { - id?: string - database: string - measurement: string - retentionPolicy: string - fields: Field[] - tags: Tags - groupBy: GroupBy - areTagsAccepted: boolean - fill?: string - rawText: string - range: DurationRange - shifts: TimeShift[] -} diff --git a/ui/src/utils/buildQueriesForLayouts.js b/ui/src/utils/buildQueriesForLayouts.js index 43db6af72..91f0af19f 100644 --- a/ui/src/utils/buildQueriesForLayouts.js +++ b/ui/src/utils/buildQueriesForLayouts.js @@ -1,5 +1,9 @@ import {buildQuery} from 'utils/influxql' import {TYPE_SHIFTED, TYPE_QUERY_CONFIG} from 'src/dashboards/constants' +import { + TEMP_VAR_DASHBOARD_TIME, + TEMP_VAR_UPPER_DASHBOARD_TIME, +} from 'src/shared/constants' import {timeRanges} from 'shared/data/timeRanges' const buildCannedDashboardQuery = (query, {lower, upper}, host) => { @@ -48,8 +52,8 @@ export const buildQueriesForLayouts = (cell, source, timeRange, host) => { queryConfig: {database, measurement, fields, shifts, rawText, range}, } = query const tR = range || { - upper: ':upperDashboardTime:', - lower: ':dashboardTime:', + upper: TEMP_VAR_UPPER_DASHBOARD_TIME, + lower: TEMP_VAR_DASHBOARD_TIME, } queryText = diff --git a/ui/test/dashboards/components/CellEditorOverlay.test.tsx b/ui/test/dashboards/components/CellEditorOverlay.test.tsx new file mode 100644 index 000000000..0c30c8caa --- /dev/null +++ b/ui/test/dashboards/components/CellEditorOverlay.test.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import {shallow} from 'enzyme' + +import CellEditorOverlay from 'src/dashboards/components/CellEditorOverlay' +import QueryMaker from 'src/dashboards/components/QueryMaker' +import { + source, + cell, + timeRange, + userDefinedTemplateVariables, + predefinedTemplateVariables, + thresholdsListColors, + gaugeColors, + lineColors, + query, +} from 'test/fixtures' + +jest.mock('src/shared/apis', () => require('mocks/shared/apis')) + +const setup = (override = {}) => { + const props = { + source, + sources: [source], + cell, + timeRange, + autoRefresh: 0, + dashboardID: '9', + queryStatus: { + queryID: null, + status: null, + }, + templates: [ + ...userDefinedTemplateVariables, + ...predefinedTemplateVariables, + ], + thresholdsListType: 'text', + thresholdsListColors, + gaugeColors, + lineColors, + editQueryStatus: () => {}, + onCancel: () => {}, + onSave: () => {}, + notify: () => {}, + ...override, + } + + const wrapper = shallow() + + return {props, wrapper} +} + +describe('Dashboards.Components.CellEditorOverlay', () => { + describe('rendering', () => { + describe('if a predefined template variable is used in the query', () => { + it('should render the query maker with isQuerySupportedByExplorer as false', () => { + const queryText = + 'SELECT mean(:fields:), mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)' + const {queryConfig} = query + const updatedQueryConfig = {...queryConfig, rawText: queryText} + const updatedQueries = [ + {...query, query: queryText, queryConfig: updatedQueryConfig}, + ] + const updatedCell = {...cell, queries: updatedQueries} + const {wrapper} = setup({cell: updatedCell}) + + const queryMaker = wrapper.find(QueryMaker) + const activeQuery = queryMaker.prop('activeQuery') + + expect(activeQuery.isQuerySupportedByExplorer).toBe(false) + }) + }) + }) +}) diff --git a/ui/test/fixtures/index.ts b/ui/test/fixtures/index.ts new file mode 100644 index 000000000..d3baa3e53 --- /dev/null +++ b/ui/test/fixtures/index.ts @@ -0,0 +1,330 @@ +import { + Source, + CellQuery, + SourceLinks, + Cell, + TimeRange, + Template, +} from 'src/types' +import {Axes, TableOptions, FieldName, DecimalPlaces} from 'src/types/dashboard' +import {ColorString, ColorNumber} from 'src/types/colors' + +export const sourceLinks: SourceLinks = { + self: '/chronograf/v1/sources/4', + kapacitors: '/chronograf/v1/sources/4/kapacitors', + proxy: '/chronograf/v1/sources/4/proxy', + queries: '/chronograf/v1/sources/4/queries', + write: '/chronograf/v1/sources/4/write', + permissions: '/chronograf/v1/sources/4/permissions', + users: '/chronograf/v1/sources/4/users', + databases: '/chronograf/v1/sources/4/dbs', + annotations: '/chronograf/v1/sources/4/annotations', + health: '/chronograf/v1/sources/4/health', +} + +export const source: Source = { + id: '4', + name: 'Influx 1', + type: 'influx', + url: 'http://localhost:8086', + default: false, + telegraf: 'telegraf', + organization: 'default', + role: 'viewer', + defaultRP: '', + links: sourceLinks, + insecureSkipVerify: false, +} + +export const query: CellQuery = { + query: + 'SELECT mean("usage_idle") AS "mean_usage_idle", mean("usage_user") AS "mean_usage_user" FROM "telegraf"."autogen"."cpu" WHERE time > :dashboardTime: GROUP BY time(:interval:) FILL(null)', + queryConfig: { + database: 'telegraf', + measurement: 'cpu', + retentionPolicy: 'autogen', + fields: [ + { + value: 'mean', + type: 'func', + alias: 'mean_usage_idle', + args: [ + { + value: 'usage_idle', + type: 'field', + alias: '', + }, + ], + }, + { + value: 'mean', + type: 'func', + alias: 'mean_usage_user', + args: [ + { + value: 'usage_user', + type: 'field', + alias: '', + }, + ], + }, + ], + tags: {}, + groupBy: { + time: 'auto', + tags: [], + }, + areTagsAccepted: false, + fill: 'null', + rawText: null, + range: null, + shifts: null, + }, +} + +export const axes: Axes = { + x: { + bounds: ['', ''], + label: '', + prefix: '', + suffix: '', + base: '10', + scale: 'linear', + }, + y: { + bounds: ['', ''], + label: '', + prefix: '', + suffix: '', + base: '10', + scale: 'linear', + }, +} + +export const fieldOptions: FieldName[] = [ + { + internalName: 'time', + displayName: '', + visible: true, + }, +] + +export const tableOptions: TableOptions = { + verticalTimeAxis: true, + sortBy: { + internalName: 'time', + displayName: '', + visible: true, + }, + wrapping: 'truncate', + fixFirstColumn: true, +} +export const lineColors: ColorString[] = [ + { + id: '574fb0a3-0a26-44d7-8d71-d4981756acb1', + type: 'scale', + hex: '#31C0F6', + name: 'Nineteen Eighty Four', + value: '0', + }, + { + id: '3b9750f9-d41d-4100-8ee6-bd2785237f35', + type: 'scale', + hex: '#A500A5', + name: 'Nineteen Eighty Four', + value: '0', + }, + { + id: '8d39064f-8124-4967-ae22-ffe14e425781', + type: 'scale', + hex: '#FF7E27', + name: 'Nineteen Eighty Four', + value: '0', + }, +] + +export const decimalPlaces: DecimalPlaces = { + isEnforced: true, + digits: 4, +} + +export const cell: Cell = { + id: '67435af2-17bf-4caa-a5fc-0dd1ffb40dab', + x: 0, + y: 0, + w: 8, + h: 4, + name: 'Untitled Line Graph', + queries: [query], + axes: axes, + type: 'line', + colors: lineColors, + legend: {}, + tableOptions: tableOptions, + fieldOptions: fieldOptions, + timeFormat: 'MM/DD/YYYY HH:mm:ss', + decimalPlaces: decimalPlaces, + links: { + self: + '/chronograf/v1/dashboards/9/cells/67435af2-17bf-4caa-a5fc-0dd1ffb40dab', + }, +} + +export const fullTimeRange = { + dashboardID: 9, + defaultGroupBy: '10s', + seconds: 300, + inputValue: 'Past 5 minutes', + lower: 'now() - 5m', + upper: null, + menuOption: 'Past 5 minutes', + format: 'influxql', +} + +export const timeRange: TimeRange = { + lower: 'now() - 5m', + upper: null, +} + +export const userDefinedTemplateVariables: Template[] = [ + { + tempVar: ':fields:', + values: [ + { + selected: false, + value: 'usage_guest', + }, + { + selected: false, + value: 'usage_guest_nice', + }, + { + selected: true, + value: 'usage_idle', + }, + { + selected: false, + value: 'usage_iowait', + }, + { + selected: false, + value: 'usage_irq', + }, + { + selected: false, + value: 'usage_nice', + }, + { + selected: false, + value: 'usage_softirq', + }, + { + selected: false, + value: 'usage_steal', + }, + { + selected: false, + value: 'usage_system', + }, + { + selected: false, + value: 'usage_user', + }, + ], + id: '2b8dca84-879c-4555-a7cf-97f2951f8643', + }, + { + tempVar: ':measurements:', + values: [ + { + selected: true, + value: 'cpu', + }, + { + selected: false, + value: 'disk', + }, + { + selected: false, + value: 'diskio', + }, + { + selected: false, + value: 'mem', + }, + { + selected: false, + value: 'processes', + }, + { + selected: false, + value: 'swap', + }, + { + selected: false, + value: 'system', + }, + ], + id: '18855209-12db-4619-9834-1d7eb643ae6e', + }, +] + +export const predefinedTemplateVariables: Template[] = [ + { + id: 'dashtime', + tempVar: ':dashboardTime:', + values: [ + { + value: 'now() - 5m', + selected: true, + }, + ], + }, + { + id: 'upperdashtime', + tempVar: ':upperDashboardTime:', + values: [ + { + value: 'now()', + selected: true, + }, + ], + }, + { + id: 'interval', + tempVar: ':interval:', + values: [ + { + value: '333', + selected: true, + }, + ], + }, +] + +export const thresholdsListColors: ColorNumber[] = [ + { + type: 'text', + hex: '#00C9FF', + id: 'base', + name: 'laser', + value: -1000000000000000000, + }, +] + +export const gaugeColors: ColorNumber[] = [ + { + type: 'min', + hex: '#00C9FF', + id: '0', + name: 'laser', + value: 0, + }, + { + type: 'max', + hex: '#9394FF', + id: '1', + name: 'comet', + value: 100, + }, +] diff --git a/ui/test/shared/components/MeasurementList.test.tsx b/ui/test/shared/components/MeasurementList.test.tsx index 9f604dd62..e1c1bc752 100644 --- a/ui/test/shared/components/MeasurementList.test.tsx +++ b/ui/test/shared/components/MeasurementList.test.tsx @@ -13,6 +13,7 @@ const setup = (override = {}) => { onToggleTagAcceptance: () => {}, query, querySource: source, + isQuerySupportedByExplorer: true, ...override, } diff --git a/ui/test/shared/components/MeasurementListItem.test.tsx b/ui/test/shared/components/MeasurementListItem.test.tsx index 356bb5e2f..245482f9f 100644 --- a/ui/test/shared/components/MeasurementListItem.test.tsx +++ b/ui/test/shared/components/MeasurementListItem.test.tsx @@ -23,6 +23,7 @@ const setup = (overrides = {}) => { measurement: 'test', numTagsActive: 3, areTagsAccepted: true, + isQuerySupportedByExplorer: true, onChooseTag: () => {}, onGroupByTag: () => {}, onAcceptReject: () => {}, diff --git a/ui/test/shared/components/TagList.test.tsx b/ui/test/shared/components/TagList.test.tsx index fa237c2f0..2d5315bbe 100644 --- a/ui/test/shared/components/TagList.test.tsx +++ b/ui/test/shared/components/TagList.test.tsx @@ -13,6 +13,7 @@ const setup = (override = {}) => { onGroupByTag: () => {}, query, querySource: source, + isQuerySupportedByExplorer: true, ...override, }