From f75d9859e50edec2ad555d4994ffec99bce8eef6 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Thu, 16 May 2019 15:50:51 -0700 Subject: [PATCH 1/2] feat(ui): add heatmap vis type --- dashboard.go | 37 ++ ui/package-lock.json | 8 +- ui/package.json | 2 +- ui/src/shared/components/AutoDomainInput.scss | 3 + ui/src/shared/components/AutoDomainInput.tsx | 14 +- .../shared/components/ColorSchemeDropdown.tsx | 23 +- ...down.scss => ColorSchemeDropdownItem.scss} | 4 +- .../components/ColorSchemeDropdownItem.tsx | 31 ++ ui/src/shared/components/FeatureFlag.tsx | 19 - ui/src/shared/components/HeatmapContainer.tsx | 107 +++++ .../components/HexColorSchemeDropdown.tsx | 55 +++ .../shared/components/HistogramContainer.scss | 5 - .../shared/components/HistogramContainer.tsx | 8 +- .../components/RefreshingViewSwitcher.tsx | 15 + ui/src/shared/components/XYContainer.scss | 5 - ui/src/shared/components/XYContainer.tsx | 14 +- ui/src/shared/components/cells/Cell.scss | 10 + ui/src/shared/constants/index.ts | 398 ------------------ ui/src/shared/utils/featureFlag.ts | 52 +++ ui/src/shared/utils/view.ts | 24 ++ ui/src/shared/utils/vis.ts | 12 +- ui/src/style/chronograf.scss | 5 +- ui/src/timeMachine/actions/index.ts | 84 +++- ...gramTransform.tsx => VisDataTransform.tsx} | 17 +- ui/src/timeMachine/components/VisSwitcher.tsx | 36 +- .../view_options/HeatmapOptions.tsx | 234 ++++++++++ .../view_options/HistogramOptions.tsx | 15 +- .../components/view_options/LineOptions.tsx | 54 ++- .../view_options/OptionsSwitcher.tsx | 3 + .../view_options/ThresholdColoring.tsx | 2 +- .../view_options/ViewTypeDropdown.tsx | 5 +- .../components/view_options/YAxisAffixes.tsx | 4 +- .../components/view_options/YAxisTitle.tsx | 2 +- ui/src/timeMachine/constants/visGraphics.tsx | 6 + ui/src/timeMachine/reducers/index.ts | 84 +++- ui/src/timeMachine/selectors/index.ts | 53 ++- ui/src/types/dashboards.ts | 22 + 37 files changed, 898 insertions(+), 574 deletions(-) create mode 100644 ui/src/shared/components/AutoDomainInput.scss rename ui/src/shared/components/{ColorSchemeDropdown.scss => ColorSchemeDropdownItem.scss} (68%) create mode 100644 ui/src/shared/components/ColorSchemeDropdownItem.tsx delete mode 100644 ui/src/shared/components/FeatureFlag.tsx create mode 100644 ui/src/shared/components/HeatmapContainer.tsx create mode 100644 ui/src/shared/components/HexColorSchemeDropdown.tsx delete mode 100644 ui/src/shared/components/HistogramContainer.scss delete mode 100644 ui/src/shared/components/XYContainer.scss create mode 100644 ui/src/shared/utils/featureFlag.ts rename ui/src/timeMachine/components/{HistogramTransform.tsx => VisDataTransform.tsx} (72%) create mode 100644 ui/src/timeMachine/components/view_options/HeatmapOptions.tsx diff --git a/dashboard.go b/dashboard.go index 0a830fc8e68..03232f57081 100644 --- a/dashboard.go +++ b/dashboard.go @@ -388,6 +388,12 @@ func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) { return nil, err } vis = hv + case "heatmap": + var hv HeatmapViewProperties + if err := json.Unmarshal(v.B, &hv); err != nil { + return nil, err + } + vis = hv } case "empty": var ev EmptyViewProperties @@ -460,6 +466,15 @@ func MarshalViewPropertiesJSON(v ViewProperties) ([]byte, error) { HistogramViewProperties: vis, } + case HeatmapViewProperties: + s = struct { + Shape string `json:"shape"` + HeatmapViewProperties + }{ + Shape: "chronograf-v2", + + HeatmapViewProperties: vis, + } case MarkdownViewProperties: s = struct { Shape string `json:"shape"` @@ -602,6 +617,26 @@ type HistogramViewProperties struct { ShowNoteWhenEmpty bool `json:"showNoteWhenEmpty"` } +// HeatmapViewProperties represents options for heatmap view in Chronograf +type HeatmapViewProperties struct { + Type string `json:"type"` + Queries []DashboardQuery `json:"queries"` + ViewColors []string `json:"colors"` + BinSize int32 `json:"binSize"` + XColumn string `json:"xColumn"` + YColumn string `json:"yColumn"` + XDomain []float64 `json:"xDomain,omitEmpty"` + YDomain []float64 `json:"yDomain,omitEmpty"` + XAxisLabel string `json:"xAxisLabel"` + YAxisLabel string `json:"yAxisLabel"` + XPrefix string `json:"xPrefix"` + XSuffix string `json:"xSuffix"` + YPrefix string `json:"yPrefix"` + YSuffix string `json:"ySuffix"` + Note string `json:"note"` + ShowNoteWhenEmpty bool `json:"showNoteWhenEmpty"` +} + // GaugeViewProperties represents options for gauge view in Chronograf type GaugeViewProperties struct { Type string `json:"type"` @@ -656,6 +691,7 @@ func (XYViewProperties) viewProperties() {} func (LinePlusSingleStatProperties) viewProperties() {} func (SingleStatViewProperties) viewProperties() {} func (HistogramViewProperties) viewProperties() {} +func (HeatmapViewProperties) viewProperties() {} func (GaugeViewProperties) viewProperties() {} func (TableViewProperties) viewProperties() {} func (MarkdownViewProperties) viewProperties() {} @@ -665,6 +701,7 @@ func (v XYViewProperties) GetType() string { return v.Type } func (v LinePlusSingleStatProperties) GetType() string { return v.Type } func (v SingleStatViewProperties) GetType() string { return v.Type } func (v HistogramViewProperties) GetType() string { return v.Type } +func (v HeatmapViewProperties) GetType() string { return v.Type } func (v GaugeViewProperties) GetType() string { return v.Type } func (v TableViewProperties) GetType() string { return v.Type } func (v MarkdownViewProperties) GetType() string { return v.Type } diff --git a/ui/package-lock.json b/ui/package-lock.json index 64510c6ec5d..e04ac9ef587 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1052,9 +1052,9 @@ } }, "@influxdata/vis": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@influxdata/vis/-/vis-0.7.0.tgz", - "integrity": "sha512-333xxwI2Iq9D9+mX4uH2U7Ha790ycJmtU1EWtvB2GaAiw/J0KLmZ4XtGqnAU2KgCwzcXQXy+8NU+9OJggtj3Vw==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@influxdata/vis/-/vis-0.8.0.tgz", + "integrity": "sha512-4MxvX+HlKQl9+2uLAr1ueDkTqmrcvokDFfp9yYEuVkD7rszMOfs5tInMhqHng49E5FXBksqPndryx/t+s5XBgw==", "requires": { "d3-array": "^2.0.3", "d3-color": "^1.2.3", @@ -10797,7 +10797,7 @@ "dependencies": { "json5": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, "requires": { diff --git a/ui/package.json b/ui/package.json index 80854e30513..11ef3f04aec 100644 --- a/ui/package.json +++ b/ui/package.json @@ -143,7 +143,7 @@ "@influxdata/influx": "0.3.4", "@influxdata/influxdb-templates": "git+ssh://git@github.com:influxdata/influxdb-templates.git", "@influxdata/react-custom-scrollbars": "4.3.8", - "@influxdata/vis": "^0.7.0", + "@influxdata/vis": "^0.8.0", "axios": "^0.18.0", "babel-polyfill": "^6.26.0", "bignumber.js": "^4.0.2", diff --git a/ui/src/shared/components/AutoDomainInput.scss b/ui/src/shared/components/AutoDomainInput.scss new file mode 100644 index 00000000000..c9785895bcf --- /dev/null +++ b/ui/src/shared/components/AutoDomainInput.scss @@ -0,0 +1,3 @@ +.auto-domain-input--custom { + padding-top: 4px; +} diff --git a/ui/src/shared/components/AutoDomainInput.tsx b/ui/src/shared/components/AutoDomainInput.tsx index 8ae2051c024..4c7a5933101 100644 --- a/ui/src/shared/components/AutoDomainInput.tsx +++ b/ui/src/shared/components/AutoDomainInput.tsx @@ -115,7 +115,11 @@ const AutoDomainInput: SFC = ({ const initialMax = domain ? String(domain[1]) : '' return ( - + @@ -141,16 +145,16 @@ const AutoDomainInput: SFC = ({ - - {showInputs && ( + {showInputs && ( + - )} - + + )} ) diff --git a/ui/src/shared/components/ColorSchemeDropdown.tsx b/ui/src/shared/components/ColorSchemeDropdown.tsx index 5afe3e7395f..c42fc4ba34c 100644 --- a/ui/src/shared/components/ColorSchemeDropdown.tsx +++ b/ui/src/shared/components/ColorSchemeDropdown.tsx @@ -1,8 +1,9 @@ // Libraries -import React, {SFC, CSSProperties} from 'react' +import React, {SFC} from 'react' // Components import {Dropdown, DropdownMenuColors} from 'src/clockface' +import ColorSchemeDropdownItem from 'src/shared/components/ColorSchemeDropdownItem' // Constants import { @@ -18,14 +19,6 @@ interface Props { onChange: (colors: Color[]) => void } -const generateGradientStyle = (colors: Color[]): CSSProperties => { - const [start, mid, stop] = colors.map(color => color.hex) - - return { - background: `linear-gradient(to right, ${start} 0%, ${mid} 50%, ${stop} 100%)`, - } -} - const findSelectedScaleID = (colors: Color[]) => { const key = (colors: Color[]) => colors.map(color => color.hex).join(', ') const needle = key(colors) @@ -44,17 +37,13 @@ const ColorSchemeDropdown: SFC = ({value, onChange}) => { selectedID={findSelectedScaleID(value)} onChange={onChange} menuColor={DropdownMenuColors.Onyx} - customClass="color-scheme-dropdown" > {LINE_COLOR_SCALES.map(({id, name, colors}) => ( -
-
-
{name}
-
+ c.hex)} + /> ))} diff --git a/ui/src/shared/components/ColorSchemeDropdown.scss b/ui/src/shared/components/ColorSchemeDropdownItem.scss similarity index 68% rename from ui/src/shared/components/ColorSchemeDropdown.scss rename to ui/src/shared/components/ColorSchemeDropdownItem.scss index 9bbc387716d..28c1f3aa0f1 100644 --- a/ui/src/shared/components/ColorSchemeDropdown.scss +++ b/ui/src/shared/components/ColorSchemeDropdownItem.scss @@ -1,10 +1,10 @@ -.color-scheme-dropdown--item { +.color-scheme-dropdown-item { display: flex; justify-content: flex-start; align-items: center; } -.color-scheme-dropdown--swatches { +.color-scheme-dropdown-item--swatches { width: 100px; height: 10px; border-radius: 5px; diff --git a/ui/src/shared/components/ColorSchemeDropdownItem.tsx b/ui/src/shared/components/ColorSchemeDropdownItem.tsx new file mode 100644 index 00000000000..f8c9cdc3aef --- /dev/null +++ b/ui/src/shared/components/ColorSchemeDropdownItem.tsx @@ -0,0 +1,31 @@ +// Libraries +import React, {CSSProperties, FunctionComponent} from 'react' + +const generateGradientStyle = (colors: string[]): CSSProperties => { + const stops = colors + .map((hex, i) => `${hex} ${Math.round((i / (colors.length - 1)) * 100)}%`) + .join(', ') + + return { + background: `linear-gradient(to right, ${stops})`, + } +} + +interface Props { + name: string + colors: string[] +} + +const ColorSchemeDropdownItem: FunctionComponent = ({name, colors}) => { + return ( +
+
+
{name}
+
+ ) +} + +export default ColorSchemeDropdownItem diff --git a/ui/src/shared/components/FeatureFlag.tsx b/ui/src/shared/components/FeatureFlag.tsx deleted file mode 100644 index 1cc27d03b92..00000000000 --- a/ui/src/shared/components/FeatureFlag.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import {PureComponent} from 'react' - -interface Props { - name?: string -} - -export default class extends PureComponent { - public render() { - if (this.isHidden) { - return null - } - - return this.props.children - } - - private get isHidden(): boolean { - return process.env.NODE_ENV !== 'development' - } -} diff --git a/ui/src/shared/components/HeatmapContainer.tsx b/ui/src/shared/components/HeatmapContainer.tsx new file mode 100644 index 00000000000..8b84796db04 --- /dev/null +++ b/ui/src/shared/components/HeatmapContainer.tsx @@ -0,0 +1,107 @@ +// Libraries +import React, {FunctionComponent} from 'react' +import {Config, Table} from '@influxdata/vis' +import {get} from 'lodash' + +// Components +import EmptyGraphMessage from 'src/shared/components/EmptyGraphMessage' +import GraphLoadingDots from 'src/shared/components/GraphLoadingDots' + +// Utils +import {useVisDomainSettings} from 'src/shared/utils/useVisDomainSettings' +import {getFormatter} from 'src/shared/utils/vis' + +// Constants +import {VIS_THEME} from 'src/shared/constants' +import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' +import {INVALID_DATA_COPY} from 'src/shared/copy/cell' + +// Types +import {RemoteDataState, HeatmapView} from 'src/types' + +interface Props { + table: Table + loading: RemoteDataState + viewProperties: HeatmapView + children: (config: Config) => JSX.Element +} + +const HeatmapContainer: FunctionComponent = ({ + table, + loading, + viewProperties: { + xColumn, + yColumn, + xDomain: storedXDomain, + yDomain: storedYDomain, + xAxisLabel, + yAxisLabel, + xPrefix, + xSuffix, + yPrefix, + ySuffix, + colors: storedColors, + binSize, + }, + children, +}) => { + const [xDomain, onSetXDomain, onResetXDomain] = useVisDomainSettings( + storedXDomain, + get(table, ['columns', xColumn, 'data'], []) + ) + + const [yDomain, onSetYDomain, onResetYDomain] = useVisDomainSettings( + storedYDomain, + get(table, ['columns', yColumn, 'data'], []) + ) + + const isValidView = + xColumn && yColumn && table.columns[xColumn] && table.columns[yColumn] + + if (!isValidView) { + return + } + + const colors: string[] = + storedColors && storedColors.length + ? storedColors + : DEFAULT_LINE_COLORS.map(c => c.hex) + + const xFormatter = getFormatter(table.columns[xColumn].type, xPrefix, xSuffix) + const yFormatter = getFormatter(table.columns[yColumn].type, yPrefix, ySuffix) + + const config: Config = { + ...VIS_THEME, + table, + xAxisLabel, + yAxisLabel, + xDomain, + onSetXDomain, + onResetXDomain, + yDomain, + onSetYDomain, + onResetYDomain, + valueFormatters: { + [xColumn]: xFormatter, + [yColumn]: yFormatter, + }, + layers: [ + { + type: 'heatmap', + x: xColumn, + y: yColumn, + colors, + binSize, + }, + ], + } + + return ( +
+ {loading === RemoteDataState.Loading && } + {children(config)} +
+ ) +} + +export default HeatmapContainer diff --git a/ui/src/shared/components/HexColorSchemeDropdown.tsx b/ui/src/shared/components/HexColorSchemeDropdown.tsx new file mode 100644 index 00000000000..d0158f97c0c --- /dev/null +++ b/ui/src/shared/components/HexColorSchemeDropdown.tsx @@ -0,0 +1,55 @@ +// Libraries +import React, {FunctionComponent} from 'react' + +// Components +import {Dropdown, DropdownMenuColors} from 'src/clockface' +import ColorSchemeDropdownItem from 'src/shared/components/ColorSchemeDropdownItem' + +interface Props { + colorSchemes: Array<{name: string; colors: string[]}> + selectedColorScheme: string[] + onSelectColorScheme: (scheme: string[]) => void +} + +const HexColorSchemeDropdown: FunctionComponent = ({ + colorSchemes, + selectedColorScheme, + onSelectColorScheme, +}) => { + const selected = colorSchemes.find( + scheme => + scheme.colors.length === selectedColorScheme.length && + scheme.colors.every((color, i) => color === selectedColorScheme[i]) + ) + + let selectedName + let resolvedSchemes + + if (selected) { + selectedName = selected.name + resolvedSchemes = colorSchemes + } else { + selectedName = 'Custom' + resolvedSchemes = [ + ...colorSchemes, + {name: 'Custom', colors: selectedColorScheme}, + ] + } + + return ( + + {resolvedSchemes.map(({name, colors}) => ( + + + + ))} + + ) +} + +export default HexColorSchemeDropdown diff --git a/ui/src/shared/components/HistogramContainer.scss b/ui/src/shared/components/HistogramContainer.scss deleted file mode 100644 index 691b1a9802e..00000000000 --- a/ui/src/shared/components/HistogramContainer.scss +++ /dev/null @@ -1,5 +0,0 @@ -.histogram-container { - width: 100%; - height: 100%; - padding: 5px 15px 15px 15px; -} diff --git a/ui/src/shared/components/HistogramContainer.tsx b/ui/src/shared/components/HistogramContainer.tsx index b440295ec9e..38b77607a40 100644 --- a/ui/src/shared/components/HistogramContainer.tsx +++ b/ui/src/shared/components/HistogramContainer.tsx @@ -9,7 +9,7 @@ import GraphLoadingDots from 'src/shared/components/GraphLoadingDots' // Utils import {useVisDomainSettings} from 'src/shared/utils/useVisDomainSettings' -import {formatNumber} from 'src/shared/utils/vis' +import {getFormatter} from 'src/shared/utils/vis' // Constants import {VIS_THEME} from 'src/shared/constants' @@ -59,6 +59,8 @@ const HistogramContainer: FunctionComponent = ({ ? colors.map(c => c.hex) : DEFAULT_LINE_COLORS.map(c => c.hex) + const xFormatter = getFormatter(table.columns[xColumn].type) + const config: Config = { ...VIS_THEME, table, @@ -66,7 +68,7 @@ const HistogramContainer: FunctionComponent = ({ xDomain, onSetXDomain, onResetXDomain, - valueFormatters: {[xColumn]: formatNumber}, + valueFormatters: {[xColumn]: xFormatter}, layers: [ { type: 'histogram', @@ -80,7 +82,7 @@ const HistogramContainer: FunctionComponent = ({ } return ( -
+
{loading === RemoteDataState.Loading && } {children(config)}
diff --git a/ui/src/shared/components/RefreshingViewSwitcher.tsx b/ui/src/shared/components/RefreshingViewSwitcher.tsx index 4fefe79b481..c33a2810686 100644 --- a/ui/src/shared/components/RefreshingViewSwitcher.tsx +++ b/ui/src/shared/components/RefreshingViewSwitcher.tsx @@ -7,6 +7,7 @@ import GaugeChart from 'src/shared/components/GaugeChart' import SingleStat from 'src/shared/components/SingleStat' import TableGraphs from 'src/shared/components/tables/TableGraphs' import HistogramContainer from 'src/shared/components/HistogramContainer' +import HeatmapContainer from 'src/shared/components/HeatmapContainer' import VisTableTransform from 'src/shared/components/VisTableTransform' import XYContainer from 'src/shared/components/XYContainer' import LatestValueTransform from 'src/shared/components/LatestValueTransform' @@ -119,6 +120,20 @@ const RefreshingViewSwitcher: FunctionComponent = ({ )} ) + case ViewType.Heatmap: + return ( + + {table => ( + + {config => } + + )} + + ) default: return
} diff --git a/ui/src/shared/components/XYContainer.scss b/ui/src/shared/components/XYContainer.scss deleted file mode 100644 index afebd3040de..00000000000 --- a/ui/src/shared/components/XYContainer.scss +++ /dev/null @@ -1,5 +0,0 @@ -.xy-container { - width: 100%; - height: 100%; - padding: 5px 15px 15px 15px; -} diff --git a/ui/src/shared/components/XYContainer.tsx b/ui/src/shared/components/XYContainer.tsx index 6f3b3455035..889694337a9 100644 --- a/ui/src/shared/components/XYContainer.tsx +++ b/ui/src/shared/components/XYContainer.tsx @@ -10,7 +10,7 @@ import GraphLoadingDots from 'src/shared/components/GraphLoadingDots' // Utils import {useVisDomainSettings} from 'src/shared/utils/useVisDomainSettings' import { - formatNumber, + getFormatter, resolveGeom, filterNoisyColumns, parseBounds, @@ -92,6 +92,12 @@ const XYContainer: FunctionComponent = ({ table ) + const yFormatter = getFormatter( + table.columns[yColumn].type, + yTickPrefix, + yTickSuffix + ) + const config: Config = { ...VIS_THEME, table, @@ -104,9 +110,7 @@ const XYContainer: FunctionComponent = ({ onSetYDomain, onResetYDomain, legendColumns, - valueFormatters: { - [yColumn]: t => `${yTickPrefix}${formatNumber(t)}${yTickSuffix}`, - }, + valueFormatters: {[yColumn]: yFormatter}, layers: [ { type: 'line', @@ -120,7 +124,7 @@ const XYContainer: FunctionComponent = ({ } return ( -
+
{loading === RemoteDataState.Loading && } {children(config)}
diff --git a/ui/src/shared/components/cells/Cell.scss b/ui/src/shared/components/cells/Cell.scss index edd037d10a6..c7c55ed5649 100644 --- a/ui/src/shared/components/cells/Cell.scss +++ b/ui/src/shared/components/cells/Cell.scss @@ -29,6 +29,10 @@ $cell--header-size: 36px; .time-machine-tables { padding-top: 0; } + + .vis-plot-container { + padding-top: $ix-marg-a; + } } .cell--view-empty { @@ -173,6 +177,12 @@ $cell--header-size: 36px; } } +.vis-plot-container { + width: 100%; + height: 100%; + padding: $ix-marg-c; +} + .vis-tooltip-container { z-index: $z--dygraph-legend; } diff --git a/ui/src/shared/constants/index.ts b/ui/src/shared/constants/index.ts index 265ba0debfa..fe3fb726818 100644 --- a/ui/src/shared/constants/index.ts +++ b/ui/src/shared/constants/index.ts @@ -5,392 +5,11 @@ import {AutoRefreshStatus} from 'src/types' export const DEFAULT_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss' export const DEFAULT_DURATION_MS = 1000 -export const DEFAULT_PIXELS = 333 - -export const PERMISSIONS = { - ViewAdmin: { - description: 'Can view or edit admin screens', - displayName: 'View Admin', - }, - ViewChronograf: { - description: 'Can use Chronograf tools', - displayName: 'View Chronograf', - }, - CreateDatabase: { - description: 'Can create databases', - displayName: 'Create Databases', - }, - CreateUserAndRole: { - description: 'Can create users and roles', - displayName: 'Create Users & Roles', - }, - AddRemoveNode: { - description: 'Can add/remove nodes from a cluster', - displayName: 'Add/Remove Nodes', - }, - DropDatabase: { - description: 'Can drop databases', - displayName: 'Drop Databases', - }, - DropData: { - displayName: 'Drop Data', - description: 'Can drop measurement and series queries', - }, - ReadData: { - displayName: 'Read', - description: 'Can read data', - }, - WriteData: { - displayName: 'Write', - description: 'Can write data', - }, - Rebalance: { - displayName: 'Rebalance', - description: 'Can rebalance a cluster', - }, - ManageShard: { - displayName: 'Manage Shards', - description: 'Can copy and delete shards', - }, - ManageContinuousQuery: { - displayName: 'Manage Continuous Queries', - description: 'Can create, show, and drop continuous queries', - }, - ManageQuery: { - displayName: 'Manage Queries', - description: 'Can show and kill queries', - }, - ManageSubscription: { - displayName: 'Manage Subscriptions', - description: 'Can show, add, and drop subscriptions', - }, - Monitor: { - displayName: 'Monitor', - description: 'Can show stats and diagnostics', - }, - CopyShard: { - displayName: 'Copy Shard', - description: 'Can copy shards', - }, -} - -export const DEFAULT_LINE_COLORS = [ - // 1 Color Palettes - [['#00C9FF'], ['#00C9FF'], ['#00C9FF']], - // 2 Color Palettes - [['#00C9FF', '#00C9FF'], ['#00C9FF', '#00C9FF'], ['#00C9FF', '#00C9FF']], - // 3 Color Palettes - [ - ['#00C9FF', '#9394FF', '#4ED8A0'], - ['#00C9FF', '#9394FF', '#4ED8A0'], - ['#00C9FF', '#9394FF', '#4ED8A0'], - ], - // 4 Color Palettes - [ - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054'], - ], - // 5 Color Palettes - [ - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00'], - ], - // 6 Color Palettes - [ - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00', '#33aa99'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00', '#33aa99'], - ['#00C9FF', '#9394FF', '#4ED8A0', '#ff0054', '#ffcc00', '#33aa99'], - ], - // 7 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - ], - ], - // 8 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - ], - ], - // 9 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - ], - ], - // 10 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - ], - ], - // 11 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - ], - ], - // 12 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - ], - ], - // 13 Color Palettes - [ - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - '#a0725b', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - '#a0725b', - ], - [ - '#00C9FF', - '#9394FF', - '#4ED8A0', - '#ff0054', - '#ffcc00', - '#33aa99', - '#9dfc5d', - '#92bcc3', - '#ca96fb', - '#ff00f0', - '#38b94a', - '#3844b9', - '#a0725b', - ], - ], -] - -export const STROKE_WIDTH = { - heavy: 3.5, - light: 1.5, -} export const DROPDOWN_MENU_MAX_HEIGHT = 240 -export const HEARTBEAT_INTERVAL = 10000 // ms - export const PRESENTATION_MODE_ANIMATION_DELAY = 0 // In milliseconds. -export const REVERT_STATE_DELAY = 1500 // ms - export const HTTP_UNAUTHORIZED = 401 export const HTTP_FORBIDDEN = 403 export const HTTP_NOT_FOUND = 404 @@ -402,35 +21,18 @@ export const AUTOREFRESH_DEFAULT = { interval: AUTOREFRESH_DEFAULT_INTERVAL, } -export const GRAPH = 'graph' -export const TABLE = 'table' -export const VIS_VIEWS = [GRAPH, TABLE] - -export const TEMP_VAR_INTERVAL = ':interval:' -export const TEMP_VAR_DASHBOARD_TIME = ':dashboardTime:' -export const AUTO_GROUP_BY = 'auto' - export const STATUS_PAGE_ROW_COUNT = 10 // TODO: calculate based on actual Status Page cells export const PAGE_HEADER_HEIGHT = 60 // TODO: get this dynamically to ensure longevity export const PAGE_CONTAINER_MARGIN = 30 // TODO: get this dynamically to ensure longevity export const LAYOUT_MARGIN = 4 export const DASHBOARD_LAYOUT_ROW_HEIGHT = 83.5 -export const DYGRAPH_CONTAINER_H_MARGIN = 16 -export const DYGRAPH_CONTAINER_V_MARGIN = 8 -export const DYGRAPH_CONTAINER_XLABEL_MARGIN = 20 - -export const IS_STATIC_LEGEND = legend => - _.get(legend, 'type', false) === 'static' - export const NOTIFICATION_TRANSITION = 250 export const FIVE_SECONDS = 5000 export const TEN_SECONDS = 10000 export const INFINITE = -1 // Resizer && Threesizer -export const HUNDRED = 100 -export const REQUIRED_HALVES = 2 export const HANDLE_VERTICAL = 'vertical' export const HANDLE_HORIZONTAL = 'horizontal' export const HANDLE_NONE = 'none' diff --git a/ui/src/shared/utils/featureFlag.ts b/ui/src/shared/utils/featureFlag.ts new file mode 100644 index 00000000000..79d10c365b2 --- /dev/null +++ b/ui/src/shared/utils/featureFlag.ts @@ -0,0 +1,52 @@ +import {FunctionComponent} from 'react' +import {CLOUD} from 'src/shared/constants' + +const OSS_FLAGS = { + heatmap: true, +} + +const CLOUD_FLAGS = { + heatmap: false, // We need to ensure the API updates have been deployed before enabling +} + +export const isFlagEnabled = (flagName: string) => { + let localStorageFlags + + try { + localStorageFlags = JSON.parse(window.localStorage.featureFlags) + } catch { + localStorageFlags = {} + } + + return ( + localStorageFlags[flagName] === true || + (CLOUD && CLOUD_FLAGS[flagName]) || + (!CLOUD && OSS_FLAGS[flagName]) + ) +} + +export const FeatureFlag: FunctionComponent<{name: string}> = ({ + name, + children, +}) => { + if (!isFlagEnabled(name)) { + return null + } + + return children as any +} + +export const toggleLocalStorageFlag = (flagName: string) => { + const featureFlags = JSON.parse(window.localStorage.featureFlags || '{}') + + featureFlags[flagName] = !featureFlags[flagName] + + window.localStorage.featureFlags = JSON.stringify(featureFlags) + + return featureFlags[flagName] +} + +// Expose utility in dev tools console for convenience +const w: any = window + +w.influx = {toggleFeature: toggleLocalStorageFlag} diff --git a/ui/src/shared/utils/view.ts b/ui/src/shared/utils/view.ts index 055d6a3775d..e4b49280bc1 100644 --- a/ui/src/shared/utils/view.ts +++ b/ui/src/shared/utils/view.ts @@ -1,4 +1,5 @@ // Constants +import {INFERNO} from '@influxdata/vis' import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes' import {DEFAULT_CELL_NAME} from 'src/dashboards/constants/index' import { @@ -12,6 +13,7 @@ import { XYView, XYViewGeom, HistogramView, + HeatmapView, LinePlusSingleStatView, SingleStatView, TableView, @@ -134,6 +136,28 @@ const NEW_VIEW_CREATORS = { showNoteWhenEmpty: false, }, }), + [ViewType.Heatmap]: (): NewView => ({ + ...defaultView(), + properties: { + queries: [], + type: ViewType.Heatmap, + shape: ViewShape.ChronografV2, + xColumn: '_time', + yColumn: '_value', + xDomain: null, + yDomain: null, + xAxisLabel: '', + yAxisLabel: '', + xPrefix: '', + xSuffix: '', + yPrefix: '', + ySuffix: '', + colors: INFERNO, + binSize: 10, + note: '', + showNoteWhenEmpty: false, + }, + }), [ViewType.SingleStat]: (): NewView => ({ ...defaultView(), properties: { diff --git a/ui/src/shared/utils/vis.ts b/ui/src/shared/utils/vis.ts index ff7f7b49fb8..178a3c075a0 100644 --- a/ui/src/shared/utils/vis.ts +++ b/ui/src/shared/utils/vis.ts @@ -1,6 +1,6 @@ // Libraries import {format} from 'd3-format' -import {isNumeric, Table} from '@influxdata/vis' +import {isNumeric, Table, ColumnType} from '@influxdata/vis' // Types import {XYViewGeom, Axis} from 'src/types' @@ -31,6 +31,16 @@ export const resolveGeom = (geom: XYViewGeom) => { return XYViewGeom.Line } +export const getFormatter = ( + columnType: ColumnType, + prefix: string = '', + suffix: string = '' +): null | ((x: any) => string) => { + return columnType === 'number' + ? x => `${prefix}${formatNumber(x)}${suffix}` + : null +} + const NOISY_LEGEND_COLUMNS = new Set(['_start', '_stop', 'result']) /* diff --git a/ui/src/style/chronograf.scss b/ui/src/style/chronograf.scss index 9cc876d5a14..81cde57cc6e 100644 --- a/ui/src/style/chronograf.scss +++ b/ui/src/style/chronograf.scss @@ -44,7 +44,7 @@ @import 'src/shared/components/inlineLabels/InlineLabels.scss'; @import 'src/shared/components/inlineLabels/InlineLabelsEditor.scss'; @import 'src/shared/components/TagInput.scss'; -@import 'src/shared/components/ColorSchemeDropdown.scss'; +@import 'src/shared/components/ColorSchemeDropdownItem.scss'; @import 'src/shared/components/ExportOverlay.scss'; @import 'src/shared/components/EditableName.scss'; @import 'src/shared/components/SingleStat.scss'; @@ -104,8 +104,7 @@ @import 'src/dataLoaders/components/side_bar/SideBar.scss'; @import 'src/dataLoaders/components/DataLoadersOverlay.scss'; @import 'src/shared/components/EmptyGraphError.scss'; -@import 'src/shared/components/XYContainer.scss'; -@import 'src/shared/components/HistogramContainer.scss'; +@import 'src/shared/components/AutoDomainInput.scss'; @import 'src/shared/components/dapperScrollbars/DapperScrollbars.scss'; @import 'src/dashboards/components/createFromTemplateOverlay/DashboardCreateFromTemplateOverlay.scss'; @import 'src/onboarding/components/SigninForm.scss'; diff --git a/ui/src/timeMachine/actions/index.ts b/ui/src/timeMachine/actions/index.ts index b0810732b20..6c8964b182c 100644 --- a/ui/src/timeMachine/actions/index.ts +++ b/ui/src/timeMachine/actions/index.ts @@ -44,8 +44,8 @@ export type Action = | SetColors | SetYAxisLabel | SetYAxisBounds - | SetYAxisPrefix - | SetYAxisSuffix + | SetAxisPrefix + | SetAxisSuffix | SetYAxisBase | SetYAxisScale | SetPrefix @@ -61,10 +61,14 @@ export type Action = | SetTableOptionsAction | SetTimeFormatAction | SetXColumnAction + | SetYColumnAction + | SetBinSizeAction + | SetColorHexesAction | SetFillColumnsAction | SetBinCountAction | SetHistogramPositionAction | SetXDomainAction + | SetYDomainAction | SetXAxisLabelAction interface SetActiveTimeMachineAction { @@ -187,12 +191,12 @@ export const setAxes = (axes: Axes): SetAxes => ({ interface SetYAxisLabel { type: 'SET_Y_AXIS_LABEL' - payload: {label: string} + payload: {yAxisLabel: string} } -export const setYAxisLabel = (label: string): SetYAxisLabel => ({ +export const setYAxisLabel = (yAxisLabel: string): SetYAxisLabel => ({ type: 'SET_Y_AXIS_LABEL', - payload: {label}, + payload: {yAxisLabel}, }) interface SetYAxisBounds { @@ -207,24 +211,30 @@ export const setYAxisBounds = ( payload: {bounds}, }) -interface SetYAxisPrefix { - type: 'SET_Y_AXIS_PREFIX' - payload: {prefix: string} +interface SetAxisPrefix { + type: 'SET_AXIS_PREFIX' + payload: {prefix: string; axis: 'x' | 'y'} } -export const setYAxisPrefix = (prefix: string): SetYAxisPrefix => ({ - type: 'SET_Y_AXIS_PREFIX', - payload: {prefix}, +export const setAxisPrefix = ( + prefix: string, + axis: 'x' | 'y' +): SetAxisPrefix => ({ + type: 'SET_AXIS_PREFIX', + payload: {prefix, axis}, }) -interface SetYAxisSuffix { - type: 'SET_Y_AXIS_SUFFIX' - payload: {suffix: string} +interface SetAxisSuffix { + type: 'SET_AXIS_SUFFIX' + payload: {suffix: string; axis: 'x' | 'y'} } -export const setYAxisSuffix = (suffix: string): SetYAxisSuffix => ({ - type: 'SET_Y_AXIS_SUFFIX', - payload: {suffix}, +export const setAxisSuffix = ( + suffix: string, + axis: 'x' | 'y' +): SetAxisSuffix => ({ + type: 'SET_AXIS_SUFFIX', + payload: {suffix, axis}, }) interface SetYAxisBase { @@ -465,6 +475,36 @@ export const setXColumn = (xColumn: string): SetXColumnAction => ({ payload: {xColumn}, }) +interface SetYColumnAction { + type: 'SET_Y_COLUMN' + payload: {yColumn: string} +} + +export const setYColumn = (yColumn: string): SetYColumnAction => ({ + type: 'SET_Y_COLUMN', + payload: {yColumn}, +}) + +interface SetBinSizeAction { + type: 'SET_BIN_SIZE' + payload: {binSize: number} +} + +export const setBinSize = (binSize: number): SetBinSizeAction => ({ + type: 'SET_BIN_SIZE', + payload: {binSize}, +}) + +interface SetColorHexesAction { + type: 'SET_COLOR_HEXES' + payload: {colors: string[]} +} + +export const setColorHexes = (colors: string[]): SetColorHexesAction => ({ + type: 'SET_COLOR_HEXES', + payload: {colors}, +}) + interface SetFillColumnsAction { type: 'SET_FILL_COLUMNS' payload: {fillColumns: string[]} @@ -509,6 +549,16 @@ export const setXDomain = (xDomain: [number, number]): SetXDomainAction => ({ payload: {xDomain}, }) +interface SetYDomainAction { + type: 'SET_VIEW_Y_DOMAIN' + payload: {yDomain: [number, number]} +} + +export const setYDomain = (yDomain: [number, number]): SetYDomainAction => ({ + type: 'SET_VIEW_Y_DOMAIN', + payload: {yDomain}, +}) + interface SetXAxisLabelAction { type: 'SET_X_AXIS_LABEL' payload: {xAxisLabel: string} diff --git a/ui/src/timeMachine/components/HistogramTransform.tsx b/ui/src/timeMachine/components/VisDataTransform.tsx similarity index 72% rename from ui/src/timeMachine/components/HistogramTransform.tsx rename to ui/src/timeMachine/components/VisDataTransform.tsx index aa00985e276..c34e709f9e1 100644 --- a/ui/src/timeMachine/components/HistogramTransform.tsx +++ b/ui/src/timeMachine/components/VisDataTransform.tsx @@ -7,6 +7,7 @@ import {Table} from '@influxdata/vis' import { getVisTable, getXColumnSelection, + getYColumnSelection, getFillColumnsSelection, } from 'src/timeMachine/selectors' @@ -16,6 +17,7 @@ import {AppState} from 'src/types' interface StateProps { table: Table xColumn: string + yColumn: string fillColumns: string[] } @@ -23,36 +25,35 @@ interface OwnProps { children: (props: { table: Table xColumn: string + yColumn: string fillColumns: string[] }) => JSX.Element } type Props = StateProps & OwnProps -const HistogramTransform: FunctionComponent = ({ +const VisDataTransform: FunctionComponent = ({ table, xColumn, + yColumn, fillColumns, children, }) => { - return children({table, xColumn, fillColumns}) + return children({table, xColumn, yColumn, fillColumns}) } const mstp = (state: AppState) => { const table = getVisTable(state) const xColumn = getXColumnSelection(state) + const yColumn = getYColumnSelection(state) const fillColumns = getFillColumnsSelection(state) return { table, xColumn, + yColumn, fillColumns, } } -const mdtp = {} - -export default connect( - mstp, - mdtp -)(HistogramTransform) +export default connect(mstp)(VisDataTransform) diff --git a/ui/src/timeMachine/components/VisSwitcher.tsx b/ui/src/timeMachine/components/VisSwitcher.tsx index cab7f252e33..eba7e7c26e6 100644 --- a/ui/src/timeMachine/components/VisSwitcher.tsx +++ b/ui/src/timeMachine/components/VisSwitcher.tsx @@ -7,8 +7,9 @@ import {Plot} from '@influxdata/vis' // Components import RawFluxDataTable from 'src/timeMachine/components/RawFluxDataTable' import HistogramContainer from 'src/shared/components/HistogramContainer' -import HistogramTransform from 'src/timeMachine/components/HistogramTransform' +import VisDataTransform from 'src/timeMachine/components/VisDataTransform' import RefreshingViewSwitcher from 'src/shared/components/RefreshingViewSwitcher' +import HeatmapContainer from 'src/shared/components/HeatmapContainer' // Utils import {getActiveTimeMachine, getTables} from 'src/timeMachine/selectors' @@ -50,15 +51,16 @@ const VisSwitcher: FunctionComponent = ({ ) } + // Histograms and heatmaps have special treatment when rendered within a time + // machine, since they allow for selecting which query response columns are + // visualized. If the column selections are invalid given the current query + // response, then we fall back to using valid selections for those fields if + // possible. This is in contrast to when these visualizations are rendered + // on a dashboard; in this case we use the selections stored in the view + // verbatim and display an error if they are invalid. if (properties.type === ViewType.Histogram) { - // Histograms have special treatment when rendered within a time machine: - // if the backing view for the histogram has `xColumn` and `fillColumn` - // selections that are invalid given the current query response, then we - // fall back to using valid selections for those fields (if available). - // When a histogram is rendered on a dashboard, we use the selections - // stored in the view verbatim and display an error if they are invalid. return ( - + {({table, xColumn, fillColumns}) => ( = ({ {config => } )} - + + ) + } + + if (properties.type === ViewType.Heatmap) { + return ( + + {({table, xColumn, yColumn}) => ( + + {config => } + + )} + ) } diff --git a/ui/src/timeMachine/components/view_options/HeatmapOptions.tsx b/ui/src/timeMachine/components/view_options/HeatmapOptions.tsx new file mode 100644 index 00000000000..c0b38dad896 --- /dev/null +++ b/ui/src/timeMachine/components/view_options/HeatmapOptions.tsx @@ -0,0 +1,234 @@ +// Libraries +import React, {FunctionComponent, ChangeEvent} from 'react' +import {connect} from 'react-redux' +import {VIRIDIS, MAGMA, INFERNO, PLASMA} from '@influxdata/vis' +import { + Dropdown, + Form, + Grid, + Input, + Columns, + InputType, +} from '@influxdata/clockface' + +// Components +import AutoDomainInput from 'src/shared/components/AutoDomainInput' +import HexColorSchemeDropdown from 'src/shared/components/HexColorSchemeDropdown' + +// Actions +import { + setXColumn, + setYColumn, + setBinSize, + setColorHexes, + setXDomain, + setYDomain, + setXAxisLabel, + setYAxisLabel, + setAxisPrefix, + setAxisSuffix, +} from 'src/timeMachine/actions' + +// Utils +import { + getXColumnSelection, + getYColumnSelection, + getNumericColumns, +} from 'src/timeMachine/selectors' + +// Types +import {ComponentStatus} from '@influxdata/clockface' +import {AppState} from 'src/types' + +const HEATMAP_COLOR_SCHEMES = [ + {name: 'Magma', colors: MAGMA}, + {name: 'Inferno', colors: INFERNO}, + {name: 'Viridis', colors: VIRIDIS}, + {name: 'Plasma', colors: PLASMA}, +] + +interface StateProps { + xColumn: string + yColumn: string + numericColumns: string[] +} + +interface DispatchProps { + onSetXColumn: typeof setXColumn + onSetYColumn: typeof setYColumn + onSetBinSize: typeof setBinSize + onSetColors: typeof setColorHexes + onSetXDomain: typeof setXDomain + onSetYDomain: typeof setYDomain + onSetXAxisLabel: typeof setXAxisLabel + onSetYAxisLabel: typeof setYAxisLabel + onSetPrefix: typeof setAxisPrefix + onSetSuffix: typeof setAxisSuffix +} + +interface OwnProps { + xDomain: [number, number] + yDomain: [number, number] + xAxisLabel: string + yAxisLabel: string + xPrefix: string + xSuffix: string + yPrefix: string + ySuffix: string + colors: string[] + binSize: number +} + +type Props = StateProps & DispatchProps & OwnProps + +const HeatmapOptions: FunctionComponent = props => { + const dataDropdownStatus = props.numericColumns.length + ? ComponentStatus.Default + : ComponentStatus.Disabled + + const onSetBinSize = (e: ChangeEvent) => { + const val = +e.target.value + + if (isNaN(val) || val < 5) { + return + } + + props.onSetBinSize(val) + } + + return ( + +

Customize Heatmap

+
Data
+ + + {props.numericColumns.map(columnName => ( + + {columnName} + + ))} + + + + + {props.numericColumns.map(columnName => ( + + {columnName} + + ))} + + +
Options
+ + + + + + +
X Axis
+ + props.onSetXAxisLabel(e.target.value)} + /> + + + + + props.onSetPrefix(e.target.value, 'x')} + /> + + + + + props.onSetSuffix(e.target.value, 'x')} + /> + + + + +
Y Axis
+ + props.onSetYAxisLabel(e.target.value)} + /> + + + + + props.onSetPrefix(e.target.value, 'y')} + /> + + + + + props.onSetSuffix(e.target.value, 'y')} + /> + + + + +
+ ) +} + +const mstp = (state: AppState) => { + const xColumn = getXColumnSelection(state) + const yColumn = getYColumnSelection(state) + const numericColumns = getNumericColumns(state) + + return {xColumn, yColumn, numericColumns} +} + +const mdtp = { + onSetXColumn: setXColumn, + onSetYColumn: setYColumn, + onSetBinSize: setBinSize, + onSetColors: setColorHexes, + onSetXDomain: setXDomain, + onSetYDomain: setYDomain, + onSetXAxisLabel: setXAxisLabel, + onSetYAxisLabel: setYAxisLabel, + onSetPrefix: setAxisPrefix, + onSetSuffix: setAxisSuffix, +} + +export default connect( + mstp, + mdtp +)(HeatmapOptions) diff --git a/ui/src/timeMachine/components/view_options/HistogramOptions.tsx b/ui/src/timeMachine/components/view_options/HistogramOptions.tsx index 56a46bc82fb..a4b0f00b964 100644 --- a/ui/src/timeMachine/components/view_options/HistogramOptions.tsx +++ b/ui/src/timeMachine/components/view_options/HistogramOptions.tsx @@ -124,12 +124,6 @@ const HistogramOptions: SFC = props => {
Options
- - onSetXAxisLabel(e.target.value)} - /> - @@ -152,10 +146,17 @@ const HistogramOptions: SFC = props => { min={0} /> +
X Axis
+ + onSetXAxisLabel(e.target.value)} + /> + ) diff --git a/ui/src/timeMachine/components/view_options/LineOptions.tsx b/ui/src/timeMachine/components/view_options/LineOptions.tsx index 9f0b95c497a..d8af35184fc 100644 --- a/ui/src/timeMachine/components/view_options/LineOptions.tsx +++ b/ui/src/timeMachine/components/view_options/LineOptions.tsx @@ -12,13 +12,10 @@ import AutoDomainInput from 'src/shared/components/AutoDomainInput' // Actions import { - setStaticLegend, setColors, setYAxisLabel, - setYAxisPrefix, - setYAxisSuffix, - setYAxisBase, - setYAxisScale, + setAxisPrefix, + setAxisSuffix, setYAxisBounds, setGeom, } from 'src/timeMachine/actions' @@ -39,15 +36,12 @@ interface OwnProps { } interface DispatchProps { - onUpdateYAxisLabel: (label: string) => void - onUpdateYAxisPrefix: (prefix: string) => void - onUpdateYAxisSuffix: (suffix: string) => void - onUpdateYAxisBase: (base: string) => void - onUpdateYAxisScale: (scale: string) => void - onUpdateYAxisBounds: (bounds: Axes['y']['bounds']) => void - onToggleStaticLegend: (isStaticLegend: boolean) => void - onUpdateColors: (colors: Color[]) => void - onSetGeom: (geom: XYViewGeom) => void + onUpdateYAxisLabel: typeof setYAxisLabel + onUpdateAxisPrefix: typeof setAxisPrefix + onUpdateAxisSuffix: typeof setAxisSuffix + onUpdateYAxisBounds: typeof setYAxisBounds + onUpdateColors: typeof setColors + onSetGeom: typeof setGeom } type Props = OwnProps & DispatchProps @@ -62,15 +56,16 @@ class LineOptions extends PureComponent { geom, onUpdateColors, onUpdateYAxisLabel, - onUpdateYAxisPrefix, - onUpdateYAxisSuffix, + onUpdateAxisPrefix, + onUpdateAxisSuffix, onSetGeom, } = this.props return ( <> -

Customize Graph

+

Customize Line Graph

+
Options
{geom && } { onUpdateColors={onUpdateColors} /> -

Left Y Axis

+
Y Axis
- onUpdateAxisPrefix(prefix, 'y')} + onUpdateYAxisSuffix={suffix => onUpdateAxisSuffix(suffix, 'y')} /> + + + ) } @@ -115,12 +112,9 @@ class LineOptions extends PureComponent { const mdtp: DispatchProps = { onUpdateYAxisLabel: setYAxisLabel, - onUpdateYAxisPrefix: setYAxisPrefix, - onUpdateYAxisSuffix: setYAxisSuffix, - onUpdateYAxisBase: setYAxisBase, - onUpdateYAxisScale: setYAxisScale, + onUpdateAxisPrefix: setAxisPrefix, + onUpdateAxisSuffix: setAxisSuffix, onUpdateYAxisBounds: setYAxisBounds, - onToggleStaticLegend: setStaticLegend, onUpdateColors: setColors, onSetGeom: setGeom, } diff --git a/ui/src/timeMachine/components/view_options/OptionsSwitcher.tsx b/ui/src/timeMachine/components/view_options/OptionsSwitcher.tsx index f1cddd33206..73cf122c16b 100644 --- a/ui/src/timeMachine/components/view_options/OptionsSwitcher.tsx +++ b/ui/src/timeMachine/components/view_options/OptionsSwitcher.tsx @@ -7,6 +7,7 @@ import GaugeOptions from 'src/timeMachine/components/view_options/GaugeOptions' import SingleStatOptions from 'src/timeMachine/components/view_options/SingleStatOptions' import TableOptions from 'src/timeMachine/components/view_options/TableOptions' import HistogramOptions from 'src/timeMachine/components/view_options/HistogramOptions' +import HeatmapOptions from 'src/timeMachine/components/view_options/HeatmapOptions' // Types import {ViewType, View, NewView} from 'src/types' @@ -37,6 +38,8 @@ class OptionsSwitcher extends PureComponent { return case ViewType.Histogram: return + case ViewType.Heatmap: + return default: return
} diff --git a/ui/src/timeMachine/components/view_options/ThresholdColoring.tsx b/ui/src/timeMachine/components/view_options/ThresholdColoring.tsx index 38fad341315..5b3b144fbc2 100644 --- a/ui/src/timeMachine/components/view_options/ThresholdColoring.tsx +++ b/ui/src/timeMachine/components/view_options/ThresholdColoring.tsx @@ -85,7 +85,7 @@ class ThresholdColoring extends PureComponent { } const mstp = (state: AppState) => { - const colors = getActiveTimeMachine(state).view.properties.colors + const colors = getActiveTimeMachine(state).view.properties.colors as Color[] return {colors} } diff --git a/ui/src/timeMachine/components/view_options/ViewTypeDropdown.tsx b/ui/src/timeMachine/components/view_options/ViewTypeDropdown.tsx index 328b587715d..2a369ea66b0 100644 --- a/ui/src/timeMachine/components/view_options/ViewTypeDropdown.tsx +++ b/ui/src/timeMachine/components/view_options/ViewTypeDropdown.tsx @@ -10,6 +10,7 @@ import {Dropdown, DropdownMenuColors} from 'src/clockface' // Utils import {getActiveTimeMachine} from 'src/timeMachine/selectors' +import {isFlagEnabled} from 'src/shared/utils/featureFlag' // Constants import {VIS_GRAPHICS} from 'src/timeMachine/constants/visGraphics' @@ -49,7 +50,9 @@ class ViewTypeDropdown extends PureComponent { } private get dropdownItems(): JSX.Element[] { - return VIS_GRAPHICS.map(g => ( + return VIS_GRAPHICS.filter( + g => !g.featureFlag || isFlagEnabled(g.featureFlag) + ).map(g => ( { return ( <> - + - + diff --git a/ui/src/timeMachine/components/view_options/YAxisTitle.tsx b/ui/src/timeMachine/components/view_options/YAxisTitle.tsx index 071fe7d86d5..df3794098e8 100644 --- a/ui/src/timeMachine/components/view_options/YAxisTitle.tsx +++ b/ui/src/timeMachine/components/view_options/YAxisTitle.tsx @@ -17,7 +17,7 @@ class YAxisTitle extends PureComponent { return ( - + diff --git a/ui/src/timeMachine/constants/visGraphics.tsx b/ui/src/timeMachine/constants/visGraphics.tsx index 110277ff443..9fbbcbe5f7c 100644 --- a/ui/src/timeMachine/constants/visGraphics.tsx +++ b/ui/src/timeMachine/constants/visGraphics.tsx @@ -588,6 +588,12 @@ export const VIS_GRAPHICS = [ name: 'Graph + Single Stat', graphic: GRAPHIC_SVGS[ViewType.LinePlusSingleStat], }, + { + type: ViewType.Heatmap, + name: 'Heatmap', + graphic: GRAPHIC_SVGS[ViewType.XY], + featureFlag: 'heatmap', + }, { type: ViewType.Histogram, name: 'Histogram', diff --git a/ui/src/timeMachine/reducers/index.ts b/ui/src/timeMachine/reducers/index.ts index 4ab4cf79975..bdef5084cd4 100644 --- a/ui/src/timeMachine/reducers/index.ts +++ b/ui/src/timeMachine/reducers/index.ts @@ -253,28 +253,36 @@ export const timeMachineReducer = ( return setViewProperties(state, {geom}) } - case 'SET_Y_AXIS_LABEL': { - const {label} = action.payload - - return setYAxis(state, {label}) - } - case 'SET_Y_AXIS_BOUNDS': { const {bounds} = action.payload return setYAxis(state, {bounds}) } - case 'SET_Y_AXIS_PREFIX': { - const {prefix} = action.payload + case 'SET_AXIS_PREFIX': { + const {prefix, axis} = action.payload + const viewType = state.view.properties.type - return setYAxis(state, {prefix}) + if (viewType === ViewType.Heatmap && axis === 'x') { + return setViewProperties(state, {xPrefix: prefix}) + } else if (viewType === ViewType.Heatmap && axis === 'y') { + return setViewProperties(state, {yPrefix: prefix}) + } else { + return setYAxis(state, {prefix}) + } } - case 'SET_Y_AXIS_SUFFIX': { - const {suffix} = action.payload + case 'SET_AXIS_SUFFIX': { + const {suffix, axis} = action.payload + const viewType = state.view.properties.type - return setYAxis(state, {suffix}) + if (viewType === ViewType.Heatmap && axis === 'x') { + return setViewProperties(state, {xSuffix: suffix}) + } else if (viewType === ViewType.Heatmap && axis === 'y') { + return setViewProperties(state, {ySuffix: suffix}) + } else { + return setYAxis(state, {suffix}) + } } case 'SET_Y_AXIS_BASE': { @@ -295,10 +303,34 @@ export const timeMachineReducer = ( return setViewProperties(state, {xColumn}) } + case 'SET_Y_COLUMN': { + const {yColumn} = action.payload + + return setViewProperties(state, {yColumn}) + } + case 'SET_X_AXIS_LABEL': { const {xAxisLabel} = action.payload - return setViewProperties(state, {xAxisLabel}) + switch (state.view.properties.type) { + case ViewType.Histogram: + case ViewType.Heatmap: + return setViewProperties(state, {xAxisLabel}) + default: + return setYAxis(state, {label: xAxisLabel}) + } + } + + case 'SET_Y_AXIS_LABEL': { + const {yAxisLabel} = action.payload + + switch (state.view.properties.type) { + case ViewType.Histogram: + case ViewType.Heatmap: + return setViewProperties(state, {yAxisLabel}) + default: + return setYAxis(state, {label: yAxisLabel}) + } } case 'SET_FILL_COLUMNS': { @@ -319,12 +351,30 @@ export const timeMachineReducer = ( return setViewProperties(state, {binCount}) } + case 'SET_BIN_SIZE': { + const {binSize} = action.payload + + return setViewProperties(state, {binSize}) + } + + case 'SET_COLOR_HEXES': { + const {colors} = action.payload + + return setViewProperties(state, {colors}) + } + case 'SET_VIEW_X_DOMAIN': { const {xDomain} = action.payload return setViewProperties(state, {xDomain}) } + case 'SET_VIEW_Y_DOMAIN': { + const {yDomain} = action.payload + + return setViewProperties(state, {yDomain}) + } + case 'SET_PREFIX': { const {prefix} = action.payload @@ -380,7 +430,9 @@ export const timeMachineReducer = ( } case 'SET_BACKGROUND_THRESHOLD_COLORING': { - const colors = state.view.properties.colors.map(color => { + const viewColors = state.view.properties.colors as Color[] + + const colors = viewColors.map(color => { if (color.type !== 'scale') { return { ...color, @@ -395,7 +447,9 @@ export const timeMachineReducer = ( } case 'SET_TEXT_THRESHOLD_COLORING': { - const colors = state.view.properties.colors.map(color => { + const viewColors = state.view.properties.colors as Color[] + + const colors = viewColors.map(color => { if (color.type !== 'scale') { return { ...color, diff --git a/ui/src/timeMachine/selectors/index.ts b/ui/src/timeMachine/selectors/index.ts index 2c9929e665f..6336f638192 100644 --- a/ui/src/timeMachine/selectors/index.ts +++ b/ui/src/timeMachine/selectors/index.ts @@ -47,7 +47,10 @@ export const getVisTable = (state: AppState): Table => { const getNumericColumnsMemoized = memoizeOne( (table: Table): string[] => { const numericColumns = Object.entries(table.columns) - .filter(([__, {type}]) => isNumeric(type)) + .filter( + ([__, {name, type}]) => + isNumeric(type) && name !== 'result' && name !== 'table' + ) .map(([name]) => name) return numericColumns @@ -77,23 +80,25 @@ export const getGroupableColumns = (state: AppState): string[] => { return getGroupableColumnsMemoized(table) } -const getXColumnSelectionMemoized = memoizeOne( - (validXColumns: string[], preference: string): string => { - if (preference && validXColumns.includes(preference)) { - return preference - } - - if (validXColumns.includes('_value')) { - return '_value' - } +const selectXYColumn = (validColumns: string[], preference: string): string => { + if (preference && validColumns.includes(preference)) { + return preference + } - if (validXColumns.length) { - return validXColumns[0] - } + if (validColumns.includes('_value')) { + return '_value' + } - return null + if (validColumns.length) { + return validColumns[0] } -) + + return null +} + +const getXColumnSelectionMemoized = memoizeOne(selectXYColumn) + +const getYColumnSelectionMemoized = memoizeOne(selectXYColumn) export const getXColumnSelection = (state: AppState): string => { const validXColumns = getNumericColumns(state) @@ -102,6 +107,13 @@ export const getXColumnSelection = (state: AppState): string => { return getXColumnSelectionMemoized(validXColumns, preference) } +export const getYColumnSelection = (state: AppState): string => { + const validYColumns = getNumericColumns(state) + const preference = get(getActiveTimeMachine(state), 'view.properties.yColumn') + + return getYColumnSelectionMemoized(validYColumns, preference) +} + const getFillColumnsSelectionMemoized = memoizeOne( (validFillColumns: string[], preference: string[]): string[] => { if (preference && preference.every(col => validFillColumns.includes(col))) { @@ -144,5 +156,16 @@ export const getSaveableView = (state: AppState): QueryView & {id?: string} => { } } + if (saveableView.properties.type === ViewType.Heatmap) { + saveableView = { + ...saveableView, + properties: { + ...saveableView.properties, + xColumn: getXColumnSelection(state), + yColumn: getYColumnSelection(state), + }, + } + } + return saveableView } diff --git a/ui/src/types/dashboards.ts b/ui/src/types/dashboards.ts index 806bc199546..832004d30ac 100644 --- a/ui/src/types/dashboards.ts +++ b/ui/src/types/dashboards.ts @@ -131,6 +131,7 @@ export type ViewProperties = | MarkdownView | EmptyView | HistogramView + | HeatmapView export type QueryViewProperties = Extract< ViewProperties, @@ -242,6 +243,26 @@ export interface HistogramView { showNoteWhenEmpty: boolean } +export interface HeatmapView { + type: ViewType.Heatmap + shape: ViewShape.ChronografV2 + queries: DashboardQuery[] + xColumn: string + yColumn: string + xDomain: [number, number] + yDomain: [number, number] + xAxisLabel: string + yAxisLabel: string + xPrefix: string + xSuffix: string + yPrefix: string + ySuffix: string + colors: string[] + binSize: number + note: string + showNoteWhenEmpty: boolean +} + export interface MarkdownView { type: ViewType.Markdown shape: ViewShape.ChronografV2 @@ -262,6 +283,7 @@ export enum ViewType { Markdown = 'markdown', LogViewer = 'log-viewer', Histogram = 'histogram', + Heatmap = 'heatmap', } export interface DashboardFile { From 905fe4872c9ed4c170e2a196e906056f1174ebe8 Mon Sep 17 00:00:00 2001 From: Christopher Henn Date: Fri, 17 May 2019 12:00:58 -0700 Subject: [PATCH 2/2] fix(ui): fix crash in time machine when opening vis settings Closes #13813 --- CHANGELOG.md | 6 ++++-- ui/src/timeMachine/selectors/index.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89afaffb29c..fc175ee0c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ ### Features -1. [13850](https://github.com/influxdata/influxdb/pull/13850): Add description field to Tasks. -1. [13924](https://github.com/influxdata/influxdb/pull/13924): Add CLI arguments for configuring session length and renewal. +1. [13945](https://github.com/influxdata/influxdb/pull/13945): Add heatmap visualization type +1. [13850](https://github.com/influxdata/influxdb/pull/13850): Add description field to Tasks +1. [13924](https://github.com/influxdata/influxdb/pull/13924): Add CLI arguments for configuring session length and renewal ### Bug Fixes @@ -13,6 +14,7 @@ 1. [13797](https://github.com/influxdata/influxdb/pull/13797): Expand tab key presses to 2 spaces in the Flux editor 1. [13823](https://github.com/influxdata/influxdb/pull/13823): Prevent dragging of Variable Dropdowns when dragging a scrollbar inside the dropdown 1. [13853](https://github.com/influxdata/influxdb/pull/13853): Improve single stat computation +1. [13945](https://github.com/influxdata/influxdb/pull/13945): Fix crash when opening histogram settings with no data ### UI Improvements 1. [#13835](https://github.com/influxdata/influxdb/pull/13835): Render checkboxes in query builder tag selection lists diff --git a/ui/src/timeMachine/selectors/index.ts b/ui/src/timeMachine/selectors/index.ts index 6336f638192..287a3a2b943 100644 --- a/ui/src/timeMachine/selectors/index.ts +++ b/ui/src/timeMachine/selectors/index.ts @@ -38,7 +38,7 @@ export const getTables = (state: AppState): FluxTable[] => const getVisTableMemoized = memoizeOne(fluxToTable) export const getVisTable = (state: AppState): Table => { - const files = getActiveTimeMachine(state).queryResults.files + const files = getActiveTimeMachine(state).queryResults.files || [] const {table} = getVisTableMemoized(files.join('\n\n')) return table