Move TimeMachine state to new state container

Co-authored-by: Iris Scholten <iris@influxdata.com>
Co-authored-by: Chris Henn <chris@chrishenn.net>
pull/4513/head
Iris Scholten 2018-09-25 11:16:15 -07:00 committed by Chris Henn
parent cb3632af4c
commit 3d99060582
42 changed files with 1380 additions and 3612 deletions

View File

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

View File

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

View File

@ -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<Props, State> {
private overlayRef: React.RefObject<HTMLDivElement> = React.createRef()
private timeMachineContainer: TimeMachineContainer
public constructor(props: Props) {
super(props)
const {cell} = props
const legend = getDeep<DashboardsModels.Legend | null>(cell, 'legend', null)
this.timeMachineContainer = new TimeMachineContainer({
...intialStateFromCell(props.cell),
timeRange: props.dashboardTimeRange,
})
const legend = getDeep<Legend | null>(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<Props, State> {
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 (
<div
className="deceo--overlay"
onKeyDown={this.handleKeyDown}
tabIndex={0}
ref={this.overlayRef}
>
<TimeMachine
fluxLinks={fluxLinks}
notify={notify}
script={script}
queryDrafts={queryDrafts}
updateScript={this.props.updateScript}
editQueryStatus={editQueryStatus}
templates={templates}
timeRange={timeRange}
source={source}
onResetFocus={this.handleResetFocus}
isInCEO={true}
sources={sources}
services={services}
updateQueryDrafts={updateQueryDrafts}
onToggleStaticLegend={this.handleToggleStaticLegend}
isStaticLegend={isStaticLegend}
queryConfigActions={this.props.queryConfigActions}
addQuery={addQuery}
deleteQuery={deleteQuery}
updateEditorTimeRange={updateEditorTimeRange}
visualizationOptions={this.visualizationOptions}
queryStatus={queryStatus}
updateScriptStatus={this.updateScriptStatus}
<Provider inject={[this.timeMachineContainer]}>
<div
className="deceo--overlay"
onKeyDown={this.handleKeyDown}
tabIndex={0}
ref={this.overlayRef}
>
{(activeEditorTab, onSetActiveEditorTab) => (
<CEOHeader
title={_.get(cell, 'name', '')}
renameCell={renameCell}
onSave={this.handleSaveCell}
onCancel={onCancel}
activeEditorTab={activeEditorTab}
onSetActiveEditorTab={onSetActiveEditorTab}
isSaveable={this.isSaveable}
/>
)}
</TimeMachine>
</div>
<TimeMachine
fluxLinks={fluxLinks}
notify={notify}
editQueryStatus={editQueryStatus}
templates={templates}
source={source}
onResetFocus={this.handleResetFocus}
isInCEO={true}
sources={sources}
services={services}
onToggleStaticLegend={this.handleToggleStaticLegend}
isStaticLegend={isStaticLegend}
queryStatus={queryStatus}
updateScriptStatus={this.updateScriptStatus}
>
{(activeEditorTab, onSetActiveEditorTab) => (
<CEOHeader
title={_.get(cell, 'name', '')}
renameCell={renameCell}
onSave={this.handleSaveCell}
onCancel={onCancel}
activeEditorTab={activeEditorTab}
onSetActiveEditorTab={onSetActiveEditorTab}
isSaveable={this.isSaveable}
/>
)}
</TimeMachine>
</div>
</Provider>
)
}
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<Props, State> {
})
}
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<string>(queryDrafts, '0.type', '') === QueryType.Flux) {
return true
@ -241,18 +179,26 @@ class CellEditorOverlay extends Component<Props, State> {
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<Props, State> {
}
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 => {

View File

@ -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<Props, State> {
}
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: () => (
<GraphTypeSelector
type={type}
onUpdateVisType={this.handleUpdateVisType}
/>
<GraphTypeSelector type={type} onUpdateVisType={onUpdateType} />
),
headerOrientation: HANDLE_VERTICAL,
},
@ -132,8 +136,8 @@ class DisplayOptions extends Component<Props, State> {
<CellNoteEditor
note={note || ''}
noteVisibility={noteVisibility}
onUpdateNote={this.handleUpdateNote}
onUpdateNoteVisibility={this.handleUpdateNoteVisibility}
onUpdateNote={onUpdateNote}
onUpdateNoteVisibility={onUpdateNoteVisibility}
/>
),
headerOrientation: HANDLE_VERTICAL,
@ -156,6 +160,15 @@ class DisplayOptions extends Component<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
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<Props, State> {
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 (
<Subscribe to={[TimeMachineContainer]}>
{(timeMachineContainer: TimeMachineContainer) => (
<DisplayOptions
{...props}
type={timeMachineContainer.state.type}
axes={timeMachineContainer.state.axes}
tableOptions={timeMachineContainer.state.tableOptions}
fieldOptions={timeMachineContainer.state.fieldOptions}
timeFormat={timeMachineContainer.state.timeFormat}
decimalPlaces={timeMachineContainer.state.decimalPlaces}
note={timeMachineContainer.state.note}
noteVisibility={timeMachineContainer.state.noteVisibility}
thresholdsListColors={timeMachineContainer.state.thresholdsListColors}
thresholdsListType={timeMachineContainer.state.thresholdsListType}
gaugeColors={timeMachineContainer.state.gaugeColors}
lineColors={timeMachineContainer.state.lineColors}
onUpdateType={timeMachineContainer.handleUpdateType}
onUpdateAxes={timeMachineContainer.handleUpdateAxes}
onUpdateTableOptions={timeMachineContainer.handleUpdateTableOptions}
onUpdateFieldOptions={timeMachineContainer.handleUpdateFieldOptions}
onUpdateTimeFormat={timeMachineContainer.handleUpdateTimeFormat}
onUpdateDecimalPlaces={timeMachineContainer.handleUpdateDecimalPlaces}
onUpdateNote={timeMachineContainer.handleUpdateNote}
onUpdateNoteVisibility={
timeMachineContainer.handleUpdateNoteVisibility
}
onUpdateThresholdsListColors={
timeMachineContainer.handleUpdateThresholdsListColors
}
onUpdateThresholdsListType={
timeMachineContainer.handleUpdateThresholdsListType
}
onUpdateGaugeColors={timeMachineContainer.handleUpdateGaugeColors}
onUpdateLineColors={timeMachineContainer.handleUpdateLineColors}
/>
)}
</Subscribe>
)
}
export default connect(null, mdtp)(DisplayOptions)
export default ConnectedDisplayOptions

View File

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

View File

@ -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<Props, State> {
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<Props, State> {
templatesIncludingDashTime = []
}
const {dashboardLinks, showAnnotationControls} = this.state
const {
dashboardLinks,
showAnnotationControls,
selectedCell,
showCellEditorOverlay,
} = this.state
return (
<Page>
<OverlayTechnology visible={this.showCellEditorOverlay}>
<OverlayTechnology visible={showCellEditorOverlay}>
<CellEditorOverlay
source={source}
sources={sources}
notify={notify}
fluxLinks={fluxLinks}
script={script}
services={this.services}
cell={selectedCell}
queryDrafts={queryDrafts}
timeRange={editorTimeRange}
updateEditorTimeRange={updateEditorTimeRange}
dashboardID={dashboardID}
queryStatus={cellQueryStatus}
onSave={this.handleSaveEditedCell}
onCancel={this.handleHideCellEditorOverlay}
templates={templatesIncludingDashTime}
editQueryStatus={this.props.editCellQueryStatus}
thresholdsListType={thresholdsListType}
thresholdsListColors={thresholdsListColors}
updateQueryDrafts={updateQueryDrafts}
gaugeColors={gaugeColors}
lineColors={lineColors}
renameCell={renameCell}
queryConfigActions={this.queryConfigActions}
addQuery={addQuery}
deleteQuery={deleteQuery}
updateScript={this.props.updateScript}
dashboardTimeRange={timeRange}
/>
</OverlayTechnology>
<DashboardHeader
@ -424,43 +370,6 @@ class DashboardPage extends Component<Props, State> {
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<Props, State> {
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<Props, State> {
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)(

View File

@ -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<TableOptions>(
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<string>(cell, 'queries.0.source', '')
if (getDeep<string>(cell, 'queries.0.type', '') === QueryType.Flux) {
script = getDeep<string>(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

View File

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

View File

@ -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<Cell>
) => 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<Props, State> {
}
}
export default SendToDashboardOverlay
const ConnectedSendToDashboardOverlay = (props: PassedProps) => {
return (
<Subscribe to={[TimeMachineContainer]}>
{(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 (
<SendToDashboardOverlay
{...props}
visualizationOptions={visualizationOptions}
/>
)
}}
</Subscribe>
)
}
export default ConnectedSendToDashboardOverlay

View File

@ -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'
)

View File

@ -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<Cell>
) => 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<Props, State> {
}
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<Props, State> {
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<Props, State> {
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<Props, State> {
<TimeMachine
service={this.service}
updateSourceLink={updateSourceLink}
queryDrafts={queryDrafts}
editQueryStatus={editQueryStatus}
templates={this.templates}
timeRange={timeRange}
source={source}
onResetFocus={this.handleResetFocus}
isInCEO={false}
sources={sources}
services={services}
updateQueryDrafts={updateQueryDrafts}
onToggleStaticLegend={this.handleToggleStaticLegend}
isStaticLegend={isStaticLegend}
queryConfigActions={this.props.queryConfigActions}
addQuery={addQuery}
deleteQuery={deleteQuery}
updateEditorTimeRange={this.handleChooseTimeRange}
manualRefresh={manualRefresh}
queryStatus={queryStatus}
script={this.activeScript}
updateScript={this.handleUpdateScript}
fluxLinks={fluxLinks}
notify={notify}
visualizationOptions={this.visualizationOptions}
>
{(activeEditorTab, onSetActiveEditorTab) => (
<DEHeader
@ -274,42 +210,79 @@ export class DataExplorer extends PureComponent<Props, State> {
)
}
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<Props, State> {
source,
dashboards,
sendDashboardCell,
script,
handleGetDashboards,
notify,
script,
} = this.props
const {isSendToDashboardVisible, isStaticLegend} = this.state
@ -367,7 +340,6 @@ export class DataExplorer extends PureComponent<Props, State> {
dashboards={dashboards}
handleGetDashboards={handleGetDashboards}
sendDashboardCell={sendDashboardCell}
visualizationOptions={this.visualizationOptions}
isStaticLegend={isStaticLegend}
/>
</OverlayTechnology>
@ -376,9 +348,7 @@ export class DataExplorer extends PureComponent<Props, State> {
}
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<Props, State> {
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<Props, State> {
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<Props, State> {
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 (
<Subscribe to={[TimeMachineContainer]}>
{(timeMachineContainer: TimeMachineContainer) => (
<DataExplorer
{...props}
queryDrafts={timeMachineContainer.state.queryDrafts}
timeRange={timeMachineContainer.state.timeRange}
script={timeMachineContainer.state.script}
onChangeScript={timeMachineContainer.handleChangeScript}
onUpdateQueryDrafts={timeMachineContainer.handleUpdateQueryDrafts}
/>
)}
</Subscribe>
)
}
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))
)

View File

@ -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<Props> {
private timeMachineContainer: TimeMachineContainer
constructor(props: Props) {
super(props)
this.timeMachineContainer = new TimeMachineContainer()
}
public render() {
return (
<div className="page">
<DataExplorer source={this.props.source} />
<Provider inject={[this.timeMachineContainer]}>
<DataExplorer source={this.props.source} />
</Provider>
</div>
)
}

View File

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

View File

@ -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<QueryConfig>
}
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

View File

@ -61,7 +61,6 @@ class ExpressionNode extends PureComponent<Props, State> {
service,
data,
scriptUpToYield,
visualizationOptions,
source,
timeRange,
queries,
@ -140,7 +139,6 @@ class ExpressionNode extends PureComponent<Props, State> {
queries={queries}
timeRange={timeRange}
declarationID={declarationID}
visualizationOptions={visualizationOptions}
/>
</Fragment>
)

View File

@ -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<Props, State> {
decimalPlaces,
editorLocation,
handleSetHoverTime,
onUpdateFieldOptions,
} = this.props
return (
<div className="time-machine-tables">
{this.showSidebar && (
@ -98,6 +101,7 @@ class TimeMachineTables extends PureComponent<Props, State> {
decimalPlaces={decimalPlaces}
editorLocation={editorLocation}
handleSetHoverTime={handleSetHoverTime}
onUpdateFieldOptions={onUpdateFieldOptions}
/>
)}
{!this.hasResults && <NoResults />}

View File

@ -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<Props, State> {
private timeSeries: React.RefObject<TimeSeries> = 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<Props, State> {
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<Props, State> {
<YieldNodeVis
data={timeSeriesFlux}
yieldName={yieldName}
visualizationOptions={visualizationOptions}
axes={axes}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
thresholdsListColors={thresholdsListColors}
gaugeColors={gaugeColors}
lineColors={lineColors}
/>
)}
</TimeSeries>
@ -74,22 +113,46 @@ class YieldFuncNode extends PureComponent<Props, State> {
)
}
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 (
<Subscribe to={[TimeMachineContainer]}>
{(timeMachineContainer: TimeMachineContainer) => (
<YieldFuncNode
{...props}
axes={timeMachineContainer.state.axes}
tableOptions={timeMachineContainer.state.tableOptions}
fieldOptions={timeMachineContainer.state.fieldOptions}
timeFormat={timeMachineContainer.state.timeFormat}
decimalPlaces={timeMachineContainer.state.decimalPlaces}
note={timeMachineContainer.state.note}
noteVisibility={timeMachineContainer.state.noteVisibility}
thresholdsListColors={timeMachineContainer.state.thresholdsListColors}
thresholdsListType={timeMachineContainer.state.thresholdsListType}
gaugeColors={timeMachineContainer.state.gaugeColors}
lineColors={timeMachineContainer.state.lineColors}
/>
)}
</Subscribe>
)
}
export default ConnectedYieldFuncNode

View File

@ -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<Props, State> {
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<Props, State> {
thresholdsListColors,
gaugeColors,
lineColors,
} = visualizationOptions
} = this.props
if (visType === VisType.Line) {
return (

View File

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

View File

@ -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<string>(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<void>
}
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,
}

View File

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

View File

@ -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<P>(
WrappedComponent: ComponentClass<P & ManualRefreshProps>
WrappedComponent:
| ComponentClass<P & ManualRefreshProps>
| StatelessComponent<P & ManualRefreshProps>
): ComponentClass<P> {
return class extends Component<P & ManualRefreshProps, ManualRefreshState> {
public constructor(props: P & ManualRefreshProps) {

View File

@ -88,6 +88,7 @@ interface Props {
cellNoteVisibility: NoteVisibility
editorLocation?: QueryUpdateState
onUpdateCellColors?: (bgColor: string, textColor: string) => void
onUpdateFieldOptions?: (fieldOptions: FieldOption[]) => void
}
class RefreshingGraph extends PureComponent<Props> {
@ -242,6 +243,7 @@ class RefreshingGraph extends PureComponent<Props> {
manualRefresh,
handleSetHoverTime,
editorLocation,
onUpdateFieldOptions,
} = this.props
const {dataType, data} = this.getTypeAndData(influxQLData, fluxData)
@ -258,6 +260,7 @@ class RefreshingGraph extends PureComponent<Props> {
decimalPlaces={decimalPlaces}
editorLocation={editorLocation}
handleSetHoverTime={handleSetHoverTime}
onUpdateFieldOptions={onUpdateFieldOptions}
/>
)
}
@ -274,6 +277,7 @@ class RefreshingGraph extends PureComponent<Props> {
decimalPlaces={decimalPlaces}
editorLocation={editorLocation}
handleSetHoverTime={handleSetHoverTime}
onUpdateFieldOptions={onUpdateFieldOptions}
/>
)
}

View File

@ -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<Props> = ({
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<Props> = ({
<DatabaseList
query={query}
querySource={source}
onChooseNamespace={actionBinder(id, isInCEO, chooseNamespace)}
onChooseNamespace={actionBinder(id, onChooseNamespace)}
/>
<MeasurementList
query={query}
querySource={source}
onChooseTag={actionBinder(id, isInCEO, chooseTag)}
onGroupByTag={actionBinder(id, isInCEO, groupByTag)}
onChooseMeasurement={actionBinder(id, isInCEO, chooseMeasurement)}
onToggleTagAcceptance={actionBinder(id, isInCEO, toggleTagAcceptance)}
onChooseTag={actionBinder(id, onChooseTag)}
onGroupByTag={actionBinder(id, onGroupByTag)}
onChooseMeasurement={actionBinder(id, onChooseMeasurement)}
onToggleTagAcceptance={actionBinder(id, onToggleTagAcceptance)}
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
/>
<FieldList
source={source}
query={query}
querySource={source}
onFill={actionBinder(id, isInCEO, fill)}
onFill={actionBinder(id, onFill)}
initialGroupByTime={initialGroupByTime}
onTimeShift={actionBinder(id, isInCEO, timeShift)}
removeFuncs={actionBinder(id, isInCEO, removeFuncs)}
onToggleField={actionBinder(id, isInCEO, toggleField)}
onGroupByTime={actionBinder(id, isInCEO, groupByTime)}
addInitialField={actionBinder(id, isInCEO, addInitialField)}
applyFuncsToField={actionBinder(id, isInCEO, applyFuncsToField)}
onTimeShift={actionBinder(id, onTimeShift)}
removeFuncs={actionBinder(id, onRemoveFuncs)}
onToggleField={actionBinder(id, onToggleField)}
onGroupByTime={actionBinder(id, onGroupByTime)}
addInitialField={actionBinder(id, onAddInitialField)}
applyFuncsToField={actionBinder(id, onApplyFuncsToField)}
isQuerySupportedByExplorer={isQuerySupportedByExplorer}
/>
</div>

View File

@ -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<Props, State> {
}
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)

View File

@ -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<void>
}
type Props = ConnectedProps & PassedProps
const QueryMaker: SFC<Props> = ({
source,
isInCEO,
actions,
queries,
timeRange,
templates,
@ -45,8 +62,20 @@ const QueryMaker: SFC<Props> = ({
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<Props> = ({
<InfluxQLEditor
query={buildText(activeQuery)}
config={activeQuery}
onUpdate={actions.editRawTextAsync}
onUpdate={onEditRawText}
templates={templates}
/>
<SchemaExplorer
source={source}
actions={actions}
query={activeQuery}
initialGroupByTime={initialGroupByTime}
initialGroupByTime={AUTO_GROUP_BY}
isQuerySupportedByExplorer={_.get(
activeQuery,
'isQuerySupportedByExplorer',
true
)}
isInCEO={isInCEO}
onFill={onFill}
onTimeShift={onTimeShift}
onChooseTag={onChooseTag}
onGroupByTag={onGroupByTag}
onGroupByTime={onGroupByTime}
onToggleField={onToggleField}
onRemoveFuncs={onRemoveFuncs}
onAddInitialField={onAddInitialField}
onChooseNamespace={onChooseNamespace}
onChooseMeasurement={onChooseMeasurement}
onApplyFuncsToField={onApplyFuncsToField}
onToggleTagAcceptance={onToggleTagAcceptance}
/>
</div>
</div>
@ -92,4 +131,27 @@ const QueryMaker: SFC<Props> = ({
)
}
export default QueryMaker
const ConnectedQueryMaker = (props: PassedProps) => (
<Subscribe to={[TimeMachineContainer]}>
{(container: TimeMachineContainer) => (
<QueryMaker
{...props}
timeRange={container.state.timeRange}
onFill={container.handleFill}
onTimeShift={container.handleTimeShift}
onChooseTag={container.handleChooseTag}
onGroupByTag={container.handleGroupByTag}
onGroupByTime={container.handleGroupByTime}
onToggleField={container.handleToggleField}
onRemoveFuncs={container.handleRemoveFuncs}
onAddInitialField={container.handleAddInitialField}
onChooseNamespace={container.handleChooseNamespace}
onChooseMeasurement={container.handleChooseMeasurement}
onApplyFuncsToField={container.handleApplyFuncsToField}
onToggleTagAcceptance={container.handleToggleTagAcceptance}
/>
)}
</Subscribe>
)
export default ConnectedQueryMaker

View File

@ -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<Props, State> {
private debouncedASTResponse: ScriptFunc
private validAST: boolean = true
@ -207,7 +203,7 @@ class TimeMachine extends PureComponent<Props, State> {
}
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<Props, State> {
isDynamicSourceSelected={this.useDynamicSource}
timeRange={timeRange}
updateEditorTimeRange={this.handleUpdateEditorTimeRange}
isInCEO={isInCEO}
/>
<div className="deceo--container">
<Threesizer
@ -267,70 +262,22 @@ class TimeMachine extends PureComponent<Props, State> {
}
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 (
<div className="deceo--top">
<div className="deceo--visualization">
<div className="graph-container">
<RefreshingGraph
source={this.source}
service={this.service}
colors={colors}
autoRefresher={autoRefresher}
queries={this.queriesForVis}
templates={templates}
editQueryStatus={this.handleEditQueryStatus}
staticLegend={isStaticLegend}
timeRange={timeRange}
manualRefresh={manualRefresh}
editorLocation={this.stateToUpdate}
showRawFluxData={isViewingRawData}
type={type}
axes={axes}
tableOptions={tableOptions}
fieldOptions={fieldOptions}
timeFormat={timeFormat}
decimalPlaces={decimalPlaces}
note={note}
noteVisibility={noteVisibility}
thresholdsListColors={thresholdsListColors}
thresholdsListType={thresholdsListType}
gaugeColors={gaugeColors}
lineColors={lineColors}
cellNote={note}
cellNoteVisibility={noteVisibility}
/>
</div>
</div>
</div>
<TimeMachineVisualization
source={this.source}
service={this.service}
autoRefresher={autoRefresher}
queries={this.queriesForVis}
templates={templates}
onEditQueryStatus={this.handleEditQueryStatus}
staticLegend={isStaticLegend}
manualRefresh={manualRefresh}
editorLocation={this.stateToUpdate}
showRawFluxData={isViewingRawData}
/>
)
}
@ -339,18 +286,14 @@ class TimeMachine extends PureComponent<Props, State> {
}
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<Props, State> {
staticLegend={isStaticLegend}
onResetFocus={onResetFocus}
stateToUpdate={this.stateToUpdate}
{...visualizationOptions}
/>
)
}
@ -435,6 +377,7 @@ class TimeMachine extends PureComponent<Props, State> {
if (!queryDrafts || !queryDrafts.length) {
return []
}
return queryDrafts.map(q => {
if (queryStatus.queryID === q.id) {
return {
@ -506,23 +449,20 @@ class TimeMachine extends PureComponent<Props, State> {
}
private get influxQLBuilder(): JSX.Element {
const {isInCEO, templates, timeRange} = this.props
const {templates} = this.props
const {activeQueryIndex} = this.state
return (
<InfluxQLQueryMaker
isInCEO={isInCEO}
source={this.source}
templates={templates}
queries={this.queriesWorkingDraft}
actions={this.queryConfigActions}
timeRange={timeRange}
onDeleteQuery={this.handleDeleteQuery}
onAddQuery={this.handleAddQuery}
activeQueryIndex={activeQueryIndex}
activeQuery={this.activeQuery}
setActiveQueryIndex={this.handleSetActiveQueryIndex}
initialGroupByTime={AUTO_GROUP_BY}
onEditRawText={this.handleEditRawText}
/>
)
}
@ -536,6 +476,7 @@ class TimeMachine extends PureComponent<Props, State> {
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<Props, State> {
}
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<Props, State> {
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<void> => {
const {templates, updateQueryDrafts, queryDrafts} = this.props
const {templates, onUpdateQueryDrafts, queryDrafts} = this.props
const id = this.activeQuery.id
const url = getDeep<string>(this.source, 'links.queries', '')
@ -675,7 +610,7 @@ class TimeMachine extends PureComponent<Props, State> {
return q
})
updateQueryDrafts(nextQueries, this.stateToUpdate)
onUpdateQueryDrafts(nextQueries)
} catch (error) {
console.error(error)
}
@ -685,7 +620,7 @@ class TimeMachine extends PureComponent<Props, State> {
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<Props, State> {
}
}) as CellQuery[]
updateQueryDrafts(queries, this.stateToUpdate)
onUpdateQueryDrafts(queries)
}
private handleChangeService = (
@ -724,15 +659,15 @@ class TimeMachine extends PureComponent<Props, State> {
}
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<Props, State> {
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<Props, State> {
// --------------- 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<Props, State> {
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<Props, State> {
}
}
export default TimeMachine
const ConnectedTimeMachine = (props: PassedProps) => {
return (
<Subscribe to={[TimeMachineContainer]}>
{(container: TimeMachineContainer) => {
const {state} = container
return (
<TimeMachine
{...props}
script={state.script}
queryDrafts={state.queryDrafts}
timeRange={state.timeRange}
onUpdateTimeRange={container.handleUpdateTimeRange}
onChangeScript={container.handleChangeScript}
onUpdateQueryDrafts={container.handleUpdateQueryDrafts}
onAddQuery={container.handleAddQuery}
onDeleteQuery={container.handleDeleteQuery}
/>
)
}}
</Subscribe>
)
}
export default ConnectedTimeMachine

View File

@ -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<Props> = ({
service,
queries,
templates,
isInCEO,
services,
timeRange,
toggleFlux,
@ -61,8 +59,6 @@ const TimeMachineControls: SFC<Props> = ({
isDynamicSourceSelected,
updateEditorTimeRange,
}) => {
const timeRangePage = isInCEO ? null : 'DataExplorer'
return (
<div className="deceo--controls">
<SourceSelector
@ -103,7 +99,6 @@ const TimeMachineControls: SFC<Props> = ({
<TimeRangeDropdown
onChooseTimeRange={updateEditorTimeRange}
selected={timeRange}
page={timeRangePage}
/>
</div>
)

View File

@ -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> = props => {
const colors: ColorString[] = getCellTypeColors({
cellType: props.type,
gaugeColors: props.gaugeColors,
thresholdsListColors: props.thresholdsListColors,
lineColors: props.lineColors,
})
return (
<div className="deceo--top">
<div className="deceo--visualization">
<div className="graph-container">
<RefreshingGraph
source={props.source}
service={props.service}
colors={colors}
autoRefresher={props.autoRefresher}
queries={props.queries}
templates={props.templates}
editQueryStatus={props.onEditQueryStatus}
staticLegend={props.staticLegend}
timeRange={props.timeRange}
manualRefresh={props.manualRefresh}
editorLocation={props.editorLocation}
showRawFluxData={props.showRawFluxData}
type={props.type}
axes={props.axes}
tableOptions={props.tableOptions}
fieldOptions={props.fieldOptions}
timeFormat={props.timeFormat}
decimalPlaces={props.decimalPlaces}
thresholdsListColors={props.thresholdsListColors}
thresholdsListType={props.thresholdsListType}
gaugeColors={props.gaugeColors}
lineColors={props.lineColors}
cellNote={props.note}
cellNoteVisibility={props.noteVisibility}
onUpdateFieldOptions={props.onUpdateFieldOptions}
/>
</div>
</div>
</div>
)
}
const ConnectedTimeMachineVisualization = (props: PassedProps) => (
<Subscribe to={[TimeMachineContainer]}>
{(container: TimeMachineContainer) => {
const {state} = container
return (
<TimeMachineVisualization
{...props}
timeRange={state.timeRange}
type={state.type}
axes={state.axes}
tableOptions={state.tableOptions}
fieldOptions={state.fieldOptions}
timeFormat={state.timeFormat}
decimalPlaces={state.decimalPlaces}
thresholdsListColors={state.thresholdsListColors}
thresholdsListType={state.thresholdsListType}
gaugeColors={state.gaugeColors}
lineColors={state.lineColors}
note={state.note}
noteVisibility={state.noteVisibility}
onUpdateFieldOptions={container.handleUpdateFieldOptions}
/>
)
}}
</Subscribe>
)
export default ConnectedTimeMachineVisualization

View File

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

View File

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

View File

@ -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<TimeMachineState> {
constructor(initialState: Partial<TimeMachineState> = {}) {
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})
}
}

View File

@ -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<TimeMachineState> {
const initialState: Partial<TimeMachineState> = {
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(<DataExplorer {...props} />)
return {
wrapper,
}
}
describe('DataExplorer.Containers.DataExplorer', () => {
describe('rendering', () => {
it('renders without errors', () => {
const {wrapper} = setup()
expect(wrapper.exists()).toBe(true)
})
})
})

View File

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

View File

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

View File

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