chronograf(v2-views): Introduce new view patterns, linting, and TypeScript 3.x (#901)
* Simplifiy color type * Fix type * Introduce V2 data shape for views * WIP Split style and parsing to separate Dygraph components * Add basic dygraph view types * Add Gauge to v2 view shapes * Upgrade TypeScript to ^3.0 * Add tsc to Circle build * Fix Dygraph component paths * Add testURL to jest config * Upgrade lodash types * Remove redundant test linter stetp * Upgrade to TypeScript ^3 * Remove TableGraph (temporarily)pull/10616/head
parent
6a86f8ee14
commit
e34d2e76ea
|
@ -22,6 +22,7 @@ jobs:
|
|||
- ~/.cache/yarn
|
||||
|
||||
- run: make test-js
|
||||
- run: make chronograf_lint
|
||||
|
||||
gotest:
|
||||
docker:
|
||||
|
|
3
Makefile
3
Makefile
|
@ -91,6 +91,9 @@ bin/$(GOOS)/go-bindata: go.mod go.sum
|
|||
|
||||
node_modules: chronograf/ui/node_modules
|
||||
|
||||
chronograf_lint:
|
||||
make -C chronograf/ui lint
|
||||
|
||||
chronograf/ui/node_modules:
|
||||
make -C chronograf/ui node_modules
|
||||
|
||||
|
|
|
@ -17,6 +17,14 @@ else
|
|||
yarn run build
|
||||
endif
|
||||
|
||||
lint: node_modules $(UISOURCES)
|
||||
ifndef YARN
|
||||
$(error Please install yarn 0.19.1+)
|
||||
else
|
||||
yarn run tsc
|
||||
endif
|
||||
|
||||
|
||||
test:
|
||||
ifndef YARN
|
||||
$(error Please install yarn 0.19.1+)
|
||||
|
@ -34,4 +42,4 @@ endif
|
|||
run:
|
||||
yarn run start
|
||||
|
||||
.PHONY: all clean test run
|
||||
.PHONY: all clean test run lint
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,7 @@ module.exports = {
|
|||
projects: [
|
||||
{
|
||||
displayName: 'test',
|
||||
testURL: 'http://localhost',
|
||||
testPathIgnorePatterns: [
|
||||
'build',
|
||||
'<rootDir>/node_modules/(?!(jest-test))',
|
||||
|
@ -26,11 +27,5 @@ module.exports = {
|
|||
displayName: 'eslint',
|
||||
testMatch: ['<rootDir>/**/*.test.js'],
|
||||
},
|
||||
{
|
||||
runner: 'jest-runner-tslint',
|
||||
displayName: 'tslint',
|
||||
moduleFileExtensions: ['ts', 'tsx'],
|
||||
testMatch: ['<rootDir>/**/*.test.ts', '<rootDir>/**/*.test.tsx'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -31,14 +31,14 @@
|
|||
"@types/d3-color": "^1.2.1",
|
||||
"@types/d3-scale": "^2.0.1",
|
||||
"@types/dygraphs": "^1.1.6",
|
||||
"@types/enzyme": "^3.1.9",
|
||||
"@types/jest": "^22.1.4",
|
||||
"@types/lodash": "^4.14.104",
|
||||
"@types/enzyme": "^3.1.14",
|
||||
"@types/jest": "^23.3.2",
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/node": "^9.4.6",
|
||||
"@types/papaparse": "^4.1.34",
|
||||
"@types/prop-types": "^15.5.2",
|
||||
"@types/qs": "^6.5.1",
|
||||
"@types/react": "^16.0.38",
|
||||
"@types/react": "^16.4.14",
|
||||
"@types/react-dnd": "^2.0.36",
|
||||
"@types/react-dnd-html5-backend": "^2.1.9",
|
||||
"@types/react-grid-layout": "^0.16.5",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"babel-preset-react": "^6.5.0",
|
||||
"babel-preset-stage-0": "^6.16.0",
|
||||
"babel-runtime": "^6.5.0",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme": "^3.6.0",
|
||||
"enzyme-to-json": "^3.3.4",
|
||||
"eslint": "^3.14.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
|
@ -74,20 +74,20 @@
|
|||
"express": "^4.14.0",
|
||||
"http-proxy-middleware": "^0.18.0",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^23.1.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-runner-eslint": "^0.6.0",
|
||||
"jest-runner-tslint": "^1.0.4",
|
||||
"jsdom": "^9.0.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"parcel": "^1.9.7",
|
||||
"prettier": "1.12.1",
|
||||
"ts-jest": "^22.4.2",
|
||||
"ts-jest": "^23.10.2",
|
||||
"tslib": "^1.9.0",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-config-prettier": "^1.10.0",
|
||||
"tslint-plugin-prettier": "^1.3.0",
|
||||
"tslint-react": "^3.5.1",
|
||||
"typescript": "2.7.2"
|
||||
"typescript": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
|
|
|
@ -40,7 +40,7 @@ interface Props {
|
|||
notify: (message: Notification | NotificationFunc) => void
|
||||
}
|
||||
|
||||
export const SourceContext = React.createContext()
|
||||
export const SourceContext = React.createContext({})
|
||||
// Acts as a 'router middleware'. The main `App` component is responsible for
|
||||
// getting the list of data sources, but not every page requires them to function.
|
||||
// Routes that do require data sources can be nested under this component.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {ColorNumber, ColorString} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {
|
||||
DecimalPlaces,
|
||||
FieldOption,
|
||||
|
@ -68,7 +68,7 @@ export interface RenameCellAction {
|
|||
export interface UpdateThresholdsListColorsAction {
|
||||
type: ActionType.UpdateThresholdsListColors
|
||||
payload: {
|
||||
thresholdsListColors: ColorNumber[]
|
||||
thresholdsListColors: Color[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ export interface UpdateThresholdsListTypeAction {
|
|||
export interface UpdateGaugeColorsAction {
|
||||
type: ActionType.UpdateGaugeColors
|
||||
payload: {
|
||||
gaugeColors: ColorNumber[]
|
||||
gaugeColors: Color[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ export interface UpdateTableOptionsAction {
|
|||
export interface UpdateLineColorsAction {
|
||||
type: ActionType.UpdateLineColors
|
||||
payload: {
|
||||
lineColors: ColorString[]
|
||||
lineColors: Color[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,7 @@ export const renameCell = (cellName: string): RenameCellAction => ({
|
|||
})
|
||||
|
||||
export const updateThresholdsListColors = (
|
||||
thresholdsListColors: ColorNumber[]
|
||||
thresholdsListColors: Color[]
|
||||
): UpdateThresholdsListColorsAction => ({
|
||||
type: ActionType.UpdateThresholdsListColors,
|
||||
payload: {
|
||||
|
@ -174,7 +174,7 @@ export const updateThresholdsListType = (
|
|||
})
|
||||
|
||||
export const updateGaugeColors = (
|
||||
gaugeColors: ColorNumber[]
|
||||
gaugeColors: Color[]
|
||||
): UpdateGaugeColorsAction => ({
|
||||
type: ActionType.UpdateGaugeColors,
|
||||
payload: {
|
||||
|
@ -199,7 +199,7 @@ export const updateTableOptions = (
|
|||
})
|
||||
|
||||
export const updateLineColors = (
|
||||
lineColors: ColorString[]
|
||||
lineColors: Color[]
|
||||
): UpdateLineColorsAction => ({
|
||||
type: ActionType.UpdateLineColors,
|
||||
payload: {
|
||||
|
|
|
@ -23,16 +23,16 @@ import {
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {Axes} from 'src/types'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
gaugeColors: ColorNumber[]
|
||||
gaugeColors: Color[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
onResetFocus: () => void
|
||||
handleUpdateAxes: (a: Axes) => void
|
||||
onUpdateDecimalPlaces: (d: DecimalPlaces) => void
|
||||
handleUpdateGaugeColors: (d: ColorNumber[]) => void
|
||||
handleUpdateGaugeColors: (d: Color[]) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -125,8 +125,8 @@ class GaugeOptions extends PureComponent<Props> {
|
|||
if (sortedColors.length <= MAX_THRESHOLDS) {
|
||||
const randomColor = _.random(0, THRESHOLD_COLORS.length - 1)
|
||||
|
||||
const maxValue = sortedColors[sortedColors.length - 1].value
|
||||
const minValue = sortedColors[0].value
|
||||
const maxValue = +sortedColors[sortedColors.length - 1].value
|
||||
const minValue = +sortedColors[0].value
|
||||
|
||||
const colorsValues = _.mapValues(gaugeColors, 'value')
|
||||
let randomValue
|
||||
|
@ -143,7 +143,7 @@ class GaugeOptions extends PureComponent<Props> {
|
|||
name: THRESHOLD_COLORS[randomColor].name,
|
||||
}
|
||||
|
||||
const updatedColors: ColorNumber[] = _.sortBy<ColorNumber>(
|
||||
const updatedColors: Color[] = _.sortBy<Color>(
|
||||
[...gaugeColors, newThreshold],
|
||||
color => color.value
|
||||
)
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import ConfirmOrCancel from 'src/shared/components/ConfirmOrCancel'
|
||||
import SourceSelector from 'src/dashboards/components/SourceSelector'
|
||||
import RadioButtons from 'src/clockface/components/radio_buttons/RadioButtons'
|
||||
import {ButtonShape} from 'src/clockface/types'
|
||||
|
||||
import {CEOTabs} from 'src/dashboards/constants'
|
||||
|
||||
import * as QueriesModels from 'src/types/queries'
|
||||
import * as SourcesModels from 'src/types/sources'
|
||||
|
||||
interface Props {
|
||||
onCancel: () => void
|
||||
onSave: () => void
|
||||
activeEditorTab: CEOTabs
|
||||
onSetActiveEditorTab: (tabName: CEOTabs) => void
|
||||
isSavable: boolean
|
||||
sources: SourcesModels.SourceOption[]
|
||||
onSetQuerySource: (source: SourcesModels.Source) => void
|
||||
selected: string
|
||||
queries: QueriesModels.QueryConfig[]
|
||||
}
|
||||
|
||||
const OverlayControls: SFC<Props> = ({
|
||||
onSave,
|
||||
sources,
|
||||
queries,
|
||||
selected,
|
||||
onCancel,
|
||||
isSavable,
|
||||
onSetQuerySource,
|
||||
activeEditorTab,
|
||||
onSetActiveEditorTab,
|
||||
}) => (
|
||||
<div className="overlay-controls">
|
||||
<SourceSelector
|
||||
sources={sources}
|
||||
selected={selected}
|
||||
onSetQuerySource={onSetQuerySource}
|
||||
queries={queries}
|
||||
/>
|
||||
<div className="overlay-controls--tabs">
|
||||
<RadioButtons
|
||||
activeButton={activeEditorTab}
|
||||
buttons={[CEOTabs.Queries, CEOTabs.Vis]}
|
||||
onChange={onSetActiveEditorTab}
|
||||
shape={ButtonShape.StretchToFit}
|
||||
/>
|
||||
</div>
|
||||
<div className="overlay-controls--right">
|
||||
<ConfirmOrCancel
|
||||
onCancel={onCancel}
|
||||
onConfirm={onSave}
|
||||
isDisabled={!isSavable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default OverlayControls
|
|
@ -12,7 +12,7 @@ describe('Threshold', () => {
|
|||
hex: '#444444',
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
value: 2,
|
||||
value: '2',
|
||||
},
|
||||
disableMaxColor: true,
|
||||
onChooseColor: () => {},
|
||||
|
|
|
@ -3,16 +3,16 @@ import React, {PureComponent, ChangeEvent, KeyboardEvent} from 'react'
|
|||
import ColorDropdown from 'src/shared/components/ColorDropdown'
|
||||
import {THRESHOLD_COLORS} from 'src/shared/constants/thresholds'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {ColorNumber, ThresholdColor} from 'src/types/colors'
|
||||
import {Color, ThresholdColor} from 'src/types/colors'
|
||||
|
||||
interface Props {
|
||||
visualizationType: string
|
||||
threshold: ColorNumber
|
||||
threshold: Color
|
||||
disableMaxColor: boolean
|
||||
onChooseColor: (threshold: ColorNumber) => void
|
||||
onValidateColorValue: (threshold: ColorNumber, targetValue: number) => boolean
|
||||
onUpdateColorValue: (threshold: ColorNumber, targetValue: number) => void
|
||||
onDeleteThreshold: (threshold: ColorNumber) => void
|
||||
onChooseColor: (threshold: Color) => void
|
||||
onValidateColorValue: (threshold: Color, targetValue: number) => boolean
|
||||
onUpdateColorValue: (threshold: Color, targetValue: number) => void
|
||||
onDeleteThreshold: (threshold: Color) => void
|
||||
isMin: boolean
|
||||
isMax: boolean
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ class Threshold extends PureComponent<Props, State> {
|
|||
onChooseColor({...threshold, hex, name})
|
||||
}
|
||||
|
||||
private get selectedColor(): ColorNumber {
|
||||
private get selectedColor(): Color {
|
||||
const {
|
||||
threshold: {hex, name, type, value, id},
|
||||
} = this.props
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
import React, {SFC} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import RefreshingGraph from 'src/shared/components/RefreshingGraph'
|
||||
import buildQueries from 'src/utils/buildQueriesForGraphs'
|
||||
import VisualizationName from 'src/dashboards/components/VisualizationName'
|
||||
|
||||
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
|
||||
|
||||
import {TimeRange, QueryConfig, Axes, Template, Source} from 'src/types'
|
||||
import {
|
||||
TableOptions,
|
||||
DecimalPlaces,
|
||||
FieldOption,
|
||||
CellType,
|
||||
} from 'src/types/dashboards'
|
||||
import {ColorString, ColorNumber} from 'src/types/colors'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
type: CellType
|
||||
source: Source
|
||||
autoRefresh: number
|
||||
templates: Template[]
|
||||
timeRange: TimeRange
|
||||
queryConfigs: QueryConfig[]
|
||||
editQueryStatus: () => void
|
||||
tableOptions: TableOptions
|
||||
timeFormat: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
fieldOptions: FieldOption[]
|
||||
resizerTopHeight: number
|
||||
thresholdsListColors: ColorNumber[]
|
||||
gaugeColors: ColorNumber[]
|
||||
lineColors: ColorString[]
|
||||
staticLegend: boolean
|
||||
isInCEO: boolean
|
||||
}
|
||||
|
||||
const DashVisualization: SFC<Props> = ({
|
||||
axes,
|
||||
type,
|
||||
source,
|
||||
isInCEO,
|
||||
templates,
|
||||
timeRange,
|
||||
lineColors,
|
||||
timeFormat,
|
||||
autoRefresh,
|
||||
gaugeColors,
|
||||
fieldOptions,
|
||||
queryConfigs,
|
||||
staticLegend,
|
||||
tableOptions,
|
||||
decimalPlaces,
|
||||
editQueryStatus,
|
||||
resizerTopHeight,
|
||||
thresholdsListColors,
|
||||
}) => {
|
||||
const colors: ColorString[] = getCellTypeColors({
|
||||
cellType: type,
|
||||
gaugeColors,
|
||||
thresholdsListColors,
|
||||
lineColors,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="graph">
|
||||
<VisualizationName />
|
||||
<div className="graph-container">
|
||||
<RefreshingGraph
|
||||
source={source}
|
||||
colors={colors}
|
||||
axes={axes}
|
||||
type={type}
|
||||
tableOptions={tableOptions}
|
||||
queries={buildQueries(queryConfigs, timeRange)}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
editQueryStatus={editQueryStatus}
|
||||
resizerTopHeight={resizerTopHeight}
|
||||
staticLegend={staticLegend}
|
||||
timeFormat={timeFormat}
|
||||
decimalPlaces={decimalPlaces}
|
||||
fieldOptions={fieldOptions}
|
||||
isInCEO={isInCEO}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
cellEditorOverlay: {
|
||||
thresholdsListColors,
|
||||
gaugeColors,
|
||||
lineColors,
|
||||
cell: {type, axes, tableOptions, fieldOptions, timeFormat, decimalPlaces},
|
||||
},
|
||||
}) => ({
|
||||
gaugeColors,
|
||||
thresholdsListColors,
|
||||
lineColors,
|
||||
type,
|
||||
axes,
|
||||
tableOptions,
|
||||
fieldOptions,
|
||||
timeFormat,
|
||||
decimalPlaces,
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, null)(DashVisualization)
|
|
@ -1,79 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import {connect} from 'react-redux'
|
||||
import {bindActionCreators} from 'redux'
|
||||
|
||||
import {renameCell} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
@ErrorHandling
|
||||
class VisualizationName extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
workingName: props.name,
|
||||
}
|
||||
}
|
||||
handleChange = e => {
|
||||
this.setState({workingName: e.target.value})
|
||||
}
|
||||
|
||||
handleBlur = () => {
|
||||
const {handleRenameCell} = this.props
|
||||
const {workingName} = this.state
|
||||
|
||||
handleRenameCell(workingName)
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.key === 'Enter' || e.key === 'Escape') {
|
||||
e.target.blur()
|
||||
}
|
||||
}
|
||||
|
||||
handleFocus = e => {
|
||||
e.target.select()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {workingName} = this.state
|
||||
|
||||
return (
|
||||
<div className="graph-heading">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control input-sm"
|
||||
value={workingName}
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
placeholder="Name this Cell..."
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {string, func} = PropTypes
|
||||
|
||||
VisualizationName.propTypes = {
|
||||
name: string.isRequired,
|
||||
handleRenameCell: func,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({
|
||||
cellEditorOverlay: {
|
||||
cell: {name},
|
||||
},
|
||||
}) => ({
|
||||
name,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleRenameCell: bindActionCreators(renameCell, dispatch),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(VisualizationName)
|
|
@ -1,7 +1,7 @@
|
|||
import {DEFAULT_TABLE_OPTIONS} from 'src/dashboards/constants'
|
||||
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
|
||||
import {CellType, Axis} from 'src/types/dashboards'
|
||||
import {ColorString, ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
export const initializeOptions = (cellType: CellType) => {
|
||||
switch (cellType) {
|
||||
|
@ -32,11 +32,11 @@ export const DEFAULT_AXIS: DefaultAxis = {
|
|||
export const TOOLTIP_Y_VALUE_FORMAT =
|
||||
'<p><strong>K/M/B</strong> = Thousand / Million / Billion<br/><strong>K/M/G</strong> = Kilo / Mega / Giga </p>'
|
||||
|
||||
interface Color {
|
||||
interface ColorArgs {
|
||||
cellType: CellType
|
||||
thresholdsListColors: ColorNumber[]
|
||||
gaugeColors: ColorNumber[]
|
||||
lineColors: ColorString[]
|
||||
thresholdsListColors: Color[]
|
||||
gaugeColors: Color[]
|
||||
lineColors: Color[]
|
||||
}
|
||||
|
||||
export const getCellTypeColors = ({
|
||||
|
@ -44,8 +44,8 @@ export const getCellTypeColors = ({
|
|||
gaugeColors,
|
||||
thresholdsListColors,
|
||||
lineColors,
|
||||
}: Color): ColorString[] => {
|
||||
let colors: ColorString[] = []
|
||||
}: ColorArgs): Color[] => {
|
||||
let colors: Color[] = []
|
||||
|
||||
switch (cellType) {
|
||||
case CellType.Gauge: {
|
||||
|
|
|
@ -115,9 +115,9 @@ export enum CEOTabs {
|
|||
Vis = 'Visualization',
|
||||
}
|
||||
|
||||
export const MAX_TOLOCALESTRING_VAL = 20 // 20 is the max input to maximumFractionDigits in spec for tolocalestring
|
||||
export const MAX_TO_LOCALE_STRING_VAL = 20 // 20 is the max input to maximumFractionDigits in spec for "to locale string"
|
||||
export const MIN_DECIMAL_PLACES = '0'
|
||||
export const MAX_DECIMAL_PLACES = MAX_TOLOCALESTRING_VAL.toString()
|
||||
export const MAX_DECIMAL_PLACES = MAX_TO_LOCALE_STRING_VAL.toString()
|
||||
|
||||
// used in importing dashboards and mapping sources
|
||||
export const DYNAMIC_SOURCE = 'dynamic'
|
||||
|
|
|
@ -79,9 +79,9 @@ interface Props extends ManualRefreshProps, WithRouterProps {
|
|||
notify: NotificationsActions.PublishNotificationActionCreator
|
||||
selectedCell: Cell
|
||||
thresholdsListType: string
|
||||
thresholdsListColors: ColorsModels.ColorNumber[]
|
||||
gaugeColors: ColorsModels.ColorNumber[]
|
||||
lineColors: ColorsModels.ColorString[]
|
||||
thresholdsListColors: ColorsModels.Color[]
|
||||
gaugeColors: ColorsModels.Color[]
|
||||
lineColors: ColorsModels.Color[]
|
||||
addCell: typeof dashboardActions.addCellAsync
|
||||
deleteCell: typeof dashboardActions.deleteCellAsync
|
||||
copyCell: typeof dashboardActions.copyDashboardCellAsync
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, {ReactElement} from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import {CellType} from 'src/types/dashboards'
|
||||
|
||||
type Graphic = ReactElement<HTMLDivElement>
|
||||
type Graphic = JSX.Element
|
||||
|
||||
interface GraphSVGs {
|
||||
[CellType.Line]: Graphic
|
||||
|
@ -14,6 +14,7 @@ interface GraphSVGs {
|
|||
[CellType.Gauge]: Graphic
|
||||
[CellType.Table]: Graphic
|
||||
}
|
||||
|
||||
const GRAPH_SVGS: GraphSVGs = {
|
||||
line: (
|
||||
<div className="viz-type-selector--graphic">
|
||||
|
|
|
@ -17,14 +17,14 @@ import {initializeOptions} from 'src/dashboards/constants/cellEditor'
|
|||
import {Action, ActionType} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {CellType, Cell} from 'src/types'
|
||||
import {ThresholdType, TableOptions} from 'src/types/dashboards'
|
||||
import {ThresholdColor, GaugeColor, LineColor} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
interface CEOInitialState {
|
||||
cell: Cell | null
|
||||
thresholdsListType: ThresholdType
|
||||
thresholdsListColors: ThresholdColor[]
|
||||
gaugeColors: GaugeColor[]
|
||||
lineColors: LineColor[]
|
||||
thresholdsListColors: Color[]
|
||||
gaugeColors: Color[]
|
||||
lineColors: Color[]
|
||||
}
|
||||
|
||||
export const initialState = {
|
||||
|
|
|
@ -19,11 +19,12 @@ import {
|
|||
DecimalPlaces,
|
||||
CellType,
|
||||
} from 'src/types/dashboards'
|
||||
import {LineColor, ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
export const dashboard = {
|
||||
id: '1',
|
||||
name: 'd1',
|
||||
default: false,
|
||||
cells: [
|
||||
{
|
||||
x: 1,
|
||||
|
@ -42,6 +43,7 @@ export const dashboard = {
|
|||
links: {
|
||||
self: '/v2/dashboards/1',
|
||||
cells: '/v2/dashboards/cells',
|
||||
copy: '/v2/dashboards/1/cells/1/copy',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -175,7 +177,7 @@ export const tableOptions: TableOptions = {
|
|||
wrapping: 'truncate',
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
export const lineColors: LineColor[] = [
|
||||
export const lineColors: Color[] = [
|
||||
{
|
||||
id: '574fb0a3-0a26-44d7-8d71-d4981756acb1',
|
||||
type: 'scale',
|
||||
|
@ -398,29 +400,29 @@ export const predefinedTemplateVariables: Template[] = [
|
|||
{...interval},
|
||||
]
|
||||
|
||||
export const thresholdsListColors: ColorNumber[] = [
|
||||
export const thresholdsListColors: Color[] = [
|
||||
{
|
||||
type: 'text',
|
||||
hex: '#00C9FF',
|
||||
id: 'base',
|
||||
name: 'laser',
|
||||
value: -1000000000000000000,
|
||||
value: '-1000000000000000000',
|
||||
},
|
||||
]
|
||||
|
||||
export const gaugeColors: ColorNumber[] = [
|
||||
export const gaugeColors: Color[] = [
|
||||
{
|
||||
type: 'min',
|
||||
hex: '#00C9FF',
|
||||
id: '0',
|
||||
name: 'laser',
|
||||
value: 0,
|
||||
value: '0',
|
||||
},
|
||||
{
|
||||
type: 'max',
|
||||
hex: '#9394FF',
|
||||
id: '1',
|
||||
name: 'comet',
|
||||
value: 100,
|
||||
value: '100',
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Dygraph from 'dygraphs/src-es5/dygraph'
|
||||
import Dygraph from 'dygraphs/src/dygraph'
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Synchronize zooming and/or selections between a set of dygraphs.
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import PageHeader from 'src/clockface/components/page_layout/PageHeader'
|
||||
|
||||
interface Props {
|
||||
onGoToNewService: () => void
|
||||
}
|
||||
|
||||
const EmptyFluxPage: SFC<Props> = ({onGoToNewService}) => (
|
||||
<div className="page">
|
||||
<PageHeader titleText="Flux Editor" fullWidth={true} />
|
||||
<div className="page-contents">
|
||||
<div className="flux-empty">
|
||||
<p>You do not have a configured Flux source</p>
|
||||
<button className="btn btn-primary btn-md" onClick={onGoToNewService}>
|
||||
Connect to Flux
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default EmptyFluxPage
|
|
@ -16,7 +16,6 @@ import {DEFAULT_LINE_COLORS} from 'src/shared/constants/graphColorPalettes'
|
|||
|
||||
// Types
|
||||
import {FluxTable} from 'src/types'
|
||||
import {DygraphSeries} from 'src/types'
|
||||
import {ViewType} from 'src/types/v2/dashboards'
|
||||
|
||||
interface Props {
|
||||
|
@ -26,25 +25,18 @@ interface Props {
|
|||
|
||||
class FluxGraph extends PureComponent<Props> {
|
||||
public render() {
|
||||
const containerStyle = {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
}
|
||||
|
||||
const {dygraphsData, labels} = fluxTablesToDygraph(this.props.data)
|
||||
|
||||
return (
|
||||
<div className="yield-node--graph">
|
||||
<Dygraph
|
||||
type={ViewType.Line}
|
||||
labels={labels}
|
||||
type={ViewType.Line}
|
||||
staticLegend={false}
|
||||
dygraphSeries={{}}
|
||||
options={this.options}
|
||||
timeSeries={dygraphsData}
|
||||
colors={DEFAULT_LINE_COLORS}
|
||||
dygraphSeries={this.dygraphSeries}
|
||||
options={this.options}
|
||||
containerStyle={containerStyle}
|
||||
handleSetHoverTime={this.props.setHoverTime}
|
||||
/>
|
||||
</div>
|
||||
|
@ -57,10 +49,6 @@ class FluxGraph extends PureComponent<Props> {
|
|||
gridLineColor: '#383846',
|
||||
}
|
||||
}
|
||||
|
||||
private get dygraphSeries(): DygraphSeries {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PureComponent, ReactElement, MouseEvent} from 'react'
|
||||
import React, {PureComponent, MouseEvent} from 'react'
|
||||
import FuncArg from 'src/flux/components/FuncArg'
|
||||
import {OnChangeArg} from 'src/types/flux'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -126,7 +126,7 @@ export default class FuncArgs extends PureComponent<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
get build(): ReactElement<HTMLDivElement> {
|
||||
get build(): JSX.Element {
|
||||
const {func, onGenerateScript} = this.props
|
||||
if (func.name === funcNames.FILTER) {
|
||||
return (
|
||||
|
|
|
@ -72,7 +72,7 @@ interface State {
|
|||
|
||||
type ScriptFunc = (script: string) => void
|
||||
|
||||
export const FluxContext = React.createContext()
|
||||
export const FluxContext = React.createContext({})
|
||||
|
||||
@ErrorHandling
|
||||
export class FluxPage extends PureComponent<Props, State> {
|
||||
|
|
|
@ -173,9 +173,6 @@ describe('influxql astToString', () => {
|
|||
const expected = `SELECT derivative("field1", 1h) / derivative("field2", 1h) FROM "myseries"`
|
||||
const actual = ast.toString()
|
||||
|
||||
// console.log('actual', actual)
|
||||
// console.log('expected', expected)
|
||||
|
||||
expect(actual).toBe(expected)
|
||||
})
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import {toString} from './ast'
|
|||
|
||||
const InfluxQL = ast => {
|
||||
return {
|
||||
// select: () =>
|
||||
toString: () => toString(ast),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ import classnames from 'classnames'
|
|||
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {ColorNumber, ThresholdColor} from 'src/types/colors'
|
||||
import {Color, ThresholdColor} from 'src/types/colors'
|
||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
|
||||
|
||||
interface Props {
|
||||
selected: ColorNumber
|
||||
selected: Color
|
||||
disabled?: boolean
|
||||
stretchToFit?: boolean
|
||||
colors: ThresholdColor[]
|
||||
|
|
|
@ -5,16 +5,16 @@ import classnames from 'classnames'
|
|||
import {ClickOutside} from 'src/shared/components/ClickOutside'
|
||||
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
|
||||
|
||||
import {ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {LINE_COLOR_SCALES} from 'src/shared/constants/graphColorPalettes'
|
||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
onChoose: (colors: ColorNumber[]) => void
|
||||
onChoose: (colors: Color[]) => void
|
||||
stretchToFit?: boolean
|
||||
disabled?: boolean
|
||||
selected: ColorNumber[]
|
||||
selected: Color[]
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, {PureComponent, ReactElement} from 'react'
|
||||
import React, {PureComponent} from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
fileTypesToAccept?: string
|
||||
containerClass?: string
|
||||
handleSubmit: (uploadContent: string, fileName: string) => void
|
||||
handleSubmit: (uploadContent: string | ArrayBuffer, fileName: string) => void
|
||||
submitText?: string
|
||||
submitOnDrop?: boolean
|
||||
submitOnUpload?: boolean
|
||||
|
@ -13,7 +13,7 @@ interface Props {
|
|||
|
||||
interface State {
|
||||
inputContent: string | null
|
||||
uploadContent: string
|
||||
uploadContent: string | ArrayBuffer
|
||||
fileName: string
|
||||
dragClass: string
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ class DragAndDrop extends PureComponent<Props, State> {
|
|||
return classnames('drag-and-drop--form', {active: !uploadContent})
|
||||
}
|
||||
|
||||
private get dragAreaHeader(): ReactElement<HTMLHeadElement> {
|
||||
private get dragAreaHeader(): JSX.Element {
|
||||
const {uploadContent, fileName} = this.state
|
||||
|
||||
if (uploadContent) {
|
||||
|
@ -120,7 +120,7 @@ class DragAndDrop extends PureComponent<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private get buttons(): ReactElement<HTMLSpanElement> | null {
|
||||
private get buttons(): JSX.Element | null {
|
||||
const {uploadContent} = this.state
|
||||
const {submitText, submitOnDrop} = this.props
|
||||
|
||||
|
@ -175,10 +175,10 @@ class DragAndDrop extends PureComponent<Props, State> {
|
|||
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file)
|
||||
reader.onload = loadEvent => {
|
||||
reader.onload = () => {
|
||||
this.setState(
|
||||
{
|
||||
uploadContent: loadEvent.target.result,
|
||||
uploadContent: reader.result,
|
||||
fileName: file.name,
|
||||
},
|
||||
this.submitOnUpload
|
||||
|
@ -201,10 +201,10 @@ class DragAndDrop extends PureComponent<Props, State> {
|
|||
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file)
|
||||
reader.onload = loadEvent => {
|
||||
reader.onload = () => {
|
||||
this.setState(
|
||||
{
|
||||
uploadContent: loadEvent.target.result,
|
||||
uploadContent: reader.result,
|
||||
fileName: file.name,
|
||||
},
|
||||
this.submitOnDrop
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
loading: RemoteDataState
|
||||
children: JSX.Element
|
||||
}
|
||||
|
||||
const GraphLoadingDots = () => (
|
||||
<div className="graph-panel__refreshing">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
)
|
||||
|
||||
class DygraphCell extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {loading} = this.props
|
||||
return (
|
||||
<div className="dygraph graph--hasYLabel" style={this.style}>
|
||||
{loading === RemoteDataState.Loading && <GraphLoadingDots />}
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
}
|
||||
|
||||
export default DygraphCell
|
|
@ -6,13 +6,14 @@ import classnames from 'classnames'
|
|||
import uuid from 'uuid'
|
||||
|
||||
import * as actions from 'src/dashboards/actions/v2/views'
|
||||
import {SeriesLegendData} from 'src/types/dygraphs'
|
||||
import DygraphLegendSort from 'src/shared/components/DygraphLegendSort'
|
||||
|
||||
import {makeLegendStyles} from 'src/shared/graphs/helpers'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {NO_CELL} from 'src/shared/constants'
|
||||
import {DygraphClass} from 'src/types'
|
||||
|
||||
// Types
|
||||
import DygraphClass, {SeriesLegendData} from 'src/external/dygraph'
|
||||
|
||||
interface Props {
|
||||
hoverTime: number
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import {PureComponent} from 'react'
|
||||
import {FluxTable} from 'src/types'
|
||||
|
||||
import {
|
||||
fluxTablesToDygraph,
|
||||
FluxTablesToDygraphResult,
|
||||
} from 'src/shared/parsing/flux/dygraph'
|
||||
|
||||
interface Props {
|
||||
tables: FluxTable[]
|
||||
children: (result: FluxTablesToDygraphResult) => JSX.Element
|
||||
}
|
||||
|
||||
class DygraphTransformation extends PureComponent<
|
||||
Props,
|
||||
FluxTablesToDygraphResult
|
||||
> {
|
||||
public static getDerivedStateFromProps(props) {
|
||||
return {...fluxTablesToDygraph(props.tables)}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
labels: [],
|
||||
dygraphsData: [],
|
||||
nonNumericColumns: [],
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {children} = this.props
|
||||
return children(this.state)
|
||||
}
|
||||
}
|
||||
|
||||
export default DygraphTransformation
|
|
@ -6,20 +6,22 @@ import {GAUGE_SPECS} from 'src/shared/constants/gaugeSpecs'
|
|||
import {
|
||||
COLOR_TYPE_MIN,
|
||||
COLOR_TYPE_MAX,
|
||||
DEFAULT_VALUE_MAX,
|
||||
DEFAULT_VALUE_MIN,
|
||||
MIN_THRESHOLDS,
|
||||
} from 'src/shared/constants/thresholds'
|
||||
import {MAX_TOLOCALESTRING_VAL} from 'src/dashboards/constants'
|
||||
import {MAX_TO_LOCALE_STRING_VAL} from 'src/dashboards/constants'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
|
||||
interface Props {
|
||||
width: string
|
||||
height: string
|
||||
gaugePosition: number
|
||||
colors?: ColorString[]
|
||||
colors?: Color[]
|
||||
prefix: string
|
||||
suffix: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
|
@ -78,12 +80,21 @@ class Gauge extends Component<Props> {
|
|||
if (!colors || colors.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// Distill out max and min values
|
||||
const minValue = Number(
|
||||
colors.find(color => color.type === COLOR_TYPE_MIN).value
|
||||
_.get(
|
||||
colors.find(color => color.type === COLOR_TYPE_MIN),
|
||||
'value',
|
||||
DEFAULT_VALUE_MIN
|
||||
)
|
||||
)
|
||||
const maxValue = Number(
|
||||
colors.find(color => color.type === COLOR_TYPE_MAX).value
|
||||
_.get(
|
||||
colors.find(color => color.type === COLOR_TYPE_MAX),
|
||||
'value',
|
||||
DEFAULT_VALUE_MAX
|
||||
)
|
||||
)
|
||||
|
||||
// The following functions must be called in the specified order
|
||||
|
@ -206,7 +217,7 @@ class Gauge extends Component<Props> {
|
|||
|
||||
// Draw Large ticks
|
||||
for (let lt = 0; lt <= lineCount; lt++) {
|
||||
// Rototion before drawing line
|
||||
// Rotation before drawing line
|
||||
ctx.rotate(startDegree)
|
||||
ctx.rotate(lt * arcLargeIncrement)
|
||||
// Draw line
|
||||
|
@ -225,7 +236,7 @@ class Gauge extends Component<Props> {
|
|||
|
||||
// Draw Small ticks
|
||||
for (let lt = 0; lt <= totalSmallLineCount; lt++) {
|
||||
// Rototion before drawing line
|
||||
// Rotation before drawing line
|
||||
ctx.rotate(startDegree)
|
||||
ctx.rotate(lt * arcSmallIncrement)
|
||||
// Draw line
|
||||
|
@ -322,7 +333,7 @@ class Gauge extends Component<Props> {
|
|||
let valueString
|
||||
|
||||
if (decimalPlaces.isEnforced) {
|
||||
const digits = Math.min(decimalPlaces.digits, MAX_TOLOCALESTRING_VAL)
|
||||
const digits = Math.min(decimalPlaces.digits, MAX_TO_LOCALE_STRING_VAL)
|
||||
valueString = value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: digits,
|
||||
|
@ -330,7 +341,7 @@ class Gauge extends Component<Props> {
|
|||
} else {
|
||||
valueString = value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: MAX_TOLOCALESTRING_VAL,
|
||||
maximumFractionDigits: MAX_TO_LOCALE_STRING_VAL,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -343,7 +354,7 @@ class Gauge extends Component<Props> {
|
|||
let valueString
|
||||
|
||||
if (decimalPlaces.isEnforced) {
|
||||
const digits = Math.min(decimalPlaces.digits, MAX_TOLOCALESTRING_VAL)
|
||||
const digits = Math.min(decimalPlaces.digits, MAX_TO_LOCALE_STRING_VAL)
|
||||
valueString = value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: digits,
|
||||
maximumFractionDigits: digits,
|
||||
|
@ -351,7 +362,7 @@ class Gauge extends Component<Props> {
|
|||
} else {
|
||||
valueString = value.toLocaleString(undefined, {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: MAX_TOLOCALESTRING_VAL,
|
||||
maximumFractionDigits: MAX_TO_LOCALE_STRING_VAL,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -2,28 +2,35 @@ import {shallow} from 'enzyme'
|
|||
import React from 'react'
|
||||
import Gauge from 'src/shared/components/Gauge'
|
||||
import GaugeChart from 'src/shared/components/GaugeChart'
|
||||
import {ViewType, ViewShape, GaugeView} from 'src/types/v2/dashboards'
|
||||
|
||||
const data = [
|
||||
const tables = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
series: [
|
||||
{
|
||||
values: [[1, 2]],
|
||||
columns: ['time', 'value'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
id: '54797afd-734d-4ca3-94b6-3a7870c53b27',
|
||||
data: [
|
||||
['', 'result', 'table', '_time', 'mean', '_measurement'],
|
||||
['', '', '0', '2018-09-27T16:50:10Z', '2', 'cpu'],
|
||||
],
|
||||
name: '_measurement=cpu',
|
||||
groupKey: {
|
||||
_measurement: 'cpu',
|
||||
},
|
||||
dataTypes: {
|
||||
'': '#datatype',
|
||||
result: 'string',
|
||||
table: 'long',
|
||||
_time: 'dateTime:RFC3339',
|
||||
mean: 'double',
|
||||
_measurement: 'string',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const defaultProps = {
|
||||
data: [],
|
||||
isFetchingInitially: false,
|
||||
cellID: '',
|
||||
const properties: GaugeView = {
|
||||
queries: [],
|
||||
colors: [],
|
||||
shape: ViewShape.ChronografV2,
|
||||
type: ViewType.Gauge,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
decimalPlaces: {
|
||||
|
@ -32,6 +39,11 @@ const defaultProps = {
|
|||
},
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
tables: [],
|
||||
properties,
|
||||
}
|
||||
|
||||
const setup = (overrides = {}) => {
|
||||
const props = {
|
||||
...defaultProps,
|
||||
|
@ -54,10 +66,10 @@ describe('GaugeChart', () => {
|
|||
|
||||
describe('when data has a value', () => {
|
||||
it('renders the correct number', () => {
|
||||
const wrapper = setup({data})
|
||||
const wrapper = setup({tables})
|
||||
|
||||
expect(wrapper.find(Gauge).exists()).toBe(true)
|
||||
expect(wrapper.find(Gauge).props().gaugePosition).toBe(2)
|
||||
expect(wrapper.find(Gauge).props().gaugePosition).toBe('2')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,41 +1,35 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
import getLastValues from 'src/shared/parsing/lastValues'
|
||||
// Components
|
||||
import Gauge from 'src/shared/components/Gauge'
|
||||
|
||||
import {DEFAULT_GAUGE_COLORS} from 'src/shared/constants/thresholds'
|
||||
import {stringifyColorValues} from 'src/shared/constants/colorOperations'
|
||||
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'src/shared/constants'
|
||||
// Parsing
|
||||
import getLastValues from 'src/shared/parsing/flux/fluxToSingleStat'
|
||||
|
||||
// Types
|
||||
import {FluxTable} from 'src/types'
|
||||
import {GaugeView} from 'src/types/v2/dashboards'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface Props {
|
||||
data: TimeSeriesServerResponse[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
cellHeight?: number
|
||||
colors?: ColorString[]
|
||||
prefix: string
|
||||
suffix: string
|
||||
resizerTopHeight?: number
|
||||
tables: FluxTable[]
|
||||
properties: GaugeView
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class GaugeChart extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
colors: stringifyColorValues(DEFAULT_GAUGE_COLORS),
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {colors, prefix, suffix, decimalPlaces} = this.props
|
||||
const {colors, prefix, suffix, decimalPlaces} = this.props.properties
|
||||
|
||||
return (
|
||||
<div className="single-stat">
|
||||
<Gauge
|
||||
width="900"
|
||||
height="300"
|
||||
colors={colors}
|
||||
height={this.height}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
gaugePosition={this.lastValueForGauge}
|
||||
|
@ -45,30 +39,10 @@ class GaugeChart extends PureComponent<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
private get height(): string {
|
||||
const {resizerTopHeight} = this.props
|
||||
|
||||
return (this.initialCellHeight || resizerTopHeight || 300).toString()
|
||||
}
|
||||
|
||||
private get initialCellHeight(): string {
|
||||
const {cellHeight} = this.props
|
||||
|
||||
if (cellHeight) {
|
||||
return (cellHeight * DASHBOARD_LAYOUT_ROW_HEIGHT).toString()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private get lastValueForGauge(): number {
|
||||
const {data} = this.props
|
||||
const {lastValues} = getLastValues(data)
|
||||
const lastValue = _.get(lastValues, 0, 0)
|
||||
|
||||
if (!lastValue) {
|
||||
return 0
|
||||
}
|
||||
const {tables} = this.props
|
||||
const {values} = getLastValues(tables)
|
||||
const lastValue = _.get(values, 0, 0)
|
||||
|
||||
return lastValue
|
||||
}
|
||||
|
|
|
@ -1,38 +1,28 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import React, {PureComponent} from 'react'
|
||||
import Dygraph from 'src/shared/components/dygraph/Dygraph'
|
||||
import DygraphCell from 'src/shared/components/DygraphCell'
|
||||
import DygraphTransformation from 'src/shared/components/DygraphTransformation'
|
||||
|
||||
// Components
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
|
||||
// Utils
|
||||
import {
|
||||
fluxTablesToDygraph,
|
||||
FluxTablesToDygraphResult,
|
||||
} from 'src/shared/parsing/flux/dygraph'
|
||||
|
||||
// Types
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {DecimalPlaces} from 'src/types/dashboards'
|
||||
import {Axes, TimeRange, RemoteDataState, FluxTable} from 'src/types'
|
||||
import {ViewType, CellQuery} from 'src/types/v2'
|
||||
import {Options} from 'src/external/dygraph'
|
||||
import {LineView} from 'src/types/v2/dashboards'
|
||||
import {TimeRange} from 'src/types/v2'
|
||||
import {FluxTable, RemoteDataState} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
axes: Axes
|
||||
type: ViewType
|
||||
queries: CellQuery[]
|
||||
timeRange: TimeRange
|
||||
colors: ColorString[]
|
||||
loading: RemoteDataState
|
||||
decimalPlaces: DecimalPlaces
|
||||
data: FluxTable[]
|
||||
properties: LineView
|
||||
timeRange: TimeRange
|
||||
tables: FluxTable[]
|
||||
viewID: string
|
||||
cellHeight: number
|
||||
staticLegend: boolean
|
||||
onZoom: () => void
|
||||
handleSetHoverTime: () => void
|
||||
activeQueryIndex?: number
|
||||
}
|
||||
|
||||
@ErrorHandlingWith(InvalidData)
|
||||
|
@ -41,49 +31,46 @@ class LineGraph extends PureComponent<Props> {
|
|||
staticLegend: false,
|
||||
}
|
||||
|
||||
private isValidData: boolean = true
|
||||
private timeSeries: FluxTablesToDygraphResult
|
||||
|
||||
public componentWillMount() {
|
||||
const {data} = this.props
|
||||
this.parseTimeSeries(data)
|
||||
}
|
||||
|
||||
public parseTimeSeries(data) {
|
||||
this.timeSeries = fluxTablesToDygraph(data)
|
||||
}
|
||||
|
||||
public componentWillUpdate(nextProps) {
|
||||
const {data, activeQueryIndex} = this.props
|
||||
if (
|
||||
data !== nextProps.data ||
|
||||
activeQueryIndex !== nextProps.activeQueryIndex
|
||||
) {
|
||||
this.parseTimeSeries(nextProps.data)
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (!this.isValidData) {
|
||||
return <InvalidData />
|
||||
}
|
||||
|
||||
const {
|
||||
axes,
|
||||
type,
|
||||
colors,
|
||||
tables,
|
||||
viewID,
|
||||
onZoom,
|
||||
loading,
|
||||
queries,
|
||||
timeRange,
|
||||
properties,
|
||||
staticLegend,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {labels, dygraphsData} = this.timeSeries
|
||||
const {axes, type, colors, queries} = properties
|
||||
|
||||
const options = {
|
||||
return (
|
||||
<DygraphTransformation tables={tables}>
|
||||
{({labels, dygraphsData}) => (
|
||||
<DygraphCell loading={loading}>
|
||||
<Dygraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
viewID={viewID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
labels={labels}
|
||||
queries={queries}
|
||||
options={this.options}
|
||||
timeRange={timeRange}
|
||||
timeSeries={dygraphsData}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
</DygraphCell>
|
||||
)}
|
||||
</DygraphTransformation>
|
||||
)
|
||||
}
|
||||
|
||||
private get options(): Partial<Options> {
|
||||
return {
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
labelsKMB: true,
|
||||
|
@ -94,68 +81,8 @@ class LineGraph extends PureComponent<Props> {
|
|||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
connectSeparatedPoints: true,
|
||||
stepPlot: type === 'line-stepplot',
|
||||
stackedGraph: type === 'line-stacked',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="dygraph graph--hasYLabel" style={this.style}>
|
||||
{loading === RemoteDataState.Loading && <GraphLoadingDots />}
|
||||
<Dygraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
viewID={viewID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
labels={labels}
|
||||
queries={queries}
|
||||
options={options}
|
||||
timeRange={timeRange}
|
||||
timeSeries={dygraphsData}
|
||||
staticLegend={staticLegend}
|
||||
dygraphSeries={{}}
|
||||
isGraphFilled={this.isGraphFilled}
|
||||
containerStyle={this.containerStyle}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
>
|
||||
{type === ViewType.LinePlusSingleStat && (
|
||||
<div>Single Stat Goes Here</div>
|
||||
)}
|
||||
</Dygraph>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private get isGraphFilled(): boolean {
|
||||
const {type} = this.props
|
||||
|
||||
if (type === ViewType.LinePlusSingleStat) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private get style(): CSSProperties {
|
||||
return {height: '100%'}
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
return {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GraphLoadingDots = () => (
|
||||
<div className="graph-panel__refreshing">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
)
|
||||
|
||||
export default LineGraph
|
||||
|
|
|
@ -5,12 +5,12 @@ import {bindActionCreators} from 'redux'
|
|||
import ColorScaleDropdown from 'src/shared/components/ColorScaleDropdown'
|
||||
|
||||
import {updateLineColors} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
lineColors: ColorNumber[]
|
||||
handleUpdateLineColors: (colors: ColorNumber[]) => void
|
||||
lineColors: Color[]
|
||||
handleUpdateLineColors: (colors: Color[]) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import {OVERLAY_TECHNOLOGY} from 'shared/constants/classNames'
|
||||
|
||||
const OverlayTechnologies = ({children}) => (
|
||||
<div className={OVERLAY_TECHNOLOGY}>{children}</div>
|
||||
)
|
||||
|
||||
const {node} = PropTypes
|
||||
|
||||
OverlayTechnologies.propTypes = {
|
||||
children: node.isRequired,
|
||||
}
|
||||
|
||||
export default OverlayTechnologies
|
|
@ -1,226 +0,0 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import LineGraph from 'src/shared/components/LineGraph'
|
||||
import GaugeChart from 'src/shared/components/GaugeChart'
|
||||
import TableGraph from 'src/shared/components/TableGraph'
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
||||
|
||||
// Constants
|
||||
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
||||
import {DEFAULT_TIME_FORMAT} from 'src/dashboards/constants'
|
||||
|
||||
// Utils
|
||||
import {buildQueries} from 'src/utils/buildQueriesForLayouts'
|
||||
|
||||
// Actions
|
||||
import {setHoverTime} from 'src/dashboards/actions/v2/hoverTime'
|
||||
|
||||
// Types
|
||||
import {TimeRange, Template, CellQuery} from 'src/types'
|
||||
import {V1View, V1ViewTypes} from 'src/types/v2/dashboards'
|
||||
|
||||
interface Props {
|
||||
link: string
|
||||
timeRange: TimeRange
|
||||
templates: Template[]
|
||||
viewID: string
|
||||
inView: boolean
|
||||
isInCEO: boolean
|
||||
timeFormat: string
|
||||
cellHeight: number
|
||||
autoRefresh: number
|
||||
manualRefresh: number
|
||||
options: V1View
|
||||
staticLegend: boolean
|
||||
onZoom: () => void
|
||||
editQueryStatus: () => void
|
||||
onSetResolution: () => void
|
||||
grabDataForDownload: () => void
|
||||
handleSetHoverTime: () => void
|
||||
}
|
||||
|
||||
class RefreshingGraph extends PureComponent<Props & WithRouterProps> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
inView: true,
|
||||
manualRefresh: 0,
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {link, inView, templates} = this.props
|
||||
const {queries, type} = this.props.options
|
||||
|
||||
if (!queries.length) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">{emptyGraphCopy}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TimeSeries
|
||||
link={link}
|
||||
inView={inView}
|
||||
queries={this.queries}
|
||||
templates={templates}
|
||||
>
|
||||
{({timeSeries, loading}) => {
|
||||
switch (type) {
|
||||
case V1ViewTypes.SingleStat:
|
||||
return this.singleStat(timeSeries)
|
||||
case V1ViewTypes.Table:
|
||||
return this.table(timeSeries)
|
||||
case V1ViewTypes.Gauge:
|
||||
return this.gauge(timeSeries)
|
||||
default:
|
||||
return this.lineGraph(timeSeries, loading)
|
||||
}
|
||||
}}
|
||||
</TimeSeries>
|
||||
)
|
||||
}
|
||||
|
||||
private singleStat = (data): JSX.Element => {
|
||||
const {cellHeight, manualRefresh} = this.props
|
||||
const {colors, decimalPlaces} = this.props.options
|
||||
|
||||
return (
|
||||
<SingleStat
|
||||
data={data}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
suffix={this.suffix}
|
||||
lineGraph={false}
|
||||
key={manualRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private table = (data): JSX.Element => {
|
||||
const {manualRefresh, handleSetHoverTime, grabDataForDownload} = this.props
|
||||
|
||||
const {
|
||||
colors,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
decimalPlaces,
|
||||
} = this.props.options
|
||||
|
||||
return (
|
||||
<TableGraph
|
||||
data={data}
|
||||
colors={colors}
|
||||
key={manualRefresh}
|
||||
tableOptions={tableOptions}
|
||||
fieldOptions={fieldOptions}
|
||||
decimalPlaces={decimalPlaces}
|
||||
timeFormat={DEFAULT_TIME_FORMAT}
|
||||
grabDataForDownload={grabDataForDownload}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private gauge = (data): JSX.Element => {
|
||||
const {cellHeight, manualRefresh} = this.props
|
||||
const {colors, decimalPlaces} = this.props.options
|
||||
|
||||
return (
|
||||
<GaugeChart
|
||||
data={data}
|
||||
colors={colors}
|
||||
prefix={this.prefix}
|
||||
suffix={this.suffix}
|
||||
key={manualRefresh}
|
||||
cellHeight={cellHeight}
|
||||
decimalPlaces={decimalPlaces}
|
||||
resizerTopHeight={100}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private lineGraph = (data, loading): JSX.Element => {
|
||||
const {
|
||||
onZoom,
|
||||
viewID,
|
||||
timeRange,
|
||||
cellHeight,
|
||||
staticLegend,
|
||||
manualRefresh,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {decimalPlaces, axes, type, colors, queries} = this.props.options
|
||||
|
||||
return (
|
||||
<LineGraph
|
||||
data={data}
|
||||
type={type}
|
||||
axes={axes}
|
||||
viewID={viewID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
loading={loading}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
cellHeight={cellHeight}
|
||||
staticLegend={staticLegend}
|
||||
decimalPlaces={decimalPlaces}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private get queries(): CellQuery[] {
|
||||
const {timeRange, options} = this.props
|
||||
const {type} = options
|
||||
const queries = buildQueries(options.queries, timeRange)
|
||||
|
||||
if (type === V1ViewTypes.SingleStat) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
if (type === V1ViewTypes.Gauge) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
return queries
|
||||
}
|
||||
|
||||
private get prefix(): string {
|
||||
const {axes} = this.props.options
|
||||
|
||||
return _.get(axes, 'y.prefix', '')
|
||||
}
|
||||
|
||||
private get suffix(): string {
|
||||
const {axes} = this.props.options
|
||||
return _.get(axes, 'y.suffix', '')
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = ({sources, routing}): Partial<Props> => {
|
||||
const sourceID = routing.locationBeforeTransitions.query.sourceID
|
||||
const source = sources.find(s => s.id === sourceID)
|
||||
const link = source.links.query
|
||||
|
||||
return {
|
||||
link,
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
handleSetHoverTime: setHoverTime,
|
||||
}
|
||||
|
||||
export default connect(mstp, mdtp)(withRouter(RefreshingGraph))
|
|
@ -0,0 +1,184 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {withRouter, WithRouterProps} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Components
|
||||
import LineGraph from 'src/shared/components/LineGraph'
|
||||
import StepPlot from 'src/shared/components/StepPlot'
|
||||
import Stacked from 'src/shared/components/Stacked'
|
||||
import GaugeChart from 'src/shared/components/GaugeChart'
|
||||
// import TableGraph from 'src/shared/components/TableGraph'
|
||||
import SingleStat from 'src/shared/components/SingleStat'
|
||||
import TimeSeries from 'src/shared/components/time_series/TimeSeries'
|
||||
import SingleStatTransform from 'src/shared/components/SingleStatTransform'
|
||||
|
||||
// Constants
|
||||
import {emptyGraphCopy} from 'src/shared/copy/cell'
|
||||
// import {DEFAULT_TIME_FORMAT} from 'src/dashboards/constants'
|
||||
|
||||
// Utils
|
||||
import {buildQueries} from 'src/utils/buildQueriesForLayouts'
|
||||
|
||||
// Actions
|
||||
import {setHoverTime} from 'src/dashboards/actions/v2/hoverTime'
|
||||
|
||||
// Types
|
||||
import {TimeRange, Template, CellQuery} from 'src/types'
|
||||
import {RefreshingViewProperties, ViewType} from 'src/types/v2/dashboards'
|
||||
|
||||
interface Props {
|
||||
link: string
|
||||
timeRange: TimeRange
|
||||
templates: Template[]
|
||||
viewID: string
|
||||
inView: boolean
|
||||
timeFormat: string
|
||||
autoRefresh: number
|
||||
manualRefresh: number
|
||||
staticLegend: boolean
|
||||
onZoom: () => void
|
||||
editQueryStatus: () => void
|
||||
onSetResolution: () => void
|
||||
grabDataForDownload: () => void
|
||||
handleSetHoverTime: () => void
|
||||
properties: RefreshingViewProperties
|
||||
}
|
||||
|
||||
class RefreshingView extends PureComponent<Props & WithRouterProps> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
inView: true,
|
||||
manualRefresh: 0,
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
link,
|
||||
inView,
|
||||
onZoom,
|
||||
viewID,
|
||||
timeRange,
|
||||
templates,
|
||||
properties,
|
||||
staticLegend,
|
||||
manualRefresh,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
if (!properties.queries.length) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">{emptyGraphCopy}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TimeSeries
|
||||
link={link}
|
||||
inView={inView}
|
||||
queries={this.queries}
|
||||
templates={templates}
|
||||
>
|
||||
{({tables, loading}) => {
|
||||
switch (properties.type) {
|
||||
case ViewType.SingleStat:
|
||||
return (
|
||||
<SingleStatTransform tables={tables}>
|
||||
{stat => <SingleStat stat={stat} properties={properties} />}
|
||||
</SingleStatTransform>
|
||||
)
|
||||
case ViewType.Table:
|
||||
return <div>YO! Imma table</div>
|
||||
case ViewType.Gauge:
|
||||
return (
|
||||
<GaugeChart
|
||||
tables={tables}
|
||||
key={manualRefresh}
|
||||
properties={properties}
|
||||
/>
|
||||
)
|
||||
case ViewType.Line:
|
||||
return (
|
||||
<LineGraph
|
||||
tables={tables}
|
||||
viewID={viewID}
|
||||
onZoom={onZoom}
|
||||
loading={loading}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
properties={properties}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
case ViewType.StepPlot:
|
||||
return (
|
||||
<StepPlot
|
||||
tables={tables}
|
||||
viewID={viewID}
|
||||
onZoom={onZoom}
|
||||
loading={loading}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
properties={properties}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
case ViewType.Stacked:
|
||||
return (
|
||||
<Stacked
|
||||
tables={tables}
|
||||
viewID={viewID}
|
||||
onZoom={onZoom}
|
||||
loading={loading}
|
||||
key={manualRefresh}
|
||||
timeRange={timeRange}
|
||||
properties={properties}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return <div>YO!</div>
|
||||
}
|
||||
}}
|
||||
</TimeSeries>
|
||||
)
|
||||
}
|
||||
|
||||
private get queries(): CellQuery[] {
|
||||
const {timeRange, properties} = this.props
|
||||
const {type} = properties
|
||||
const queries = buildQueries(properties.queries, timeRange)
|
||||
|
||||
if (type === ViewType.SingleStat) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
if (type === ViewType.Gauge) {
|
||||
return [queries[0]]
|
||||
}
|
||||
|
||||
return queries
|
||||
}
|
||||
}
|
||||
|
||||
const mstp = ({sources, routing}): Partial<Props> => {
|
||||
const sourceID = routing.locationBeforeTransitions.query.sourceID
|
||||
const source = sources.find(s => s.id === sourceID)
|
||||
const link = source.links.query
|
||||
|
||||
return {
|
||||
link,
|
||||
}
|
||||
}
|
||||
|
||||
const mdtp = {
|
||||
handleSetHoverTime: setHoverTime,
|
||||
}
|
||||
|
||||
export default connect(mstp, mdtp)(withRouter(RefreshingView))
|
|
@ -1,168 +0,0 @@
|
|||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import ResizeHandle from 'shared/components/ResizeHandle'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
const maximumNumChildren = 2
|
||||
const defaultMinTopHeight = 200
|
||||
const defaultMinBottomHeight = 200
|
||||
const defaultInitialTopHeight = '50%'
|
||||
const defaultInitialBottomHeight = '50%'
|
||||
|
||||
@ErrorHandling
|
||||
class ResizeContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
isDragging: false,
|
||||
topHeight: props.initialTopHeight,
|
||||
bottomHeight: props.initialBottomHeight,
|
||||
}
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
minTopHeight: defaultMinTopHeight,
|
||||
minBottomHeight: defaultMinBottomHeight,
|
||||
initialTopHeight: defaultInitialTopHeight,
|
||||
initialBottomHeight: defaultInitialBottomHeight,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
bottomHeightPixels: this.bottom.getBoundingClientRect().height,
|
||||
topHeightPixels: this.top.getBoundingClientRect().height,
|
||||
})
|
||||
}
|
||||
|
||||
handleStartDrag = () => {
|
||||
this.setState({isDragging: true})
|
||||
}
|
||||
|
||||
handleStopDrag = () => {
|
||||
this.setState({isDragging: false})
|
||||
}
|
||||
|
||||
handleMouseLeave = () => {
|
||||
this.setState({isDragging: false})
|
||||
}
|
||||
|
||||
handleDrag = e => {
|
||||
if (!this.state.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
const {minTopHeight, minBottomHeight} = this.props
|
||||
const oneHundred = 100
|
||||
const containerHeight = parseInt(
|
||||
getComputedStyle(this.resizeContainer).height,
|
||||
10
|
||||
)
|
||||
// verticalOffset moves the resize handle as many pixels as the page-heading is taking up.
|
||||
const verticalOffset = window.innerHeight - containerHeight
|
||||
const newTopPanelPercent = Math.ceil(
|
||||
(e.pageY - verticalOffset) / containerHeight * oneHundred
|
||||
)
|
||||
const newBottomPanelPercent = oneHundred - newTopPanelPercent
|
||||
|
||||
// Don't trigger a resize unless the change in size is greater than minResizePercentage
|
||||
const minResizePercentage = 0.5
|
||||
if (
|
||||
Math.abs(newTopPanelPercent - parseFloat(this.state.topHeight)) <
|
||||
minResizePercentage
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const topHeightPixels = newTopPanelPercent / oneHundred * containerHeight
|
||||
const bottomHeightPixels =
|
||||
newBottomPanelPercent / oneHundred * containerHeight
|
||||
|
||||
// Don't trigger a resize if the new sizes are too small
|
||||
if (
|
||||
topHeightPixels < minTopHeight ||
|
||||
bottomHeightPixels < minBottomHeight
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({
|
||||
topHeight: `${newTopPanelPercent}%`,
|
||||
bottomHeight: `${newBottomPanelPercent}%`,
|
||||
bottomHeightPixels,
|
||||
topHeightPixels,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
topHeightPixels,
|
||||
bottomHeightPixels,
|
||||
topHeight,
|
||||
bottomHeight,
|
||||
isDragging,
|
||||
} = this.state
|
||||
const {containerClass, children, theme} = this.props
|
||||
|
||||
if (React.Children.count(children) > maximumNumChildren) {
|
||||
console.error(
|
||||
`There cannot be more than ${maximumNumChildren}' children in ResizeContainer`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(`resize--container ${containerClass}`, {
|
||||
'resize--dragging': isDragging,
|
||||
})}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
onMouseUp={this.handleStopDrag}
|
||||
onMouseMove={this.handleDrag}
|
||||
ref={r => (this.resizeContainer = r)}
|
||||
>
|
||||
<div
|
||||
className="resize--top"
|
||||
style={{height: topHeight}}
|
||||
ref={r => (this.top = r)}
|
||||
>
|
||||
{React.cloneElement(children[0], {
|
||||
resizerBottomHeight: bottomHeightPixels,
|
||||
resizerTopHeight: topHeightPixels,
|
||||
})}
|
||||
</div>
|
||||
<ResizeHandle
|
||||
theme={theme}
|
||||
isDragging={isDragging}
|
||||
onHandleStartDrag={this.handleStartDrag}
|
||||
top={topHeight}
|
||||
/>
|
||||
<div
|
||||
className="resize--bottom"
|
||||
style={{height: bottomHeight, top: topHeight}}
|
||||
ref={r => (this.bottom = r)}
|
||||
>
|
||||
{React.cloneElement(children[1], {
|
||||
resizerBottomHeight: bottomHeightPixels,
|
||||
resizerTopHeight: topHeightPixels,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {node, number, string} = PropTypes
|
||||
|
||||
ResizeContainer.propTypes = {
|
||||
children: node.isRequired,
|
||||
containerClass: string.isRequired,
|
||||
minTopHeight: number,
|
||||
minBottomHeight: number,
|
||||
initialTopHeight: string,
|
||||
initialBottomHeight: string,
|
||||
theme: string,
|
||||
}
|
||||
|
||||
export default ResizeContainer
|
|
@ -1,34 +1,23 @@
|
|||
// Libraries
|
||||
import React, {PureComponent, CSSProperties} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import getLastValues from 'src/shared/parsing/lastValues'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {SMALL_CELL_HEIGHT} from 'src/shared/graphs/helpers'
|
||||
import {DYGRAPH_CONTAINER_V_MARGIN} from 'src/shared/constants'
|
||||
// Constants
|
||||
import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {CellType, DecimalPlaces} from 'src/types/dashboards'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
// Types
|
||||
import {CellType} from 'src/types/dashboards'
|
||||
import {SingleStatView} from 'src/types/v2/dashboards'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
interface Props {
|
||||
decimalPlaces: DecimalPlaces
|
||||
cellHeight: number
|
||||
colors: ColorString[]
|
||||
prefix?: string
|
||||
suffix?: string
|
||||
lineGraph: boolean
|
||||
staticLegendHeight?: number
|
||||
data: TimeSeriesServerResponse[]
|
||||
properties: SingleStatView
|
||||
stat: number
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class SingleStat extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div className="single-stat" style={this.containerStyle}>
|
||||
|
@ -37,33 +26,18 @@ class SingleStat extends PureComponent<Props> {
|
|||
)
|
||||
}
|
||||
|
||||
private get renderShadow(): JSX.Element {
|
||||
const {lineGraph} = this.props
|
||||
|
||||
return lineGraph && <div className="single-stat--shadow" />
|
||||
}
|
||||
|
||||
private get prefixSuffixValue(): string {
|
||||
const {prefix, suffix} = this.props
|
||||
const {prefix, suffix} = this.props.properties
|
||||
|
||||
return `${prefix}${this.roundedLastValue}${suffix}`
|
||||
}
|
||||
|
||||
private get lastValue(): number {
|
||||
const {data} = this.props
|
||||
const {lastValues, series} = getLastValues(data)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
|
||||
return lastValues[firstAlphabeticalIndex]
|
||||
return this.props.stat
|
||||
}
|
||||
|
||||
private get roundedLastValue(): string {
|
||||
const {decimalPlaces} = this.props
|
||||
const {decimalPlaces} = this.props.properties
|
||||
|
||||
if (this.lastValue === null) {
|
||||
return `${0}`
|
||||
|
@ -84,41 +58,21 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
const {staticLegendHeight} = this.props
|
||||
|
||||
const height = `calc(100% - ${staticLegendHeight +
|
||||
DYGRAPH_CONTAINER_V_MARGIN * 2}px)`
|
||||
|
||||
const {backgroundColor} = this.coloration
|
||||
|
||||
if (staticLegendHeight) {
|
||||
return {
|
||||
backgroundColor,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundColor,
|
||||
}
|
||||
}
|
||||
|
||||
private get coloration(): CSSProperties {
|
||||
const {data, colors, lineGraph} = this.props
|
||||
const {colors} = this.props.properties
|
||||
|
||||
const {lastValues, series} = getLastValues(data)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
const lastValue = lastValues[firstAlphabeticalIndex]
|
||||
|
||||
const {bgColor, textColor} = generateThresholdsListHexs({
|
||||
colors,
|
||||
lastValue,
|
||||
cellType: lineGraph ? CellType.LinePlusSingleStat : CellType.SingleStat,
|
||||
lastValue: this.props.stat,
|
||||
cellType: CellType.SingleStat,
|
||||
})
|
||||
|
||||
return {
|
||||
|
@ -128,22 +82,8 @@ class SingleStat extends PureComponent<Props> {
|
|||
}
|
||||
|
||||
private get resizerBox(): JSX.Element {
|
||||
const {lineGraph, cellHeight} = this.props
|
||||
const {color} = this.coloration
|
||||
|
||||
if (lineGraph) {
|
||||
const className = classnames('single-stat--value', {
|
||||
small: cellHeight <= SMALL_CELL_HEIGHT,
|
||||
})
|
||||
|
||||
return (
|
||||
<span className={className} style={{color}}>
|
||||
{this.prefixSuffixValue}
|
||||
{this.renderShadow}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const viewBox = `0 0 ${this.prefixSuffixValue.length * 55} 100`
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Parsing
|
||||
import getLastValues from 'src/shared/parsing/flux/fluxToSingleStat'
|
||||
|
||||
// Types
|
||||
import {FluxTable} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
tables: FluxTable[]
|
||||
children: (stat: number) => JSX.Element
|
||||
}
|
||||
|
||||
export default class SingleStatTransform extends PureComponent<Props> {
|
||||
public render() {
|
||||
const lastValue = +this.lastValue
|
||||
if (!_.isNumber(lastValue)) {
|
||||
return (
|
||||
<div>
|
||||
Could not display single stat because your values are non-numeric
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return this.props.children(lastValue)
|
||||
}
|
||||
|
||||
private get lastValue(): number {
|
||||
const {tables} = this.props
|
||||
const {series, values} = getLastValues(tables)
|
||||
const firstAlphabeticalSeriesName = _.sortBy(series)[0]
|
||||
|
||||
const firstAlphabeticalIndex = _.indexOf(
|
||||
series,
|
||||
firstAlphabeticalSeriesName
|
||||
)
|
||||
|
||||
return values[firstAlphabeticalIndex]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import Dygraph from 'src/shared/components/dygraph/Dygraph'
|
||||
import DygraphCell from 'src/shared/components/DygraphCell'
|
||||
import DygraphTransformation from 'src/shared/components/DygraphTransformation'
|
||||
|
||||
// Components
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
|
||||
// Types
|
||||
import {Options} from 'src/external/dygraph'
|
||||
import {StackedView} from 'src/types/v2/dashboards'
|
||||
import {TimeRange} from 'src/types/v2'
|
||||
import {FluxTable, RemoteDataState} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
loading: RemoteDataState
|
||||
properties: StackedView
|
||||
timeRange: TimeRange
|
||||
tables: FluxTable[]
|
||||
viewID: string
|
||||
staticLegend: boolean
|
||||
onZoom: () => void
|
||||
handleSetHoverTime: () => void
|
||||
}
|
||||
|
||||
@ErrorHandlingWith(InvalidData)
|
||||
class Stacked extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
tables,
|
||||
viewID,
|
||||
onZoom,
|
||||
loading,
|
||||
timeRange,
|
||||
properties,
|
||||
staticLegend,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {axes, type, colors, queries} = properties
|
||||
|
||||
return (
|
||||
<DygraphTransformation tables={tables}>
|
||||
{({labels, dygraphsData}) => (
|
||||
<DygraphCell loading={loading}>
|
||||
<Dygraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
viewID={viewID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
labels={labels}
|
||||
queries={queries}
|
||||
options={this.options}
|
||||
timeRange={timeRange}
|
||||
timeSeries={dygraphsData}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
</DygraphCell>
|
||||
)}
|
||||
</DygraphTransformation>
|
||||
)
|
||||
}
|
||||
|
||||
private get options(): Partial<Options> {
|
||||
return {
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
labelsKMB: true,
|
||||
fillGraph: true,
|
||||
axisLabelWidth: 60,
|
||||
animatedZooms: true,
|
||||
drawAxesAtZero: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
connectSeparatedPoints: true,
|
||||
stackedGraph: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Stacked
|
|
@ -0,0 +1,89 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import Dygraph from 'src/shared/components/dygraph/Dygraph'
|
||||
import DygraphCell from 'src/shared/components/DygraphCell'
|
||||
import DygraphTransformation from 'src/shared/components/DygraphTransformation'
|
||||
|
||||
// Components
|
||||
import {ErrorHandlingWith} from 'src/shared/decorators/errors'
|
||||
import InvalidData from 'src/shared/components/InvalidData'
|
||||
|
||||
// Types
|
||||
import {Options} from 'src/external/dygraph'
|
||||
import {StepPlotView} from 'src/types/v2/dashboards'
|
||||
import {TimeRange} from 'src/types/v2'
|
||||
import {FluxTable, RemoteDataState} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
loading: RemoteDataState
|
||||
properties: StepPlotView
|
||||
timeRange: TimeRange
|
||||
tables: FluxTable[]
|
||||
viewID: string
|
||||
staticLegend: boolean
|
||||
onZoom: () => void
|
||||
handleSetHoverTime: () => void
|
||||
}
|
||||
|
||||
@ErrorHandlingWith(InvalidData)
|
||||
class StepPlot extends PureComponent<Props> {
|
||||
public static defaultProps: Partial<Props> = {
|
||||
staticLegend: false,
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
tables,
|
||||
viewID,
|
||||
onZoom,
|
||||
loading,
|
||||
timeRange,
|
||||
properties,
|
||||
staticLegend,
|
||||
handleSetHoverTime,
|
||||
} = this.props
|
||||
|
||||
const {axes, type, colors, queries} = properties
|
||||
|
||||
return (
|
||||
<DygraphTransformation tables={tables}>
|
||||
{({labels, dygraphsData}) => (
|
||||
<DygraphCell loading={loading}>
|
||||
<Dygraph
|
||||
type={type}
|
||||
axes={axes}
|
||||
viewID={viewID}
|
||||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
labels={labels}
|
||||
queries={queries}
|
||||
options={this.options}
|
||||
timeRange={timeRange}
|
||||
timeSeries={dygraphsData}
|
||||
staticLegend={staticLegend}
|
||||
handleSetHoverTime={handleSetHoverTime}
|
||||
/>
|
||||
</DygraphCell>
|
||||
)}
|
||||
</DygraphTransformation>
|
||||
)
|
||||
}
|
||||
|
||||
private get options(): Partial<Options> {
|
||||
return {
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
labelsKMB: true,
|
||||
fillGraph: true,
|
||||
axisLabelWidth: 60,
|
||||
animatedZooms: true,
|
||||
drawAxesAtZero: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
connectSeparatedPoints: true,
|
||||
stepPlot: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StepPlot
|
File diff suppressed because it is too large
Load Diff
|
@ -9,7 +9,7 @@ import Threshold from 'src/dashboards/components/Threshold'
|
|||
import ColorDropdown from 'src/shared/components/ColorDropdown'
|
||||
|
||||
import {updateThresholdsListColors} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {ColorNumber} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
import {
|
||||
THRESHOLD_COLORS,
|
||||
|
@ -24,8 +24,8 @@ interface Props {
|
|||
onResetFocus: () => void
|
||||
showListHeading: boolean
|
||||
thresholdsListType: string
|
||||
thresholdsListColors: ColorNumber[]
|
||||
handleUpdateThresholdsListColors: (c: ColorNumber[]) => void
|
||||
thresholdsListColors: Color[]
|
||||
handleUpdateThresholdsListColors: (c: Color[]) => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -99,8 +99,8 @@ class ThresholdsList extends PureComponent<Props> {
|
|||
|
||||
const randomColor = _.random(0, THRESHOLD_COLORS.length - 1)
|
||||
|
||||
const maxValue = DEFAULT_VALUE_MIN
|
||||
const minValue = DEFAULT_VALUE_MAX
|
||||
const maxValue = +DEFAULT_VALUE_MIN
|
||||
const minValue = +DEFAULT_VALUE_MAX
|
||||
|
||||
let randomValue = _.round(_.random(minValue, maxValue, true), 2)
|
||||
|
||||
|
@ -108,7 +108,7 @@ class ThresholdsList extends PureComponent<Props> {
|
|||
const colorsValues = _.mapValues(thresholdsListColors, 'value')
|
||||
do {
|
||||
randomValue = _.round(_.random(minValue, maxValue, true), 2)
|
||||
} while (_.includes(colorsValues, randomValue))
|
||||
} while (_.includes(colorsValues, `${randomValue}`))
|
||||
}
|
||||
|
||||
const newThreshold = {
|
||||
|
@ -122,7 +122,7 @@ class ThresholdsList extends PureComponent<Props> {
|
|||
const updatedColors = _.sortBy(
|
||||
[...thresholdsListColors, newThreshold],
|
||||
color => color.value
|
||||
)
|
||||
) as Color[]
|
||||
|
||||
handleUpdateThresholdsListColors(updatedColors)
|
||||
onResetFocus()
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
import NewsFeed from 'src/status/components/NewsFeed'
|
||||
import GettingStarted from 'src/status/components/GettingStarted'
|
||||
|
||||
import {Cell} from 'src/types/dashboards'
|
||||
import {Source} from 'src/types/sources'
|
||||
import {TimeRange} from 'src/types/queries'
|
||||
|
||||
interface Props {
|
||||
timeRange: TimeRange
|
||||
cell: Cell
|
||||
source: Source
|
||||
}
|
||||
|
||||
const WidgetCell: SFC<Props> = ({cell, source}) => {
|
||||
switch (cell.type) {
|
||||
case 'news': {
|
||||
return <NewsFeed source={source} />
|
||||
}
|
||||
case 'guide': {
|
||||
return <GettingStarted />
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<p data-test="data-explorer-no-results">Nothing to show</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WidgetCell
|
|
@ -12,7 +12,7 @@ import {getView} from 'src/dashboards/apis/v2/view'
|
|||
|
||||
// Types
|
||||
import {CellQuery, RemoteDataState, Template, TimeRange} from 'src/types'
|
||||
import {Cell, View, ViewShape} from 'src/types/v2'
|
||||
import {Cell, View} from 'src/types/v2'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
|
@ -89,10 +89,6 @@ export default class CellComponent extends Component<Props, State> {
|
|||
return null
|
||||
}
|
||||
|
||||
if (view.properties.shape === ViewShape.Empty) {
|
||||
return this.emptyGraph
|
||||
}
|
||||
|
||||
return (
|
||||
<ViewComponent
|
||||
view={view}
|
||||
|
@ -101,23 +97,11 @@ export default class CellComponent extends Component<Props, State> {
|
|||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
manualRefresh={manualRefresh}
|
||||
onSummonOverlay={this.handleSummonOverlay}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private get emptyGraph(): JSX.Element {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<button
|
||||
className="no-query--button btn btn-md btn-primary"
|
||||
onClick={this.handleSummonOverlay}
|
||||
>
|
||||
<span className="icon plus" /> Add Data
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private handleSummonOverlay = (): void => {
|
||||
// TODO: add back in once CEO is refactored
|
||||
}
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
import React, {Component} from 'react'
|
||||
|
||||
// Components
|
||||
import RefreshingGraph from 'src/shared/components/RefreshingGraph'
|
||||
import Markdown from 'src/shared/components/views/Markdown'
|
||||
import RefreshingView from 'src/shared/components/RefreshingView'
|
||||
|
||||
// Constants
|
||||
import {text} from 'src/shared/components/views/gettingsStarted'
|
||||
|
||||
// Types
|
||||
import {TimeRange, Template} from 'src/types'
|
||||
import {View, ViewType} from 'src/types/v2'
|
||||
import {text} from 'src/shared/components/views/gettingsStarted'
|
||||
import {View, ViewType, ViewShape} from 'src/types/v2'
|
||||
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
|
@ -19,6 +21,7 @@ interface Props {
|
|||
autoRefresh: number
|
||||
manualRefresh: number
|
||||
onZoom: (range: TimeRange) => void
|
||||
onSummonOverlay: () => void
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
|
@ -37,24 +40,41 @@ class ViewComponent extends Component<Props> {
|
|||
templates,
|
||||
} = this.props
|
||||
|
||||
if (view.properties.shape === ViewShape.Empty) {
|
||||
return this.emptyGraph
|
||||
}
|
||||
|
||||
if (view.properties.type === ViewType.Markdown) {
|
||||
return <Markdown text={text} />
|
||||
}
|
||||
|
||||
return (
|
||||
<RefreshingGraph
|
||||
<RefreshingView
|
||||
viewID={view.id}
|
||||
onZoom={onZoom}
|
||||
timeRange={timeRange}
|
||||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
options={view.properties}
|
||||
properties={view.properties}
|
||||
manualRefresh={manualRefresh}
|
||||
grabDataForDownload={this.grabDataForDownload}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private get emptyGraph(): JSX.Element {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
<button
|
||||
className="no-query--button btn btn-md btn-primary"
|
||||
onClick={this.props.onSummonOverlay}
|
||||
>
|
||||
<span className="icon plus" /> Add Data
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private grabDataForDownload = cellData => {
|
||||
this.setState({cellData})
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import NanoDate from 'nano-date'
|
|||
import ReactResizeDetector from 'react-resize-detector'
|
||||
|
||||
// Components
|
||||
import D from 'src/external/dygraph'
|
||||
import Dygraphs from 'src/external/dygraph'
|
||||
import DygraphLegend from 'src/shared/components/DygraphLegend'
|
||||
import StaticLegend from 'src/shared/components/StaticLegend'
|
||||
import Crosshair from 'src/shared/components/crosshair/Crosshair'
|
||||
|
@ -35,38 +35,29 @@ const {LOG, BASE_10, BASE_2} = AXES_SCALE_OPTIONS
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
// Types
|
||||
import {
|
||||
Axes,
|
||||
TimeRange,
|
||||
DygraphData,
|
||||
DygraphClass,
|
||||
DygraphSeries,
|
||||
Constructable,
|
||||
} from 'src/types'
|
||||
import {LineColor} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
import {Axes, TimeRange} from 'src/types'
|
||||
import {CellQuery, ViewType} from 'src/types/v2/dashboards'
|
||||
|
||||
const Dygraphs = D as Constructable<DygraphClass>
|
||||
import {DygraphData, DygraphSeries, Options} from 'src/external/dygraph'
|
||||
|
||||
interface Props {
|
||||
type: ViewType
|
||||
viewID?: string
|
||||
queries?: CellQuery[]
|
||||
timeSeries: DygraphData
|
||||
labels: string[]
|
||||
options: dygraphs.Options
|
||||
containerStyle: object // TODO
|
||||
dygraphSeries: DygraphSeries
|
||||
timeRange?: TimeRange
|
||||
colors: LineColor[]
|
||||
options: Partial<Options>
|
||||
colors: Color[]
|
||||
handleSetHoverTime: (t: string) => void
|
||||
viewID?: string
|
||||
axes?: Axes
|
||||
isGraphFilled?: boolean
|
||||
staticLegend?: boolean
|
||||
mode?: string
|
||||
queries?: CellQuery[]
|
||||
timeRange?: TimeRange
|
||||
dygraphSeries?: DygraphSeries
|
||||
underlayCallback?: () => void
|
||||
setResolution?: (w: number) => void
|
||||
onZoom?: (timeRange: TimeRange) => void
|
||||
mode?: string
|
||||
underlayCallback?: () => void
|
||||
isGraphFilled?: boolean
|
||||
staticLegend?: boolean
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -92,17 +83,17 @@ class Dygraph extends Component<Props, State> {
|
|||
...DEFAULT_AXIS,
|
||||
},
|
||||
},
|
||||
containerStyle: {},
|
||||
isGraphFilled: true,
|
||||
onZoom: () => {},
|
||||
staticLegend: false,
|
||||
setResolution: () => {},
|
||||
handleSetHoverTime: () => {},
|
||||
underlayCallback: () => {},
|
||||
dygraphSeries: {},
|
||||
}
|
||||
|
||||
private graphRef: React.RefObject<HTMLDivElement>
|
||||
private dygraph: DygraphClass
|
||||
private dygraph: Dygraphs
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
@ -127,7 +118,7 @@ class Dygraph extends Component<Props, State> {
|
|||
|
||||
const timeSeries = this.timeSeries
|
||||
|
||||
let defaultOptions = {
|
||||
let defaultOptions: Partial<Options> = {
|
||||
...options,
|
||||
labels,
|
||||
fillGraph,
|
||||
|
@ -311,21 +302,30 @@ class Dygraph extends Component<Props, State> {
|
|||
return null
|
||||
}
|
||||
|
||||
private get containerStyle(): CSSProperties {
|
||||
return {
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'calc(100% - 16px)',
|
||||
position: 'absolute',
|
||||
top: '8px',
|
||||
}
|
||||
}
|
||||
|
||||
private get dygraphStyle(): CSSProperties {
|
||||
const {containerStyle, staticLegend} = this.props
|
||||
const {staticLegend} = this.props
|
||||
const {staticLegendHeight} = this.state
|
||||
|
||||
if (staticLegend) {
|
||||
const cellVerticalPadding = 16
|
||||
|
||||
return {
|
||||
...containerStyle,
|
||||
...this.containerStyle,
|
||||
zIndex: 2,
|
||||
height: `calc(100% - ${staticLegendHeight + cellVerticalPadding}px)`,
|
||||
}
|
||||
}
|
||||
|
||||
return {...containerStyle, zIndex: 2}
|
||||
return {...this.containerStyle, zIndex: 2}
|
||||
}
|
||||
|
||||
private getYRange = (timeSeries: DygraphData): [number, number] => {
|
||||
|
|
|
@ -14,7 +14,7 @@ import AutoRefresh from 'src/utils/AutoRefresh'
|
|||
export const DEFAULT_TIME_SERIES = [{response: {results: []}}]
|
||||
|
||||
interface RenderProps {
|
||||
timeSeries: FluxTable[]
|
||||
tables: FluxTable[]
|
||||
loading: RemoteDataState
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ interface Props {
|
|||
interface State {
|
||||
loading: RemoteDataState
|
||||
isFirstFetch: boolean
|
||||
timeSeries: FluxTable[]
|
||||
tables: FluxTable[]
|
||||
}
|
||||
|
||||
class TimeSeries extends Component<Props, State> {
|
||||
|
@ -41,9 +41,9 @@ class TimeSeries extends Component<Props, State> {
|
|||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
timeSeries: [],
|
||||
loading: RemoteDataState.NotStarted,
|
||||
isFirstFetch: false,
|
||||
tables: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ class TimeSeries extends Component<Props, State> {
|
|||
}
|
||||
|
||||
if (!queries.length) {
|
||||
return this.setState({timeSeries: []})
|
||||
return this.setState({tables: []})
|
||||
}
|
||||
|
||||
this.setState({loading: RemoteDataState.Loading, isFirstFetch})
|
||||
|
@ -81,7 +81,7 @@ class TimeSeries extends Component<Props, State> {
|
|||
const TEMP_RES = 300
|
||||
|
||||
try {
|
||||
const timeSeries = await fetchTimeSeries(
|
||||
const tables = await fetchTimeSeries(
|
||||
link,
|
||||
this.queries,
|
||||
TEMP_RES,
|
||||
|
@ -89,7 +89,7 @@ class TimeSeries extends Component<Props, State> {
|
|||
)
|
||||
|
||||
this.setState({
|
||||
timeSeries,
|
||||
tables,
|
||||
loading: RemoteDataState.Done,
|
||||
})
|
||||
} catch (err) {
|
||||
|
@ -98,9 +98,9 @@ class TimeSeries extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {timeSeries, loading, isFirstFetch} = this.state
|
||||
const {tables, loading, isFirstFetch} = this.state
|
||||
|
||||
const hasValues = _.some(timeSeries, s => {
|
||||
const hasValues = _.some(tables, s => {
|
||||
const data = _.get(s, 'data', [])
|
||||
return !!data.length
|
||||
})
|
||||
|
@ -121,7 +121,7 @@ class TimeSeries extends Component<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
return this.props.children({timeSeries, loading})
|
||||
return this.props.children({tables, loading})
|
||||
}
|
||||
|
||||
private get queries(): string[] {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import chroma from 'chroma-js'
|
||||
import uuid from 'uuid'
|
||||
import {LineColor} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
const COLOR_TYPE_SCALE = 'scale'
|
||||
|
||||
|
@ -208,10 +208,7 @@ export const LINE_COLOR_SCALES = [
|
|||
return {name, colors, id}
|
||||
})
|
||||
|
||||
export const validateLineColors = (
|
||||
colors: LineColor[],
|
||||
numSeries = 0
|
||||
): LineColor[] => {
|
||||
export const validateLineColors = (colors: Color[], numSeries = 0): Color[] => {
|
||||
const multipleSeriesButOneColor = numSeries > 1 && colors.length < 2
|
||||
if (!colors || colors.length === 0 || multipleSeriesButOneColor) {
|
||||
return DEFAULT_LINE_COLORS
|
||||
|
@ -225,7 +222,7 @@ export const validateLineColors = (
|
|||
}
|
||||
|
||||
export const getLineColorsHexes = (
|
||||
colors: LineColor[],
|
||||
colors: Color[],
|
||||
numSeries: number
|
||||
): string[] => {
|
||||
const validatedColors = validateLineColors(colors, numSeries) // ensures safe defaults
|
||||
|
|
|
@ -4,9 +4,9 @@ export const MAX_THRESHOLDS = 5
|
|||
export const MIN_THRESHOLDS = 2
|
||||
|
||||
export const COLOR_TYPE_MIN = 'min'
|
||||
export const DEFAULT_VALUE_MIN = 0
|
||||
export const DEFAULT_VALUE_MIN = '0'
|
||||
export const COLOR_TYPE_MAX = 'max'
|
||||
export const DEFAULT_VALUE_MAX = 100
|
||||
export const DEFAULT_VALUE_MAX = '100'
|
||||
export const COLOR_TYPE_THRESHOLD = 'threshold'
|
||||
|
||||
export const THRESHOLD_TYPE_TEXT = 'text'
|
||||
|
@ -115,7 +115,7 @@ export const DEFAULT_THRESHOLDS_LIST_COLORS = [
|
|||
hex: THRESHOLD_COLORS[11].hex,
|
||||
id: THRESHOLD_TYPE_BASE,
|
||||
name: THRESHOLD_COLORS[11].name,
|
||||
value: -999999999999999999,
|
||||
value: '-999999999999999999',
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
// Libraries
|
||||
import _ from 'lodash'
|
||||
import {FluxTable, DygraphValue} from 'src/types'
|
||||
|
||||
// Types
|
||||
import {FluxTable} from 'src/types'
|
||||
import {DygraphValue} from 'src/external/dygraph'
|
||||
|
||||
const COLUMN_BLACKLIST = new Set([
|
||||
'_time',
|
||||
|
@ -47,7 +51,7 @@ export const fluxTablesToDygraph = (
|
|||
}
|
||||
|
||||
const uniqueColumnName = Object.entries(table.groupKey).reduce(
|
||||
(acc, [k, v]) => acc + `[${k}=${v}]`,
|
||||
(acc, [k, v]) => `${acc}[${k}=${v}]`,
|
||||
columnName
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import _ from 'lodash'
|
||||
import {FluxTable} from 'src/types'
|
||||
import {parseTablesByTime} from 'src/shared/parsing/flux/parseTablesByTime'
|
||||
|
||||
export interface LastValues {
|
||||
values: number[]
|
||||
series: string[]
|
||||
}
|
||||
|
||||
export default (tables: FluxTable[]): LastValues => {
|
||||
const {tablesByTime} = parseTablesByTime(tables)
|
||||
|
||||
const lastValues = _.reduce(
|
||||
tablesByTime,
|
||||
(acc, table) => {
|
||||
const lastTime = _.last(Object.keys(table))
|
||||
const values = table[lastTime]
|
||||
_.forEach(values, (value, series) => {
|
||||
acc.series.push(series)
|
||||
acc.values.push(value)
|
||||
})
|
||||
return acc
|
||||
},
|
||||
{values: [], series: []}
|
||||
)
|
||||
|
||||
return lastValues
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import {FluxTable} from 'src/types'
|
||||
|
||||
const COLUMN_BLACKLIST = new Set([
|
||||
'_time',
|
||||
'result',
|
||||
'table',
|
||||
'_start',
|
||||
'_stop',
|
||||
'',
|
||||
])
|
||||
|
||||
const NUMERIC_DATATYPES = ['double', 'long', 'int', 'float']
|
||||
|
||||
interface TableByTime {
|
||||
[time: string]: {[columnName: string]: string}
|
||||
}
|
||||
interface ParseTablesByTimeResult {
|
||||
tablesByTime: TableByTime[]
|
||||
allColumnNames: string[]
|
||||
nonNumericColumns: string[]
|
||||
}
|
||||
|
||||
export const parseTablesByTime = (
|
||||
tables: FluxTable[]
|
||||
): ParseTablesByTimeResult => {
|
||||
const allColumnNames = []
|
||||
const nonNumericColumns = []
|
||||
|
||||
const tablesByTime = tables.map(table => {
|
||||
const header = table.data[0] as string[]
|
||||
const columnNames: {[k: number]: string} = {}
|
||||
|
||||
for (let i = 0; i < header.length; i++) {
|
||||
const columnName = header[i]
|
||||
const dataType = table.dataTypes[columnName]
|
||||
|
||||
if (COLUMN_BLACKLIST.has(columnName)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (table.groupKey[columnName]) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!NUMERIC_DATATYPES.includes(dataType)) {
|
||||
nonNumericColumns.push(columnName)
|
||||
continue
|
||||
}
|
||||
|
||||
const uniqueColumnName = Object.entries(table.groupKey).reduce(
|
||||
(acc, [k, v]) => acc + `[${k}=${v}]`,
|
||||
columnName
|
||||
)
|
||||
|
||||
columnNames[i] = uniqueColumnName
|
||||
allColumnNames.push(uniqueColumnName)
|
||||
}
|
||||
|
||||
const timeIndex = header.indexOf('_time')
|
||||
|
||||
if (timeIndex < 0) {
|
||||
throw new Error('Could not find time index in FluxTable')
|
||||
}
|
||||
|
||||
const result = {}
|
||||
for (let i = 1; i < table.data.length; i++) {
|
||||
const row = table.data[i]
|
||||
const time = row[timeIndex].toString()
|
||||
|
||||
result[time] = Object.entries(columnNames).reduce(
|
||||
(acc, [valueIndex, columnName]) => ({
|
||||
...acc,
|
||||
[columnName]: row[valueIndex],
|
||||
}),
|
||||
{}
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
return {nonNumericColumns, tablesByTime, allColumnNames}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import lastValues from 'src/shared/parsing/lastValues'
|
||||
|
||||
describe('lastValues', () => {
|
||||
it('returns the correct value when response is empty', () => {
|
||||
expect(lastValues([])).toEqual({
|
||||
lastValues: [''],
|
||||
series: [''],
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,28 +0,0 @@
|
|||
import _ from 'lodash'
|
||||
import {Data} from 'src/types/dygraphs'
|
||||
import {TimeSeriesServerResponse} from 'src/types/series'
|
||||
|
||||
interface Result {
|
||||
lastValues: number[]
|
||||
series: string[]
|
||||
}
|
||||
|
||||
export default function(
|
||||
timeSeriesResponse: TimeSeriesServerResponse[] | Data | null
|
||||
): Result {
|
||||
const values = _.get(
|
||||
timeSeriesResponse,
|
||||
['0', 'response', 'results', '0', 'series', '0', 'values'],
|
||||
[['', '']]
|
||||
)
|
||||
|
||||
const series = _.get(
|
||||
timeSeriesResponse,
|
||||
['0', 'response', 'results', '0', 'series', '0', 'columns'],
|
||||
['', '']
|
||||
).slice(1)
|
||||
|
||||
const lastValues = values[values.length - 1].slice(1) // remove time with slice 1
|
||||
|
||||
return {lastValues, series}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React, {SFC, ReactElement} from 'react'
|
||||
import React, {SFC} from 'react'
|
||||
|
||||
const InfluxTableHead: SFC = (): ReactElement<HTMLTableHeaderCellElement> => {
|
||||
const InfluxTableHead: SFC = (): JSX.Element => {
|
||||
return (
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {PureComponent, ReactElement} from 'react'
|
||||
import React, {PureComponent} from 'react'
|
||||
import {Link} from 'react-router'
|
||||
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
|
@ -42,7 +42,7 @@ class InfluxTableRow extends PureComponent<Props> {
|
|||
this.props.onDeleteSource(this.props.source)
|
||||
}
|
||||
|
||||
private get connectButton(): ReactElement<HTMLDivElement> {
|
||||
private get connectButton(): JSX.Element {
|
||||
const {source} = this.props
|
||||
if (this.isCurrentSource) {
|
||||
return (
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
interface ColorBase {
|
||||
export interface Color {
|
||||
type: string
|
||||
hex: string
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type ColorString = ColorBase & {value: string}
|
||||
export type ColorNumber = ColorBase & {value: number}
|
||||
export type LineColor = ColorString
|
||||
export type GaugeColor = ColorString | ColorNumber
|
||||
|
||||
export interface ThresholdColor {
|
||||
hex: string
|
||||
name: string
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Template, TimeRange, QueryConfig} from 'src/types'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
export interface Axis {
|
||||
label: string
|
||||
|
@ -68,7 +68,7 @@ export interface Cell {
|
|||
queries: CellQuery[]
|
||||
type: CellType
|
||||
axes: Axes
|
||||
colors: ColorString[]
|
||||
colors: Color[]
|
||||
tableOptions: TableOptions
|
||||
fieldOptions: FieldOption[]
|
||||
timeFormat: string
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,26 +32,17 @@ import {
|
|||
SourceLinks,
|
||||
SourceAuthenticationMethod,
|
||||
} from './sources'
|
||||
import {DropdownAction, DropdownItem, Constructable} from './shared'
|
||||
import {DropdownAction, DropdownItem} from './shared'
|
||||
import {
|
||||
Notification,
|
||||
NotificationFunc,
|
||||
NotificationAction,
|
||||
} from './notifications'
|
||||
import {FluxTable, ScriptStatus, SchemaFilter, RemoteDataState} from './flux'
|
||||
import {
|
||||
DygraphSeries,
|
||||
DygraphValue,
|
||||
DygraphAxis,
|
||||
DygraphClass,
|
||||
DygraphData,
|
||||
} from './dygraphs'
|
||||
import {JSONFeedData} from './status'
|
||||
import {AnnotationInterface} from './annotations'
|
||||
import {WriteDataMode} from './dataExplorer'
|
||||
|
||||
export {
|
||||
Constructable,
|
||||
Template,
|
||||
TemplateQuery,
|
||||
TemplateValue,
|
||||
|
@ -79,11 +70,6 @@ export {
|
|||
DropdownAction,
|
||||
DropdownItem,
|
||||
TimeRange,
|
||||
DygraphData,
|
||||
DygraphSeries,
|
||||
DygraphValue,
|
||||
DygraphAxis,
|
||||
DygraphClass,
|
||||
Notification,
|
||||
NotificationFunc,
|
||||
NotificationAction,
|
||||
|
@ -97,7 +83,6 @@ export {
|
|||
ScriptStatus,
|
||||
SchemaFilter,
|
||||
RemoteDataState,
|
||||
JSONFeedData,
|
||||
AnnotationInterface,
|
||||
TemplateType,
|
||||
TemplateValueType,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {ReactNode} from 'react'
|
||||
import {DygraphData, Options} from 'src/types/dygraphs'
|
||||
|
||||
export interface DropdownItem {
|
||||
text: string
|
||||
|
@ -17,7 +16,3 @@ export interface PageSection {
|
|||
component: ReactNode
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface Constructable<T> {
|
||||
new (container: HTMLElement | string, data: DygraphData, options?: Options): T
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {QueryConfig} from 'src/types'
|
||||
import {ColorString} from 'src/types/colors'
|
||||
import {Color} from 'src/types/colors'
|
||||
|
||||
export interface Axis {
|
||||
label: string
|
||||
|
@ -66,15 +66,117 @@ export interface MarkDownProperties {
|
|||
export interface View {
|
||||
id: string
|
||||
name: string
|
||||
properties: MarkDownProperties | V1View
|
||||
properties: ViewProperties
|
||||
}
|
||||
|
||||
export type RefreshingViewProperties =
|
||||
| LineView
|
||||
| StepPlotView
|
||||
| StackedView
|
||||
| BarChartView
|
||||
| LinePlusSingleStatView
|
||||
| SingleStatView
|
||||
| TableView
|
||||
| GaugeView
|
||||
|
||||
export type ViewProperties = RefreshingViewProperties | MarkdownView | EmptyView
|
||||
|
||||
export interface EmptyView {
|
||||
type: ViewShape.Empty
|
||||
shape: ViewShape.Empty
|
||||
}
|
||||
|
||||
export interface LineView {
|
||||
type: ViewType.Line
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
axes: Axes
|
||||
colors: Color[]
|
||||
legend: Legend
|
||||
}
|
||||
|
||||
export interface StackedView {
|
||||
type: ViewType.Stacked
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
axes: Axes
|
||||
colors: Color[]
|
||||
legend: Legend
|
||||
}
|
||||
|
||||
export interface StepPlotView {
|
||||
type: ViewType.StepPlot
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
axes: Axes
|
||||
colors: Color[]
|
||||
legend: Legend
|
||||
}
|
||||
|
||||
export interface BarChartView {
|
||||
type: ViewType.Bar
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
axes: Axes
|
||||
colors: Color[]
|
||||
legend: Legend
|
||||
}
|
||||
|
||||
export interface LinePlusSingleStatView {
|
||||
type: ViewType.LinePlusSingleStat
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
axes: Axes
|
||||
colors: Color[]
|
||||
legend: Legend
|
||||
prefix: string
|
||||
suffix: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
}
|
||||
|
||||
export interface SingleStatView {
|
||||
type: ViewType.SingleStat
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
colors: Color[]
|
||||
prefix: string
|
||||
suffix: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
}
|
||||
|
||||
export interface GaugeView {
|
||||
type: ViewType.Gauge
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
colors: Color[]
|
||||
prefix: string
|
||||
suffix: string
|
||||
decimalPlaces: DecimalPlaces
|
||||
}
|
||||
|
||||
export interface TableView {
|
||||
type: ViewType.Table
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape.ChronografV2
|
||||
colors: Color[]
|
||||
tableOptions: TableOptions
|
||||
fieldOptions: FieldOption[]
|
||||
decimalPlaces: DecimalPlaces
|
||||
timeFormat: string
|
||||
}
|
||||
|
||||
export interface MarkdownView {
|
||||
type: ViewType.Markdown
|
||||
shape: ViewShape.ChronografV2
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface V1View {
|
||||
type: V1ViewTypes
|
||||
type: ViewType
|
||||
queries: CellQuery[]
|
||||
shape: ViewShape
|
||||
axes: Axes
|
||||
colors: ColorString[]
|
||||
colors: Color[]
|
||||
tableOptions: TableOptions
|
||||
fieldOptions: FieldOption[]
|
||||
timeFormat: string
|
||||
|
@ -85,31 +187,27 @@ export interface V1View {
|
|||
}
|
||||
|
||||
export enum ViewShape {
|
||||
ChronografV2 = 'chronografV2',
|
||||
ChronografV1 = 'chronografV1',
|
||||
Empty = 'empty',
|
||||
}
|
||||
|
||||
export enum ViewType {
|
||||
Markdown = 'markdown',
|
||||
}
|
||||
|
||||
export enum V1ViewTypes {
|
||||
Line = 'line',
|
||||
Stacked = 'line-stacked',
|
||||
StepPlot = 'line-stepplot',
|
||||
Bar = 'bar',
|
||||
Line = 'line',
|
||||
Stacked = 'stacked',
|
||||
StepPlot = 'step-plot',
|
||||
LinePlusSingleStat = 'line-plus-single-stat',
|
||||
SingleStat = 'single-stat',
|
||||
Gauge = 'gauge',
|
||||
Table = 'table',
|
||||
Alerts = 'alerts',
|
||||
News = 'news',
|
||||
Guide = 'guide',
|
||||
Markdown = 'markdown',
|
||||
}
|
||||
|
||||
interface DashboardLinks {
|
||||
self: string
|
||||
cells: string
|
||||
copy: string
|
||||
}
|
||||
|
||||
export interface Dashboard {
|
||||
|
|
|
@ -1,679 +0,0 @@
|
|||
import {
|
||||
timeSeriesToDygraph,
|
||||
timeSeriesToTableGraph,
|
||||
} from 'src/utils/timeSeriesTransformers'
|
||||
|
||||
import {
|
||||
filterTableColumns,
|
||||
transformTableData,
|
||||
} from 'src/dashboards/utils/tableGraph'
|
||||
|
||||
import {DEFAULT_SORT_DIRECTION} from 'src/shared/constants/tableGraph'
|
||||
import {
|
||||
DEFAULT_TIME_FORMAT,
|
||||
DEFAULT_DECIMAL_PLACES,
|
||||
} from 'src/dashboards/constants'
|
||||
|
||||
describe('timeSeriesToDygraph', () => {
|
||||
it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = {
|
||||
labels: ['time', `m1.f1`, `m1.f2`],
|
||||
timeSeries: [
|
||||
[new Date(1000), 1, null],
|
||||
[new Date(2000), 2, 3],
|
||||
[new Date(4000), null, 4],
|
||||
],
|
||||
dygraphSeries: {
|
||||
'm1.f1': {
|
||||
axis: 'y',
|
||||
},
|
||||
'm1.f2': {
|
||||
axis: 'y',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can sort numerical timestamps correctly', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[100, 1], [3000, 3], [200, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = {
|
||||
labels: ['time', 'm1.f1'],
|
||||
timeSeries: [[new Date(100), 1], [new Date(200), 2], [new Date(3000), 3]],
|
||||
}
|
||||
|
||||
expect(actual.timeSeries).toEqual(expected.timeSeries)
|
||||
})
|
||||
|
||||
it('can parse multiple responses into two axes', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm3',
|
||||
columns: ['time', 'f3'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = {
|
||||
'm1.f1': {
|
||||
axis: 'y',
|
||||
},
|
||||
'm1.f2': {
|
||||
axis: 'y',
|
||||
},
|
||||
'm3.f3': {
|
||||
axis: 'y2',
|
||||
},
|
||||
}
|
||||
|
||||
expect(actual.dygraphSeries).toEqual(expected)
|
||||
})
|
||||
|
||||
it('can parse multiple responses with the same field and measurement', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = {
|
||||
labels: ['time', `m1.f1`, `m1.f1`],
|
||||
timeSeries: [
|
||||
[new Date(1000), 1, null],
|
||||
[new Date(2000), 2, 3],
|
||||
[new Date(4000), null, 4],
|
||||
],
|
||||
dygraphSeries: {
|
||||
'm1.f1': {
|
||||
axis: 'y2',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('it does not use multiple axes if being used for the DataExplorer', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'm1',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse, 'data-explorer')
|
||||
|
||||
const expected = {}
|
||||
|
||||
expect(actual.dygraphSeries).toEqual(expected)
|
||||
})
|
||||
|
||||
it('parses a raw InfluxDB response into a dygraph friendly data format', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToDygraph(influxResponse)
|
||||
|
||||
const expected = ['time', `ma.f1`, `mb.f1`, `mc.f1`, `mc.f2`]
|
||||
|
||||
expect(actual.labels).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('timeSeriesToTableGraph', () => {
|
||||
it('parses raw data into a nested array of data', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = [
|
||||
['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2'],
|
||||
[1000, 1, 1, null, null],
|
||||
[2000, 2, 2, 3, 3],
|
||||
[4000, null, null, 4, 4],
|
||||
]
|
||||
|
||||
expect(actual.data).toEqual(expected)
|
||||
})
|
||||
|
||||
it('parses raw data into a table-readable format with the first row being labels', () => {
|
||||
const influxResponse = [
|
||||
{
|
||||
response: {
|
||||
results: [
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mb',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'ma',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[1000, 1], [2000, 2]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f2'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
statement_id: 0,
|
||||
series: [
|
||||
{
|
||||
name: 'mc',
|
||||
columns: ['time', 'f1'],
|
||||
values: [[2000, 3], [4000, 4]],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = ['time', 'ma.f1', 'mb.f1', 'mc.f1', 'mc.f2']
|
||||
|
||||
expect(actual.data[0]).toEqual(expected)
|
||||
})
|
||||
|
||||
it('returns an array of an empty array if there is an empty response', () => {
|
||||
const influxResponse = []
|
||||
const actual = timeSeriesToTableGraph(influxResponse)
|
||||
const expected = [[]]
|
||||
|
||||
expect(actual.data).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('filterTableColumns', () => {
|
||||
it("returns a nested array of fieldnamesthat only include columns whose corresponding fieldName's visibility is true", () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: false},
|
||||
{internalName: 'f2', displayName: 'F2', visible: false},
|
||||
]
|
||||
|
||||
const actual = filterTableColumns(data, fieldOptions)
|
||||
const expected = [['time'], [1000], [2000], [3000]]
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
|
||||
it('returns an array of an empty array if all fieldOptions are not visible', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: false},
|
||||
{internalName: 'f1', displayName: '', visible: false},
|
||||
{internalName: 'f2', displayName: 'F2', visible: false},
|
||||
]
|
||||
|
||||
const actual = filterTableColumns(data, fieldOptions)
|
||||
const expected = [[]]
|
||||
expect(actual).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('transformTableData', () => {
|
||||
it('sorts the data based on the provided sortFieldName', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: true},
|
||||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = transformTableData(
|
||||
data,
|
||||
sort,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [
|
||||
['time', 'f1', 'f2'],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
[1000, 3000, 2000],
|
||||
]
|
||||
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('filters out columns that should not be visible', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: false},
|
||||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = transformTableData(
|
||||
data,
|
||||
sort,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [['time', 'f2'], [1000, 2000], [2000, 3000], [3000, 1000]]
|
||||
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('filters out invisible columns after sorting', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
verticalTimeAxis: true,
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: false},
|
||||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = transformTableData(
|
||||
data,
|
||||
sort,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [['time', 'f2'], [2000, 3000], [3000, 1000], [1000, 2000]]
|
||||
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('if verticalTimeAxis is false', () => {
|
||||
it('transforms data', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
|
||||
const sort = {field: 'time', direction: DEFAULT_SORT_DIRECTION}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
verticalTimeAxis: false,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: true},
|
||||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = transformTableData(
|
||||
data,
|
||||
sort,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [
|
||||
['time', 1000, 2000, 3000],
|
||||
['f1', 3000, 1000, 2000],
|
||||
['f2', 2000, 3000, 1000],
|
||||
]
|
||||
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
|
||||
it('transforms data after filtering out invisible columns', () => {
|
||||
const data = [
|
||||
['time', 'f1', 'f2'],
|
||||
[1000, 3000, 2000],
|
||||
[2000, 1000, 3000],
|
||||
[3000, 2000, 1000],
|
||||
]
|
||||
|
||||
const sort = {field: 'f1', direction: DEFAULT_SORT_DIRECTION}
|
||||
const sortBy = {internalName: 'time', displayName: 'Time', visible: true}
|
||||
const tableOptions = {
|
||||
sortBy,
|
||||
fixFirstColumn: true,
|
||||
verticalTimeAxis: false,
|
||||
}
|
||||
const timeFormat = DEFAULT_TIME_FORMAT
|
||||
const decimalPlaces = DEFAULT_DECIMAL_PLACES
|
||||
const fieldOptions = [
|
||||
{internalName: 'time', displayName: 'Time', visible: true},
|
||||
{internalName: 'f1', displayName: '', visible: false},
|
||||
{internalName: 'f2', displayName: 'F2', visible: true},
|
||||
]
|
||||
|
||||
const actual = transformTableData(
|
||||
data,
|
||||
sort,
|
||||
fieldOptions,
|
||||
tableOptions,
|
||||
timeFormat,
|
||||
decimalPlaces
|
||||
)
|
||||
|
||||
const expected = [['time', 2000, 3000, 1000], ['f2', 3000, 1000, 2000]]
|
||||
|
||||
expect(actual.transformedData).toEqual(expected)
|
||||
})
|
||||
})
|
|
@ -1,88 +0,0 @@
|
|||
import {fastMap, fastReduce} from 'src/utils/fast'
|
||||
import {groupByTimeSeriesTransform} from 'src/utils/groupByTimeSeriesTransform'
|
||||
|
||||
import {
|
||||
TimeSeriesServerResponse,
|
||||
TimeSeries,
|
||||
TimeSeriesValue,
|
||||
} from 'src/types/series'
|
||||
import {DygraphSeries, DygraphValue} from 'src/types'
|
||||
|
||||
interface Label {
|
||||
label: string
|
||||
seriesIndex: number
|
||||
responseIndex: number
|
||||
}
|
||||
|
||||
export interface TimeSeriesToDygraphReturnType {
|
||||
labels: string[]
|
||||
timeSeries: DygraphValue[][]
|
||||
dygraphSeries: DygraphSeries
|
||||
}
|
||||
|
||||
interface TimeSeriesToTableGraphReturnType {
|
||||
data: TimeSeriesValue[][]
|
||||
sortedLabels: Label[]
|
||||
}
|
||||
|
||||
export const timeSeriesToDygraph = (
|
||||
raw: TimeSeriesServerResponse[],
|
||||
pathname: string = ''
|
||||
): TimeSeriesToDygraphReturnType => {
|
||||
const isTable = false
|
||||
const isInDataExplorer = pathname.includes('data-explorer')
|
||||
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
|
||||
raw,
|
||||
isTable
|
||||
)
|
||||
|
||||
const labels = [
|
||||
'time',
|
||||
...fastMap<Label, string>(sortedLabels, ({label}) => label),
|
||||
]
|
||||
|
||||
const timeSeries = fastMap<TimeSeries, DygraphValue[]>(
|
||||
sortedTimeSeries,
|
||||
({time, values}) => [new Date(time as string), ...values]
|
||||
)
|
||||
|
||||
const dygraphSeries = fastReduce<Label, DygraphSeries>(
|
||||
sortedLabels,
|
||||
(acc, {label, responseIndex}) => {
|
||||
if (!isInDataExplorer) {
|
||||
acc[label] = {
|
||||
axis: responseIndex === 0 ? 'y' : 'y2',
|
||||
}
|
||||
}
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
|
||||
return {labels, timeSeries, dygraphSeries}
|
||||
}
|
||||
|
||||
export const timeSeriesToTableGraph = (
|
||||
raw: TimeSeriesServerResponse[]
|
||||
): TimeSeriesToTableGraphReturnType => {
|
||||
const isTable = true
|
||||
const {sortedLabels, sortedTimeSeries} = groupByTimeSeriesTransform(
|
||||
raw,
|
||||
isTable
|
||||
)
|
||||
|
||||
const labels = [
|
||||
'time',
|
||||
...fastMap<Label, string>(sortedLabels, ({label}) => label),
|
||||
]
|
||||
|
||||
const tableData = fastMap<TimeSeries, TimeSeriesValue[]>(
|
||||
sortedTimeSeries,
|
||||
({time, values}) => [time, ...values]
|
||||
)
|
||||
const data = tableData.length ? [labels, ...tableData] : [[]]
|
||||
return {
|
||||
data,
|
||||
sortedLabels,
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"baseUrl": "./"
|
||||
},
|
||||
"exclude": ["assets", "build", "node_modules"]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
129
view.go
129
view.go
|
@ -91,6 +91,7 @@ func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) {
|
|||
|
||||
var t struct {
|
||||
Shape string `json:"shape"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(v.B, &t); err != nil {
|
||||
|
@ -99,6 +100,39 @@ func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) {
|
|||
|
||||
var vis ViewProperties
|
||||
switch t.Shape {
|
||||
case "chronograf-v2":
|
||||
switch t.Type {
|
||||
case "line":
|
||||
var lv LineViewProperties
|
||||
if err := json.Unmarshal(v.B, &lv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = lv
|
||||
case "single-stat":
|
||||
var ssv SingleStatViewProperties
|
||||
if err := json.Unmarshal(v.B, &ssv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = ssv
|
||||
case "gauge":
|
||||
var gv GaugeViewProperties
|
||||
if err := json.Unmarshal(v.B, &gv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = gv
|
||||
case "step-plot":
|
||||
var spv StepPlotViewProperties
|
||||
if err := json.Unmarshal(v.B, &spv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = spv
|
||||
case "stacked":
|
||||
var sv StackedViewProperties
|
||||
if err := json.Unmarshal(v.B, &sv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vis = sv
|
||||
}
|
||||
case "chronograf-v1":
|
||||
var qv V1ViewProperties
|
||||
if err := json.Unmarshal(v.B, &qv); err != nil {
|
||||
|
@ -121,6 +155,46 @@ func UnmarshalViewPropertiesJSON(b []byte) (ViewProperties, error) {
|
|||
func MarshalViewPropertiesJSON(v ViewProperties) ([]byte, error) {
|
||||
var s interface{}
|
||||
switch vis := v.(type) {
|
||||
case SingleStatViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
SingleStatViewProperties
|
||||
}{
|
||||
Shape: "chronograf-v2",
|
||||
SingleStatViewProperties: vis,
|
||||
}
|
||||
case GaugeViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
GaugeViewProperties
|
||||
}{
|
||||
Shape: "chronograf-v2",
|
||||
GaugeViewProperties: vis,
|
||||
}
|
||||
case LineViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
LineViewProperties
|
||||
}{
|
||||
Shape: "chronograf-v2",
|
||||
LineViewProperties: vis,
|
||||
}
|
||||
case StepPlotViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
StepPlotViewProperties
|
||||
}{
|
||||
Shape: "chronograf-v2",
|
||||
StepPlotViewProperties: vis,
|
||||
}
|
||||
case StackedViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
StackedViewProperties
|
||||
}{
|
||||
Shape: "chronograf-v2",
|
||||
StackedViewProperties: vis,
|
||||
}
|
||||
case V1ViewProperties:
|
||||
s = struct {
|
||||
Shape string `json:"shape"`
|
||||
|
@ -196,6 +270,54 @@ func (u ViewUpdate) MarshalJSON() ([]byte, error) {
|
|||
})
|
||||
}
|
||||
|
||||
// LineViewProperties represents options for line view in Chronograf
|
||||
type LineViewProperties struct {
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
Legend Legend `json:"legend"`
|
||||
ViewColors []ViewColor `json:"colors"`
|
||||
}
|
||||
|
||||
// StepPlotViewProperties represents options for step plot view in Chronograf
|
||||
type StepPlotViewProperties struct {
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
Legend Legend `json:"legend"`
|
||||
ViewColors []ViewColor `json:"colors"`
|
||||
}
|
||||
|
||||
// StackedViewProperties represents options for stacked view in Chronograf
|
||||
type StackedViewProperties struct {
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
Type string `json:"type"`
|
||||
Legend Legend `json:"legend"`
|
||||
ViewColors []ViewColor `json:"colors"`
|
||||
}
|
||||
|
||||
// SingleStatViewProperties represents options for single stat view in Chronograf
|
||||
type SingleStatViewProperties struct {
|
||||
Type string `json:"type"`
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Prefix string `json:"prefix"`
|
||||
Suffix string `json:"suffix"`
|
||||
ViewColors []ViewColor `json:"colors"`
|
||||
DecimalPlaces DecimalPlaces `json:"decimalPlaces"`
|
||||
}
|
||||
|
||||
// GaugeViewProperties represents options for gauge view in Chronograf
|
||||
type GaugeViewProperties struct {
|
||||
Type string `json:"type"`
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Prefix string `json:"prefix"`
|
||||
Suffix string `json:"suffix"`
|
||||
ViewColors []ViewColor `json:"colors"`
|
||||
DecimalPlaces DecimalPlaces `json:"decimalPlaces"`
|
||||
}
|
||||
|
||||
// V1ViewProperties represents V1 Chronograf view shapes
|
||||
type V1ViewProperties struct {
|
||||
Queries []DashboardQuery `json:"queries"`
|
||||
Axes map[string]Axis `json:"axes"`
|
||||
|
@ -208,7 +330,12 @@ type V1ViewProperties struct {
|
|||
DecimalPlaces DecimalPlaces `json:"decimalPlaces"`
|
||||
}
|
||||
|
||||
func (V1ViewProperties) ViewProperties() {}
|
||||
func (V1ViewProperties) ViewProperties() {}
|
||||
func (LineViewProperties) ViewProperties() {}
|
||||
func (StepPlotViewProperties) ViewProperties() {}
|
||||
func (SingleStatViewProperties) ViewProperties() {}
|
||||
func (StackedViewProperties) ViewProperties() {}
|
||||
func (GaugeViewProperties) ViewProperties() {}
|
||||
|
||||
/////////////////////////////
|
||||
// Old Chronograf Types
|
||||
|
|
Loading…
Reference in New Issue