diff --git a/ui/package.json b/ui/package.json index 739d14b2e..6dc33750f 100644 --- a/ui/package.json +++ b/ui/package.json @@ -139,6 +139,7 @@ "redux-thunk": "^1.0.3", "reselect": "^3.0.1", "rome": "^2.1.22", + "unstated": "^2.1.1", "uuid": "^3.2.1" } } diff --git a/ui/src/dashboards/actions/cellEditorOverlay.ts b/ui/src/dashboards/actions/cellEditorOverlay.ts index 07349fae3..6ca210ea3 100644 --- a/ui/src/dashboards/actions/cellEditorOverlay.ts +++ b/ui/src/dashboards/actions/cellEditorOverlay.ts @@ -1,17 +1,4 @@ // Types -import {ColorNumber, ColorString} from 'src/types/colors' -import { - DecimalPlaces, - FieldOption, - Axes, - Cell, - CellType, - ThresholdType, - TableOptions, - CellQuery, - NewDefaultCell, - NoteVisibility, -} from 'src/types/dashboards' import {TimeRange} from 'src/types' import {CEOInitialState} from 'src/dashboards/reducers/cellEditorOverlay' @@ -20,66 +7,21 @@ export interface State { } export enum ActionType { - LoadCEO = 'LOAD_CEO', ClearCEO = 'CLEAR_CEO', - ChangeCellType = 'CHANGE_CELL_TYPE', RenameCell = 'RENAME_CELL', - UpdateThresholdsListColors = 'UPDATE_THRESHOLDS_LIST_COLORS', - UpdateThresholdsListType = 'UPDATE_THRESHOLDS_LIST_TYPE', - UpdateGaugeColors = 'UPDATE_GAUGE_COLORS', - UpdateAxes = 'UPDATE_AXES', - UpdateTableOptions = 'UPDATE_TABLE_OPTIONS', - UpdateLineColors = 'UPDATE_LINE_COLORS', - ChangeTimeFormat = 'CHANGE_TIME_FORMAT', - ChangeDecimalPlaces = 'CHANGE_DECIMAL_PLACES', - UpdateFieldOptions = 'UPDATE_FIELD_OPTIONS', - UpdateQueryDrafts = 'UPDATE_QUERY_DRAFTS', - UpdateQueryDraft = 'UPDATE_QUERY_DRAFT', - UpdateCellNote = 'UPDATE_CELL_NOTE', - UpdateCellNoteVisibility = 'UPDATE_CELL_NOTE_VISIBILITY', UpdateEditorTimeRange = 'UPDATE_EDITOR_TIME_RANGE', - UpdateScript = 'UPDATE_SCRIPT', + UpdateFieldOptions = 'UPDATE_FIELD_OPTIONS', } export type Action = - | LoadCEOAction | ClearCEOAction - | ChangeCellTypeAction | RenameCellAction - | UpdateThresholdsListColorsAction - | UpdateThresholdsListTypeAction - | UpdateGaugeColorsAction - | UpdateAxesAction - | UpdateTableOptionsAction - | UpdateLineColorsAction - | ChangeTimeFormatAction - | ChangeDecimalPlacesAction - | UpdateFieldOptionsAction - | UpdateQueryDraftsAction - | UpdateCellNoteAction - | UpdateCellNoteVisibilityAction | UpdateEditorTimeRangeAction - | UpdateScriptAction - -export interface LoadCEOAction { - type: ActionType.LoadCEO - payload: { - cell: Cell | NewDefaultCell - timeRange: TimeRange - } -} export interface ClearCEOAction { type: ActionType.ClearCEO } -export interface ChangeCellTypeAction { - type: ActionType.ChangeCellType - payload: { - cellType: CellType - } -} - export interface RenameCellAction { type: ActionType.RenameCell payload: { @@ -87,97 +29,6 @@ export interface RenameCellAction { } } -export interface UpdateThresholdsListColorsAction { - type: ActionType.UpdateThresholdsListColors - payload: { - thresholdsListColors: ColorNumber[] - } -} - -export interface UpdateThresholdsListTypeAction { - type: ActionType.UpdateThresholdsListType - payload: { - thresholdsListType: ThresholdType - } -} - -export interface UpdateGaugeColorsAction { - type: ActionType.UpdateGaugeColors - payload: { - gaugeColors: ColorNumber[] - } -} - -export interface UpdateAxesAction { - type: ActionType.UpdateAxes - payload: { - axes: Axes - } -} - -export interface UpdateTableOptionsAction { - type: ActionType.UpdateTableOptions - payload: { - tableOptions: TableOptions - } -} - -export interface UpdateLineColorsAction { - type: ActionType.UpdateLineColors - payload: { - lineColors: ColorString[] - } -} - -export interface ChangeTimeFormatAction { - type: ActionType.ChangeTimeFormat - payload: { - timeFormat: string - } -} - -export interface ChangeDecimalPlacesAction { - type: ActionType.ChangeDecimalPlaces - payload: { - decimalPlaces: DecimalPlaces - } -} - -export interface UpdateFieldOptionsAction { - type: ActionType.UpdateFieldOptions - payload: { - fieldOptions: FieldOption[] - } -} - -export interface UpdateQueryDraftsAction { - type: ActionType.UpdateQueryDrafts - payload: { - queryDrafts: CellQuery[] - } -} - -export interface UpdateQueryDraftAction { - type: ActionType.UpdateQueryDraft - payload: { - queryDraft: CellQuery - } -} - -export interface UpdateCellNoteAction { - type: ActionType.UpdateCellNote - payload: { - note: string - } -} - -export interface UpdateCellNoteVisibilityAction { - type: ActionType.UpdateCellNoteVisibility - payload: { - noteVisibility: NoteVisibility - } -} - export interface UpdateEditorTimeRangeAction { type: ActionType.UpdateEditorTimeRange payload: { @@ -185,24 +36,6 @@ export interface UpdateEditorTimeRangeAction { } } -export interface UpdateScriptAction { - type: ActionType.UpdateScript - payload: { - script: string - } -} - -export const loadCEO = ( - cell: Cell | NewDefaultCell, - timeRange: TimeRange -): LoadCEOAction => ({ - type: ActionType.LoadCEO, - payload: { - cell, - timeRange, - }, -}) - export const clearCEO = (): ClearCEOAction => ({ type: ActionType.ClearCEO, }) diff --git a/ui/src/dashboards/components/CellEditorOverlay.tsx b/ui/src/dashboards/components/CellEditorOverlay.tsx index 2f622147a..7a0e7b552 100644 --- a/ui/src/dashboards/components/CellEditorOverlay.tsx +++ b/ui/src/dashboards/components/CellEditorOverlay.tsx @@ -1,6 +1,7 @@ // Libraries import React, {Component} from 'react' import _ from 'lodash' +import {Provider} from 'unstated' // Components import {ErrorHandling} from 'src/shared/decorators/errors' @@ -11,15 +12,10 @@ import CEOHeader from 'src/dashboards/components/CEOHeader' import {getDeep} from 'src/utils/wrappers' import {buildQuery} from 'src/utils/influxql' import {getTimeRange} from 'src/dashboards/utils/cellGetters' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' +import {intialStateFromCell} from 'src/shared/utils/timeMachine' // Actions -import { - QueryConfigActions, - addQueryAsync, - deleteQueryAsync, - updateEditorTimeRange as updateEditorTimeRangeAction, - updateScript, -} from 'src/shared/actions/queries' import {editCellQueryStatus} from 'src/dashboards/actions' // Constants @@ -29,43 +25,34 @@ import {IS_STATIC_LEGEND} from 'src/shared/constants' import {STATIC_LEGEND} from 'src/dashboards/constants/cellEditor' // Types -import * as ColorsModels from 'src/types/colors' -import * as DashboardsModels from 'src/types/dashboards' import * as QueriesModels from 'src/types/queries' import * as SourcesModels from 'src/types/sources' -import {Service, NotificationAction} from 'src/types' +import {Service, NotificationAction, TimeRange} from 'src/types' import {Template} from 'src/types/tempVars' -import {NewDefaultCell, ThresholdType, QueryType} from 'src/types/dashboards' +import { + Cell, + Legend, + CellQuery, + NewDefaultCell, + QueryType, +} from 'src/types/dashboards' import {Links, ScriptStatus} from 'src/types/flux' -import {VisualizationOptions} from 'src/types/dataExplorer' interface Props { fluxLinks: Links - script: string sources: SourcesModels.Source[] services: Service[] notify: NotificationAction editQueryStatus: typeof editCellQueryStatus onCancel: () => void - onSave: (cell: DashboardsModels.Cell | NewDefaultCell) => void + onSave: (cell: Cell | NewDefaultCell) => void source: SourcesModels.Source dashboardID: number queryStatus: QueriesModels.QueryStatus templates: Template[] - timeRange: QueriesModels.TimeRange - thresholdsListType: ThresholdType - thresholdsListColors: ColorsModels.ColorNumber[] - gaugeColors: ColorsModels.ColorNumber[] - lineColors: ColorsModels.ColorString[] - cell: DashboardsModels.Cell | NewDefaultCell - queryDrafts: DashboardsModels.CellQuery[] + cell: Cell | NewDefaultCell renameCell: (name: string) => void - updateQueryDrafts: (queryDrafts: DashboardsModels.CellQuery[]) => void - queryConfigActions: QueryConfigActions - addQuery: typeof addQueryAsync - deleteQuery: typeof deleteQueryAsync - updateScript: typeof updateScript - updateEditorTimeRange: typeof updateEditorTimeRangeAction + dashboardTimeRange: TimeRange } interface State { @@ -76,19 +63,21 @@ interface State { @ErrorHandling class CellEditorOverlay extends Component { private overlayRef: React.RefObject = React.createRef() + private timeMachineContainer: TimeMachineContainer public constructor(props: Props) { super(props) - const {cell} = props - const legend = getDeep(cell, 'legend', null) + this.timeMachineContainer = new TimeMachineContainer({ + ...intialStateFromCell(props.cell), + timeRange: props.dashboardTimeRange, + }) + + const legend = getDeep(props, 'cell.legend', null) this.state = { isStaticLegend: IS_STATIC_LEGEND(legend), - status: { - type: 'none', - text: '', - }, + status: {type: 'none', text: ''}, } } @@ -98,78 +87,63 @@ class CellEditorOverlay extends Component { public render() { const { - addQuery, cell, - deleteQuery, editQueryStatus, fluxLinks, notify, onCancel, - queryDrafts, renameCell, - script, services, source, sources, templates, - timeRange, - updateEditorTimeRange, - updateQueryDrafts, queryStatus, } = this.props const {isStaticLegend} = this.state return ( -
- +
- {(activeEditorTab, onSetActiveEditorTab) => ( - - )} - -
+ + {(activeEditorTab, onSetActiveEditorTab) => ( + + )} + +
+ ) } private get isSaveable(): boolean { - const {queryDrafts} = this.props + const {queryDrafts} = this.timeMachineContainer.state const {status} = this.state if (this.isFluxSource) { @@ -192,44 +166,8 @@ class CellEditorOverlay extends Component { }) } - private get visualizationOptions(): VisualizationOptions { - const { - cell, - lineColors, - gaugeColors, - thresholdsListType, - thresholdsListColors, - } = this.props - - const { - type, - tableOptions, - fieldOptions, - timeFormat, - decimalPlaces, - note, - noteVisibility, - } = cell - const axes = _.get(cell, 'axes') - - return { - type, - axes, - tableOptions, - fieldOptions, - timeFormat, - decimalPlaces, - note, - noteVisibility, - thresholdsListColors, - gaugeColors, - lineColors, - thresholdsListType, - } - } - private get isFluxSource(): boolean { - const {queryDrafts} = this.props + const {queryDrafts} = this.timeMachineContainer.state if (getDeep(queryDrafts, '0.type', '') === QueryType.Flux) { return true @@ -241,18 +179,26 @@ class CellEditorOverlay extends Component { this.setState({status}) } - private handleSaveCell = () => { + private collectCell = (): Cell | NewDefaultCell => { + const {cell} = this.props const { + script, queryDrafts, + type, + axes, + tableOptions, + fieldOptions, + timeFormat, + decimalPlaces, + note, + noteVisibility, thresholdsListColors, gaugeColors, lineColors, - cell, - script, - } = this.props + } = this.timeMachineContainer.state const {isStaticLegend} = this.state - let queries: DashboardsModels.CellQuery[] + let queries: CellQuery[] if (this.isFluxSource) { queries = [ @@ -285,20 +231,35 @@ class CellEditorOverlay extends Component { } const colors = getCellTypeColors({ - cellType: cell.type, + cellType: type, gaugeColors, thresholdsListColors, lineColors, }) - const newCell: DashboardsModels.Cell | NewDefaultCell = { + const newCell = { ...cell, queries, colors, + axes, + tableOptions, + fieldOptions, + timeFormat, + decimalPlaces, + note, + noteVisibility, + type, legend: isStaticLegend ? STATIC_LEGEND : {}, } - this.props.onSave(newCell) + return newCell + } + + private handleSaveCell = () => { + const {onSave} = this.props + const cell = this.collectCell() + + onSave(cell) } private handleKeyDown = e => { diff --git a/ui/src/dashboards/components/DisplayOptions.tsx b/ui/src/dashboards/components/DisplayOptions.tsx index 449e32372..083e8878e 100644 --- a/ui/src/dashboards/components/DisplayOptions.tsx +++ b/ui/src/dashboards/components/DisplayOptions.tsx @@ -1,7 +1,7 @@ // Libraries import React, {Component} from 'react' -import {connect} from 'react-redux' import _ from 'lodash' +import {Subscribe} from 'unstated' // Components import GraphTypeSelector from 'src/dashboards/components/GraphTypeSelector' @@ -13,21 +13,8 @@ import NoteOptions from 'src/dashboards/components/NoteOptions' import CellNoteEditor from 'src/shared/components/TimeMachine/CellNoteEditor' import Threesizer from 'src/shared/components/threesizer/Threesizer' -// Actions -import { - updateDecimalPlaces, - updateGaugeColors, - updateAxes, - updateTableOptions, - updateFieldOptions, - updateTimeFormat, - updateVisType, - updateNote, - updateNoteVisibility, - updateThresholdsListColors, - updateThresholdsListType, - updateLineColors, -} from 'src/shared/actions/visualizations' +// Utils +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' // Constants import {HANDLE_VERTICAL} from 'src/shared/constants' @@ -35,42 +22,55 @@ import {QueryUpdateState} from 'src/shared/actions/queries' import {DEFAULT_AXES} from 'src/dashboards/constants/cellEditor' // Types -import { - CellType, - FieldOption, - ThresholdType, - DecimalPlaces, - NewDefaultCell, - NoteVisibility, - TableOptions as TableOptionsInterface, -} from 'src/types/dashboards' import {buildDefaultYLabel} from 'src/shared/presenters' import {ErrorHandling} from 'src/shared/decorators/errors' -import {Axes, Cell, QueryConfig} from 'src/types' +import {Axes, QueryConfig, CellType} from 'src/types' +import { + FieldOption, + DecimalPlaces, + NoteVisibility, + ThresholdType, + TableOptions as TableOptionsInterface, +} from 'src/types/dashboards' import {ColorNumber, ColorString} from 'src/types/colors' -import {VisualizationOptions} from 'src/types/dataExplorer' -interface Props extends VisualizationOptions { - cell: Cell | NewDefaultCell +interface ConnectedProps { + type: CellType + axes: Axes | null + tableOptions: TableOptionsInterface + fieldOptions: FieldOption[] + timeFormat: string + decimalPlaces: DecimalPlaces + note: string + noteVisibility: NoteVisibility + thresholdsListColors: ColorNumber[] + thresholdsListType: ThresholdType + gaugeColors: ColorNumber[] + lineColors: ColorString[] + onUpdateDecimalPlaces: TimeMachineContainer['handleUpdateDecimalPlaces'] + onUpdateGaugeColors: TimeMachineContainer['handleUpdateGaugeColors'] + onUpdateAxes: TimeMachineContainer['handleUpdateAxes'] + onUpdateTableOptions: TimeMachineContainer['handleUpdateTableOptions'] + onUpdateFieldOptions: TimeMachineContainer['handleUpdateFieldOptions'] + onUpdateTimeFormat: TimeMachineContainer['handleUpdateTimeFormat'] + onUpdateType: TimeMachineContainer['handleUpdateType'] + onUpdateNote: TimeMachineContainer['handleUpdateNote'] + onUpdateLineColors: TimeMachineContainer['handleUpdateLineColors'] + onUpdateNoteVisibility: TimeMachineContainer['handleUpdateNoteVisibility'] + onUpdateThresholdsListColors: TimeMachineContainer['handleUpdateThresholdsListColors'] + onUpdateThresholdsListType: TimeMachineContainer['handleUpdateThresholdsListType'] +} + +interface PassedProps { queryConfigs: QueryConfig[] staticLegend: boolean stateToUpdate: QueryUpdateState onResetFocus: () => void onToggleStaticLegend: (isStaticLegend: boolean) => void - onUpdateDecimalPlaces: typeof updateDecimalPlaces - onUpdateGaugeColors: typeof updateGaugeColors - onUpdateAxes: typeof updateAxes - onUpdateTableOptions: typeof updateTableOptions - onUpdateFieldOptions: typeof updateFieldOptions - onUpdateTimeFormat: typeof updateTimeFormat - onUpdateVisType: typeof updateVisType - onUpdateNote: typeof updateNote - onUpdateLineColors: typeof updateLineColors - onUpdateNoteVisibility: typeof updateNoteVisibility - onUpdateThresholdsListColors: typeof updateThresholdsListColors - onUpdateThresholdsListType: typeof updateThresholdsListType } +type Props = PassedProps & ConnectedProps + interface State { defaultYLabel: string } @@ -103,17 +103,21 @@ class DisplayOptions extends Component { } private get threesizerDivisions() { - const {type, note, noteVisibility} = this.props + const { + type, + note, + noteVisibility, + onUpdateType, + onUpdateNote, + onUpdateNoteVisibility, + } = this.props return [ { name: 'Visualization Type', headerButtons: [], menuOptions: [], render: () => ( - + ), headerOrientation: HANDLE_VERTICAL, }, @@ -132,8 +136,8 @@ class DisplayOptions extends Component { ), headerOrientation: HANDLE_VERTICAL, @@ -156,6 +160,15 @@ class DisplayOptions extends Component { timeFormat, tableOptions, fieldOptions, + onUpdateAxes, + onUpdateDecimalPlaces, + onUpdateGaugeColors, + onUpdateThresholdsListColors, + onUpdateThresholdsListType, + onUpdateFieldOptions, + onUpdateLineColors, + onUpdateTableOptions, + onUpdateTimeFormat, } = this.props const {defaultYLabel} = this.state @@ -168,9 +181,9 @@ class DisplayOptions extends Component { axes={this.axes} decimalPlaces={decimalPlaces} gaugeColors={gaugeColors} - onUpdateAxes={this.handleUpdateAxes} - onUpdateDecimalPlaces={this.handleUpdateDecimalPlaces} - onUpdateGaugeColors={this.handleUpdateGaugeColors} + onUpdateAxes={onUpdateAxes} + onUpdateDecimalPlaces={onUpdateDecimalPlaces} + onUpdateGaugeColors={onUpdateGaugeColors} /> ) case CellType.Note: @@ -181,12 +194,12 @@ class DisplayOptions extends Component { onResetFocus={onResetFocus} axes={this.axes} decimalPlaces={decimalPlaces} - onUpdateAxes={this.handleUpdateAxes} + onUpdateAxes={onUpdateAxes} thresholdsListType={thresholdsListType} thresholdsListColors={thresholdsListColors} - onUpdateDecimalPlaces={this.handleUpdateDecimalPlaces} - onUpdateThresholdsListType={this.handleUpdateThresholdsListType} - onUpdateThresholdsListColors={this.handleUpdateThresholdsListColors} + onUpdateDecimalPlaces={onUpdateDecimalPlaces} + onUpdateThresholdsListType={onUpdateThresholdsListType} + onUpdateThresholdsListColors={onUpdateThresholdsListColors} /> ) case CellType.Table: @@ -200,12 +213,12 @@ class DisplayOptions extends Component { decimalPlaces={decimalPlaces} thresholdsListType={thresholdsListType} thresholdsListColors={thresholdsListColors} - onUpdateDecimalPlaces={this.handleUpdateDecimalPlaces} - onUpdateFieldOptions={this.handleUpdateFieldOptions} - onUpdateTableOptions={this.handleUpdateTableOptions} - onUpdateTimeFormat={this.handleUpdateTimeFormat} - onUpdateThresholdsListColors={this.handleUpdateThresholdsListColors} - onUpdateThresholdsListType={this.handleUpdateThresholdsListType} + onUpdateDecimalPlaces={onUpdateDecimalPlaces} + onUpdateFieldOptions={onUpdateFieldOptions} + onUpdateTableOptions={onUpdateTableOptions} + onUpdateTimeFormat={onUpdateTimeFormat} + onUpdateThresholdsListColors={onUpdateThresholdsListColors} + onUpdateThresholdsListType={onUpdateThresholdsListType} /> ) default: @@ -217,19 +230,15 @@ class DisplayOptions extends Component { staticLegend={staticLegend} defaultYLabel={defaultYLabel} decimalPlaces={decimalPlaces} - onUpdateAxes={this.handleUpdateAxes} + onUpdateAxes={onUpdateAxes} onToggleStaticLegend={onToggleStaticLegend} - onUpdateLineColors={this.handleUpdateLineColors} - onUpdateDecimalPlaces={this.handleUpdateDecimalPlaces} + onUpdateLineColors={onUpdateLineColors} + onUpdateDecimalPlaces={onUpdateDecimalPlaces} /> ) } } - private get stateToUpdate(): QueryUpdateState { - return this.props.stateToUpdate - } - private get axes(): Axes { return this.props.axes || DEFAULT_AXES } @@ -242,95 +251,50 @@ class DisplayOptions extends Component { return '' } - - private handleUpdateAxes = (axes: Axes): void => { - const {onUpdateAxes} = this.props - - onUpdateAxes(axes, this.stateToUpdate) - } - - private handleUpdateDecimalPlaces = (decimalPlaces: DecimalPlaces): void => { - const {onUpdateDecimalPlaces} = this.props - - onUpdateDecimalPlaces(decimalPlaces, this.stateToUpdate) - } - - private handleUpdateGaugeColors = (gaugeColors: ColorNumber[]): void => { - const {onUpdateGaugeColors} = this.props - - onUpdateGaugeColors(gaugeColors, this.stateToUpdate) - } - - private handleUpdateTimeFormat = (timeFormat: string): void => { - const {onUpdateTimeFormat} = this.props - - onUpdateTimeFormat(timeFormat, this.stateToUpdate) - } - - private handleUpdateTableOptions = ( - tableOptions: TableOptionsInterface - ): void => { - const {onUpdateTableOptions} = this.props - - onUpdateTableOptions(tableOptions, this.stateToUpdate) - } - - private handleUpdateFieldOptions = (fieldOptions: FieldOption[]): void => { - const {onUpdateFieldOptions} = this.props - - onUpdateFieldOptions(fieldOptions, this.stateToUpdate) - } - - private handleUpdateVisType = (type: CellType): void => { - const {onUpdateVisType} = this.props - - onUpdateVisType(type, this.stateToUpdate) - } - - private handleUpdateNote = (note: string): void => { - const {onUpdateNote} = this.props - - onUpdateNote(note, this.stateToUpdate) - } - - private handleUpdateNoteVisibility = (visibility: NoteVisibility): void => { - const {onUpdateNoteVisibility} = this.props - - onUpdateNoteVisibility(visibility, this.stateToUpdate) - } - - private handleUpdateThresholdsListColors = (colors: ColorNumber[]): void => { - const {onUpdateThresholdsListColors} = this.props - - onUpdateThresholdsListColors(colors, this.stateToUpdate) - } - - private handleUpdateThresholdsListType = (type: ThresholdType): void => { - const {onUpdateThresholdsListType} = this.props - - onUpdateThresholdsListType(type, this.stateToUpdate) - } - - private handleUpdateLineColors = (colors: ColorString[]): void => { - const {onUpdateLineColors} = this.props - - onUpdateLineColors(colors, this.stateToUpdate) - } } -const mdtp = { - onUpdateGaugeColors: updateGaugeColors, - onUpdateAxes: updateAxes, - onUpdateDecimalPlaces: updateDecimalPlaces, - onUpdateTableOptions: updateTableOptions, - onUpdateFieldOptions: updateFieldOptions, - onUpdateTimeFormat: updateTimeFormat, - onUpdateVisType: updateVisType, - onUpdateNote: updateNote, - onUpdateNoteVisibility: updateNoteVisibility, - onUpdateThresholdsListColors: updateThresholdsListColors, - onUpdateThresholdsListType: updateThresholdsListType, - onUpdateLineColors: updateLineColors, +const ConnectedDisplayOptions = (props: PassedProps) => { + // TODO: Have individual display option components subscribe directly to + // relevant props, rather than passing them through here + return ( + + {(timeMachineContainer: TimeMachineContainer) => ( + + )} + + ) } -export default connect(null, mdtp)(DisplayOptions) +export default ConnectedDisplayOptions diff --git a/ui/src/dashboards/constants/cellEditor.ts b/ui/src/dashboards/constants/cellEditor.ts index 4d8beb671..42d2b5b80 100644 --- a/ui/src/dashboards/constants/cellEditor.ts +++ b/ui/src/dashboards/constants/cellEditor.ts @@ -53,28 +53,25 @@ export const getCellTypeColors = ({ thresholdsListColors, lineColors, }: Color): ColorString[] => { - let colors: ColorString[] = [] - switch (cellType) { case CellType.Gauge: { - colors = stringifyColorValues(gaugeColors) - break + return stringifyColorValues(gaugeColors) } case CellType.SingleStat: case CellType.Table: { - colors = stringifyColorValues(thresholdsListColors) - break + return stringifyColorValues(thresholdsListColors) } case CellType.Bar: case CellType.Line: case CellType.LinePlusSingleStat: case CellType.Stacked: case CellType.StepPlot: { - colors = stringifyColorValues(lineColors) + return stringifyColorValues(lineColors) + } + default: { + return [] } } - - return colors } export const STATIC_LEGEND: Legend = { diff --git a/ui/src/dashboards/containers/DashboardPage.tsx b/ui/src/dashboards/containers/DashboardPage.tsx index f80f89566..23578f035 100644 --- a/ui/src/dashboards/containers/DashboardPage.tsx +++ b/ui/src/dashboards/containers/DashboardPage.tsx @@ -22,15 +22,6 @@ import { dismissEditingAnnotation, } from 'src/shared/actions/annotations' import * as cellEditorOverlayActions from 'src/dashboards/actions/cellEditorOverlay' -import { - queryConfigActions, - QueryConfigActions, - addQueryAsync, - deleteQueryAsync, - updateQueryDrafts as updateQueryDraftsAction, - updateEditorTimeRange as updateEditorTimeRangeAction, - updateScript, -} from 'src/shared/actions/queries' import * as appActions from 'src/shared/actions/app' import * as errorActions from 'src/shared/actions/errors' @@ -68,7 +59,6 @@ import {ManualRefreshProps} from 'src/shared/components/ManualRefresh' import {Location} from 'history' import {InjectedRouter} from 'react-router' import * as AppActions from 'src/types/actions/app' -import * as ColorsModels from 'src/types/colors' import * as DashboardsModels from 'src/types/dashboards' import * as ErrorsActions from 'src/types/actions/errors' import * as QueriesModels from 'src/types/queries' @@ -81,8 +71,6 @@ import {Links} from 'src/types/flux' interface Props extends ManualRefreshProps, WithRouterProps { fluxLinks: Links - script: string - updateScript: typeof updateScript source: SourcesModels.Source sources: SourcesModels.Source[] params: { @@ -112,17 +100,9 @@ interface Props extends ManualRefreshProps, WithRouterProps { notify: NotificationAction annotationsDisplaySetting: AnnotationsDisplaySetting onGetAnnotationsAsync: typeof getAnnotationsAsync - handleLoadCEO: typeof cellEditorOverlayActions.loadCEO handleClearCEO: typeof cellEditorOverlayActions.clearCEO handleDismissEditingAnnotation: typeof dismissEditingAnnotation - selectedCell: DashboardsModels.Cell - queryDrafts: DashboardsModels.CellQuery[] editorTimeRange: QueriesModels.TimeRange - updateQueryDrafts: (queryDrafts: DashboardsModels.CellQuery[]) => void - thresholdsListType: DashboardsModels.ThresholdType - thresholdsListColors: ColorsModels.ColorNumber[] - gaugeColors: ColorsModels.ColorNumber[] - lineColors: ColorsModels.ColorString[] setDashTimeV1: typeof dashboardActions.setDashTimeV1 setZoomedTimeRange: typeof dashboardActions.setZoomedTimeRange updateDashboard: typeof dashboardActions.updateDashboard @@ -139,27 +119,12 @@ interface Props extends ManualRefreshProps, WithRouterProps { rehydrateTemplatesAsync: typeof dashboardActions.rehydrateTemplatesAsync updateTemplateQueryParams: typeof dashboardActions.updateTemplateQueryParams updateQueryParams: typeof dashboardActions.updateQueryParams - addQuery: typeof addQueryAsync - deleteQuery: typeof deleteQueryAsync - fill: typeof queryConfigActions.fill - timeShift: typeof queryConfigActions.timeShift - chooseTag: typeof queryConfigActions.chooseTag - groupByTag: typeof queryConfigActions.groupByTag - groupByTime: typeof queryConfigActions.groupByTime - toggleField: typeof queryConfigActions.toggleField - removeFuncs: typeof queryConfigActions.removeFuncs - addInitialField: typeof queryConfigActions.addInitialField - chooseNamespace: typeof queryConfigActions.chooseNamespace - chooseMeasurement: typeof queryConfigActions.chooseMeasurement - applyFuncsToField: typeof queryConfigActions.applyFuncsToField - toggleTagAcceptance: typeof queryConfigActions.toggleTagAcceptance - updateEditorTimeRange: typeof updateEditorTimeRangeAction } interface State { scrollTop: number windowHeight: number - selectedCell: DashboardsModels.Cell | null + selectedCell: DashboardsModels.Cell | DashboardsModels.NewDefaultCell | null dashboardLinks: DashboardsModels.DashboardSwitcherLinks servicesStatus: RemoteDataState showAnnotationControls: boolean @@ -245,40 +210,28 @@ class DashboardPage extends Component { public render() { const { - script, notify, fluxLinks, isUsingAuth, meRole, source, sources, - addQuery, - deleteQuery, timeRange, timeRange: {lower, upper}, - editorTimeRange, renameCell, zoomedTimeRange, zoomedTimeRange: {lower: zoomedLower, upper: zoomedUpper}, dashboard, dashboardID, - lineColors, - gaugeColors, autoRefresh, - queryDrafts, - selectedCell, manualRefresh, onManualRefresh, cellQueryStatus, - updateQueryDrafts, - thresholdsListType, - thresholdsListColors, inPresentationMode, showTemplateVariableControlBar, handleChooseAutoRefresh, handleClickPresentationButton, toggleTemplateVariableControlBar, - updateEditorTimeRange, } = this.props const low = zoomedLower || lower @@ -326,38 +279,31 @@ class DashboardPage extends Component { templatesIncludingDashTime = [] } - const {dashboardLinks, showAnnotationControls} = this.state + const { + dashboardLinks, + showAnnotationControls, + selectedCell, + showCellEditorOverlay, + } = this.state return ( - + { return getDeep(dashboard, 'templates', []).map(t => t.tempVar) } - private get queryConfigActions(): QueryConfigActions { - const { - fill, - timeShift, - chooseTag, - groupByTag, - groupByTime, - toggleField, - removeFuncs, - addInitialField, - chooseNamespace, - chooseMeasurement, - applyFuncsToField, - toggleTagAcceptance, - } = this.props - return { - fill, - timeShift, - chooseTag, - groupByTag, - groupByTime, - toggleField, - removeFuncs, - addInitialField, - chooseNamespace, - chooseMeasurement, - applyFuncsToField, - toggleTagAcceptance, - } - } - - private get showCellEditorOverlay(): boolean { - const {selectedCell} = this.props - const {showCellEditorOverlay} = this.state - return !!selectedCell && showCellEditorOverlay - } - private get showDashboard(): boolean { const {dashboard} = this.props const {servicesStatus} = this.state @@ -552,12 +461,8 @@ class DashboardPage extends Component { this.handleHideCellEditorOverlay() } - private handleShowCellEditorOverlay = ( - cell: DashboardsModels.Cell | DashboardsModels.NewDefaultCell - ): void => { - const {handleLoadCEO, timeRange} = this.props - handleLoadCEO(cell, timeRange) - this.setState({showCellEditorOverlay: true}) + private handleShowCellEditorOverlay = (cell: DashboardsModels.Cell): void => { + this.setState({selectedCell: cell, showCellEditorOverlay: true}) } private handleHideCellEditorOverlay = () => { @@ -599,7 +504,8 @@ class DashboardPage extends Component { private handleAddCell = (): void => { const {dashboard} = this.props const emptyCell = getNewDashboardCell(dashboard) - this.handleShowCellEditorOverlay(emptyCell) + + this.setState({selectedCell: emptyCell, showCellEditorOverlay: true}) } private handleCloneCell = (cell: DashboardsModels.Cell): void => { @@ -703,16 +609,7 @@ const mstp = (state, {params: {dashboardID}}) => { sources, services, auth: {me, isUsingAuth}, - cellEditorOverlay: { - cell, - queryDrafts, - thresholdsListType, - thresholdsListColors, - gaugeColors, - lineColors, - timeRange: editorTimeRange, - script, - }, + cellEditorOverlay: {cell, timeRange: editorTimeRange}, } = state const meRole = _.get(me, 'role', null) @@ -726,7 +623,6 @@ const mstp = (state, {params: {dashboardID}}) => { const selectedCell = cell return { - script, sources, services, meRole, @@ -740,19 +636,13 @@ const mstp = (state, {params: {dashboardID}}) => { cellQueryStatus, inPresentationMode, selectedCell, - queryDrafts, editorTimeRange, - thresholdsListType, - thresholdsListColors, - gaugeColors, - lineColors, showTemplateVariableControlBar, annotationsDisplaySetting: displaySetting, } } const mdtp = { - updateScript, setDashTimeV1: dashboardActions.setDashTimeV1, setZoomedTimeRange: dashboardActions.setZoomedTimeRange, updateDashboard: dashboardActions.updateDashboard, @@ -775,28 +665,11 @@ const mdtp = { handleClickPresentationButton: appActions.delayEnablePresentationMode, errorThrown: errorActions.errorThrown, notify: notifyActions.notify, - handleLoadCEO: cellEditorOverlayActions.loadCEO, handleClearCEO: cellEditorOverlayActions.clearCEO, onGetAnnotationsAsync: getAnnotationsAsync, handleDismissEditingAnnotation: dismissEditingAnnotation, - updateQueryDrafts: updateQueryDraftsAction, fetchServicesAsync: fetchAllFluxServicesAsync, renameCell: cellEditorOverlayActions.renameCell, - addQuery: addQueryAsync, - deleteQuery: deleteQueryAsync, - fill: queryConfigActions.fill, - timeShift: queryConfigActions.timeShift, - chooseTag: queryConfigActions.chooseTag, - groupByTag: queryConfigActions.groupByTag, - groupByTime: queryConfigActions.groupByTime, - toggleField: queryConfigActions.toggleField, - removeFuncs: queryConfigActions.removeFuncs, - addInitialField: queryConfigActions.addInitialField, - chooseNamespace: queryConfigActions.chooseNamespace, - chooseMeasurement: queryConfigActions.chooseMeasurement, - applyFuncsToField: queryConfigActions.applyFuncsToField, - toggleTagAcceptance: queryConfigActions.toggleTagAcceptance, - updateEditorTimeRange: updateEditorTimeRangeAction, } export default connect(mstp, mdtp)( diff --git a/ui/src/dashboards/reducers/cellEditorOverlay.ts b/ui/src/dashboards/reducers/cellEditorOverlay.ts index e032d4387..10ef8ce99 100644 --- a/ui/src/dashboards/reducers/cellEditorOverlay.ts +++ b/ui/src/dashboards/reducers/cellEditorOverlay.ts @@ -1,171 +1,32 @@ // libraries import _ from 'lodash' -import uuid from 'uuid' // actions import {Action, ActionType} from 'src/dashboards/actions/cellEditorOverlay' -// utils -import {getDeep} from 'src/utils/wrappers' -import defaultQueryConfig from 'src/utils/defaultQueryConfig' - // constants -import { - DEFAULT_THRESHOLDS_LIST_COLORS, - DEFAULT_GAUGE_COLORS, - validateGaugeColors, - validateThresholdsListColors, - getThresholdsListType, -} from 'src/shared/constants/thresholds' -import { - DEFAULT_LINE_COLORS, - validateLineColors, -} from 'src/shared/constants/graphColorPalettes' -import {initializeOptions} from 'src/dashboards/constants/cellEditor' -import {editor} from 'src/flux/constants' // types -import {CellType, Cell, TimeRange} from 'src/types' -import { - CellQuery, - ThresholdType, - TableOptions, - QueryType, -} from 'src/types/dashboards' -import {ThresholdColor, GaugeColor, LineColor} from 'src/types/colors' +import {Cell, TimeRange} from 'src/types' import {NewDefaultCell} from 'src/types/dashboards' export interface CEOInitialState { cell: Cell | NewDefaultCell | null - thresholdsListType: ThresholdType - thresholdsListColors: ThresholdColor[] - gaugeColors: GaugeColor[] - lineColors: LineColor[] - queryDrafts: CellQuery[] timeRange: TimeRange - script: string } export const initialState = { cell: null, - thresholdsListType: ThresholdType.Text, - thresholdsListColors: DEFAULT_THRESHOLDS_LIST_COLORS, - gaugeColors: DEFAULT_GAUGE_COLORS, - lineColors: DEFAULT_LINE_COLORS, - queryDrafts: null, timeRange: null, - script: editor.DEFAULT_SCRIPT, -} - -const getNewQueryDrafts = (type: string, sourceLink?: string): CellQuery[] => { - const id = uuid.v4() - const newQueryConfig = { - ...defaultQueryConfig({id}), - } - const newQueryDraft: CellQuery = { - query: '', - queryConfig: newQueryConfig, - source: sourceLink || '', - id, - type, - } - return [newQueryDraft] } export default (state = initialState, action: Action): CEOInitialState => { switch (action.type) { - case ActionType.LoadCEO: { - const {cell, timeRange} = action.payload - - const tableOptions = getDeep( - cell, - 'tableOptions', - initializeOptions(CellType.Table) - ) - - // QueryDrafts corresponds to InfluxQL queries, script to Flux queries - // When saved, either the InfluxQL query or the script is saved to cell.queries - - let queryDrafts: CellQuery[] - let script = editor.DEFAULT_SCRIPT - const sourceLink = getDeep(cell, 'queries.0.source', '') - - if (getDeep(cell, 'queries.0.type', '') === QueryType.Flux) { - script = getDeep(cell, 'queries.0.query', editor.DEFAULT_SCRIPT) - - queryDrafts = getNewQueryDrafts(QueryType.Flux, sourceLink) - } else { - queryDrafts = cell.queries.map(q => { - const id = uuid.v4() - const queryConfig = {...q.queryConfig, id} - - return {...q, queryConfig, id} - }) - } - if (_.isEmpty(queryDrafts)) { - queryDrafts = getNewQueryDrafts(QueryType.InfluxQL) - } - - if ((cell as Cell).colors) { - const colors = (cell as Cell).colors - - const thresholdsListType = getThresholdsListType(colors) - const thresholdsListColors = validateThresholdsListColors( - colors, - thresholdsListType - ) - const gaugeColors = validateGaugeColors(colors) - - const lineColors = validateLineColors(colors) - - return { - ...state, - cell: {...cell, tableOptions}, - thresholdsListType, - thresholdsListColors, - gaugeColors, - lineColors, - queryDrafts, - timeRange, - script, - } - } - return { - ...state, - cell: {...cell, tableOptions}, - queryDrafts, - timeRange, - script, - } - } - case ActionType.ClearCEO: { const cell = null const timeRange = null - const script = editor.DEFAULT_SCRIPT - return {...state, cell, timeRange, script} - } - - case ActionType.ChangeCellType: { - const {cellType} = action.payload - const cell = {...state.cell, type: cellType} - - return {...state, cell} - } - - case ActionType.UpdateCellNote: { - const {note} = action.payload - const cell = {...state.cell, note} - - return {...state, cell} - } - - case ActionType.UpdateCellNoteVisibility: { - const {noteVisibility} = action.payload - const cell = {...state.cell, noteVisibility} - - return {...state, cell} + return {...state, cell, timeRange} } case ActionType.RenameCell: { @@ -175,87 +36,11 @@ export default (state = initialState, action: Action): CEOInitialState => { return {...state, cell} } - case ActionType.UpdateThresholdsListColors: { - const {thresholdsListColors} = action.payload - - return {...state, thresholdsListColors} - } - - case ActionType.UpdateThresholdsListType: { - const {thresholdsListType} = action.payload - - const thresholdsListColors = state.thresholdsListColors.map(color => ({ - ...color, - type: thresholdsListType, - })) - - return {...state, thresholdsListType, thresholdsListColors} - } - - case ActionType.UpdateGaugeColors: { - const {gaugeColors} = action.payload - - return {...state, gaugeColors} - } - - case ActionType.UpdateAxes: { - const {axes} = action.payload - const cell = {...state.cell, axes} - - return {...state, cell} - } - - case ActionType.UpdateTableOptions: { - const {tableOptions} = action.payload - const cell = {...state.cell, tableOptions} - - return {...state, cell} - } - - case ActionType.ChangeTimeFormat: { - const {timeFormat} = action.payload - const cell = {...state.cell, timeFormat} - - return {...state, cell} - } - - case ActionType.ChangeDecimalPlaces: { - const {decimalPlaces} = action.payload - const cell = {...state.cell, decimalPlaces} - - return {...state, cell} - } - - case ActionType.UpdateFieldOptions: { - const {fieldOptions} = action.payload - const cell = {...state.cell, fieldOptions} - - return {...state, cell} - } - - case ActionType.UpdateLineColors: { - const {lineColors} = action.payload - - return {...state, lineColors} - } - - case ActionType.UpdateQueryDrafts: { - const {queryDrafts} = action.payload - - return {...state, queryDrafts} - } - case ActionType.UpdateEditorTimeRange: { const {timeRange} = action.payload return {...state, timeRange} } - - case ActionType.UpdateScript: { - const {script} = action.payload - - return {...state, script} - } } return state diff --git a/ui/src/data_explorer/actions/queries.ts b/ui/src/data_explorer/actions/queries.ts index e129fa771..9faa2e451 100644 --- a/ui/src/data_explorer/actions/queries.ts +++ b/ui/src/data_explorer/actions/queries.ts @@ -1,60 +1,17 @@ // Types -import {ColorNumber, ColorString} from 'src/types/colors' -import {TimeRange, CellQuery, Status, CellType, Axes} from 'src/types' -import { - DecimalPlaces, - FieldOption, - ThresholdType, - TableOptions, - NoteVisibility, -} from 'src/types/dashboards' import {DEState} from 'src/types/dataExplorer' +import {Status} from 'src/types' export interface State { dataExplorer: DEState } export enum ActionType { - LoadDE = 'LOAD_DE', - UpdateQueryDrafts = 'DE_UPDATE_QUERY_DRAFTS', - UpdateEditorTimeRange = 'DE_UPDATE_EDITOR_TIME_RANGE', - UpdateQueryStatus = 'DE_UPDATE_QUERY_STATUS', - UpdateScript = 'DE_UPDATE_SCRIPT', UpdateSourceLink = 'DE_UPDATE_SOURCE_LINK', - ChangeVisualizationType = 'DE_CHANGE_Visualization_TYPE', - UpdateThresholdsListColors = 'DE_UPDATE_THRESHOLDS_LIST_COLORS', - UpdateThresholdsListType = 'DE_UPDATE_THRESHOLDS_LIST_TYPE', - UpdateGaugeColors = 'DE_UPDATE_GAUGE_COLORS', - UpdateAxes = 'DE_UPDATE_AXES', - UpdateTableOptions = 'DE_UPDATE_TABLE_OPTIONS', - UpdateLineColors = 'DE_UPDATE_LINE_COLORS', - ChangeTimeFormat = 'DE_CHANGE_TIME_FORMAT', - ChangeDecimalPlaces = 'DE_CHANGE_DECIMAL_PLACES', - UpdateFieldOptions = 'DE_UPDATE_FIELD_OPTIONS', - UpdateQueryDraft = 'DE_UPDATE_QUERY_DRAFT', - UpdateNote = 'DE_UPDATE_NOTE', - UpdateNoteVisibility = 'DE_UPDATE_NOTE_VISIBILITY', + EditQueryStatus = 'EDIT_QUERY_STATUS', } -export type Action = - | LoadDEAction - | UpdateEditorTimeRangeAction - | UpdateQueryDraftsAction - | UpdateQueryStatusAction - | UpdateScriptAction - | UpdateSourceLinkAction - | ChangeVisualizationTypeAction - | UpdateThresholdsListColorsAction - | UpdateThresholdsListTypeAction - | UpdateGaugeColorsAction - | UpdateAxesAction - | UpdateTableOptionsAction - | UpdateLineColorsAction - | ChangeTimeFormatAction - | ChangeDecimalPlacesAction - | UpdateFieldOptionsAction - | UpdateCellNoteAction - | UpdateCellNoteVisibilityAction +export type Action = UpdateSourceLinkAction | EditQueryStatusAction export interface UpdateSourceLinkAction { type: ActionType.UpdateSourceLink @@ -63,138 +20,6 @@ export interface UpdateSourceLinkAction { } } -export interface LoadDEAction { - type: ActionType.LoadDE - payload: { - queries: CellQuery[] - timeRange: TimeRange - } -} - -export interface UpdateQueryDraftsAction { - type: ActionType.UpdateQueryDrafts - payload: { - queryDrafts: CellQuery[] - } -} - -export interface UpdateEditorTimeRangeAction { - type: ActionType.UpdateEditorTimeRange - payload: { - timeRange: TimeRange - } -} - -export interface UpdateQueryStatusAction { - type: ActionType.UpdateQueryStatus - payload: { - queryID: string - status: Status - } -} - -export interface UpdateScriptAction { - type: ActionType.UpdateScript - payload: { - script: string - } -} - -export interface ChangeVisualizationTypeAction { - type: ActionType.ChangeVisualizationType - payload: { - cellType: CellType - } -} - -export interface UpdateThresholdsListColorsAction { - type: ActionType.UpdateThresholdsListColors - payload: { - thresholdsListColors: ColorNumber[] - } -} - -export interface UpdateThresholdsListTypeAction { - type: ActionType.UpdateThresholdsListType - payload: { - thresholdsListType: ThresholdType - } -} - -export interface UpdateGaugeColorsAction { - type: ActionType.UpdateGaugeColors - payload: { - gaugeColors: ColorNumber[] - } -} - -export interface UpdateAxesAction { - type: ActionType.UpdateAxes - payload: { - axes: Axes - } -} - -export interface UpdateTableOptionsAction { - type: ActionType.UpdateTableOptions - payload: { - tableOptions: TableOptions - } -} - -export interface UpdateLineColorsAction { - type: ActionType.UpdateLineColors - payload: { - lineColors: ColorString[] - } -} - -export interface ChangeTimeFormatAction { - type: ActionType.ChangeTimeFormat - payload: { - timeFormat: string - } -} - -export interface ChangeDecimalPlacesAction { - type: ActionType.ChangeDecimalPlaces - payload: { - decimalPlaces: DecimalPlaces - } -} - -export interface UpdateFieldOptionsAction { - type: ActionType.UpdateFieldOptions - payload: { - fieldOptions: FieldOption[] - } -} - -export interface UpdateCellNoteAction { - type: ActionType.UpdateNote - payload: { - note: string - } -} - -export interface UpdateCellNoteVisibilityAction { - type: ActionType.UpdateNoteVisibility - payload: { - noteVisibility: NoteVisibility - } -} - -export const loadDE = ( - queries: CellQuery[], - timeRange: TimeRange -): LoadDEAction => ({ - type: ActionType.LoadDE, - payload: { - queries, - timeRange, - }, -}) - export const updateSourceLink = ( sourceLink: string ): UpdateSourceLinkAction => ({ @@ -203,3 +28,19 @@ export const updateSourceLink = ( sourceLink, }, }) + +interface EditQueryStatusAction { + type: ActionType.EditQueryStatus + payload: { + queryID: string + status: Status + } +} + +export const editQueryStatus = ( + queryID: string, + status: Status +): EditQueryStatusAction => ({ + type: ActionType.EditQueryStatus, + payload: {queryID, status}, +}) diff --git a/ui/src/data_explorer/components/SendToDashboardOverlay.tsx b/ui/src/data_explorer/components/SendToDashboardOverlay.tsx index 0a87024c1..9eeee376c 100644 --- a/ui/src/data_explorer/components/SendToDashboardOverlay.tsx +++ b/ui/src/data_explorer/components/SendToDashboardOverlay.tsx @@ -1,10 +1,12 @@ // Libraries import React, {PureComponent} from 'react' import _ from 'lodash' +import {Subscribe} from 'unstated' // Utils import {getNewDashboardCell} from 'src/dashboards/utils/cellGetters' import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' // Components import { @@ -44,7 +46,7 @@ import {getDeep} from 'src/utils/wrappers' import {VisualizationOptions} from 'src/types/dataExplorer' import {ColorString} from 'src/types/colors' -interface Props { +interface PassedProps { queryConfig: QueryConfig script: string dashboards: Dashboard[] @@ -56,12 +58,17 @@ interface Props { dashboard: Dashboard, newCell: Partial ) => Promise<{success: boolean; dashboard: Dashboard}> - visualizationOptions: VisualizationOptions isStaticLegend: boolean handleGetDashboards: () => Dashboard[] notify: (message: Notification) => void } +interface ConnectedProps { + visualizationOptions: VisualizationOptions +} + +type Props = PassedProps & ConnectedProps + interface State { selectedIDs: string[] name: string @@ -306,4 +313,49 @@ class SendToDashboardOverlay extends PureComponent { } } -export default SendToDashboardOverlay +const ConnectedSendToDashboardOverlay = (props: PassedProps) => { + return ( + + {(timeMachineContainer: TimeMachineContainer) => { + const { + type, + tableOptions, + fieldOptions, + timeFormat, + decimalPlaces, + note, + noteVisibility, + axes, + thresholdsListColors, + thresholdsListType, + gaugeColors, + lineColors, + } = timeMachineContainer.state + + const visualizationOptions = { + type, + axes, + tableOptions, + fieldOptions, + timeFormat, + decimalPlaces, + note, + noteVisibility, + thresholdsListColors, + gaugeColors, + lineColors, + thresholdsListType, + } + + return ( + + ) + }} + + ) +} + +export default ConnectedSendToDashboardOverlay diff --git a/ui/src/data_explorer/constants/index.ts b/ui/src/data_explorer/constants/index.ts index 3723100b0..a704e9676 100644 --- a/ui/src/data_explorer/constants/index.ts +++ b/ui/src/data_explorer/constants/index.ts @@ -1,3 +1,5 @@ +import {timeRanges} from 'src/shared/data/timeRanges' + export const INFLUXQL_FUNCTIONS: string[] = [ 'mean', 'median', @@ -210,3 +212,7 @@ export const METAQUERY_TEMPLATE_OPTIONS: Array< export const WRITE_DATA_DOCS_LINK = 'https://docs.influxdata.com/influxdb/latest/write_protocols/line_protocol_tutorial/' + +export const DEFAULT_TIME_RANGE = timeRanges.find( + tr => tr.lower === 'now() - 1h' +) diff --git a/ui/src/data_explorer/containers/DataExplorer.tsx b/ui/src/data_explorer/containers/DataExplorer.tsx index 48b545c20..2a89f165a 100644 --- a/ui/src/data_explorer/containers/DataExplorer.tsx +++ b/ui/src/data_explorer/containers/DataExplorer.tsx @@ -2,20 +2,20 @@ import React, {PureComponent} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' -import {withRouter, InjectedRouter} from 'react-router' +import {withRouter, InjectedRouter, WithRouterProps} from 'react-router' import {Location} from 'history' import qs from 'qs' import uuid from 'uuid' import _ from 'lodash' +import {Subscribe} from 'unstated' // Utils import {stripPrefix} from 'src/utils/basepath' import {GlobalAutoRefresher} from 'src/utils/AutoRefresher' import {getConfig} from 'src/dashboards/utils/cellGetters' import {buildRawText} from 'src/utils/influxql' - -// Constants -import {timeRanges} from 'src/shared/data/timeRanges' +import {defaultQueryDraft} from 'src/shared/utils/timeMachine' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' // Components import WriteDataForm from 'src/data_explorer/components/WriteDataForm' @@ -34,20 +34,8 @@ import { sendDashboardCellAsync, } from 'src/dashboards/actions' import {writeLineProtocolAsync} from 'src/data_explorer/actions/view/write' -import { - loadDE as loadDEAction, - updateSourceLink as updateSourceLinkAction, -} from 'src/data_explorer/actions/queries' -import { - queryConfigActions as queryConfigModifiers, - updateQueryDrafts as updateQueryDraftsAction, - updateQueryStatus as editQueryStatusAction, - updateScript as updateScriptAction, - addQueryAsync, - deleteQueryAsync, - updateEditorTimeRange, - QueryUpdateState, -} from 'src/shared/actions/queries' +import {updateSourceLink as updateSourceLinkAction} from 'src/data_explorer/actions/queries' +import {editQueryStatus as editQueryStatusAction} from 'src/data_explorer/actions/queries' import {fetchAllFluxServicesAsync} from 'src/shared/actions/services' import {notify as notifyAction} from 'src/shared/actions/notifications' @@ -62,45 +50,31 @@ import { import { Source, Service, - TimeRange, Dashboard, - CellQuery, QueryConfig, QueryStatus, Template, TemplateType, TemplateValueType, - CellType, - Axes, Notification, + Cell, + QueryType, + CellQuery, + TimeRange, } from 'src/types' import {ErrorHandling} from 'src/shared/decorators/errors' import {Links} from 'src/types/flux' -import {ColorNumber, ColorString} from 'src/types/colors' -import { - DecimalPlaces, - FieldOption, - ThresholdType, - TableOptions, - NoteVisibility, - QueryType, - Cell, -} from 'src/types/dashboards' -import {VisualizationOptions} from 'src/types/dataExplorer' -interface Props { +interface PassedProps { source: Source sources: Source[] services: Service[] queryConfigs: QueryConfig[] updateSourceLink: typeof updateSourceLinkAction - queryConfigActions: typeof queryConfigModifiers autoRefresh: number handleChooseAutoRefresh: () => void router?: InjectedRouter location?: Location - setTimeRange: (range: TimeRange) => void - timeRange: TimeRange manualRefresh: number dashboards: Dashboard[] onManualRefresh: () => void @@ -111,33 +85,24 @@ interface Props { dashboard: Dashboard, newCell: Partial ) => Promise<{success: boolean; dashboard: Dashboard}> - updateQueryDrafts: typeof updateQueryDraftsAction - loadDE: typeof loadDEAction - addQuery: typeof addQueryAsync - deleteQuery: typeof deleteQueryAsync - queryDrafts: CellQuery[] editQueryStatus: typeof editQueryStatusAction queryStatus: QueryStatus fluxLinks: Links - script: string - updateScript: typeof updateScriptAction fetchServicesAsync: typeof fetchAllFluxServicesAsync notify: (message: Notification) => void sourceLink: string - thresholdsListType: ThresholdType - thresholdsListColors: ColorNumber[] - gaugeColors: ColorNumber[] - lineColors: ColorString[] - visType: CellType - axes: Axes - tableOptions: TableOptions - timeFormat: string - decimalPlaces: DecimalPlaces - fieldOptions: FieldOption[] - note: string - noteVisibility: NoteVisibility } +interface ConnectedProps { + queryDrafts: CellQuery[] + timeRange: TimeRange + script: string + onUpdateQueryDrafts: (queryDrafts: CellQuery[]) => void + onChangeScript: TimeMachineContainer['handleChangeScript'] +} + +type Props = PassedProps & ConnectedProps + interface State { isWriteFormVisible: boolean isSendToDashboardVisible: boolean @@ -159,31 +124,10 @@ export class DataExplorer extends PureComponent { } public async componentDidMount() { - const {loadDE, timeRange, autoRefresh, queryDrafts} = this.props - const {query, script} = this.queryString - const isFlux = !!script + const {autoRefresh} = this.props await this.fetchFluxServices() - - if (isFlux) { - this.createNewQueryDraft() - } else if (_.isEmpty(query)) { - let drafts = [] - if (!_.isEmpty(queryDrafts)) { - drafts = queryDrafts - } - loadDE(drafts, timeRange) - } else if (!_.isEmpty(queryDrafts)) { - const matchingQueryDraft = queryDrafts.find(q => q.query === query) - - if (matchingQueryDraft) { - loadDE(queryDrafts, timeRange) - } else { - await this.createNewQueryDraft() - } - } else { - await this.createNewQueryDraft() - } + await this.resolveQueryParams() GlobalAutoRefresher.poll(autoRefresh) this.setState({isComponentMounted: true}) @@ -191,11 +135,17 @@ export class DataExplorer extends PureComponent { public componentDidUpdate(prevProps: Props) { const {autoRefresh} = this.props + if (autoRefresh !== prevProps.autoRefresh) { GlobalAutoRefresher.poll(autoRefresh) } - this.updateQueryStringQuery() + if ( + prevProps.location === this.props.location && + this.state.isComponentMounted + ) { + this.writeQueryParams() + } } public componentWillUnmount() { @@ -207,18 +157,14 @@ export class DataExplorer extends PureComponent { source, sources, services, - timeRange, manualRefresh, onManualRefresh, editQueryStatus, - updateQueryDrafts, - queryDrafts, - addQuery, - deleteQuery, queryStatus, fluxLinks, notify, updateSourceLink, + timeRange, } = this.props const {isStaticLegend, isComponentMounted} = this.state @@ -234,29 +180,19 @@ export class DataExplorer extends PureComponent { {(activeEditorTab, onSetActiveEditorTab) => ( { ) } - private get shouldUpdateQueryString(): boolean { - const {queryDrafts} = this.props - const query = _.get(queryDrafts, '0.query', '') - const {query: existing} = this.queryString - const isFlux = !!this.service + private async resolveQueryParams() { + const { + source, + sourceLink, + queryDrafts, + onUpdateQueryDrafts, + onChangeScript, + } = this.props + const {query, script} = this.readQueryParams() - return !_.isEmpty(query) && query !== existing && !isFlux - } + if (script) { + const queryDraft = {...defaultQueryDraft(QueryType.Flux), query: script} - private handleUpdateScript = ( - script: string, - stateToUpdate: QueryUpdateState - ) => { - const {router} = this.props - const pathname = stripPrefix(location.pathname) - const qsNew = qs.stringify({script}) - - router.push(`${pathname}?${qsNew}`) - this.props.updateScript(script, stateToUpdate) - } - - private updateQueryStringQuery() { - if (!this.shouldUpdateQueryString) { + onUpdateQueryDrafts([queryDraft]) + onChangeScript(script) return } - const {queryDrafts, router} = this.props - const query = _.get(queryDrafts, '0.query', '') - const qsNew = qs.stringify({query}) - const pathname = stripPrefix(location.pathname) + if (query) { + if (queryDrafts.find(q => q.query === query)) { + // Has matching query draft already loaded + return + } - router.push(`${pathname}?${qsNew}`) + const queryConfig = await getConfig( + source.links.queries, + uuid.v4(), + query, + this.templates + ) + + const queryDraft = { + query, + queryConfig, + source: sourceLink, + type: QueryType.InfluxQL, + } + + onUpdateQueryDrafts([queryDraft]) + return + } + + if (!queryDrafts.length) { + const queryDraft = defaultQueryDraft(QueryType.InfluxQL) + + onUpdateQueryDrafts([queryDraft]) + return + } } - private get queryString(): {query?: string; script?: string} { - return qs.parse(location.search, {ignoreQueryPrefix: true}) + private readQueryParams(): {query?: string; script?: string} { + const {query, script} = qs.parse(location.search, {ignoreQueryPrefix: true}) + + return {query, script} + } + + private writeQueryParams() { + const {router, queryDrafts, script} = this.props + const query = _.get(queryDrafts, '0.query') + const isFlux = _.get(queryDrafts, '0.type') === QueryType.Flux + + let queryParams + + if (isFlux && script) { + queryParams = {script} + } else if (!isFlux && query) { + queryParams = {query} + } + + const pathname = stripPrefix(location.pathname) + const search = queryParams ? `?${qs.stringify(queryParams)}` : '' + + router.push(pathname + search) } private get service(): Service { @@ -347,9 +320,9 @@ export class DataExplorer extends PureComponent { source, dashboards, sendDashboardCell, - script, handleGetDashboards, notify, + script, } = this.props const {isSendToDashboardVisible, isStaticLegend} = this.state @@ -367,7 +340,6 @@ export class DataExplorer extends PureComponent { dashboards={dashboards} handleGetDashboards={handleGetDashboards} sendDashboardCell={sendDashboardCell} - visualizationOptions={this.visualizationOptions} isStaticLegend={isStaticLegend} /> @@ -376,9 +348,7 @@ export class DataExplorer extends PureComponent { } private get templates(): Template[] { - const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h') - - const timeRange = this.props.timeRange || {lower, upper} + const {timeRange} = this.props const low = timeRange.lower const up = timeRange.upper @@ -436,10 +406,6 @@ export class DataExplorer extends PureComponent { this.setState({isWriteFormVisible: true}) } - private handleChooseTimeRange = (timeRange: TimeRange): void => { - this.props.setTimeRange(timeRange) - } - private async fetchFluxServices() { const {fetchServicesAsync, sources} = this.props if (!sources.length) { @@ -455,49 +421,20 @@ export class DataExplorer extends PureComponent { private get activeQueryConfig(): QueryConfig { const {queryDrafts} = this.props + return _.get(queryDrafts, '0.queryConfig') } private get rawText(): string { const {timeRange} = this.props + if (this.activeQueryConfig) { return buildRawText(this.activeQueryConfig, timeRange) } + return '' } - private get visualizationOptions(): VisualizationOptions { - const { - visType, - tableOptions, - fieldOptions, - timeFormat, - decimalPlaces, - note, - noteVisibility, - axes, - thresholdsListColors, - thresholdsListType, - gaugeColors, - lineColors, - } = this.props - - return { - type: visType, - axes, - tableOptions, - fieldOptions, - timeFormat, - decimalPlaces, - note, - noteVisibility, - thresholdsListColors, - gaugeColors, - lineColors, - thresholdsListType, - } - } - private toggleSendToDashboard = () => { this.setState({ isSendToDashboardVisible: !this.state.isSendToDashboardVisible, @@ -511,47 +448,23 @@ export class DataExplorer extends PureComponent { private handleResetFocus = () => { return } +} - private async createNewQueryDraft() { - const {source, loadDE, timeRange, sourceLink} = this.props - - const {query} = this.queryString - const queryConfig = await getConfig( - source.links.queries, - uuid.v4(), - query, - this.templates - ) - - const isFlux = !!this.service - - if (isFlux) { - const queryDraft = { - query, - queryConfig, - source: sourceLink, - type: QueryType.Flux, - } - loadDE([queryDraft], timeRange) - } else { - const queryDraft = { - query, - queryConfig, - source: source.links.self, - type: QueryType.InfluxQL, - } - loadDE([queryDraft], timeRange) - } - } - - private get activeScript(): string { - const {script} = this.queryString - if (script) { - return script - } - - return this.props.script - } +const ConnectedDataExplorer = (props: PassedProps & WithRouterProps) => { + return ( + + {(timeMachineContainer: TimeMachineContainer) => ( + + )} + + ) } const mstp = state => { @@ -559,25 +472,7 @@ const mstp = state => { app: { persisted: {autoRefresh}, }, - dataExplorer: { - queryDrafts, - timeRange, - queryStatus, - script, - sourceLink, - visType, - thresholdsListType, - thresholdsListColors, - gaugeColors, - lineColors, - axes, - tableOptions, - timeFormat, - decimalPlaces, - fieldOptions, - note, - noteVisibility, - }, + dataExplorer: {timeRange, queryStatus, sourceLink}, dashboardUI: {dashboards}, sources, services, @@ -587,26 +482,12 @@ const mstp = state => { return { fluxLinks: links.flux, autoRefresh, - queryDrafts, timeRange, dashboards, sources, services, queryStatus, - script, sourceLink, - visType, - thresholdsListType, - thresholdsListColors, - gaugeColors, - lineColors, - axes, - tableOptions, - timeFormat, - decimalPlaces, - fieldOptions, - note, - noteVisibility, } } @@ -614,21 +495,16 @@ const mdtp = dispatch => { return { handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch), errorThrownAction: bindActionCreators(errorThrown, dispatch), - setTimeRange: bindActionCreators(updateEditorTimeRange, dispatch), writeLineProtocol: bindActionCreators(writeLineProtocolAsync, dispatch), - queryConfigActions: bindActionCreators(queryConfigModifiers, dispatch), handleGetDashboards: bindActionCreators(getDashboardsAsync, dispatch), sendDashboardCell: bindActionCreators(sendDashboardCellAsync, dispatch), - loadDE: bindActionCreators(loadDEAction, dispatch), - updateQueryDrafts: bindActionCreators(updateQueryDraftsAction, dispatch), - addQuery: bindActionCreators(addQueryAsync, dispatch), - deleteQuery: bindActionCreators(deleteQueryAsync, dispatch), editQueryStatus: bindActionCreators(editQueryStatusAction, dispatch), - updateScript: bindActionCreators(updateScriptAction, dispatch), fetchServicesAsync: bindActionCreators(fetchAllFluxServicesAsync, dispatch), notify: bindActionCreators(notifyAction, dispatch), updateSourceLink: bindActionCreators(updateSourceLinkAction, dispatch), } } -export default connect(mstp, mdtp)(withRouter(ManualRefresh(DataExplorer))) +export default connect(mstp, mdtp)( + withRouter(ManualRefresh(ConnectedDataExplorer)) +) diff --git a/ui/src/data_explorer/containers/DataExplorerPage.tsx b/ui/src/data_explorer/containers/DataExplorerPage.tsx index ac2b866fa..70b1b1192 100644 --- a/ui/src/data_explorer/containers/DataExplorerPage.tsx +++ b/ui/src/data_explorer/containers/DataExplorerPage.tsx @@ -1,19 +1,33 @@ import React, {PureComponent} from 'react' +import {Provider} from 'unstated' + import DataExplorer from './DataExplorer' -import {Source} from 'src/types' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' import {ErrorHandling} from 'src/shared/decorators/errors' +import {Source} from 'src/types' + interface Props { source: Source } @ErrorHandling class DataExplorerPage extends PureComponent { + private timeMachineContainer: TimeMachineContainer + + constructor(props: Props) { + super(props) + + this.timeMachineContainer = new TimeMachineContainer() + } + public render() { return (
- + + +
) } diff --git a/ui/src/data_explorer/reducers/queries.ts b/ui/src/data_explorer/reducers/queries.ts index f9ccceb47..b7b972d20 100644 --- a/ui/src/data_explorer/reducers/queries.ts +++ b/ui/src/data_explorer/reducers/queries.ts @@ -1,198 +1,29 @@ -// libraries +// Libraries import _ from 'lodash' -import uuid from 'uuid' -// actions +// Actions import {Action, ActionType} from 'src/data_explorer/actions/queries' -// utils -import defaultQueryConfig from 'src/utils/defaultQueryConfig' - -// constants -import {timeRanges} from 'src/shared/data/timeRanges' -import {editor} from 'src/flux/constants' -import { - DEFAULT_THRESHOLDS_LIST_COLORS, - DEFAULT_GAUGE_COLORS, -} from 'src/shared/constants/thresholds' -import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' -import {DEFAULT_AXES} from 'src/dashboards/constants/cellEditor' -import { - DEFAULT_TABLE_OPTIONS, - DEFAULT_TIME_FORMAT, - DEFAULT_DECIMAL_PLACES, - DEFAULT_FIELD_OPTIONS, -} from 'src/dashboards/constants' - -// types -import {CellType} from 'src/types' +// Types import {DEState} from 'src/types/dataExplorer' -import { - CellQuery, - ThresholdType, - NoteVisibility, - QueryType, -} from 'src/types/dashboards' - -const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h') export const initialState: DEState = { - queryDrafts: [], - timeRange: {lower, upper}, - queryStatus: {queryID: null, status: null}, - script: editor.DEFAULT_SCRIPT, sourceLink: '', - visType: CellType.Line, - thresholdsListType: ThresholdType.Text, - thresholdsListColors: DEFAULT_THRESHOLDS_LIST_COLORS, - gaugeColors: DEFAULT_GAUGE_COLORS, - lineColors: DEFAULT_LINE_COLORS, - axes: DEFAULT_AXES, - tableOptions: DEFAULT_TABLE_OPTIONS, - timeFormat: DEFAULT_TIME_FORMAT, - decimalPlaces: DEFAULT_DECIMAL_PLACES, - fieldOptions: DEFAULT_FIELD_OPTIONS, - note: '', - noteVisibility: NoteVisibility.Default, + queryStatus: {queryID: null, status: null}, } export default (state = initialState, action: Action): DEState => { switch (action.type) { - case ActionType.LoadDE: { - const {timeRange, queries} = action.payload - - let queryDrafts: CellQuery[] = queries.map(q => { - const id = _.get(q, 'queryConfig.id') - return {...q, id} - }) - - if (_.isEmpty(queryDrafts)) { - const id = uuid.v4() - const newQueryConfig = { - ...defaultQueryConfig({id}), - } - const newQueryDraft: CellQuery = { - query: '', - queryConfig: newQueryConfig, - source: '', - id, - type: QueryType.InfluxQL, - } - queryDrafts = [newQueryDraft] - } - - return { - ...state, - queryDrafts, - timeRange, - } - } - - case ActionType.UpdateQueryDrafts: { - const {queryDrafts} = action.payload - - return {...state, queryDrafts} - } - - case ActionType.UpdateEditorTimeRange: { - const {timeRange} = action.payload - - return {...state, timeRange} - } - - case ActionType.UpdateQueryStatus: { - const {queryID, status} = action.payload - const queryStatus = {queryID, status} - - return {...state, queryStatus} - } - - case ActionType.UpdateScript: { - const {script} = action.payload - - return {...state, script} - } - case ActionType.UpdateSourceLink: { const {sourceLink} = action.payload return {...state, sourceLink} } - case ActionType.ChangeVisualizationType: { - const {cellType} = action.payload + case ActionType.EditQueryStatus: { + const {queryID, status} = action.payload - return {...state, visType: cellType} - } - - case ActionType.UpdateThresholdsListColors: { - const {thresholdsListColors} = action.payload - - return {...state, thresholdsListColors} - } - - case ActionType.UpdateThresholdsListType: { - const {thresholdsListType} = action.payload - - const thresholdsListColors = state.thresholdsListColors.map(color => { - return {...color, type: thresholdsListType} - }) - - return {...state, thresholdsListColors, thresholdsListType} - } - - case ActionType.UpdateGaugeColors: { - const {gaugeColors} = action.payload - - return {...state, gaugeColors} - } - - case ActionType.UpdateAxes: { - const {axes} = action.payload - - return {...state, axes} - } - - case ActionType.UpdateTableOptions: { - const {tableOptions} = action.payload - - return {...state, tableOptions} - } - - case ActionType.ChangeTimeFormat: { - const {timeFormat} = action.payload - - return {...state, timeFormat} - } - - case ActionType.ChangeDecimalPlaces: { - const {decimalPlaces} = action.payload - - return {...state, decimalPlaces} - } - - case ActionType.UpdateFieldOptions: { - const {fieldOptions} = action.payload - - return {...state, fieldOptions} - } - - case ActionType.UpdateLineColors: { - const {lineColors} = action.payload - - return {...state, lineColors} - } - - case ActionType.UpdateNote: { - const {note} = action.payload - - return {...state, note} - } - - case ActionType.UpdateNoteVisibility: { - const {noteVisibility} = action.payload - - return {...state, noteVisibility} + return {...state, queryStatus: {queryID, status}} } } diff --git a/ui/src/data_explorer/reducers/queryConfigs.ts b/ui/src/data_explorer/reducers/queryConfigs.ts deleted file mode 100644 index 704945253..000000000 --- a/ui/src/data_explorer/reducers/queryConfigs.ts +++ /dev/null @@ -1,183 +0,0 @@ -import _ from 'lodash' - -import defaultQueryConfig from 'src/utils/defaultQueryConfig' -import {QueryConfig} from 'src/types' -import {Action} from 'src/data_explorer/actions/view' - -import { - fill, - timeShift, - chooseTag, - groupByTag, - removeFuncs, - groupByTime, - toggleField, - editRawText, - updateRawQuery, - chooseNamespace, - chooseMeasurement, - addInitialField, - applyFuncsToField, - toggleTagAcceptance, -} from 'src/utils/queryTransitions' - -interface State { - [queryID: string]: Readonly -} - -const queryConfigs = (state: State = {}, action: Action): State => { - switch (action.type) { - case 'DE_CHOOSE_NAMESPACE': { - const {queryID, database, retentionPolicy} = action.payload - const nextQueryConfig = chooseNamespace(state[queryID], { - database, - retentionPolicy, - }) - - return {...state, [queryID]: {...nextQueryConfig, rawText: null}} - } - - case 'DE_CHOOSE_MEASUREMENT': { - const {queryID, measurement} = action.payload - - const nextQueryConfig = chooseMeasurement(state[queryID], measurement) - - return { - ...state, - [queryID]: { - ...nextQueryConfig, - rawText: state[queryID].rawText, - }, - } - } - - // there is an additional reducer for this same action in the ui reducer - case 'DE_ADD_QUERY': { - const {queryID} = action.payload - - return { - ...state, - [queryID]: defaultQueryConfig({id: queryID}), - } - } - - // there is an additional reducer for this same action in the ui reducer - case 'DE_DELETE_QUERY': { - const {queryID} = action.payload - return _.omit(state, queryID) - } - - case 'DE_UPDATE_QUERY_CONFIG': { - const {config} = action.payload - return {...state, [config.id]: config} - } - - case 'DE_EDIT_RAW_TEXT': { - const {queryID, rawText} = action.payload - const nextQueryConfig = editRawText(state[queryID], rawText) - - return { - ...state, - [queryID]: nextQueryConfig, - } - } - - case 'DE_GROUP_BY_TIME': { - const {queryID, time} = action.payload - const nextQueryConfig = groupByTime(state[queryID], time) - - return {...state, [queryID]: nextQueryConfig} - } - - case 'DE_TOGGLE_TAG_ACCEPTANCE': { - const {queryID} = action.payload - const nextQueryConfig = toggleTagAcceptance(state[queryID]) - - return {...state, [queryID]: nextQueryConfig} - } - - case 'DE_TOGGLE_FIELD': { - const {queryID, fieldFunc} = action.payload - const nextQueryConfig = toggleField(state[queryID], fieldFunc) - - return {...state, [queryID]: {...nextQueryConfig, rawText: null}} - } - - case 'DE_APPLY_FUNCS_TO_FIELD': { - const {queryID, fieldFunc, groupBy} = action.payload - const nextQueryConfig = applyFuncsToField( - state[queryID], - fieldFunc, - groupBy - ) - - return {...state, [queryID]: nextQueryConfig} - } - - case 'DE_CHOOSE_TAG': { - const {queryID, tag} = action.payload - const nextQueryConfig = chooseTag(state[queryID], tag) - - return {...state, [queryID]: nextQueryConfig} - } - - case 'DE_GROUP_BY_TAG': { - const {queryID, tagKey} = action.payload - const nextQueryConfig = groupByTag(state[queryID], tagKey) - return {...state, [queryID]: nextQueryConfig} - } - - case 'DE_FILL': { - const {queryID, value} = action.payload - const nextQueryConfig = fill(state[queryID], value) - - return { - ...state, - [queryID]: nextQueryConfig, - } - } - - case 'DE_UPDATE_RAW_QUERY': { - const {queryID, text} = action.payload - const nextQueryConfig = updateRawQuery(state[queryID], text) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) - } - - case 'DE_EDIT_QUERY_STATUS': { - const {queryID, status} = action.payload - const nextState = { - [queryID]: {...state[queryID], status}, - } - - return {...state, ...nextState} - } - - case 'DE_REMOVE_FUNCS': { - const {queryID, fields} = action.payload - const nextQuery = removeFuncs(state[queryID], fields) - - // fields with no functions cannot have a group by time - return {...state, [queryID]: nextQuery} - } - - // Adding the first feild applies a groupBy time - case 'DE_ADD_INITIAL_FIELD': { - const {queryID, field, groupBy} = action.payload - const nextQuery = addInitialField(state[queryID], field, groupBy) - - return {...state, [queryID]: nextQuery} - } - - case 'DE_TIME_SHIFT': { - const {queryID, shift} = action.payload - const nextQuery = timeShift(state[queryID], shift) - - return {...state, [queryID]: nextQuery} - } - } - return state -} - -export default queryConfigs diff --git a/ui/src/flux/components/ExpressionNode.tsx b/ui/src/flux/components/ExpressionNode.tsx index 53903c976..637cbae01 100644 --- a/ui/src/flux/components/ExpressionNode.tsx +++ b/ui/src/flux/components/ExpressionNode.tsx @@ -61,7 +61,6 @@ class ExpressionNode extends PureComponent { service, data, scriptUpToYield, - visualizationOptions, source, timeRange, queries, @@ -140,7 +139,6 @@ class ExpressionNode extends PureComponent { queries={queries} timeRange={timeRange} declarationID={declarationID} - visualizationOptions={visualizationOptions} /> ) diff --git a/ui/src/flux/components/TimeMachineTables.tsx b/ui/src/flux/components/TimeMachineTables.tsx index 4484f21f5..c49399743 100644 --- a/ui/src/flux/components/TimeMachineTables.tsx +++ b/ui/src/flux/components/TimeMachineTables.tsx @@ -27,6 +27,7 @@ interface Props { handleSetHoverTime?: (hovertime: string) => void colors: ColorString[] editorLocation?: QueryUpdateState + onUpdateFieldOptions?: (fieldOptions: FieldOption[]) => void } interface State { @@ -77,7 +78,9 @@ class TimeMachineTables extends PureComponent { decimalPlaces, editorLocation, handleSetHoverTime, + onUpdateFieldOptions, } = this.props + return (
{this.showSidebar && ( @@ -98,6 +101,7 @@ class TimeMachineTables extends PureComponent { decimalPlaces={decimalPlaces} editorLocation={editorLocation} handleSetHoverTime={handleSetHoverTime} + onUpdateFieldOptions={onUpdateFieldOptions} /> )} {!this.hasResults && } diff --git a/ui/src/flux/components/YieldFuncNode.tsx b/ui/src/flux/components/YieldFuncNode.tsx index 6453ec69b..3927a24be 100644 --- a/ui/src/flux/components/YieldFuncNode.tsx +++ b/ui/src/flux/components/YieldFuncNode.tsx @@ -1,15 +1,39 @@ import React, {PureComponent} from 'react' import _ from 'lodash' +import {Subscribe} from 'unstated' import {ErrorHandling} from 'src/shared/decorators/errors' import YieldNodeVis from 'src/flux/components/YieldNodeVis' import TimeSeries from 'src/shared/components/time_series/TimeSeries' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' -import {FluxTable, Service, Source, TimeRange, Query} from 'src/types' +import {FluxTable, Service, Source, TimeRange, Query, Axes} from 'src/types' import {Func} from 'src/types/flux' -import {VisualizationOptions} from 'src/types/dataExplorer' -interface Props { +import { + FieldOption, + DecimalPlaces, + NoteVisibility, + ThresholdType, + TableOptions as TableOptionsInterface, +} from 'src/types/dashboards' +import {ColorNumber, ColorString} from 'src/types/colors' + +interface ConnectedProps { + axes: Axes | null + tableOptions: TableOptionsInterface + fieldOptions: FieldOption[] + timeFormat: string + decimalPlaces: DecimalPlaces + note: string + noteVisibility: NoteVisibility + thresholdsListColors: ColorNumber[] + thresholdsListType: ThresholdType + gaugeColors: ColorNumber[] + lineColors: ColorString[] +} + +interface PassedProps { service: Service source: Source timeRange: TimeRange @@ -20,9 +44,10 @@ interface Props { declarationID?: string script: string queries: Query[] - visualizationOptions: VisualizationOptions } +type Props = ConnectedProps & PassedProps + interface State { data: FluxTable[] } @@ -31,12 +56,12 @@ interface State { class YieldFuncNode extends PureComponent { private timeSeries: React.RefObject = React.createRef() - public componentDidUpdate(prevProps) { + public componentDidUpdate(prevProps: Props) { if (!this.timeSeries.current) { return } - if (this.haveVisOptionsChanged(prevProps.visualizationOptions)) { + if (this.haveVisOptionsChanged(prevProps)) { this.timeSeries.current.forceUpdate() } } @@ -44,11 +69,18 @@ class YieldFuncNode extends PureComponent { public render() { const { func, - visualizationOptions, source, service, timeRange, queries, + axes, + tableOptions, + fieldOptions, + timeFormat, + decimalPlaces, + thresholdsListColors, + gaugeColors, + lineColors, } = this.props const yieldName = _.get(func, 'args.0.value', 'result') @@ -66,7 +98,14 @@ class YieldFuncNode extends PureComponent { )} @@ -74,22 +113,46 @@ class YieldFuncNode extends PureComponent { ) } - private haveVisOptionsChanged( - visualizationOptions: VisualizationOptions - ): boolean { + private haveVisOptionsChanged(prevProps: Props): boolean { const visProps: string[] = [ 'axes', - 'colors', + 'lineColors', + 'gaugeColors', + 'thresholdsListColors', + 'thresholdsListType', 'tableOptions', 'fieldOptions', 'decimalPlaces', 'timeFormat', ] - const prevVisValues = _.pick(visualizationOptions, visProps) - const curVisValues = _.pick(this.props.visualizationOptions, visProps) + const prevVisValues = _.pick(prevProps, visProps) + const curVisValues = _.pick(this.props, visProps) return !_.isEqual(prevVisValues, curVisValues) } } -export default YieldFuncNode +const ConnectedYieldFuncNode = (props: PassedProps) => { + return ( + + {(timeMachineContainer: TimeMachineContainer) => ( + + )} + + ) +} + +export default ConnectedYieldFuncNode diff --git a/ui/src/flux/components/YieldNodeVis.tsx b/ui/src/flux/components/YieldNodeVis.tsx index 7581c0766..a48339aaa 100644 --- a/ui/src/flux/components/YieldNodeVis.tsx +++ b/ui/src/flux/components/YieldNodeVis.tsx @@ -7,15 +7,28 @@ import {Radio} from 'src/reusable_ui' import {ErrorHandling} from 'src/shared/decorators/errors' import {FluxTable} from 'src/types' -import {VisualizationOptions} from 'src/types/dataExplorer' import {DataType} from 'src/shared/constants' import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' -import {CellType} from 'src/types/dashboards' +import { + CellType, + Axes, + TableOptions, + FieldOption, + DecimalPlaces, +} from 'src/types/dashboards' +import {ColorNumber, ColorString} from 'src/types/colors' interface Props { data: FluxTable[] yieldName: string - visualizationOptions: VisualizationOptions + axes: Axes | null + tableOptions: TableOptions + fieldOptions: FieldOption[] + timeFormat: string + decimalPlaces: DecimalPlaces + thresholdsListColors: ColorNumber[] + gaugeColors: ColorNumber[] + lineColors: ColorString[] } enum VisType { @@ -71,9 +84,8 @@ class YieldNodeVis extends PureComponent { private get vis(): JSX.Element { const {visType} = this.state - const {data, visualizationOptions} = this.props - const { + data, tableOptions, timeFormat, decimalPlaces, @@ -81,7 +93,7 @@ class YieldNodeVis extends PureComponent { thresholdsListColors, gaugeColors, lineColors, - } = visualizationOptions + } = this.props if (visType === VisType.Line) { return ( diff --git a/ui/src/localStorage.ts b/ui/src/localStorage.ts index 7719fc739..e92d92676 100644 --- a/ui/src/localStorage.ts +++ b/ui/src/localStorage.ts @@ -5,23 +5,8 @@ import { notifyLoadLocalSettingsFailed, } from 'src/shared/copy/notifications' -import { - DEFAULT_THRESHOLDS_LIST_COLORS, - DEFAULT_GAUGE_COLORS, -} from 'src/shared/constants/thresholds' -import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' -import {DEFAULT_AXES} from 'src/dashboards/constants/cellEditor' -import { - DEFAULT_TABLE_OPTIONS, - DEFAULT_TIME_FORMAT, - DEFAULT_DECIMAL_PLACES, - DEFAULT_FIELD_OPTIONS, -} from 'src/dashboards/constants' -import {editor} from 'src/flux/constants' import {defaultTableData} from 'src/logs/constants' -import {CellType} from 'src/types' -import {ThresholdType, NoteVisibility} from 'src/types/dashboards' import {LocalStorage} from 'src/types/localStorage' const VERSION = process.env.npm_package_version @@ -61,7 +46,6 @@ export const loadLocalStorage = (errorsQueue: any[]): LocalStorage | {} => { export const saveToLocalStorage = ({ app: {persisted}, timeRange, - dataExplorer, dashTimeV1: {ranges}, logs, script, @@ -80,52 +64,25 @@ export const saveToLocalStorage = ({ window.localStorage.setItem( 'state', JSON.stringify( - _.omit( - { - ...appPersisted, - VERSION, - GIT_SHA, - timeRange, - dashTimeV1, - dataExplorer: { - ...dataExplorer, - queryDrafts: dataExplorer.queryDrafts || [], - timeRange: dataExplorer.timeRange || {}, - sourceLink: dataExplorer.sourceLink || '', - queryStatus: dataExplorer.queryStatus || {}, - script: dataExplorer.script || editor.DEFAULT_SCRIPT, - visType: dataExplorer.visType || CellType.Line, - thresholdsListType: - dataExplorer.thresholdsListType || ThresholdType.Text, - thresholdsListColors: - dataExplorer.thresholdsListColors || - DEFAULT_THRESHOLDS_LIST_COLORS, - gaugeColors: dataExplorer.gaugeColors || DEFAULT_GAUGE_COLORS, - lineColors: dataExplorer.lineColors || DEFAULT_LINE_COLORS, - axes: dataExplorer.axes || DEFAULT_AXES, - tableOptions: dataExplorer.tableOptions || DEFAULT_TABLE_OPTIONS, - timeFormat: dataExplorer.timeFormat || DEFAULT_TIME_FORMAT, - decimalPlaces: - dataExplorer.decimalPlaces || DEFAULT_DECIMAL_PLACES, - fieldOptions: dataExplorer.fieldOptions || DEFAULT_FIELD_OPTIONS, - noteVisibility: - dataExplorer.noteVisibility || NoteVisibility.Default, - }, - script, - logs: { - ...minimalLogs, - histogramData: [], - tableData: {}, - queryCount: 0, - tableInfiniteData: { - forward: defaultTableData, - backward: defaultTableData, - }, - tableTime: minimalLogs.tableTime || {}, + _.omit({ + ...appPersisted, + VERSION, + GIT_SHA, + timeRange, + dashTimeV1, + script, + logs: { + ...minimalLogs, + histogramData: [], + tableData: {}, + queryCount: 0, + tableInfiniteData: { + forward: defaultTableData, + backward: defaultTableData, }, + tableTime: minimalLogs.tableTime || {}, }, - 'dataExplorerQueryConfigs' - ) + }) ) ) } catch (err) { diff --git a/ui/src/shared/actions/queries.ts b/ui/src/shared/actions/queries.ts index b4dcf8092..a6ed034a9 100644 --- a/ui/src/shared/actions/queries.ts +++ b/ui/src/shared/actions/queries.ts @@ -1,541 +1,4 @@ -// Libraries -import uuid from 'uuid' - -// Utils -import { - fill, - timeShift, - chooseTag, - groupByTag, - removeFuncs, - groupByTime, - toggleField, - chooseNamespace, - chooseMeasurement, - addInitialField, - applyFuncsToField, - toggleTagAcceptance, -} from 'src/utils/queryTransitions' -import {getDeep} from 'src/utils/wrappers' -import {getTimeRange} from 'src/dashboards/utils/cellGetters' -import {buildQuery} from 'src/utils/influxql' -import defaultQueryConfig from 'src/utils/defaultQueryConfig' - -// Constants -import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' - -// Types -import { - Status, - Field, - GroupBy, - Tag, - TimeShift, - ApplyFuncsToFieldArgs, - CellQuery, - TimeRange, - QueryType, -} from 'src/types' -import { - State as CEOState, - ActionType as CEOActionType, -} from 'src/dashboards/actions/cellEditorOverlay' -import {ActionType as DashboardActionType} from 'src/dashboards/actions' -import { - State as DEState, - ActionType as DEActionType, -} from 'src/data_explorer/actions/queries' - -type State = CEOState & DEState -type GetState = () => State - export enum QueryUpdateState { CEO = 'cellEditorOverlay', DE = 'dataExplorer', } - -interface UpdateQueryDraftsAction { - type: CEOActionType.UpdateQueryDrafts | DEActionType.UpdateQueryDrafts - payload: {queryDrafts: CellQuery[]} -} - -interface UpdateEditorTimeRangeAction { - type: CEOActionType.UpdateEditorTimeRange | DEActionType.UpdateEditorTimeRange - payload: {timeRange: TimeRange} -} - -interface UpdateQueryStatusAction { - type: DashboardActionType.EditCellQueryStatus | DEActionType.UpdateQueryStatus - payload: {queryID: string; status: Status} -} - -interface UpdateScriptAction { - type: CEOActionType.UpdateScript | DEActionType.UpdateScript - payload: {script: string} -} - -export const updateQueryDrafts = ( - queryDrafts: CellQuery[], - stateToUpdate: QueryUpdateState -): UpdateQueryDraftsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateQueryDrafts - : DEActionType.UpdateQueryDrafts - return { - type, - payload: { - queryDrafts, - }, - } as UpdateQueryDraftsAction -} - -export const updateEditorTimeRange = ( - timeRange: TimeRange, - stateToUpdate: QueryUpdateState -) => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateEditorTimeRange - : DEActionType.UpdateEditorTimeRange - return { - type, - payload: { - timeRange, - }, - } as UpdateEditorTimeRangeAction -} - -export const updateQueryStatus = ( - queryID, - status, - stateToUpdate: QueryUpdateState -) => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? DashboardActionType.EditCellQueryStatus - : DEActionType.UpdateQueryStatus - - return { - type, - payload: { - queryID, - status, - }, - } as UpdateQueryStatusAction -} - -export const updateScript = ( - script: string, - stateToUpdate: QueryUpdateState -) => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateScript - : DEActionType.UpdateScript - - return { - type, - payload: { - script, - }, - } as UpdateScriptAction -} - -export const toggleFieldAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - fieldFunc: Field -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = { - ...toggleField(query.queryConfig, fieldFunc), - rawText: null, - } - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const groupByTimeAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - time: string -) => (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = groupByTime(query.queryConfig, time) - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const fillAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - value: string -) => (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = fill(query.queryConfig, value) - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const removeFuncsAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - fields: Field[] -) => (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = removeFuncs(query.queryConfig, fields) - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const applyFuncsToFieldAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - fieldFunc: ApplyFuncsToFieldArgs, - groupBy?: GroupBy -) => (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = applyFuncsToField( - query.queryConfig, - fieldFunc, - groupBy - ) - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const chooseTagAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - tag: Tag -) => (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = chooseTag(query.queryConfig, tag) - - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -interface DBRP { - database: string - retentionPolicy: string -} - -export const chooseNamespaceAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - {database, retentionPolicy}: DBRP -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = chooseNamespace(query.queryConfig, { - database, - retentionPolicy, - }) - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const chooseMeasurementAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - measurement: string -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = { - ...chooseMeasurement(query.queryConfig, measurement), - rawText: getDeep(query, 'queryConfig.rawText', ''), - } - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const groupByTagAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - tagKey: string -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = groupByTag(query.queryConfig, tagKey) - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const toggleTagAcceptanceAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = toggleTagAcceptance(query.queryConfig) - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const addInitialFieldAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - field: Field, - groupBy: GroupBy -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = addInitialField(query.queryConfig, field, groupBy) - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const editQueryStatus = ( - queryID: string, - stateToUpdate: QueryUpdateState, - status: Status -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = {...query.queryConfig, status} - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const timeShiftAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState, - shift: TimeShift -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.map(query => { - if (query.id === queryID) { - const nextQueryConfig = timeShift(query.queryConfig, shift) - return { - ...query, - query: buildQuery( - TYPE_QUERY_CONFIG, - getTimeRange(nextQueryConfig), - nextQueryConfig - ), - queryConfig: nextQueryConfig, - } - } - return query - }) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export type QueryConfigActions = typeof queryConfigActions & { - editRawTextAsync?: (text: string) => Promise -} - -export const addQueryAsync = (stateToUpdate: QueryUpdateState) => async ( - dispatch, - getState: GetState -) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const queryID = uuid.v4() - - const newQueryDraft: CellQuery = { - query: '', - queryConfig: defaultQueryConfig({id: queryID}), - source: '', - id: queryID, - type: QueryType.InfluxQL, - } - const updatedQueryDrafts = [...queryDrafts, newQueryDraft] - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const deleteQueryAsync = ( - queryID: string, - stateToUpdate: QueryUpdateState -) => async (dispatch, getState: GetState) => { - const queryDrafts = getDeep(getState(), `${stateToUpdate}.queryDrafts`, []) - - const updatedQueryDrafts = queryDrafts.filter(query => query.id !== queryID) - dispatch(updateQueryDrafts(updatedQueryDrafts, stateToUpdate)) -} - -export const queryConfigActions = { - fill: fillAsync, - timeShift: timeShiftAsync, - chooseTag: chooseTagAsync, - groupByTag: groupByTagAsync, - groupByTime: groupByTimeAsync, - toggleField: toggleFieldAsync, - removeFuncs: removeFuncsAsync, - addInitialField: addInitialFieldAsync, - applyFuncsToField: applyFuncsToFieldAsync, - toggleTagAcceptance: toggleTagAcceptanceAsync, - chooseNamespace: chooseNamespaceAsync, - chooseMeasurement: chooseMeasurementAsync, -} diff --git a/ui/src/shared/actions/visualizations.ts b/ui/src/shared/actions/visualizations.ts index 583f659b2..6209d62f0 100644 --- a/ui/src/shared/actions/visualizations.ts +++ b/ui/src/shared/actions/visualizations.ts @@ -1,297 +1,18 @@ -// Constants -import {QueryUpdateState} from 'src/shared/actions/queries' - -// Types import {ActionType as CEOActionType} from 'src/dashboards/actions/cellEditorOverlay' -import {ActionType as DEActionType} from 'src/data_explorer/actions/queries' - -import {ColorNumber, ColorString} from 'src/types/colors' -import { - DecimalPlaces, - FieldOption, - Axes, - CellType, - ThresholdType, - TableOptions, - NoteVisibility, -} from 'src/types/dashboards' - -export interface ChangeVisualizationTypeAction { - type: CEOActionType.ChangeCellType | DEActionType.ChangeVisualizationType - payload: { - cellType: CellType - } -} - -export interface UpdateThresholdsListColorsAction { - type: - | CEOActionType.UpdateThresholdsListColors - | DEActionType.UpdateThresholdsListColors - payload: { - thresholdsListColors: ColorNumber[] - } -} - -export interface UpdateThresholdsListTypeAction { - type: - | CEOActionType.UpdateThresholdsListType - | DEActionType.UpdateThresholdsListType - payload: { - thresholdsListType: ThresholdType - } -} - -export interface UpdateGaugeColorsAction { - type: CEOActionType.UpdateGaugeColors | DEActionType.UpdateGaugeColors - payload: { - gaugeColors: ColorNumber[] - } -} - -export interface UpdateAxesAction { - type: CEOActionType.UpdateAxes | DEActionType.UpdateAxes - payload: { - axes: Axes - } -} - -export interface UpdateTableOptionsAction { - type: CEOActionType.UpdateTableOptions | DEActionType.UpdateTableOptions - payload: { - tableOptions: TableOptions - } -} - -export interface UpdateLineColorsAction { - type: CEOActionType.UpdateLineColors | DEActionType.UpdateLineColors - payload: { - lineColors: ColorString[] - } -} - -export interface ChangeTimeFormatAction { - type: CEOActionType.ChangeTimeFormat | DEActionType.ChangeTimeFormat - payload: { - timeFormat: string - } -} - -export interface ChangeDecimalPlacesAction { - type: CEOActionType.ChangeDecimalPlaces | DEActionType.ChangeDecimalPlaces - payload: { - decimalPlaces: DecimalPlaces - } -} +import {FieldOption} from 'src/types/dashboards' export interface UpdateFieldOptionsAction { - type: CEOActionType.UpdateFieldOptions | DEActionType.UpdateFieldOptions + type: CEOActionType.UpdateFieldOptions payload: { fieldOptions: FieldOption[] } } -export interface UpdateNoteAction { - type: CEOActionType.UpdateCellNote | DEActionType.UpdateNote - payload: { - note: string - } -} - -export interface UpdateNoteVisibilityAction { - type: - | CEOActionType.UpdateCellNoteVisibility - | DEActionType.UpdateNoteVisibility - payload: { - noteVisibility: NoteVisibility - } -} - -export const updateVisType = ( - cellType: CellType, - stateToUpdate: QueryUpdateState -): ChangeVisualizationTypeAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.ChangeCellType - : DEActionType.ChangeVisualizationType - return { - type, - payload: { - cellType, - }, - } as ChangeVisualizationTypeAction -} - -export const updateNote = ( - note: string, - stateToUpdate: QueryUpdateState -): UpdateNoteAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateCellNote - : DEActionType.UpdateNote - return { - type, - payload: { - note, - }, - } as UpdateNoteAction -} - -export const updateNoteVisibility = ( - noteVisibility: NoteVisibility, - stateToUpdate: QueryUpdateState -): UpdateNoteVisibilityAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateCellNoteVisibility - : DEActionType.UpdateNoteVisibility - return { - type, - payload: { - noteVisibility, - }, - } as UpdateNoteVisibilityAction -} - -export const updateThresholdsListColors = ( - thresholdsListColors: ColorNumber[], - stateToUpdate: QueryUpdateState -): UpdateThresholdsListColorsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateThresholdsListColors - : DEActionType.UpdateThresholdsListColors - return { - type, - payload: { - thresholdsListColors, - }, - } as UpdateThresholdsListColorsAction -} - -export const updateThresholdsListType = ( - thresholdsListType: ThresholdType, - stateToUpdate: QueryUpdateState -): UpdateThresholdsListTypeAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateThresholdsListType - : DEActionType.UpdateThresholdsListType - return { - type, - payload: { - thresholdsListType, - }, - } as UpdateThresholdsListTypeAction -} - -export const updateGaugeColors = ( - gaugeColors: ColorNumber[], - stateToUpdate: QueryUpdateState -): UpdateGaugeColorsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateGaugeColors - : DEActionType.UpdateGaugeColors - return { - type, - payload: { - gaugeColors, - }, - } as UpdateGaugeColorsAction -} - -export const updateAxes = ( - axes: Axes, - stateToUpdate: QueryUpdateState -): UpdateAxesAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateAxes - : DEActionType.UpdateAxes - return { - type, - payload: { - axes, - }, - } as UpdateAxesAction -} - -export const updateTableOptions = ( - tableOptions: TableOptions, - stateToUpdate: QueryUpdateState -): UpdateTableOptionsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateTableOptions - : DEActionType.UpdateTableOptions - return { - type, - payload: { - tableOptions, - }, - } as UpdateTableOptionsAction -} - -export const updateLineColors = ( - lineColors: ColorString[], - stateToUpdate: QueryUpdateState -): UpdateLineColorsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateLineColors - : DEActionType.UpdateLineColors - return { - type, - payload: { - lineColors, - }, - } as UpdateLineColorsAction -} - -export const updateTimeFormat = ( - timeFormat: string, - stateToUpdate: QueryUpdateState -): ChangeTimeFormatAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.ChangeTimeFormat - : DEActionType.ChangeTimeFormat - return { - type, - payload: { - timeFormat, - }, - } as ChangeTimeFormatAction -} - -export const updateDecimalPlaces = ( - decimalPlaces: DecimalPlaces, - stateToUpdate: QueryUpdateState -): ChangeDecimalPlacesAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.ChangeDecimalPlaces - : DEActionType.ChangeDecimalPlaces - return { - type, - payload: { - decimalPlaces, - }, - } as ChangeDecimalPlacesAction -} - export const updateFieldOptions = ( - fieldOptions: FieldOption[], - stateToUpdate: QueryUpdateState + fieldOptions: FieldOption[] ): UpdateFieldOptionsAction => { - const type = - stateToUpdate === QueryUpdateState.CEO - ? CEOActionType.UpdateFieldOptions - : DEActionType.UpdateFieldOptions return { - type, + type: CEOActionType.UpdateFieldOptions, payload: { fieldOptions, }, diff --git a/ui/src/shared/components/ManualRefresh.tsx b/ui/src/shared/components/ManualRefresh.tsx index 9cc83b991..4bb3d00bb 100644 --- a/ui/src/shared/components/ManualRefresh.tsx +++ b/ui/src/shared/components/ManualRefresh.tsx @@ -1,4 +1,4 @@ -import React, {Component, ComponentClass} from 'react' +import React, {Component, ComponentClass, StatelessComponent} from 'react' export interface ManualRefreshProps { manualRefresh: number @@ -10,7 +10,9 @@ interface ManualRefreshState { } function ManualRefresh

( - WrappedComponent: ComponentClass

+ WrappedComponent: + | ComponentClass

+ | StatelessComponent

): ComponentClass

{ return class extends Component

{ public constructor(props: P & ManualRefreshProps) { diff --git a/ui/src/shared/components/RefreshingGraph.tsx b/ui/src/shared/components/RefreshingGraph.tsx index 276f1691a..b35ae3e4f 100644 --- a/ui/src/shared/components/RefreshingGraph.tsx +++ b/ui/src/shared/components/RefreshingGraph.tsx @@ -88,6 +88,7 @@ interface Props { cellNoteVisibility: NoteVisibility editorLocation?: QueryUpdateState onUpdateCellColors?: (bgColor: string, textColor: string) => void + onUpdateFieldOptions?: (fieldOptions: FieldOption[]) => void } class RefreshingGraph extends PureComponent { @@ -242,6 +243,7 @@ class RefreshingGraph extends PureComponent { manualRefresh, handleSetHoverTime, editorLocation, + onUpdateFieldOptions, } = this.props const {dataType, data} = this.getTypeAndData(influxQLData, fluxData) @@ -258,6 +260,7 @@ class RefreshingGraph extends PureComponent { decimalPlaces={decimalPlaces} editorLocation={editorLocation} handleSetHoverTime={handleSetHoverTime} + onUpdateFieldOptions={onUpdateFieldOptions} /> ) } @@ -274,6 +277,7 @@ class RefreshingGraph extends PureComponent { decimalPlaces={decimalPlaces} editorLocation={editorLocation} handleSetHoverTime={handleSetHoverTime} + onUpdateFieldOptions={onUpdateFieldOptions} /> ) } diff --git a/ui/src/shared/components/SchemaExplorer.tsx b/ui/src/shared/components/SchemaExplorer.tsx index f2f46e59c..f698d5ef6 100644 --- a/ui/src/shared/components/SchemaExplorer.tsx +++ b/ui/src/shared/components/SchemaExplorer.tsx @@ -6,43 +6,51 @@ import DatabaseList from 'src/shared/components/DatabaseList' import MeasurementList from 'src/shared/components/MeasurementList' import FieldList from 'src/shared/components/FieldList' +// Utiles +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' + // Types import {QueryConfig, Source} from 'src/types' -import {QueryConfigActions, QueryUpdateState} from 'src/shared/actions/queries' -const actionBinder = (id, isInCEO, action) => (...args) => { - const stateToUpdate = isInCEO ? QueryUpdateState.CEO : QueryUpdateState.DE - return action(id, stateToUpdate, ...args) +const actionBinder = (id, action) => (...args) => { + return action(id, ...args) } interface Props { - isInCEO: boolean query: QueryConfig - actions: QueryConfigActions source: Source initialGroupByTime: string isQuerySupportedByExplorer?: boolean + onFill: TimeMachineContainer['handleFill'] + onTimeShift: TimeMachineContainer['handleTimeShift'] + onChooseTag: TimeMachineContainer['handleChooseTag'] + onGroupByTag: TimeMachineContainer['handleGroupByTag'] + onGroupByTime: TimeMachineContainer['handleGroupByTime'] + onToggleField: TimeMachineContainer['handleToggleField'] + onRemoveFuncs: TimeMachineContainer['handleRemoveFuncs'] + onAddInitialField: TimeMachineContainer['handleAddInitialField'] + onChooseNamespace: TimeMachineContainer['handleChooseNamespace'] + onChooseMeasurement: TimeMachineContainer['handleChooseMeasurement'] + onApplyFuncsToField: TimeMachineContainer['handleApplyFuncsToField'] + onToggleTagAcceptance: TimeMachineContainer['handleToggleTagAcceptance'] } const SchemaExplorer: SFC = ({ - isInCEO, query, source, initialGroupByTime, - actions: { - fill, - timeShift, - chooseTag, - groupByTag, - groupByTime, - toggleField, - removeFuncs, - addInitialField, - chooseNamespace, - chooseMeasurement, - applyFuncsToField, - toggleTagAcceptance, - }, + onFill, + onTimeShift, + onChooseTag, + onGroupByTag, + onGroupByTime, + onToggleField, + onRemoveFuncs, + onAddInitialField, + onChooseNamespace, + onChooseMeasurement, + onApplyFuncsToField, + onToggleTagAcceptance, isQuerySupportedByExplorer = true, }) => { const {id} = query @@ -52,29 +60,29 @@ const SchemaExplorer: SFC = ({

diff --git a/ui/src/shared/components/TableGraph.tsx b/ui/src/shared/components/TableGraph.tsx index 5c60533be..bcc9b1dfb 100644 --- a/ui/src/shared/components/TableGraph.tsx +++ b/ui/src/shared/components/TableGraph.tsx @@ -7,14 +7,12 @@ import moment from 'moment' import {ColumnSizer, SizedColumnProps, AutoSizer} from 'react-virtualized' import {MultiGrid, PropsMultiGrid} from 'src/shared/components/MultiGrid' import InvalidData from 'src/shared/components/InvalidData' -import {bindActionCreators} from 'redux' import {fastReduce} from 'src/utils/fast' import {timeSeriesToTableGraph} from 'src/utils/timeSeriesTransformers' import { computeFieldOptions, getDefaultTimeField, } from 'src/dashboards/utils/tableGraph' -import {updateFieldOptions} from 'src/shared/actions/visualizations' import {QueryUpdateState} from 'src/shared/actions/queries' import { ASCENDING, @@ -74,10 +72,10 @@ interface Props { decimalPlaces: DecimalPlaces fieldOptions: FieldOption[] hoverTime: string - handleUpdateFieldOptions: typeof updateFieldOptions handleSetHoverTime?: (hovertime: string) => void colors: ColorString[] editorLocation?: QueryUpdateState + onUpdateFieldOptions?: (fieldOptions: FieldOption[]) => void } interface State { @@ -508,9 +506,10 @@ class TableGraph extends PureComponent { } private handleUpdateFieldOptions = (fieldOptions: FieldOption[]): void => { - const {handleUpdateFieldOptions, editorLocation} = this.props - if (editorLocation) { - handleUpdateFieldOptions(fieldOptions, editorLocation) + const {onUpdateFieldOptions} = this.props + + if (onUpdateFieldOptions) { + onUpdateFieldOptions(fieldOptions) } } @@ -833,8 +832,4 @@ const mstp = ({dashboardUI}) => ({ hoverTime: dashboardUI.hoverTime, }) -const mapDispatchToProps = dispatch => ({ - handleUpdateFieldOptions: bindActionCreators(updateFieldOptions, dispatch), -}) - -export default connect(mstp, mapDispatchToProps)(TableGraph) +export default connect(mstp)(TableGraph) diff --git a/ui/src/shared/components/TimeMachine/InfluxQLQueryMaker.tsx b/ui/src/shared/components/TimeMachine/InfluxQLQueryMaker.tsx index d9df70c23..94e036ad7 100644 --- a/ui/src/shared/components/TimeMachine/InfluxQLQueryMaker.tsx +++ b/ui/src/shared/components/TimeMachine/InfluxQLQueryMaker.tsx @@ -1,6 +1,7 @@ // Libraries import React, {SFC} from 'react' import _ from 'lodash' +import {Subscribe} from 'unstated' // Components import EmptyQuery from 'src/shared/components/EmptyQuery' @@ -8,36 +9,52 @@ import QueryTabList from 'src/shared/components/QueryTabList' import InfluxQLEditor from 'src/dashboards/components/InfluxQLEditor' import SchemaExplorer from 'src/shared/components/SchemaExplorer' import FancyScrollbar from 'src/shared/components/FancyScrollbar' + +// Utils +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' import {buildQuery} from 'src/utils/influxql' import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' import {TEMPLATE_RANGE} from 'src/tempVars/constants' +import {AUTO_GROUP_BY} from 'src/shared/constants' // Types import {QueryConfig, Source, TimeRange, Template} from 'src/types' -import {QueryConfigActions} from 'src/shared/actions/queries' const buildText = (q: QueryConfig): string => q.rawText || buildQuery(TYPE_QUERY_CONFIG, q.range || TEMPLATE_RANGE, q) || '' -interface Props { +interface ConnectedProps { + timeRange: TimeRange + onFill: TimeMachineContainer['handleFill'] + onTimeShift: TimeMachineContainer['handleTimeShift'] + onChooseTag: TimeMachineContainer['handleChooseTag'] + onGroupByTag: TimeMachineContainer['handleGroupByTag'] + onGroupByTime: TimeMachineContainer['handleGroupByTime'] + onToggleField: TimeMachineContainer['handleToggleField'] + onRemoveFuncs: TimeMachineContainer['handleRemoveFuncs'] + onAddInitialField: TimeMachineContainer['handleAddInitialField'] + onChooseNamespace: TimeMachineContainer['handleChooseNamespace'] + onChooseMeasurement: TimeMachineContainer['handleChooseMeasurement'] + onApplyFuncsToField: TimeMachineContainer['handleApplyFuncsToField'] + onToggleTagAcceptance: TimeMachineContainer['handleToggleTagAcceptance'] +} + +interface PassedProps { source: Source queries: QueryConfig[] - timeRange: TimeRange - actions: QueryConfigActions setActiveQueryIndex: (index: number) => void - onDeleteQuery: (index: number) => void activeQueryIndex: number activeQuery: QueryConfig - onAddQuery: () => void templates: Template[] - initialGroupByTime: string - isInCEO: boolean + onAddQuery: () => void + onDeleteQuery: (index: number) => void + onEditRawText: (text: string) => Promise } +type Props = ConnectedProps & PassedProps + const QueryMaker: SFC = ({ source, - isInCEO, - actions, queries, timeRange, templates, @@ -45,8 +62,20 @@ const QueryMaker: SFC = ({ activeQuery, onDeleteQuery, activeQueryIndex, - initialGroupByTime, setActiveQueryIndex, + onEditRawText, + onFill, + onTimeShift, + onChooseTag, + onGroupByTag, + onGroupByTime, + onToggleField, + onRemoveFuncs, + onAddInitialField, + onChooseNamespace, + onChooseMeasurement, + onApplyFuncsToField, + onToggleTagAcceptance, }) => { if (!activeQuery || !activeQuery.id) { return ( @@ -71,20 +100,30 @@ const QueryMaker: SFC = ({ @@ -92,4 +131,27 @@ const QueryMaker: SFC = ({ ) } -export default QueryMaker +const ConnectedQueryMaker = (props: PassedProps) => ( + + {(container: TimeMachineContainer) => ( + + )} + +) + +export default ConnectedQueryMaker diff --git a/ui/src/shared/components/TimeMachine/TimeMachine.tsx b/ui/src/shared/components/TimeMachine/TimeMachine.tsx index 8fbd5f1ff..249159cf7 100644 --- a/ui/src/shared/components/TimeMachine/TimeMachine.tsx +++ b/ui/src/shared/components/TimeMachine/TimeMachine.tsx @@ -1,14 +1,15 @@ // Libraries import React, {PureComponent} from 'react' import _ from 'lodash' +import {Subscribe} from 'unstated' // Components import Threesizer from 'src/shared/components/threesizer/Threesizer' -import RefreshingGraph from 'src/shared/components/RefreshingGraph' import InfluxQLQueryMaker from 'src/shared/components/TimeMachine/InfluxQLQueryMaker' import DisplayOptions from 'src/dashboards/components/DisplayOptions' import TimeMachineBottom from 'src/shared/components/TimeMachine/TimeMachineBottom' import TimeMachineControls from 'src/shared/components/TimeMachine/TimeMachineControls' +import TimeMachineVisualization from 'src/shared/components/TimeMachine/TimeMachineVisualization' import FluxQueryBuilder from 'src/flux/components/FluxQueryBuilder' // Utils @@ -28,7 +29,7 @@ import { } from 'src/flux/helpers/scriptBuilder' import {AutoRefresher} from 'src/utils/AutoRefresher' import buildQueries from 'src/utils/buildQueriesForGraphs' -import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' // Actions import {validateSuccess} from 'src/shared/copy/notifications' @@ -37,12 +38,12 @@ import {updateSourceLink as updateSourceLinkAction} from 'src/data_explorer/acti // Constants import {HANDLE_HORIZONTAL} from 'src/shared/constants' -import {AUTO_GROUP_BY, PREDEFINED_TEMP_VARS} from 'src/shared/constants' +import {PREDEFINED_TEMP_VARS} from 'src/shared/constants' import {CEOTabs} from 'src/dashboards/constants' import {builder, emptyAST} from 'src/flux/constants' // Types -import {QueryConfigActions, QueryUpdateState} from 'src/shared/actions/queries' +import {QueryUpdateState} from 'src/shared/actions/queries' import { TimeRange, QueryConfig, @@ -67,47 +68,40 @@ import { DeleteFuncNodeArgs, ScriptStatus, } from 'src/types/flux' -import {ColorString} from 'src/types/colors' -import {VisualizationOptions} from 'src/types/dataExplorer' -interface Props { +interface ConnectedProps { + script: string + queryDrafts: CellQuery[] + onChangeScript: (script: string, stateToUpdate: QueryUpdateState) => void + onUpdateQueryDrafts: TimeMachineContainer['handleUpdateQueryDrafts'] + onAddQuery: () => void + onDeleteQuery: (queryID: string) => void + timeRange: TimeRange + onUpdateTimeRange: (timeRange: TimeRange) => void +} + +interface PassedProps { fluxLinks: Links source: Source service?: Service - script: string sources: Source[] isInCEO: boolean services: Service[] - timeRange: TimeRange templates: Template[] isStaticLegend: boolean - queryDrafts: CellQuery[] onResetFocus: () => void updateSourceLink?: typeof updateSourceLinkAction - updateScript: (script: string, stateToUpdate: QueryUpdateState) => void - queryConfigActions: QueryConfigActions notify: NotificationAction editQueryStatus: ( queryID: string, status: Status, stateToUpdate: QueryUpdateState ) => void - updateQueryDrafts: ( - queryDrafts: CellQuery[], - stateToUpdate: QueryUpdateState - ) => void onToggleStaticLegend: (isStaticLegend: boolean) => void children: ( activeEditorTab: CEOTabs, onSetActiveEditorTab: (activeEditorTab: CEOTabs) => void ) => JSX.Element - addQuery: (stateToUpdate: QueryUpdateState) => void - deleteQuery: (queryID: string, stateToUpdate: QueryUpdateState) => void - updateEditorTimeRange: ( - timeRange: TimeRange, - stateToUpdate: QueryUpdateState - ) => void - visualizationOptions: VisualizationOptions manualRefresh?: number queryStatus: QueryStatus updateScriptStatus?: (status: ScriptStatus) => void @@ -138,6 +132,8 @@ type ScriptFunc = (script: string) => void export const FluxContext = React.createContext(undefined) +type Props = PassedProps & ConnectedProps + class TimeMachine extends PureComponent { private debouncedASTResponse: ScriptFunc private validAST: boolean = true @@ -207,7 +203,7 @@ class TimeMachine extends PureComponent { } public render() { - const {services, timeRange, templates, isInCEO, script} = this.props + const {services, timeRange, templates, script} = this.props const {autoRefreshDuration, isViewingRawData} = this.state const horizontalDivisions = [ @@ -254,7 +250,6 @@ class TimeMachine extends PureComponent { isDynamicSourceSelected={this.useDynamicSource} timeRange={timeRange} updateEditorTimeRange={this.handleUpdateEditorTimeRange} - isInCEO={isInCEO} />
{ } private renderVisualization = () => { - const { - timeRange, - templates, - isStaticLegend, - manualRefresh, - visualizationOptions: { - type, - axes, - tableOptions, - fieldOptions, - timeFormat, - decimalPlaces, - note, - noteVisibility, - thresholdsListColors, - thresholdsListType, - gaugeColors, - lineColors, - }, - } = this.props + const {templates, isStaticLegend, manualRefresh} = this.props const {autoRefresher, isViewingRawData} = this.state - const colors: ColorString[] = getCellTypeColors({ - cellType: type, - gaugeColors, - thresholdsListColors, - lineColors, - }) - return ( -
-
-
- -
-
-
+ ) } @@ -339,18 +286,14 @@ class TimeMachine extends PureComponent { } private get editorTab() { - const { - onResetFocus, - isStaticLegend, - onToggleStaticLegend, - visualizationOptions, - } = this.props + const {onResetFocus, isStaticLegend, onToggleStaticLegend} = this.props const {activeEditorTab} = this.state if (activeEditorTab === CEOTabs.Queries) { if (this.isFluxSource) { return this.fluxBuilder } + return this.influxQLBuilder } @@ -361,7 +304,6 @@ class TimeMachine extends PureComponent { staticLegend={isStaticLegend} onResetFocus={onResetFocus} stateToUpdate={this.stateToUpdate} - {...visualizationOptions} /> ) } @@ -435,6 +377,7 @@ class TimeMachine extends PureComponent { if (!queryDrafts || !queryDrafts.length) { return [] } + return queryDrafts.map(q => { if (queryStatus.queryID === q.id) { return { @@ -506,23 +449,20 @@ class TimeMachine extends PureComponent { } private get influxQLBuilder(): JSX.Element { - const {isInCEO, templates, timeRange} = this.props + const {templates} = this.props const {activeQueryIndex} = this.state return ( ) } @@ -536,6 +476,7 @@ class TimeMachine extends PureComponent { private get queriesForVis(): Query[] { const {script, timeRange, queryDrafts} = this.props const id = _.get(queryDrafts, 'id', '') + if (this.isFluxSource) { if (!this.validAST) { return [] @@ -575,9 +516,9 @@ class TimeMachine extends PureComponent { } private handleUpdateEditorTimeRange = (timeRange: TimeRange) => { - const {updateEditorTimeRange} = this.props + const {onUpdateTimeRange} = this.props - updateEditorTimeRange(timeRange, this.stateToUpdate) + onUpdateTimeRange(timeRange) } private handleEditQueryStatus = (queryID: string, status: Status) => { @@ -610,18 +551,12 @@ class TimeMachine extends PureComponent { return getDeep(queryDrafts, '0.source', '') === '' } - private get queryConfigActions() { - const {queryConfigActions} = this.props - - return {...queryConfigActions, editRawTextAsync: this.handleEditRawText} - } - // 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 (text: string): Promise => { - const {templates, updateQueryDrafts, queryDrafts} = this.props + const {templates, onUpdateQueryDrafts, queryDrafts} = this.props const id = this.activeQuery.id const url = getDeep(this.source, 'links.queries', '') @@ -675,7 +610,7 @@ class TimeMachine extends PureComponent { return q }) - updateQueryDrafts(nextQueries, this.stateToUpdate) + onUpdateQueryDrafts(nextQueries) } catch (error) { console.error(error) } @@ -685,7 +620,7 @@ class TimeMachine extends PureComponent { selectedSource: Source | Service, type: string ) { - const {queryDrafts, updateQueryDrafts} = this.props + const {queryDrafts, onUpdateQueryDrafts} = this.props const queries: CellQuery[] = queryDrafts.map(q => { const queryConfig = _.get(q, 'queryConfig') @@ -697,7 +632,7 @@ class TimeMachine extends PureComponent { } }) as CellQuery[] - updateQueryDrafts(queries, this.stateToUpdate) + onUpdateQueryDrafts(queries) } private handleChangeService = ( @@ -724,15 +659,15 @@ class TimeMachine extends PureComponent { } private handleAddQuery = () => { - const {queryDrafts, addQuery} = this.props + const {queryDrafts, onAddQuery} = this.props const newIndex = queryDrafts.length - addQuery(this.stateToUpdate) + onAddQuery() this.handleSetActiveQueryIndex(newIndex) } private handleDeleteQuery = (index: number) => { - const {queryDrafts, deleteQuery, isInCEO} = this.props + const {queryDrafts, onDeleteQuery} = this.props const queryToDelete = queryDrafts.find((__, i) => i === index) const activeQueryId = this.activeQuery.id const activeQueryIndex = queryDrafts.findIndex( @@ -755,8 +690,7 @@ class TimeMachine extends PureComponent { this.handleSetActiveQueryIndex(newIndex) - const stateToUpdate = isInCEO ? QueryUpdateState.CEO : QueryUpdateState.DE - deleteQuery(queryToDelete.id, stateToUpdate) + onDeleteQuery(queryToDelete.id) } private handleChangeAutoRefreshDuration = (autoRefreshDuration: number) => { @@ -773,7 +707,7 @@ class TimeMachine extends PureComponent { // --------------- FLUX ---------------- private get getContext(): Context { - const {timeRange, visualizationOptions} = this.props + const {timeRange} = this.props return { onAddNode: this.handleAddNode, onChangeArg: this.handleChangeArg, @@ -788,12 +722,11 @@ class TimeMachine extends PureComponent { source: this.source, queries: this.queriesForVis, timeRange, - visualizationOptions, } } private updateScript(script: string) { - this.props.updateScript(script, this.stateToUpdate) + this.props.onChangeScript(script, this.stateToUpdate) } private getASTResponse = async ( @@ -987,4 +920,28 @@ class TimeMachine extends PureComponent { } } -export default TimeMachine +const ConnectedTimeMachine = (props: PassedProps) => { + return ( + + {(container: TimeMachineContainer) => { + const {state} = container + + return ( + + ) + }} + + ) +} + +export default ConnectedTimeMachine diff --git a/ui/src/shared/components/TimeMachine/TimeMachineControls.tsx b/ui/src/shared/components/TimeMachine/TimeMachineControls.tsx index e63c87164..7386e53de 100644 --- a/ui/src/shared/components/TimeMachine/TimeMachineControls.tsx +++ b/ui/src/shared/components/TimeMachine/TimeMachineControls.tsx @@ -17,7 +17,6 @@ import * as SourcesModels from 'src/types/sources' import {Service, Template} from 'src/types' interface Props { - isInCEO: boolean isFluxSource: boolean source: SourcesModels.Source sources: SourcesModels.SourceOption[] @@ -46,7 +45,6 @@ const TimeMachineControls: SFC = ({ service, queries, templates, - isInCEO, services, timeRange, toggleFlux, @@ -61,8 +59,6 @@ const TimeMachineControls: SFC = ({ isDynamicSourceSelected, updateEditorTimeRange, }) => { - const timeRangePage = isInCEO ? null : 'DataExplorer' - return (
= ({
) diff --git a/ui/src/shared/components/TimeMachine/TimeMachineVisualization.tsx b/ui/src/shared/components/TimeMachine/TimeMachineVisualization.tsx new file mode 100644 index 000000000..3c17a3215 --- /dev/null +++ b/ui/src/shared/components/TimeMachine/TimeMachineVisualization.tsx @@ -0,0 +1,135 @@ +import React, {SFC} from 'react' +import {Subscribe} from 'unstated' + +import RefreshingGraph from 'src/shared/components/RefreshingGraph' + +import {TimeMachineContainer} from 'src/shared/utils/TimeMachineContainer' +import {getCellTypeColors} from 'src/dashboards/constants/cellEditor' + +import { + CellType, + Axes, + TimeRange, + Source, + Service, + Query, + Template, + Status, +} from 'src/types' +import {ColorString, ColorNumber} from 'src/types/colors' +import {QueryUpdateState} from 'src/shared/actions/queries' +import { + TableOptions, + FieldOption, + DecimalPlaces, + NoteVisibility, + ThresholdType, +} from 'src/types/dashboards' +import {AutoRefresher} from 'src/utils/AutoRefresher' + +interface ConnectedProps { + timeRange: TimeRange + onUpdateFieldOptions: TimeMachineContainer['handleUpdateFieldOptions'] + type: CellType + axes: Axes | null + tableOptions: TableOptions + fieldOptions: FieldOption[] + timeFormat: string + decimalPlaces: DecimalPlaces + note: string + noteVisibility: NoteVisibility + thresholdsListColors: ColorNumber[] + thresholdsListType: ThresholdType + gaugeColors: ColorNumber[] + lineColors: ColorString[] +} + +interface PassedProps { + source: Source + service: Service + autoRefresher: AutoRefresher + queries: Query[] + templates: Template[] + onEditQueryStatus: (queryID: string, status: Status) => void + staticLegend: boolean + manualRefresh: number + editorLocation?: QueryUpdateState + showRawFluxData?: boolean +} + +type Props = PassedProps & ConnectedProps + +const TimeMachineVisualization: SFC = props => { + const colors: ColorString[] = getCellTypeColors({ + cellType: props.type, + gaugeColors: props.gaugeColors, + thresholdsListColors: props.thresholdsListColors, + lineColors: props.lineColors, + }) + + return ( +
+
+
+ +
+
+
+ ) +} + +const ConnectedTimeMachineVisualization = (props: PassedProps) => ( + + {(container: TimeMachineContainer) => { + const {state} = container + + return ( + + ) + }} + +) + +export default ConnectedTimeMachineVisualization diff --git a/ui/src/shared/constants/graphColorPalettes.ts b/ui/src/shared/constants/graphColorPalettes.ts index ce0a1c2d4..dba3c7873 100644 --- a/ui/src/shared/constants/graphColorPalettes.ts +++ b/ui/src/shared/constants/graphColorPalettes.ts @@ -208,7 +208,7 @@ export const LINE_COLOR_SCALES = [ return {name, colors, id} }) -export const validateLineColors = ( +export const getLineColors = ( colors: LineColor[], numSeries = 0 ): LineColor[] => { @@ -228,7 +228,7 @@ export const getLineColorsHexes = ( colors: LineColor[], numSeries: number ): string[] => { - const validatedColors = validateLineColors(colors, numSeries) // ensures safe defaults + const validatedColors = getLineColors(colors, numSeries) // ensures safe defaults const colorsHexArray = validatedColors.map(color => color.hex) if (numSeries === 1 || numSeries === 0) { diff --git a/ui/src/shared/constants/thresholds.ts b/ui/src/shared/constants/thresholds.ts index 40421187c..e834a4642 100644 --- a/ui/src/shared/constants/thresholds.ts +++ b/ui/src/shared/constants/thresholds.ts @@ -119,7 +119,9 @@ export const DEFAULT_THRESHOLDS_LIST_COLORS = [ }, ] -export const validateThresholdsListColors = (colors, type) => { +export const getThresholdsListColors = colors => { + const type = getThresholdsListType(colors) + if (!colors || colors.length === 0) { return DEFAULT_THRESHOLDS_LIST_COLORS } @@ -159,19 +161,17 @@ export const validateThresholdsListColors = (colors, type) => { return containsBaseColor ? formattedColors : formattedColorsWithBase } -export const getThresholdsListType = colors => { +const getThresholdsListType = colors => { const type = _.get(colors, ['0', 'type'], false) - if (type) { - if (_.includes([THRESHOLD_TYPE_TEXT, THRESHOLD_TYPE_BG], type)) { - return type - } + if (type && _.includes([THRESHOLD_TYPE_TEXT, THRESHOLD_TYPE_BG], type)) { + return type } return THRESHOLD_TYPE_TEXT } -export const validateGaugeColors = colors => { +export const getGaugeColors = colors => { if (!colors || colors.length < MIN_THRESHOLDS) { return DEFAULT_GAUGE_COLORS } diff --git a/ui/src/shared/utils/TimeMachineContainer.ts b/ui/src/shared/utils/TimeMachineContainer.ts new file mode 100644 index 000000000..ae49c876a --- /dev/null +++ b/ui/src/shared/utils/TimeMachineContainer.ts @@ -0,0 +1,340 @@ +// Libraries +import {Container} from 'unstated' +import uuid from 'uuid' + +// Utils +import { + fill, + timeShift, + chooseTag, + groupByTag, + removeFuncs, + groupByTime, + toggleField, + chooseNamespace, + chooseMeasurement, + addInitialField, + applyFuncsToField, + toggleTagAcceptance, +} from 'src/utils/queryTransitions' +import {getTimeRange} from 'src/dashboards/utils/cellGetters' +import {buildQuery} from 'src/utils/influxql' +import defaultQueryConfig from 'src/utils/defaultQueryConfig' + +// Constants +import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants' +import { + DEFAULT_THRESHOLDS_LIST_COLORS, + DEFAULT_GAUGE_COLORS, +} from 'src/shared/constants/thresholds' +import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' +import {DEFAULT_AXES} from 'src/dashboards/constants/cellEditor' +import { + DEFAULT_TABLE_OPTIONS, + DEFAULT_TIME_FORMAT, + DEFAULT_DECIMAL_PLACES, + DEFAULT_FIELD_OPTIONS, +} from 'src/dashboards/constants' +import {DEFAULT_TIME_RANGE} from 'src/data_explorer/constants' + +// Types +import { + Status, + Field, + GroupBy, + Tag, + TimeShift, + ApplyFuncsToFieldArgs, + CellQuery, + QueryType, + TimeRange, + CellType, + QueryConfig, +} from 'src/types' +import { + DecimalPlaces, + FieldOption, + ThresholdType, + TableOptions, + NoteVisibility, + Axes, +} from 'src/types/dashboards' +import {ColorString, ColorNumber} from 'src/types/colors' + +export interface TimeMachineState { + script: string + queryDrafts: CellQuery[] + timeRange: TimeRange + type: CellType + axes: Axes | null + tableOptions: TableOptions + fieldOptions: FieldOption[] + timeFormat: string + decimalPlaces: DecimalPlaces + note: string + noteVisibility: NoteVisibility + thresholdsListColors: ColorNumber[] + thresholdsListType: ThresholdType + gaugeColors: ColorNumber[] + lineColors: ColorString[] +} + +export class TimeMachineContainer extends Container { + constructor(initialState: Partial = {}) { + super() + + this.state = { + script: '', + queryDrafts: [], + timeRange: DEFAULT_TIME_RANGE, + type: CellType.Line, + note: '', + noteVisibility: NoteVisibility.Default, + thresholdsListType: ThresholdType.Text, + thresholdsListColors: DEFAULT_THRESHOLDS_LIST_COLORS, + gaugeColors: DEFAULT_GAUGE_COLORS, + lineColors: DEFAULT_LINE_COLORS, + axes: DEFAULT_AXES, + tableOptions: DEFAULT_TABLE_OPTIONS, + timeFormat: DEFAULT_TIME_FORMAT, + decimalPlaces: DEFAULT_DECIMAL_PLACES, + fieldOptions: DEFAULT_FIELD_OPTIONS, + ...initialState, + } + } + + public handleChangeScript = (script: string) => { + this.setState({script}) + } + + public handleUpdateTimeRange = (timeRange: TimeRange) => { + this.setState({timeRange}) + } + + public handleUpdateQueryDrafts = (queryDrafts: CellQuery[]) => { + this.setState({queryDrafts}) + } + + public handleToggleField = (queryID: string, fieldFunc: Field) => { + const {queryDrafts} = this.state + const updatedQueryDrafts = queryDrafts.map(query => { + if (query.id === queryID) { + const nextQueryConfig = { + ...toggleField(query.queryConfig, fieldFunc), + rawText: null, + } + + return { + ...query, + query: buildQuery( + TYPE_QUERY_CONFIG, + getTimeRange(nextQueryConfig), + nextQueryConfig + ), + queryConfig: nextQueryConfig, + } + } + return query + }) + + this.setState({queryDrafts: updatedQueryDrafts}) + } + + public handleGroupByTime = (queryID: string, time: string) => { + const {queryDrafts} = this.state + const updatedQueryDrafts = queryDrafts.map(query => { + if (query.id === queryID) { + const nextQueryConfig = groupByTime(query.queryConfig, time) + + return { + ...query, + query: buildQuery( + TYPE_QUERY_CONFIG, + getTimeRange(nextQueryConfig), + nextQueryConfig + ), + queryConfig: nextQueryConfig, + } + } + return query + }) + + this.setState({queryDrafts: updatedQueryDrafts}) + } + + public handleFill = (queryID: string, value: string) => { + const {queryDrafts} = this.state + const updatedQueryDrafts = queryDrafts.map(query => { + if (query.id === queryID) { + const nextQueryConfig = fill(query.queryConfig, value) + + return { + ...query, + query: buildQuery( + TYPE_QUERY_CONFIG, + getTimeRange(nextQueryConfig), + nextQueryConfig + ), + queryConfig: nextQueryConfig, + } + } + return query + }) + + this.setState({queryDrafts: updatedQueryDrafts}) + } + + public handleRemoveFuncs = (queryID: string, fields: Field[]) => { + this.updateQueryDrafts(queryID, q => removeFuncs(q, fields)) + } + + public handleApplyFuncsToField = ( + queryID: string, + fieldFunc: ApplyFuncsToFieldArgs, + groupBy?: GroupBy + ) => { + this.updateQueryDrafts(queryID, q => + applyFuncsToField(q, fieldFunc, groupBy) + ) + } + + public handleChooseTag = (queryID: string, tag: Tag) => { + this.updateQueryDrafts(queryID, q => chooseTag(q, tag)) + } + + public handleChooseNamespace = ( + queryID: string, + options: {database: string; retentionPolicy: string} + ) => { + this.updateQueryDrafts(queryID, q => chooseNamespace(q, options)) + } + + public handleChooseMeasurement = (queryID: string, measurement: string) => { + this.updateQueryDrafts(queryID, q => ({ + ...chooseMeasurement(q, measurement), + rawText: q.rawText || '', + })) + } + + public handleGroupByTag = (queryID: string, tagKey: string) => { + this.updateQueryDrafts(queryID, q => groupByTag(q, tagKey)) + } + + public handleToggleTagAcceptance = (queryID: string) => { + this.updateQueryDrafts(queryID, q => toggleTagAcceptance(q)) + } + + public handleAddInitialField = ( + queryID: string, + field: Field, + groupBy: GroupBy + ) => { + this.updateQueryDrafts(queryID, q => addInitialField(q, field, groupBy)) + } + + public handleEditQueryStatus = (queryID: string, status: Status) => { + this.updateQueryDrafts(queryID, q => ({...q, status})) + } + + public handleTimeShift = (queryID: string, shift: TimeShift) => { + this.updateQueryDrafts(queryID, q => timeShift(q, shift)) + } + + public handleAddQuery = () => { + const {queryDrafts} = this.state + const queryID = uuid.v4() + const newQueryDraft: CellQuery = { + query: '', + queryConfig: defaultQueryConfig({id: queryID}), + source: '', + id: queryID, + type: QueryType.InfluxQL, + } + + this.setState({queryDrafts: [...queryDrafts, newQueryDraft]}) + } + + public handleDeleteQuery = (queryID: string) => { + const {queryDrafts} = this.state + const updatedQueryDrafts = queryDrafts.filter(query => query.id !== queryID) + + this.setState({queryDrafts: updatedQueryDrafts}) + } + + public handleUpdateType = (type: CellType) => { + this.setState({type}) + } + + public handleUpdateNote = (note: string) => { + this.setState({note}) + } + + public handleUpdateNoteVisibility = (noteVisibility: NoteVisibility) => { + this.setState({noteVisibility}) + } + + public handleUpdateThresholdsListColors = ( + thresholdsListColors: ColorNumber[] + ) => { + this.setState({thresholdsListColors}) + } + + public handleUpdateThresholdsListType = ( + thresholdsListType: ThresholdType + ) => { + this.setState({thresholdsListType}) + } + + public handleUpdateGaugeColors = (gaugeColors: ColorNumber[]) => { + this.setState({gaugeColors}) + } + + public handleUpdateAxes = (axes: Axes) => { + this.setState({axes}) + } + + public handleUpdateTableOptions = (tableOptions: TableOptions) => { + this.setState({tableOptions}) + } + + public handleUpdateLineColors = (lineColors: ColorString[]) => { + this.setState({lineColors}) + } + + public handleUpdateTimeFormat = (timeFormat: string) => { + this.setState({timeFormat}) + } + + public handleUpdateDecimalPlaces = (decimalPlaces: DecimalPlaces) => { + this.setState({decimalPlaces}) + } + + public handleUpdateFieldOptions = (fieldOptions: FieldOption[]) => { + this.setState({fieldOptions}) + } + + private updateQueryDrafts = ( + queryID: string, + nextQueryConfigFn: ((q: QueryConfig) => QueryConfig) + ) => { + const {queryDrafts} = this.state + const updatedQueryDrafts = queryDrafts.map(query => { + if (query.id === queryID) { + const nextQueryConfig = nextQueryConfigFn(query.queryConfig) + + return { + ...query, + query: buildQuery( + TYPE_QUERY_CONFIG, + getTimeRange(nextQueryConfig), + nextQueryConfig + ), + queryConfig: nextQueryConfig, + } + } + return query + }) + + this.setState({queryDrafts: updatedQueryDrafts}) + } +} diff --git a/ui/src/shared/utils/timeMachine.ts b/ui/src/shared/utils/timeMachine.ts new file mode 100644 index 000000000..3a4e0a40b --- /dev/null +++ b/ui/src/shared/utils/timeMachine.ts @@ -0,0 +1,87 @@ +// Libraries +import uuid from 'uuid' +import {get} from 'lodash' + +// Utils +import defaultQueryConfig from 'src/utils/defaultQueryConfig' +import {getLineColors} from 'src/shared/constants/graphColorPalettes' +import { + getThresholdsListColors, + getGaugeColors, +} from 'src/shared/constants/thresholds' + +// Types +import {Cell, NewDefaultCell, CellQuery, QueryType} from 'src/types' +import {TimeMachineState} from 'src/shared/utils/TimeMachineContainer' + +export function intialStateFromCell( + cell: Cell | NewDefaultCell +): Partial { + const initialState: Partial = { + queryDrafts: initialQueryDrafts(cell), + type: cell.type, + fieldOptions: cell.fieldOptions, + timeFormat: cell.timeFormat, + decimalPlaces: cell.decimalPlaces, + note: cell.note, + noteVisibility: cell.noteVisibility, + } + + if (get(cell, 'queries.0.type') === QueryType.Flux) { + initialState.script = get(cell, 'queries.0.query', '') + } + + if (cell.tableOptions) { + initialState.tableOptions = cell.tableOptions + } + + const axes = (cell as Cell).axes + const colors = (cell as Cell).colors + + if (axes) { + initialState.axes = axes + } + + if (colors) { + initialState.gaugeColors = getGaugeColors(colors) + initialState.thresholdsListColors = getThresholdsListColors(colors) + initialState.lineColors = getLineColors(colors) + } + + return initialState +} + +function initialQueryDrafts(cell: Cell | NewDefaultCell): CellQuery[] { + const queries: CellQuery[] = get(cell, 'queries', []) + + if (!cell.queries.length) { + return [defaultQueryDraft(QueryType.InfluxQL)] + } + + if (cell.queries[0].type === QueryType.Flux) { + return [defaultQueryDraft(QueryType.Flux, cell.queries[0].source)] + } + + return queries.map(q => { + const id = uuid.v4() + const queryConfig = {...q.queryConfig, id} + + return {...q, queryConfig, id} + }) +} + +export function defaultQueryDraft( + type: QueryType, + source: string = '' +): CellQuery { + const id = uuid.v4() + const defaultDraft: CellQuery = { + id, + type, + source, + query: '', + queryConfig: defaultQueryConfig({id}), + } + + return defaultDraft +} diff --git a/ui/src/types/dataExplorer.ts b/ui/src/types/dataExplorer.ts index 2cabe5cdc..ffe209500 100644 --- a/ui/src/types/dataExplorer.ts +++ b/ui/src/types/dataExplorer.ts @@ -1,11 +1,5 @@ -import { - ThresholdColor, - GaugeColor, - LineColor, - ColorNumber, - ColorString, -} from 'src/types/colors' -import {TimeRange, CellQuery, QueryStatus, CellType, Axes} from 'src/types' +import {ColorNumber, ColorString} from 'src/types/colors' +import {CellType, Axes, Status} from 'src/types' import { DecimalPlaces, FieldOption, @@ -21,23 +15,11 @@ export enum WriteDataMode { } export interface DEState { - queryDrafts: CellQuery[] - timeRange: TimeRange - queryStatus: QueryStatus - script: string sourceLink: string - thresholdsListType: ThresholdType - thresholdsListColors: ThresholdColor[] - gaugeColors: GaugeColor[] - lineColors: LineColor[] - visType: CellType - axes: Axes - tableOptions: TableOptions - timeFormat: string - decimalPlaces: DecimalPlaces - fieldOptions: FieldOption[] - note: string - noteVisibility: NoteVisibility + queryStatus: { + queryID: string | null + status: Status + } } export interface VisualizationOptions { diff --git a/ui/src/types/flux.ts b/ui/src/types/flux.ts index a02fca599..6db6dd1ff 100644 --- a/ui/src/types/flux.ts +++ b/ui/src/types/flux.ts @@ -1,5 +1,4 @@ import {Service, Source, TimeRange, Query} from 'src/types' -import {VisualizationOptions} from 'src/types/dataExplorer' // function definitions export type OnDeleteFuncNode = (ids: DeleteFuncNodeArgs) => void @@ -44,7 +43,6 @@ export interface Context { source: Source timeRange: TimeRange queries: Query[] - visualizationOptions: VisualizationOptions } export interface DeleteFuncNodeArgs { diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index dd7f0f477..c6f6814d1 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -4,6 +4,7 @@ import {Links, Organization, Role, Permission, User, Me} from './auth' import { PBCell, Cell, + NewDefaultCell, CellQuery, Legend, Axes, @@ -81,6 +82,7 @@ export { TemplateQuery, TemplateValue, Cell, + NewDefaultCell, CellQuery, CellType, PBCell, diff --git a/ui/src/types/localStorage.ts b/ui/src/types/localStorage.ts index 76b64cc8c..c9eeb6a35 100644 --- a/ui/src/types/localStorage.ts +++ b/ui/src/types/localStorage.ts @@ -1,13 +1,10 @@ -import {QueryConfig, TimeRange} from 'src/types' -import {DEState} from 'src/types/dataExplorer' +import {TimeRange} from 'src/types' import {LogsState} from 'src/types/logs' export interface LocalStorage { VERSION: VERSION app: App dashTimeV1: DashTimeV1 - dataExplorer: DEState - dataExplorerQueryConfigs: DataExplorerQueryConfigs timeRange: TimeRange script: string logs: LogsState @@ -24,10 +21,6 @@ export interface DashTimeV1 { ranges: DashboardTimeRange[] } -export interface DataExplorerQueryConfigs { - [id: string]: QueryConfig -} - interface DashboardTimeRange { dashboardID: number defaultGroupBy: string diff --git a/ui/test/dashboards/reducers/cellEditorOverlay.test.ts b/ui/test/dashboards/reducers/cellEditorOverlay.test.ts index 6832f1454..1d3bea654 100644 --- a/ui/test/dashboards/reducers/cellEditorOverlay.test.ts +++ b/ui/test/dashboards/reducers/cellEditorOverlay.test.ts @@ -2,74 +2,16 @@ import reducer, {initialState} from 'src/dashboards/reducers/cellEditorOverlay' // Actions -import { - loadCEO, - clearCEO, - renameCell, - Action, -} from 'src/dashboards/actions/cellEditorOverlay' -import { - updateVisType, - updateThresholdsListColors, - updateThresholdsListType, - updateGaugeColors, - updateLineColors, - updateAxes, -} from 'src/shared/actions/visualizations' -import { - updateEditorTimeRange, - updateQueryDrafts, - QueryUpdateState, -} from 'src/shared/actions/queries' - -// Constants -import {DEFAULT_TABLE_OPTIONS} from 'src/dashboards/constants' -import { - validateGaugeColors, - validateThresholdsListColors, - getThresholdsListType, -} from 'src/shared/constants/thresholds' -import {validateLineColors} from 'src/shared/constants/graphColorPalettes' +import {clearCEO, renameCell} from 'src/dashboards/actions/cellEditorOverlay' // Fixtures -import {cell, axes, timeRange, query} from 'test/fixtures' - -// Types -import {Cell} from 'src/types/dashboards' +import {cell} from 'test/fixtures' const defaultCell = { ...cell, } -const defaultThresholdsListType = getThresholdsListType(defaultCell.colors) -const defaultThresholdsListColors = validateThresholdsListColors( - defaultCell.colors, - defaultThresholdsListType -) -const defaultGaugeColors = validateGaugeColors(defaultCell.colors) -const defaultLineColors = validateLineColors(defaultCell.colors) - -let state - describe('Dashboards.Reducers.cellEditorOverlay', () => { - it('should show cell editor overlay', () => { - const actual = reducer(initialState, loadCEO(defaultCell, timeRange)) - const expected = { - cell: defaultCell, - gaugeColors: defaultGaugeColors, - thresholdsListColors: defaultThresholdsListColors, - thresholdsListType: defaultThresholdsListType, - tableOptions: DEFAULT_TABLE_OPTIONS, - timeRange, - } - - expect(actual.cell).toEqual(expected.cell) - expect(actual.gaugeColors).toBe(expected.gaugeColors) - expect(actual.thresholdsListColors).toBe(expected.thresholdsListColors) - expect(actual.thresholdsListType).toBe(expected.thresholdsListType) - expect(actual.timeRange).toBe(expected.timeRange) - }) - it('should hide cell editor overlay', () => { const actual = reducer(initialState, clearCEO()) const expected = null @@ -78,94 +20,10 @@ describe('Dashboards.Reducers.cellEditorOverlay', () => { expect(actual.timeRange).toBe(expected) }) - it('should change the cell editor visualization type', () => { - const action = updateVisType( - defaultCell.type, - QueryUpdateState.CEO - ) as Action - const actual = reducer(initialState, action) - const expected = defaultCell.type - - expect(actual.cell.type).toBe(expected) - }) - it('should change the name of the cell', () => { const actual = reducer(initialState, renameCell(defaultCell.name)) const expected = defaultCell.name expect(actual.cell.name).toBe(expected) }) - - it('should update the cell single stat colors', () => { - const action = updateThresholdsListColors( - defaultThresholdsListColors, - QueryUpdateState.CEO - ) as Action - const actual = reducer(initialState, action) - const expected = defaultThresholdsListColors - - expect(actual.thresholdsListColors).toBe(expected) - }) - - it('should toggle the single stat type', () => { - const action = updateThresholdsListType( - defaultThresholdsListType, - QueryUpdateState.CEO - ) as Action - const actual = reducer(initialState, action) - const expected = defaultThresholdsListType - - expect(actual.thresholdsListType).toBe(expected) - }) - - it('should update the cell gauge colors', () => { - const action = updateGaugeColors( - defaultGaugeColors, - QueryUpdateState.CEO - ) as Action - const actual = reducer(initialState, action) - const expected = defaultGaugeColors - - expect(actual.gaugeColors).toBe(expected) - }) - - it('should update the cell axes', () => { - const action = updateAxes(axes, QueryUpdateState.CEO) as Action - const actual = reducer(initialState, action) - const expected = axes - const actualCell = actual.cell as Cell - - expect(actualCell.axes).toBe(expected) - }) - - it('should update the cell line graph colors', () => { - const action = updateLineColors( - defaultLineColors, - QueryUpdateState.CEO - ) as Action - const actual = reducer(initialState, action) - const expected = defaultLineColors - - expect(actual.lineColors).toBe(expected) - }) - - it('it can update a query', () => { - state = {queryDrafts: [query]} - const updatedQuery = {...query, source: '/chronograf/v1/sources/12'} - const queries = [updatedQuery] - const action = updateQueryDrafts(queries, QueryUpdateState.CEO) as Action - const actual = reducer(state, action) - expect(actual.queryDrafts).toEqual([updatedQuery]) - }) - - it('it can update a timeRange', () => { - state = {timeRange} - const updatedTimeRange = {...timeRange, lower: 'now() - 15m'} - const action = updateEditorTimeRange( - updatedTimeRange, - QueryUpdateState.CEO - ) as Action - const actual = reducer(state, action) - expect(actual.timeRange).toEqual(updatedTimeRange) - }) }) diff --git a/ui/test/data_explorer/containers/DataExplorer.test.tsx b/ui/test/data_explorer/containers/DataExplorer.test.tsx deleted file mode 100644 index 37ddee4b9..000000000 --- a/ui/test/data_explorer/containers/DataExplorer.test.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react' -import {DataExplorer} from 'src/data_explorer/containers/DataExplorer' -import {CellType} from 'src/types' -import {shallow} from 'enzyme' -import {source, query, timeRange} from 'test/resources' - -const queryConfigActions = { - chooseNamespace: jest.fn(), - chooseMeasurement: jest.fn(), - chooseTag: jest.fn(), - groupByTag: jest.fn(), - addQuery: jest.fn(), - toggleField: jest.fn(), - groupByTime: jest.fn(), - toggleTagAcceptance: jest.fn(), - applyFuncsToField: jest.fn(), - editRawTextAsync: jest.fn(), - addInitialField: jest.fn(), - editQueryStatus: jest.fn(), - deleteQuery: jest.fn(), - fill: jest.fn(), - removeFuncs: jest.fn(), - editRawText: jest.fn(), - setTimeRange: jest.fn(), - updateRawQuery: jest.fn(), - updateQueryConfig: jest.fn(), - timeShift: jest.fn(), -} - -const setup = () => { - const props = { - source, - sources: [source], - services: [], - queryConfigs: [query], - queryConfigActions, - autoRefresh: 1000, - handleChooseAutoRefresh: () => {}, - setTimeRange: () => {}, - timeRange, - manualRefresh: 0, - dashboards: [], - onManualRefresh: () => {}, - errorThrownAction: () => {}, - writeLineProtocol: () => {}, - handleGetDashboards: () => [], - addDashboardCell: jest.fn(() => Promise.resolve()), - updateQueryDrafts: jest.fn(() => Promise.resolve()), - loadDE: jest.fn(() => Promise.resolve()), - addQuery: jest.fn(() => Promise.resolve()), - deleteQuery: jest.fn(() => Promise.resolve()), - queryDrafts: [], - editQueryStatus: jest.fn(() => Promise.resolve()), - queryStatus: null, - fluxLinks: null, - script: '', - updateScript: jest.fn(), - fetchServicesAsync: jest.fn(), - notify: jest.fn(), - sourceLink: '', - updateSourceLink: jest.fn(), - thresholdsListType: null, - thresholdsListColors: [], - gaugeColors: [], - lineColors: [], - visType: CellType.Line, - axes: null, - tableOptions: null, - timeFormat: '', - decimalPlaces: null, - fieldOptions: [], - note: '', - noteVisibility: null, - sendDashboardCell: jest.fn(() => Promise.resolve()), - } - - const wrapper = shallow() - return { - wrapper, - } -} - -describe('DataExplorer.Containers.DataExplorer', () => { - describe('rendering', () => { - it('renders without errors', () => { - const {wrapper} = setup() - expect(wrapper.exists()).toBe(true) - }) - }) -}) diff --git a/ui/test/data_explorer/reducers/queries.test.ts b/ui/test/data_explorer/reducers/queries.test.ts deleted file mode 100644 index 8f700af57..000000000 --- a/ui/test/data_explorer/reducers/queries.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import reducer from 'src/data_explorer/reducers/queries' - -import { - updateQueryDrafts, - updateEditorTimeRange, - QueryUpdateState, -} from 'src/shared/actions/queries' -import {loadDE, Action} from 'src/data_explorer/actions/queries' - -import {query, timeRange} from 'test/fixtures' - -let state - -describe('DataExplorer.Reducers.Queries', () => { - it('loads the timeRange and queries', () => { - const actual = reducer(state, loadDE([query], timeRange)) - expect(actual.queryDrafts).toEqual([query]) - expect(actual.timeRange).toEqual(timeRange) - }) - - it('it can update a query', () => { - state = {queryDrafts: [query]} - const updatedQuery = {...query, source: '/chronograf/v1/sources/12'} - const queries = [updatedQuery] - const action = updateQueryDrafts(queries, QueryUpdateState.DE) as Action - const actual = reducer(state, action) - expect(actual.queryDrafts).toEqual([updatedQuery]) - }) - - it('it can update a timeRange', () => { - state = {timeRange} - const updatedTimeRange = {...timeRange, lower: 'now() - 15m'} - const action = updateEditorTimeRange( - updatedTimeRange, - QueryUpdateState.DE - ) as Action - const actual = reducer(state, action) - expect(actual.timeRange).toEqual(updatedTimeRange) - }) -}) diff --git a/ui/test/data_explorer/reducers/queryConfig.test.ts b/ui/test/data_explorer/reducers/queryConfig.test.ts deleted file mode 100644 index 12ae5b6f0..000000000 --- a/ui/test/data_explorer/reducers/queryConfig.test.ts +++ /dev/null @@ -1,599 +0,0 @@ -import reducer from 'src/data_explorer/reducers/queryConfigs' - -import defaultQueryConfig from 'src/utils/defaultQueryConfig' -import { - fill, - timeShift, - chooseTag, - groupByTag, - groupByTime, - toggleField, - removeFuncs, - editRawText, - updateRawQuery, - editQueryStatus, - chooseNamespace, - chooseMeasurement, - applyFuncsToField, - addInitialField, - updateQueryConfig, - toggleTagAcceptance, - ActionAddQuery, -} from 'src/data_explorer/actions/view' - -import {LINEAR, NULL_STRING} from 'src/shared/constants/queryFillOptions' - -const fakeAddQueryAction = (queryID: string): ActionAddQuery => { - return { - type: 'DE_ADD_QUERY', - payload: { - queryID, - }, - } -} - -function buildInitialState(queryID, params?) { - return { - ...defaultQueryConfig({ - id: queryID, - }), - ...params, - } -} - -describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { - const queryID = '123' - - it('can add a query', () => { - const state = reducer({}, fakeAddQueryAction(queryID)) - - const actual = state[queryID] - const expected = defaultQueryConfig({ - id: queryID, - }) - expect(actual).toEqual(expected) - }) - - describe('choosing db, rp, and measurement', () => { - let state - beforeEach(() => { - state = reducer({}, fakeAddQueryAction(queryID)) - }) - - it('sets the db and rp', () => { - const newState = reducer( - state, - chooseNamespace(queryID, { - database: 'telegraf', - retentionPolicy: 'monitor', - }) - ) - - expect(newState[queryID].database).toBe('telegraf') - expect(newState[queryID].retentionPolicy).toBe('monitor') - }) - - it('sets the measurement', () => { - const newState = reducer(state, chooseMeasurement(queryID, 'mem')) - - expect(newState[queryID].measurement).toBe('mem') - }) - }) - - describe('a query has measurements and fields', () => { - let state - beforeEach(() => { - const one = reducer({}, fakeAddQueryAction(queryID)) - const two = reducer( - one, - chooseNamespace(queryID, { - database: '_internal', - retentionPolicy: 'daily', - }) - ) - const three = reducer(two, chooseMeasurement(queryID, 'disk')) - const field = { - value: 'a great field', - type: 'field', - } - const groupBy = {} - - state = reducer(three, addInitialField(queryID, field, groupBy)) - }) - - describe('choosing a new namespace', () => { - it('clears out the old measurement and fields', () => { - // what about tags? - expect(state[queryID].measurement).toBe('disk') - expect(state[queryID].fields.length).toBe(1) - - const newState = reducer( - state, - chooseNamespace(queryID, { - database: 'newdb', - retentionPolicy: 'newrp', - }) - ) - - expect(newState[queryID].measurement).toBe(null) - expect(newState[queryID].fields.length).toBe(0) - }) - }) - - describe('choosing a new measurement', () => { - it('leaves the namespace and clears out the old fields', () => { - // what about tags? - expect(state[queryID].fields.length).toBe(1) - - const newState = reducer( - state, - chooseMeasurement(queryID, 'newmeasurement') - ) - - expect(state[queryID].database).toBe(newState[queryID].database) - expect(state[queryID].retentionPolicy).toBe( - newState[queryID].retentionPolicy - ) - expect(newState[queryID].fields.length).toBe(0) - }) - }) - - describe('DE_TOGGLE_FIELD', () => { - it('can toggle multiple fields', () => { - expect(state[queryID].fields.length).toBe(1) - - const newState = reducer( - state, - toggleField(queryID, { - value: 'f2', - type: 'field', - }) - ) - - expect(newState[queryID].fields.length).toBe(2) - expect(newState[queryID].fields[1].alias).toEqual('mean_f2') - expect(newState[queryID].fields[1].args).toEqual([ - { - value: 'f2', - type: 'field', - }, - ]) - expect(newState[queryID].fields[1].value).toEqual('mean') - }) - - it('applies a func to newly selected fields', () => { - expect(state[queryID].fields.length).toBe(1) - expect(state[queryID].fields[0].type).toBe('func') - expect(state[queryID].fields[0].value).toBe('mean') - - const newState = reducer( - state, - toggleField(queryID, { - value: 'f2', - type: 'field', - }) - ) - - expect(newState[queryID].fields[1].value).toBe('mean') - expect(newState[queryID].fields[1].alias).toBe('mean_f2') - expect(newState[queryID].fields[1].args).toEqual([ - { - value: 'f2', - type: 'field', - }, - ]) - expect(newState[queryID].fields[1].type).toBe('func') - }) - - it('adds the field property to query config if not found', () => { - delete state[queryID].fields - expect(state[queryID].fields).toBe(undefined) - - const newState = reducer( - state, - toggleField(queryID, { - value: 'fk1', - type: 'field', - }) - ) - - expect(newState[queryID].fields.length).toBe(1) - }) - }) - }) - - describe('DE_APPLY_FUNCS_TO_FIELD', () => { - it('applies new functions to a field', () => { - const f1 = { - value: 'f1', - type: 'field', - } - const f2 = { - value: 'f2', - type: 'field', - } - - const initialState = { - [queryID]: buildInitialState(queryID, { - id: '123', - database: 'db1', - measurement: 'm1', - fields: [ - { - value: 'fn1', - type: 'func', - args: [f1], - alias: `fn1_${f1.value}`, - }, - { - value: 'fn1', - type: 'func', - args: [f2], - alias: `fn1_${f2.value}`, - }, - { - value: 'fn2', - type: 'func', - args: [f1], - alias: `fn2_${f1.value}`, - }, - ], - }), - } - - const action = applyFuncsToField(queryID, { - field: { - value: 'f1', - type: 'field', - }, - funcs: [ - { - value: 'fn3', - type: 'func', - }, - { - value: 'fn4', - type: 'func', - }, - ], - }) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].fields).toEqual([ - { - value: 'fn3', - type: 'func', - args: [f1], - alias: `fn3_${f1.value}`, - }, - { - value: 'fn4', - type: 'func', - args: [f1], - alias: `fn4_${f1.value}`, - }, - { - value: 'fn1', - type: 'func', - args: [f2], - alias: `fn1_${f2.value}`, - }, - ]) - }) - }) - - describe('DE_REMOVE_FUNCS', () => { - it('removes all functions and group by time when one field has no funcs applied', () => { - const f1 = { - value: 'f1', - type: 'field', - } - const f2 = { - value: 'f2', - type: 'field', - } - const fields = [ - { - value: 'fn1', - type: 'func', - args: [f1], - alias: `fn1_${f1.value}`, - }, - { - value: 'fn1', - type: 'func', - args: [f2], - alias: `fn1_${f2.value}`, - }, - ] - const groupBy = { - time: '1m', - tags: [], - } - - const initialState = { - [queryID]: buildInitialState(queryID, { - id: '123', - database: 'db1', - measurement: 'm1', - fields, - groupBy, - }), - } - - const action = removeFuncs(queryID, fields, groupBy) - - const nextState = reducer(initialState, action) - const actual = nextState[queryID].fields - const expected = [f1, f2] - - expect(actual).toEqual(expected) - expect(nextState[queryID].groupBy.time).toBe(null) - }) - }) - - describe('DE_CHOOSE_TAG', () => { - it('adds a tag key/value to the query', () => { - const initialState = { - [queryID]: buildInitialState(queryID, { - tags: { - k1: ['v0'], - k2: ['foo'], - }, - }), - } - - const action = chooseTag(queryID, { - key: 'k1', - value: 'v1', - }) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].tags).toEqual({ - k1: ['v0', 'v1'], - k2: ['foo'], - }) - }) - - it("creates a new entry if it's the first key", () => { - const initialState = { - [queryID]: buildInitialState(queryID, { - tags: {}, - }), - } - const action = chooseTag(queryID, { - key: 'k1', - value: 'v1', - }) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].tags).toEqual({ - k1: ['v1'], - }) - }) - - it('removes a value that is already in the list', () => { - const initialState = { - [queryID]: buildInitialState(queryID, { - tags: { - k1: ['v1'], - }, - }), - } - const action = chooseTag(queryID, { - key: 'k1', - value: 'v1', - }) - - const nextState = reducer(initialState, action) - - // TODO: this should probably remove the `k1` property entirely from the tags object - expect(nextState[queryID].tags).toEqual({}) - }) - }) - - describe('DE_GROUP_BY_TAG', () => { - it('adds a tag key/value to the query', () => { - const initialState = { - [queryID]: buildInitialState(queryID, { - id: '123', - database: 'db1', - measurement: 'm1', - fields: [], - tags: {}, - groupBy: { - tags: [], - time: null, - }, - }), - } - const action = groupByTag(queryID, 'k1') - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].groupBy).toEqual({ - time: null, - tags: ['k1'], - }) - }) - - it('removes a tag if the given tag key is already in the GROUP BY list', () => { - const query = { - id: '123', - database: 'db1', - measurement: 'm1', - fields: [], - tags: {}, - groupBy: { - tags: ['k1'], - time: null, - }, - } - - const initialState = { - [queryID]: buildInitialState(queryID, query), - } - const action = groupByTag(queryID, 'k1') - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].groupBy).toEqual({ - time: null, - tags: [], - }) - }) - }) - - describe('DE_TOGGLE_TAG_ACCEPTANCE', () => { - it('it toggles areTagsAccepted', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const action = toggleTagAcceptance(queryID) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].areTagsAccepted).toBe( - !initialState[queryID].areTagsAccepted - ) - }) - }) - - describe('DE_GROUP_BY_TIME', () => { - it('applys the appropriate group by time', () => { - const time = '100y' - const initialState = { - [queryID]: buildInitialState(queryID), - } - - const action = groupByTime(queryID, time) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].groupBy.time).toBe(time) - }) - }) - - it('updates entire config', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const id = {id: queryID} - const expected = defaultQueryConfig(id) - const action = updateQueryConfig(expected) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID]).toEqual(expected) - }) - - it("updates a query's raw text", () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const text = 'foo' - const action = updateRawQuery(queryID, text) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].rawText).toBe('foo') - }) - - it("updates a query's raw status", () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const status = {success: 'Your query was very nice'} - const action = editQueryStatus(queryID, status) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].status).toEqual(status) - }) - - describe('DE_FILL', () => { - it('applies an explicit fill when group by time is used', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const time = '10s' - const action = groupByTime(queryID, time) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].fill).toBe(NULL_STRING) - }) - - it('updates fill to non-null-string non-number string value', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const action = fill(queryID, LINEAR) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].fill).toBe(LINEAR) - }) - - it('updates fill to string integer value', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const INT_STRING = '1337' - const action = fill(queryID, INT_STRING) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].fill).toBe(INT_STRING) - }) - - it('updates fill to string float value', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - const FLOAT_STRING = '1.337' - const action = fill(queryID, FLOAT_STRING) - - const nextState = reducer(initialState, action) - - expect(nextState[queryID].fill).toBe(FLOAT_STRING) - }) - }) - - describe('DE_TIME_SHIFT', () => { - it('can shift the time', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - - const shift = { - quantity: '1', - unit: 'd', - duration: '1d', - label: 'label', - } - - const action = timeShift(queryID, shift) - const nextState = reducer(initialState, action) - - expect(nextState[queryID].shifts).toEqual([shift]) - }) - }) - - describe('DE_EDIT_RAW_TEXT', () => { - it('can edit the raw text', () => { - const initialState = { - [queryID]: buildInitialState(queryID), - } - - const rawText = 'im the raw text' - const action = editRawText(queryID, rawText) - const nextState = reducer(initialState, action) - - expect(nextState[queryID].rawText).toEqual(rawText) - }) - }) -}) diff --git a/ui/yarn.lock b/ui/yarn.lock index 7f22a02f1..7359f3899 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2126,6 +2126,10 @@ create-react-class@^15.5.1, create-react-class@^15.5.2, create-react-class@^15.5 loose-envify "^1.3.1" object-assign "^4.1.1" +create-react-context@^0.1.5: + version "0.1.6" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.1.6.tgz#0f425931d907741127acc6e31acb4f9015dd9fdc" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -8722,6 +8726,12 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +unstated@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/unstated/-/unstated-2.1.1.tgz#36b124dfb2e7a12d39d0bb9c46dfb6e51276e3a2" + dependencies: + create-react-context "^0.1.5" + upath@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd"