From 04e3c7eaa7a953dd2586c5fedf19dbc52a3b677d Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 12:09:05 -0700 Subject: [PATCH 01/51] Convert component to TS --- ...kOutsideInput.js => ClickOutsideInput.tsx} | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) rename ui/src/shared/components/{ClickOutsideInput.js => ClickOutsideInput.tsx} (55%) diff --git a/ui/src/shared/components/ClickOutsideInput.js b/ui/src/shared/components/ClickOutsideInput.tsx similarity index 55% rename from ui/src/shared/components/ClickOutsideInput.js rename to ui/src/shared/components/ClickOutsideInput.tsx index fb431b0c27..a56433766f 100644 --- a/ui/src/shared/components/ClickOutsideInput.js +++ b/ui/src/shared/components/ClickOutsideInput.tsx @@ -1,20 +1,28 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {Component, MouseEvent, ChangeEvent} from 'react' -import onClickOutside from 'shared/components/OnClickOutside' +import onClickOutside from 'src/shared/components/OnClickOutside' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + min: string + id: string + type: string + customPlaceholder: string + customValue: string + onGetRef: (el: HTMLInputElement) => void + onFocus: () => void + onChange: (e: ChangeEvent) => void + onKeyDown: () => void + handleClickOutsideInput: (e: MouseEvent) => void +} + @ErrorHandling -class ClickOutsideInput extends Component { +class ClickOutsideInput extends Component { constructor(props) { super(props) } - handleClickOutside = e => { - this.props.handleClickOutsideInput(e) - } - - render() { + public render() { const { id, min, @@ -43,21 +51,10 @@ class ClickOutsideInput extends Component { /> ) } -} -const {func, string} = PropTypes - -ClickOutsideInput.propTypes = { - min: string, - id: string.isRequired, - type: string.isRequired, - customPlaceholder: string.isRequired, - customValue: string.isRequired, - onGetRef: func.isRequired, - onFocus: func.isRequired, - onChange: func.isRequired, - onKeyDown: func.isRequired, - handleClickOutsideInput: func.isRequired, + public handleClickOutside = (e): void => { + this.props.handleClickOutsideInput(e) + } } export default onClickOutside(ClickOutsideInput) From 43820a910503d7b093e0b7b750f2c16496cb37ad Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 12:09:12 -0700 Subject: [PATCH 02/51] Convert component TS --- .../shared/components/{OptIn.js => OptIn.tsx} | 218 +++++++++--------- 1 file changed, 115 insertions(+), 103 deletions(-) rename ui/src/shared/components/{OptIn.js => OptIn.tsx} (64%) diff --git a/ui/src/shared/components/OptIn.js b/ui/src/shared/components/OptIn.tsx similarity index 64% rename from ui/src/shared/components/OptIn.js rename to ui/src/shared/components/OptIn.tsx index 2160648e7e..50c40ec140 100644 --- a/ui/src/shared/components/OptIn.js +++ b/ui/src/shared/components/OptIn.tsx @@ -1,14 +1,42 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {Component, ChangeEvent, KeyboardEvent, MouseEvent} from 'react' import classnames from 'classnames' import uuid from 'uuid' -import ClickOutsideInput from 'shared/components/ClickOutsideInput' +import ClickOutsideInput from 'src/shared/components/ClickOutsideInput' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + min: string + fixedPlaceholder: string + fixedValue: string + customPlaceholder: string + customValue: string + onSetValue: (value: string) => void + type: string | number +} + +interface State { + fixedValue: string + customValue: string + useCustomValue: boolean +} + @ErrorHandling -class OptIn extends Component { +export default class OptIn extends Component { + public static defaultProps: Partial = { + fixedValue: '', + customPlaceholder: 'Custom Value', + fixedPlaceholder: 'auto', + customValue: '', + } + + private id: string + private isCustomValueInputFocused: boolean + private grooveKnobContainer: HTMLElement + private grooveKnob: HTMLElement + private customValueInput: HTMLInputElement + constructor(props) { super(props) @@ -24,84 +52,7 @@ class OptIn extends Component { this.isCustomValueInputFocused = false } - useFixedValue = () => { - this.setState({useCustomValue: false, customValue: ''}, () => - this.setValue() - ) - } - - useCustomValue = () => { - this.setState({useCustomValue: true}, () => this.setValue()) - } - - handleClickToggle = () => { - const useCustomValueNext = !this.state.useCustomValue - if (useCustomValueNext) { - this.useCustomValue() - this.customValueInput.focus() - } else { - this.useFixedValue() - } - } - - handleFocusCustomValueInput = () => { - this.isCustomValueInputFocused = true - this.useCustomValue() - } - - handleChangeCustomValue = e => { - this.setCustomValue(e.target.value) - } - - handleKeyDownCustomValueInput = e => { - if (e.key === 'Enter' || e.key === 'Tab') { - if (e.key === 'Enter') { - this.customValueInput.blur() - } - this.considerResetCustomValue() - } - } - - handleClickOutsideInput = e => { - if ( - e.target.id !== this.grooveKnob.id && - e.target.id !== this.grooveKnobContainer.id && - this.isCustomValueInputFocused - ) { - this.considerResetCustomValue() - } - } - - considerResetCustomValue = () => { - const customValue = this.customValueInput.value.trim() - - this.setState({customValue}) - - if (customValue === '') { - this.useFixedValue() - } - - this.isCustomValueInputFocused = false - } - - setCustomValue = value => { - this.setState({customValue: value}, this.setValue) - } - - setValue = () => { - const {onSetValue} = this.props - const {useCustomValue, fixedValue, customValue} = this.state - - if (useCustomValue) { - onSetValue(customValue) - } else { - onSetValue(fixedValue) - } - } - - handleInputRef = el => (this.customValueInput = el) - - render() { + public render() { const {fixedPlaceholder, customPlaceholder, type, min} = this.props const {useCustomValue, customValue} = this.state @@ -143,25 +94,86 @@ class OptIn extends Component { ) } + + private useFixedValue = (): void => { + this.setState({useCustomValue: false, customValue: ''}, () => + this.setValue() + ) + } + + private useCustomValue = (): void => { + this.setState({useCustomValue: true}, () => this.setValue()) + } + + private handleClickToggle = (): void => { + const useCustomValueNext = !this.state.useCustomValue + if (useCustomValueNext) { + this.useCustomValue() + this.customValueInput.focus() + } else { + this.useFixedValue() + } + } + + private handleFocusCustomValueInput = (): void => { + this.isCustomValueInputFocused = true + this.useCustomValue() + } + + private handleChangeCustomValue = ( + e: ChangeEvent + ): void => { + this.setCustomValue(e.target.value) + } + + private handleKeyDownCustomValueInput = ( + e: KeyboardEvent + ): void => { + if (e.key === 'Enter' || e.key === 'Tab') { + if (e.key === 'Enter') { + this.customValueInput.blur() + } + this.considerResetCustomValue() + } + } + + private handleClickOutsideInput = (e: MouseEvent): void => { + if ( + e.currentTarget.id !== this.grooveKnob.id && + e.currentTarget.id !== this.grooveKnobContainer.id && + this.isCustomValueInputFocused + ) { + this.considerResetCustomValue() + } + } + + private considerResetCustomValue = (): void => { + const customValue = this.customValueInput.value.trim() + + this.setState({customValue}) + + if (customValue === '') { + this.useFixedValue() + } + + this.isCustomValueInputFocused = false + } + + private setCustomValue = (value): void => { + this.setState({customValue: value}, this.setValue) + } + + private setValue = (): void => { + const {onSetValue} = this.props + const {useCustomValue, fixedValue, customValue} = this.state + + if (useCustomValue) { + onSetValue(customValue) + } else { + onSetValue(fixedValue) + } + } + + private handleInputRef = (el: HTMLInputElement) => + (this.customValueInput = el) } - -OptIn.defaultProps = { - fixedValue: '', - customPlaceholder: 'Custom Value', - fixedPlaceholder: 'auto', - customValue: '', -} - -const {func, oneOf, string} = PropTypes - -OptIn.propTypes = { - min: string, - fixedPlaceholder: string, - fixedValue: string, - customPlaceholder: string, - customValue: string, - onSetValue: func.isRequired, - type: oneOf(['text', 'number']), -} - -export default OptIn From 8c20f318f9a60fabdd0d5fe835e053be3ec85c67 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 12:53:59 -0700 Subject: [PATCH 03/51] Convert a curated selection of shared components to TypeScript --- ui/src/admin/components/EmptyRow.js | 20 ------ ui/src/admin/components/EmptyRow.tsx | 16 +++++ ...meIndicator.js => CustomTimeIndicator.tsx} | 19 ++--- .../{EmptyQuery.js => EmptyQuery.tsx} | 15 ++-- ui/src/shared/components/LoadingDots.js | 18 ----- ui/src/shared/components/LoadingDots.tsx | 24 +++++++ ui/src/shared/components/QueryStatus.js | 54 --------------- ui/src/shared/components/QueryStatus.tsx | 69 +++++++++++++++++++ ...MarkTooltip.js => QuestionMarkTooltip.tsx} | 17 ++--- .../{SplashPage.js => SplashPage.tsx} | 14 ++-- .../components/{Tooltip.js => Tooltip.tsx} | 16 ++--- .../{WidgetCell.js => WidgetCell.tsx} | 29 ++++---- 12 files changed, 159 insertions(+), 152 deletions(-) delete mode 100644 ui/src/admin/components/EmptyRow.js create mode 100644 ui/src/admin/components/EmptyRow.tsx rename ui/src/shared/components/{CustomTimeIndicator.js => CustomTimeIndicator.tsx} (73%) rename ui/src/shared/components/{EmptyQuery.js => EmptyQuery.tsx} (57%) delete mode 100644 ui/src/shared/components/LoadingDots.js create mode 100644 ui/src/shared/components/LoadingDots.tsx delete mode 100644 ui/src/shared/components/QueryStatus.js create mode 100644 ui/src/shared/components/QueryStatus.tsx rename ui/src/shared/components/{QuestionMarkTooltip.js => QuestionMarkTooltip.tsx} (63%) rename ui/src/shared/components/{SplashPage.js => SplashPage.tsx} (64%) rename ui/src/shared/components/{Tooltip.js => Tooltip.tsx} (55%) rename ui/src/shared/components/{WidgetCell.js => WidgetCell.tsx} (72%) diff --git a/ui/src/admin/components/EmptyRow.js b/ui/src/admin/components/EmptyRow.js deleted file mode 100644 index af21790388..0000000000 --- a/ui/src/admin/components/EmptyRow.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -const EmptyRow = ({tableName}) => ( - - -

- You don't have any {tableName},
why not create one? -

- - -) - -const {string} = PropTypes - -EmptyRow.propTypes = { - tableName: string.isRequired, -} - -export default EmptyRow diff --git a/ui/src/admin/components/EmptyRow.tsx b/ui/src/admin/components/EmptyRow.tsx new file mode 100644 index 0000000000..664de5e7ea --- /dev/null +++ b/ui/src/admin/components/EmptyRow.tsx @@ -0,0 +1,16 @@ +import React, {SFC} from 'react' + +interface Props { + tableName: string +} +const EmptyRow: SFC = ({tableName}) => ( + + +

+ You don't have any {tableName},
why not create one? +

+ + +) + +export default EmptyRow diff --git a/ui/src/shared/components/CustomTimeIndicator.js b/ui/src/shared/components/CustomTimeIndicator.tsx similarity index 73% rename from ui/src/shared/components/CustomTimeIndicator.js rename to ui/src/shared/components/CustomTimeIndicator.tsx index 92609fe712..339bf46901 100644 --- a/ui/src/shared/components/CustomTimeIndicator.js +++ b/ui/src/shared/components/CustomTimeIndicator.tsx @@ -1,10 +1,17 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import _ from 'lodash' import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants' -const CustomTimeIndicator = ({queries}) => { +interface Query { + query: string +} + +interface Props { + queries: Query[] +} + +const CustomTimeIndicator: SFC = ({queries}) => { const q = queries.find(({query}) => !query.includes(TEMP_VAR_DASHBOARD_TIME)) const customLower = _.get(q, ['queryConfig', 'range', 'lower'], null) const customUpper = _.get(q, ['queryConfig', 'range', 'upper'], null) @@ -20,10 +27,4 @@ const CustomTimeIndicator = ({queries}) => { return {customTimeRange} } -const {arrayOf, shape} = PropTypes - -CustomTimeIndicator.propTypes = { - queries: arrayOf(shape()), -} - export default CustomTimeIndicator diff --git a/ui/src/shared/components/EmptyQuery.js b/ui/src/shared/components/EmptyQuery.tsx similarity index 57% rename from ui/src/shared/components/EmptyQuery.js rename to ui/src/shared/components/EmptyQuery.tsx index ebc8420e8d..458a6bf566 100644 --- a/ui/src/shared/components/EmptyQuery.js +++ b/ui/src/shared/components/EmptyQuery.tsx @@ -1,7 +1,10 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' -const EmptyQueryState = ({onAddQuery}) => ( +interface Props { + onAddQuery: () => void +} + +const EmptyQueryState: SFC = ({onAddQuery}) => (
This Graph has no Queries

@@ -11,10 +14,4 @@ const EmptyQueryState = ({onAddQuery}) => (
) -const {func} = PropTypes - -EmptyQueryState.propTypes = { - onAddQuery: func.isRequired, -} - export default EmptyQueryState diff --git a/ui/src/shared/components/LoadingDots.js b/ui/src/shared/components/LoadingDots.js deleted file mode 100644 index d799fa2f35..0000000000 --- a/ui/src/shared/components/LoadingDots.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' - -const LoadingDots = ({className}) => ( -
-
-
-
-
-) - -const {string} = PropTypes - -LoadingDots.propTypes = { - className: string, -} - -export default LoadingDots diff --git a/ui/src/shared/components/LoadingDots.tsx b/ui/src/shared/components/LoadingDots.tsx new file mode 100644 index 0000000000..9ec4c07ddd --- /dev/null +++ b/ui/src/shared/components/LoadingDots.tsx @@ -0,0 +1,24 @@ +import React, {Component} from 'react' + +interface Props { + className?: string +} +class LoadingDots extends Component { + public static defaultProps: Partial = { + className: '', + } + + public render() { + const {className} = this.props + + return ( +
+
+
+
+
+ ) + } +} + +export default LoadingDots diff --git a/ui/src/shared/components/QueryStatus.js b/ui/src/shared/components/QueryStatus.js deleted file mode 100644 index cd8f7225c4..0000000000 --- a/ui/src/shared/components/QueryStatus.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import LoadingDots from 'shared/components/LoadingDots' -import classnames from 'classnames' - -const QueryStatus = ({status, children}) => { - if (!status) { - return
{children}
- } - - if (status.loading) { - return ( -
- - {children} -
- ) - } - - return ( -
- - - {status.error || status.warn || status.success} - - {children} -
- ) -} - -const {node, shape, string} = PropTypes - -QueryStatus.propTypes = { - status: shape({ - error: string, - success: string, - warn: string, - }), - children: node, -} - -export default QueryStatus diff --git a/ui/src/shared/components/QueryStatus.tsx b/ui/src/shared/components/QueryStatus.tsx new file mode 100644 index 0000000000..af17c7b7e7 --- /dev/null +++ b/ui/src/shared/components/QueryStatus.tsx @@ -0,0 +1,69 @@ +import React, {Component, ReactElement} from 'react' +import LoadingDots from 'src/shared/components/LoadingDots' +import classnames from 'classnames' + +interface Status { + error: string + success: string + warn: string + loading: boolean +} + +interface Props { + children: ReactElement + status: Status +} + +class QueryStatus extends Component { + public render() { + const {status, children} = this.props + + if (!status) { + return
{children}
+ } + + if (!!status.loading) { + return ( +
+ + {children} +
+ ) + } + + return ( +
+ + {this.icon} + {status.error || status.warn || status.success} + + {children} +
+ ) + } + + private get className(): string { + const {status} = this.props + + return classnames('query-status-output', { + 'query-status-output--error': status.error, + 'query-status-output--success': status.success, + 'query-status-output--warning': status.warn, + }) + } + + private get icon(): JSX.Element { + const {status} = this.props + return ( + + ) + } +} + +export default QueryStatus diff --git a/ui/src/shared/components/QuestionMarkTooltip.js b/ui/src/shared/components/QuestionMarkTooltip.tsx similarity index 63% rename from ui/src/shared/components/QuestionMarkTooltip.js rename to ui/src/shared/components/QuestionMarkTooltip.tsx index 52a4837868..9f2fc5398e 100644 --- a/ui/src/shared/components/QuestionMarkTooltip.js +++ b/ui/src/shared/components/QuestionMarkTooltip.tsx @@ -1,8 +1,12 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import ReactTooltip from 'react-tooltip' -const QuestionMarkTooltip = ({tipID, tipContent}) => ( +interface Props { + tipID: string + tipContent: string +} + +const QuestionMarkTooltip: SFC = ({tipID, tipContent}) => (
(
) -const {string} = PropTypes - -QuestionMarkTooltip.propTypes = { - tipID: string.isRequired, - tipContent: string.isRequired, -} - export default QuestionMarkTooltip diff --git a/ui/src/shared/components/SplashPage.js b/ui/src/shared/components/SplashPage.tsx similarity index 64% rename from ui/src/shared/components/SplashPage.js rename to ui/src/shared/components/SplashPage.tsx index 43b7324c4a..34287f62cf 100644 --- a/ui/src/shared/components/SplashPage.js +++ b/ui/src/shared/components/SplashPage.tsx @@ -1,7 +1,10 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC, ReactElement} from 'react' -const SplashPage = ({children}) => ( +interface Props { + children: ReactElement +} + +const SplashPage: SFC = ({children}) => (
@@ -14,9 +17,4 @@ const SplashPage = ({children}) => (
) -const {node} = PropTypes -SplashPage.propTypes = { - children: node, -} - export default SplashPage diff --git a/ui/src/shared/components/Tooltip.js b/ui/src/shared/components/Tooltip.tsx similarity index 55% rename from ui/src/shared/components/Tooltip.js rename to ui/src/shared/components/Tooltip.tsx index 37fb9c8bec..bfdb70db3b 100644 --- a/ui/src/shared/components/Tooltip.js +++ b/ui/src/shared/components/Tooltip.tsx @@ -1,8 +1,11 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC, ReactElement} from 'react' import ReactTooltip from 'react-tooltip' -const Tooltip = ({tip, children}) => ( +interface Props { + tip: string + children: ReactElement +} +const Tooltip: SFC = ({tip, children}) => (
{children}
(
) -const {shape, string} = PropTypes - -Tooltip.propTypes = { - tip: string, - children: shape({}), -} - export default Tooltip diff --git a/ui/src/shared/components/WidgetCell.js b/ui/src/shared/components/WidgetCell.tsx similarity index 72% rename from ui/src/shared/components/WidgetCell.js rename to ui/src/shared/components/WidgetCell.tsx index c2930f6a62..5dd8d78ef1 100644 --- a/ui/src/shared/components/WidgetCell.js +++ b/ui/src/shared/components/WidgetCell.tsx @@ -1,13 +1,25 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, {SFC} from 'react' import AlertsApp from 'src/alerts/containers/AlertsApp' import NewsFeed from 'src/status/components/NewsFeed' import GettingStarted from 'src/status/components/GettingStarted' +import {Cell} from 'src/types/dashboard' +import {Source} from 'src/types/sources' import {RECENT_ALERTS_LIMIT} from 'src/status/constants' -const WidgetCell = ({cell, source, timeRange}) => { +interface TimeRange { + lower: string + upper: string +} + +interface Props { + timeRange: TimeRange + cell: Cell + source: Source +} + +const WidgetCell: SFC = ({cell, source, timeRange}) => { switch (cell.type) { case 'alerts': { return ( @@ -35,15 +47,4 @@ const WidgetCell = ({cell, source, timeRange}) => { } } -const {shape, string} = PropTypes - -WidgetCell.propTypes = { - timeRange: shape({ - lower: string, - upper: string, - }), - source: shape({}), - cell: shape({}), -} - export default WidgetCell From 1bcff7c1db0940cd54853d25a21fb460039f1285 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 14:14:42 -0700 Subject: [PATCH 04/51] Convert AlertsTable to TS --- ui/src/alerts/components/{AlertsTable.js => AlertsTable.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/src/alerts/components/{AlertsTable.js => AlertsTable.tsx} (100%) diff --git a/ui/src/alerts/components/AlertsTable.js b/ui/src/alerts/components/AlertsTable.tsx similarity index 100% rename from ui/src/alerts/components/AlertsTable.js rename to ui/src/alerts/components/AlertsTable.tsx From 58d3d24313a70b0b887f8a0ca17276f765b07209 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:15:07 -0700 Subject: [PATCH 05/51] Convert component to TypeScript --- ui/src/shared/components/SingleStat.js | 98 ------------------- ui/src/shared/components/SingleStat.tsx | 125 ++++++++++++++++++++++++ ui/src/shared/parsing/lastValues.ts | 3 +- 3 files changed, 127 insertions(+), 99 deletions(-) delete mode 100644 ui/src/shared/components/SingleStat.js create mode 100644 ui/src/shared/components/SingleStat.tsx diff --git a/ui/src/shared/components/SingleStat.js b/ui/src/shared/components/SingleStat.js deleted file mode 100644 index 4a396dd345..0000000000 --- a/ui/src/shared/components/SingleStat.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, {PureComponent} from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import getLastValues from 'shared/parsing/lastValues' -import _ from 'lodash' - -import {SMALL_CELL_HEIGHT} from 'shared/graphs/helpers' -import {DYGRAPH_CONTAINER_V_MARGIN} from 'shared/constants' -import {generateThresholdsListHexs} from 'shared/constants/colorOperations' -import {colorsStringSchema} from 'shared/schemas' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class SingleStat extends PureComponent { - render() { - const { - data, - cellHeight, - isFetchingInitially, - colors, - prefix, - suffix, - lineGraph, - staticLegendHeight, - } = this.props - - // If data for this graph is being fetched for the first time, show a graph-wide spinner. - if (isFetchingInitially) { - return ( -
-

-

- ) - } - const {lastValues, series} = getLastValues(data) - const firstAlphabeticalSeriesName = _.sortBy(series)[0] - - const firstAlphabeticalindex = _.indexOf( - series, - firstAlphabeticalSeriesName - ) - const lastValue = lastValues[firstAlphabeticalindex] - const precision = 100.0 - const roundedValue = Math.round(+lastValue * precision) / precision - - const {bgColor, textColor} = generateThresholdsListHexs({ - colors, - lastValue, - cellType: lineGraph ? 'line-plus-single-stat' : 'single-stat', - }) - const backgroundColor = bgColor - const color = textColor - - const height = `calc(100% - ${staticLegendHeight + - DYGRAPH_CONTAINER_V_MARGIN * 2}px)` - - const singleStatStyles = staticLegendHeight - ? { - backgroundColor, - color, - height, - } - : { - backgroundColor, - color, - } - - return ( -
- - {prefix} - {roundedValue} - {suffix} - {lineGraph &&
} - -
- ) - } -} - -const {arrayOf, bool, number, shape, string} = PropTypes - -SingleStat.propTypes = { - data: arrayOf(shape()).isRequired, - isFetchingInitially: bool, - cellHeight: number, - colors: colorsStringSchema, - prefix: string, - suffix: string, - lineGraph: bool, - staticLegendHeight: number, -} - -export default SingleStat diff --git a/ui/src/shared/components/SingleStat.tsx b/ui/src/shared/components/SingleStat.tsx new file mode 100644 index 0000000000..0b7ef2cbe0 --- /dev/null +++ b/ui/src/shared/components/SingleStat.tsx @@ -0,0 +1,125 @@ +import React, {PureComponent} 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' +import {generateThresholdsListHexs} from 'src/shared/constants/colorOperations' +import {ColorNumber} from 'src/types/colors' +import {Data} from 'src/types/dygraphs' +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + isFetchingInitially: boolean + cellHeight: number + colors: ColorNumber[] + prefix?: string + suffix?: string + lineGraph: boolean + staticLegendHeight: number + data: Data +} + +@ErrorHandling +class SingleStat extends PureComponent { + public static defaultProps: Partial = { + prefix: '', + suffix: '', + } + + public render() { + const {isFetchingInitially} = this.props + + if (isFetchingInitially) { + return ( +
+

+

+ ) + } + + return ( +
+ + {this.completeValue} + {this.renderShadow} + +
+ ) + } + + private get renderShadow(): JSX.Element { + const {lineGraph} = this.props + + return lineGraph &&
+ } + + private get completeValue(): string { + const {prefix, suffix} = this.props + + return `${prefix}${this.roundedLastValue}${suffix}` + } + + private get roundedLastValue(): string { + const {data} = this.props + const {lastValues, series} = getLastValues(data) + const firstAlphabeticalSeriesName = _.sortBy(series)[0] + + const firstAlphabeticalindex = _.indexOf( + series, + firstAlphabeticalSeriesName + ) + const lastValue = lastValues[firstAlphabeticalindex] + const HUNDRED = 100.0 + const roundedValue = Math.round(+lastValue * HUNDRED) / HUNDRED + + return `${roundedValue}` + } + + private get styles() { + const {data, colors, lineGraph, staticLegendHeight} = this.props + + 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 ? 'line-plus-single-stat' : 'single-stat', + }) + + const backgroundColor = bgColor + const color = textColor + + const height = `calc(100% - ${staticLegendHeight + + DYGRAPH_CONTAINER_V_MARGIN * 2}px)` + + return staticLegendHeight + ? { + backgroundColor, + color, + height, + } + : { + backgroundColor, + color, + } + } + + private get className(): string { + const {cellHeight} = this.props + + return classnames('single-stat--value', { + 'single-stat--small': cellHeight === SMALL_CELL_HEIGHT, + }) + } +} + +export default SingleStat diff --git a/ui/src/shared/parsing/lastValues.ts b/ui/src/shared/parsing/lastValues.ts index 0de8de3ede..a3b9eea94b 100644 --- a/ui/src/shared/parsing/lastValues.ts +++ b/ui/src/shared/parsing/lastValues.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import {Data} from 'src/types/dygraphs' interface Result { lastValues: number[] @@ -24,7 +25,7 @@ export interface TimeSeriesResponse { } export default function( - timeSeriesResponse: TimeSeriesResponse[] | null + timeSeriesResponse: TimeSeriesResponse[] | Data | null ): Result { const values = _.get( timeSeriesResponse, From f9e0371672c1e9e05fad9c8e3fc06c6458abb1b4 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:15:33 -0700 Subject: [PATCH 06/51] Fix color dropdown by adding maxHeight attribute --- ui/src/shared/components/ColorDropdown.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js index 48d6484101..71ac90bb31 100644 --- a/ui/src/shared/components/ColorDropdown.js +++ b/ui/src/shared/components/ColorDropdown.js @@ -5,6 +5,7 @@ import classnames from 'classnames' import OnClickOutside from 'shared/components/OnClickOutside' import FancyScrollbar from 'shared/components/FancyScrollbar' import {ErrorHandling} from 'src/shared/decorators/errors' +import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' @ErrorHandling class ColorDropdown extends Component { @@ -63,7 +64,11 @@ class ColorDropdown extends Component {
{visible ? (
- + {colors.map((color, i) => (
Date: Fri, 11 May 2018 10:55:35 -0700 Subject: [PATCH 07/51] Convert formatting to typescript --- ui/src/utils/{formatting.js => formatting.ts} | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) rename ui/src/utils/{formatting.js => formatting.ts} (73%) diff --git a/ui/src/utils/formatting.js b/ui/src/utils/formatting.ts similarity index 73% rename from ui/src/utils/formatting.js rename to ui/src/utils/formatting.ts index 881c1203ec..e685a21168 100644 --- a/ui/src/utils/formatting.js +++ b/ui/src/utils/formatting.ts @@ -1,8 +1,9 @@ -const KMB_LABELS = ['K', 'M', 'B', 'T', 'Q'] -const KMG2_BIG_LABELS = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] -const KMG2_SMALL_LABELS = ['m', 'u', 'n', 'p', 'f', 'a', 'z', 'y'] +import _ from 'lodash' +const KMB_LABELS: string[] = ['K', 'M', 'B', 'T', 'Q'] +const KMG2_BIG_LABELS: string[] = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] +const KMG2_SMALL_LABELS: string[] = ['m', 'u', 'n', 'p', 'f', 'a', 'z', 'y'] -const pow = (base, exp) => { +const pow = (base: number, exp: number): number => { if (exp < 0) { return 1.0 / Math.pow(base, -exp) } @@ -10,12 +11,12 @@ const pow = (base, exp) => { return Math.pow(base, exp) } -const round_ = (num, places) => { +const roundNum = (num, places): number => { const shift = Math.pow(10, places) return Math.round(num * shift) / shift } -const floatFormat = (x, optPrecision) => { +const floatFormat = (x: number, optPrecision: number): string => { // Avoid invalid precision values; [1, 21] is the valid range. const p = Math.min(Math.max(1, optPrecision || 2), 21) @@ -41,7 +42,12 @@ const floatFormat = (x, optPrecision) => { } // taken from https://github.com/danvk/dygraphs/blob/aaec6de56dba8ed712fd7b9d949de47b46a76ccd/src/dygraph-utils.js#L1103 -export const numberValueFormatter = (x, opts, prefix, suffix) => { +export const numberValueFormatter = ( + x: number, + opts: (name: string) => number, + prefix: string, + suffix: string +): string => { const sigFigs = opts('sigFigs') if (sigFigs !== null) { @@ -65,7 +71,7 @@ export const numberValueFormatter = (x, opts, prefix, suffix) => { ) { label = x.toExponential(digits) } else { - label = `${round_(x, digits)}` + label = `${roundNum(x, digits)}` } if (kmb || kmg2) { @@ -89,15 +95,17 @@ export const numberValueFormatter = (x, opts, prefix, suffix) => { let n = pow(k, kLabels.length) for (let j = kLabels.length - 1; j >= 0; j -= 1, n /= k) { if (absx >= n) { - label = round_(x / n, digits) + kLabels[j] + label = roundNum(x / n, digits) + kLabels[j] break } } if (kmg2) { - const xParts = String(x.toExponential()).split('e-') + const xParts = String(x.toExponential()) + .split('e-') + .map(Number) if (xParts.length === 2 && xParts[1] >= 3 && xParts[1] <= 24) { if (xParts[1] % 3 > 0) { - label = round_(xParts[0] / pow(10, xParts[1] % 3), digits) + label = roundNum(xParts[0] / pow(10, xParts[1] % 3), digits) } else { label = Number(xParts[0]).toFixed(2) } @@ -109,7 +117,7 @@ export const numberValueFormatter = (x, opts, prefix, suffix) => { return `${prefix}${label}${suffix}` } -export const formatBytes = bytes => { +export const formatBytes = (bytes: number) => { if (bytes === 0) { return '0 Bytes' } @@ -126,7 +134,7 @@ export const formatBytes = bytes => { return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` } -export const formatRPDuration = duration => { +export const formatRPDuration = (duration: string | null): string => { if (!duration) { return } @@ -137,13 +145,11 @@ export const formatRPDuration = duration => { let adjustedTime = duration const durationMatcher = /(?:(\d*)d)?(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?/ - const [ - _match, // eslint-disable-line no-unused-vars - days = 0, - hours = 0, - minutes = 0, - seconds = 0, - ] = duration.match(durationMatcher) + const result = duration.match(durationMatcher) + const days = _.get(result, 1, '0') + const hours = _.get(result, 2, '0') + const minutes = _.get(result, 3, '0') + const seconds = _.get(result, 4, '0') const hoursInDay = 24 if (days) { @@ -151,9 +157,9 @@ export const formatRPDuration = duration => { adjustedTime += +hours === 0 ? '' : `${hours}h` adjustedTime += +minutes === 0 ? '' : `${minutes}m` adjustedTime += +seconds === 0 ? '' : `${seconds}s` - } else if (hours > hoursInDay) { - const hoursRemainder = hours % hoursInDay - const daysQuotient = (hours - hoursRemainder) / hoursInDay + } else if (+hours > hoursInDay) { + const hoursRemainder = +hours % hoursInDay + const daysQuotient = (+hours - hoursRemainder) / hoursInDay adjustedTime = `${daysQuotient}d` adjustedTime += +hoursRemainder === 0 ? '' : `${hoursRemainder}h` adjustedTime += +minutes === 0 ? '' : `${minutes}m` From bfbc9b654fc5e52f5cc94071c78a764de071e9ef Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 15:29:39 -0700 Subject: [PATCH 08/51] Split Search Bar into its own component and type-ify AlertsTable --- ui/src/alerts/components/AlertsTable.tsx | 213 ++++++++++------------- ui/src/alerts/components/SearchBar.tsx | 52 ++++++ ui/src/types/alerts.ts | 7 + 3 files changed, 147 insertions(+), 125 deletions(-) create mode 100644 ui/src/alerts/components/SearchBar.tsx create mode 100644 ui/src/types/alerts.ts diff --git a/ui/src/alerts/components/AlertsTable.tsx b/ui/src/alerts/components/AlertsTable.tsx index 72e7fd3c18..58f955ab70 100644 --- a/ui/src/alerts/components/AlertsTable.tsx +++ b/ui/src/alerts/components/AlertsTable.tsx @@ -1,34 +1,95 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' import _ from 'lodash' import classnames from 'classnames' import {Link} from 'react-router' import uuid from 'uuid' -import InfiniteScroll from 'shared/components/InfiniteScroll' +import InfiniteScroll from 'src/shared/components/InfiniteScroll' +import SearchBar from 'src/alerts/components/SearchBar' import {ErrorHandling} from 'src/shared/decorators/errors' import {ALERTS_TABLE} from 'src/alerts/constants/tableSizing' +import {Alert} from 'src/types/alerts' +import {Source} from 'src/types' + +enum Direction { + ASC = 'asc', + DESC = 'desc', + NONE = 'none', +} +interface Props { + alerts: Alert[] + source: Source + shouldNotBeFilterable: boolean + limit: number + isAlertsMaxedOut: boolean + alertsCount: number + onGetMoreAlerts: () => void +} +interface State { + searchTerm: string + filteredAlerts: Alert[] + sortDirection: Direction + sortKey: string +} @ErrorHandling -class AlertsTable extends Component { +class AlertsTable extends PureComponent { constructor(props) { super(props) this.state = { searchTerm: '', filteredAlerts: this.props.alerts, - sortDirection: null, - sortKey: null, + sortDirection: Direction.NONE, + sortKey: '', } } - componentWillReceiveProps(newProps) { + public componentWillReceiveProps(newProps) { this.filterAlerts(this.state.searchTerm, newProps.alerts) } - filterAlerts = (searchTerm, newAlerts) => { + public render() { + const { + shouldNotBeFilterable, + limit, + onGetMoreAlerts, + isAlertsMaxedOut, + alertsCount, + } = this.props + + return shouldNotBeFilterable ? ( +
+ {this.renderTable()} + {limit && alertsCount ? ( + + ) : null} +
+ ) : ( +
+
+

{this.props.alerts.length} Alerts

+ {this.props.alerts.length ? ( + + ) : null} +
+
{this.renderTable()}
+
+ ) + } + + private filterAlerts = (searchTerm: string, newAlerts?: Alert[]): void => { const alerts = newAlerts || this.props.alerts const filterText = searchTerm.toLowerCase() const filteredAlerts = alerts.filter(({name, host, level}) => { @@ -41,20 +102,22 @@ class AlertsTable extends Component { this.setState({searchTerm, filteredAlerts}) } - changeSort = key => () => { + private changeSort = (key: string): (() => void) => (): void => { // if we're using the key, reverse order; otherwise, set it with ascending if (this.state.sortKey === key) { - const reverseDirection = - this.state.sortDirection === 'asc' ? 'desc' : 'asc' + const reverseDirection: Direction = + this.state.sortDirection === Direction.ASC + ? Direction.DESC + : Direction.ASC this.setState({sortDirection: reverseDirection}) } else { - this.setState({sortKey: key, sortDirection: 'asc'}) + this.setState({sortKey: key, sortDirection: Direction.ASC}) } } - sortableClasses = key => { + private sortableClasses = (key: string): string => { if (this.state.sortKey === key) { - if (this.state.sortDirection === 'asc') { + if (this.state.sortDirection === Direction.ASC) { return 'alert-history-table--th sortable-header sorting-ascending' } return 'alert-history-table--th sortable-header sorting-descending' @@ -62,18 +125,22 @@ class AlertsTable extends Component { return 'alert-history-table--th sortable-header' } - sort = (alerts, key, direction) => { + private sort = ( + alerts: Alert[], + key: string, + direction: Direction + ): Alert[] => { switch (direction) { - case 'asc': - return _.sortBy(alerts, e => e[key]) - case 'desc': - return _.sortBy(alerts, e => e[key]).reverse() + case Direction.ASC: + return _.sortBy(alerts, e => e[key]) + case Direction.DESC: + return _.sortBy(alerts, e => e[key]).reverse() default: return alerts } } - renderTable() { + private renderTable(): JSX.Element { const { source: {id}, } = this.props @@ -176,7 +243,7 @@ class AlertsTable extends Component { ) } - renderTableEmpty() { + private renderTableEmpty(): JSX.Element { const { source: {id}, shouldNotBeFilterable, @@ -206,110 +273,6 @@ class AlertsTable extends Component {
) } - - render() { - const { - shouldNotBeFilterable, - limit, - onGetMoreAlerts, - isAlertsMaxedOut, - alertsCount, - } = this.props - - return shouldNotBeFilterable ? ( -
- {this.renderTable()} - {limit && alertsCount ? ( - - ) : null} -
- ) : ( -
-
-

{this.props.alerts.length} Alerts

- {this.props.alerts.length ? ( - - ) : null} -
-
{this.renderTable()}
-
- ) - } -} - -@ErrorHandling -class SearchBar extends Component { - constructor(props) { - super(props) - - this.state = { - searchTerm: '', - } - } - - componentWillMount() { - const waitPeriod = 300 - this.handleSearch = _.debounce(this.handleSearch, waitPeriod) - } - - handleSearch = () => { - this.props.onSearch(this.state.searchTerm) - } - - handleChange = e => { - this.setState({searchTerm: e.target.value}, this.handleSearch) - } - - render() { - return ( -
- - -
- ) - } -} - -const {arrayOf, bool, func, number, shape, string} = PropTypes - -AlertsTable.propTypes = { - alerts: arrayOf( - shape({ - name: string, - time: string, - value: string, - host: string, - level: string, - }) - ), - source: shape({ - id: string.isRequired, - name: string.isRequired, - }).isRequired, - shouldNotBeFilterable: bool, - limit: number, - onGetMoreAlerts: func, - isAlertsMaxedOut: bool, - alertsCount: number, -} - -SearchBar.propTypes = { - onSearch: func.isRequired, } export default AlertsTable diff --git a/ui/src/alerts/components/SearchBar.tsx b/ui/src/alerts/components/SearchBar.tsx new file mode 100644 index 0000000000..9906c8aaa4 --- /dev/null +++ b/ui/src/alerts/components/SearchBar.tsx @@ -0,0 +1,52 @@ +import React, {PureComponent, ChangeEvent} from 'react' +import _ from 'lodash' + +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + onSearch: (s: string) => void +} +interface State { + searchTerm: string +} + +@ErrorHandling +class SearchBar extends PureComponent { + constructor(props) { + super(props) + + this.state = { + searchTerm: '', + } + } + + public componentWillMount() { + const waitPeriod = 300 + this.handleSearch = _.debounce(this.handleSearch, waitPeriod) + } + + public render() { + return ( +
+ + +
+ ) + } + + private handleSearch = () => { + this.props.onSearch(this.state.searchTerm) + } + + private handleChange = (e: ChangeEvent): void => { + this.setState({searchTerm: e.target.value}, this.handleSearch) + } +} + +export default SearchBar diff --git a/ui/src/types/alerts.ts b/ui/src/types/alerts.ts new file mode 100644 index 0000000000..78137f8b6f --- /dev/null +++ b/ui/src/types/alerts.ts @@ -0,0 +1,7 @@ +export interface Alert { + name: string + time: string + value: string + host: string + level: string +} From 322cc48d012f0b9191075e3b46f200f5b61a12af Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 15:30:05 -0700 Subject: [PATCH 09/51] Convert AlertsApp to TS --- ui/src/alerts/containers/{AlertsApp.js => AlertsApp.tsx} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename ui/src/alerts/containers/{AlertsApp.js => AlertsApp.tsx} (98%) diff --git a/ui/src/alerts/containers/AlertsApp.js b/ui/src/alerts/containers/AlertsApp.tsx similarity index 98% rename from ui/src/alerts/containers/AlertsApp.js rename to ui/src/alerts/containers/AlertsApp.tsx index 31af501a32..03c4b1c880 100644 --- a/ui/src/alerts/containers/AlertsApp.js +++ b/ui/src/alerts/containers/AlertsApp.tsx @@ -1,4 +1,4 @@ -import React, {Component} from 'react' +import React, {PureComponent} from 'react' import PropTypes from 'prop-types' import SourceIndicator from 'shared/components/SourceIndicator' @@ -16,7 +16,7 @@ import moment from 'moment' import {timeRanges} from 'shared/data/timeRanges' @ErrorHandling -class AlertsApp extends Component { +class AlertsApp extends PureComponent { constructor(props) { super(props) From a28dfed8ff63ccaa15d3beaac224b369f6da280c Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:50:20 -0700 Subject: [PATCH 10/51] Convert ColorDropdown to TypeScript --- ui/src/shared/components/ColorDropdown.js | 110 ------------------ ui/src/shared/components/ColorDropdown.tsx | 125 +++++++++++++++++++++ 2 files changed, 125 insertions(+), 110 deletions(-) delete mode 100644 ui/src/shared/components/ColorDropdown.js create mode 100644 ui/src/shared/components/ColorDropdown.tsx diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js deleted file mode 100644 index 48d6484101..0000000000 --- a/ui/src/shared/components/ColorDropdown.js +++ /dev/null @@ -1,110 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' - -import classnames from 'classnames' -import OnClickOutside from 'shared/components/OnClickOutside' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class ColorDropdown extends Component { - constructor(props) { - super(props) - - this.state = { - visible: false, - } - } - - handleToggleMenu = () => { - const {disabled} = this.props - - if (disabled) { - return - } - this.setState({visible: !this.state.visible}) - } - - handleClickOutside = () => { - this.setState({visible: false}) - } - - handleColorClick = color => () => { - this.props.onChoose(color) - this.setState({visible: false}) - } - - render() { - const {visible} = this.state - const {colors, selected, disabled, stretchToFit} = this.props - - const dropdownClassNames = classnames('color-dropdown', { - open: visible, - 'color-dropdown--stretch': stretchToFit, - }) - const toggleClassNames = classnames( - 'btn btn-sm btn-default color-dropdown--toggle', - {active: visible, 'color-dropdown__disabled': disabled} - ) - - return ( -
-
-
-
{selected.name}
- -
- {visible ? ( -
- - {colors.map((color, i) => ( -
- - {color.name} -
- ))} -
-
- ) : null} -
- ) - } -} - -const {arrayOf, bool, func, shape, string} = PropTypes - -ColorDropdown.propTypes = { - selected: shape({ - hex: string.isRequired, - name: string.isRequired, - }).isRequired, - onChoose: func.isRequired, - colors: arrayOf( - shape({ - hex: string.isRequired, - name: string.isRequired, - }).isRequired - ).isRequired, - stretchToFit: bool, - disabled: bool, -} - -export default OnClickOutside(ColorDropdown) diff --git a/ui/src/shared/components/ColorDropdown.tsx b/ui/src/shared/components/ColorDropdown.tsx new file mode 100644 index 0000000000..7fd97e9012 --- /dev/null +++ b/ui/src/shared/components/ColorDropdown.tsx @@ -0,0 +1,125 @@ +import React, {Component} from 'react' + +import classnames from 'classnames' +import {ClickOutside} from 'src/shared/components/ClickOutside' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' +import {ErrorHandling} from 'src/shared/decorators/errors' +import {ColorNumber} from 'src/types/colors' +import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' + +interface Props { + selected: ColorNumber + disabled: boolean + stretchToFit: boolean + colors: ColorNumber[] + onChoose: (colors: ColorNumber[]) => void +} + +interface State { + visible: boolean +} + +@ErrorHandling +export default class ColorDropdown extends Component { + constructor(props) { + super(props) + + this.state = { + visible: false, + } + } + + public render() { + const {visible} = this.state + const {selected} = this.props + + return ( + +
+
+
+
{selected.name}
+ +
+ {visible && this.renderMenu} +
+ + ) + } + + private get dropdownClassNames(): string { + const {stretchToFit} = this.props + const {visible} = this.state + + return classnames('color-dropdown', { + open: visible, + 'color-dropdown--stretch': stretchToFit, + }) + } + + private get buttonClassNames(): string { + const {disabled} = this.props + const {visible} = this.state + + return classnames('btn btn-sm btn-default color-dropdown--toggle', { + active: visible, + 'color-dropdown__disabled': disabled, + }) + } + + private get renderMenu(): JSX.Element { + const {colors, selected} = this.props + + return ( +
+ + {colors.map((color, i) => ( +
+ + {color.name} +
+ ))} +
+
+ ) + } + + private handleToggleMenu = (): void => { + const {disabled} = this.props + + if (disabled) { + return + } + this.setState({visible: !this.state.visible}) + } + + private handleClickOutside = (): void => { + this.setState({visible: false}) + } + + private handleColorClick = color => (): void => { + this.props.onChoose(color) + this.setState({visible: false}) + } +} From 5ebf5137db5733543619d765a26a6c2c0baf2770 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:50:31 -0700 Subject: [PATCH 11/51] Convert ColorScaleDropdown to TypeScript --- .../shared/components/ColorScaleDropdown.js | 119 ---------------- .../shared/components/ColorScaleDropdown.tsx | 130 ++++++++++++++++++ 2 files changed, 130 insertions(+), 119 deletions(-) delete mode 100644 ui/src/shared/components/ColorScaleDropdown.js create mode 100644 ui/src/shared/components/ColorScaleDropdown.tsx diff --git a/ui/src/shared/components/ColorScaleDropdown.js b/ui/src/shared/components/ColorScaleDropdown.js deleted file mode 100644 index d82fcc9866..0000000000 --- a/ui/src/shared/components/ColorScaleDropdown.js +++ /dev/null @@ -1,119 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import uuid from 'uuid' -import classnames from 'classnames' - -import OnClickOutside from 'shared/components/OnClickOutside' -import FancyScrollbar from 'shared/components/FancyScrollbar' - -import {LINE_COLOR_SCALES} from 'src/shared/constants/graphColorPalettes' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class ColorScaleDropdown extends Component { - constructor(props) { - super(props) - - this.state = { - expanded: false, - } - } - - handleToggleMenu = () => { - const {disabled} = this.props - - if (disabled) { - return - } - this.setState({expanded: !this.state.expanded}) - } - - handleClickOutside = () => { - this.setState({expanded: false}) - } - - handleDropdownClick = colorScale => () => { - this.props.onChoose(colorScale) - this.setState({expanded: false}) - } - - generateGradientStyle = colors => ({ - background: `linear-gradient(to right, ${colors[0].hex} 0%,${ - colors[1].hex - } 50%,${colors[2].hex} 100%)`, - }) - - render() { - const {expanded} = this.state - const {selected, disabled, stretchToFit} = this.props - - const dropdownClassNames = classnames('color-dropdown', { - open: expanded, - 'color-dropdown--stretch': stretchToFit, - }) - const toggleClassNames = classnames( - 'btn btn-sm btn-default color-dropdown--toggle', - {active: expanded, 'color-dropdown__disabled': disabled} - ) - - return ( -
-
-
-
{selected[0].name}
- -
- {expanded ? ( -
- - {LINE_COLOR_SCALES.map(colorScale => ( -
-
- - {colorScale.name} - -
- ))} - -
- ) : null} -
- ) - } -} - -const {arrayOf, bool, func, shape, string} = PropTypes - -ColorScaleDropdown.propTypes = { - selected: arrayOf( - shape({ - type: string.isRequired, - hex: string.isRequired, - id: string.isRequired, - name: string.isRequired, - }).isRequired - ).isRequired, - onChoose: func.isRequired, - stretchToFit: bool, - disabled: bool, -} - -export default OnClickOutside(ColorScaleDropdown) diff --git a/ui/src/shared/components/ColorScaleDropdown.tsx b/ui/src/shared/components/ColorScaleDropdown.tsx new file mode 100644 index 0000000000..f15e4cd9f3 --- /dev/null +++ b/ui/src/shared/components/ColorScaleDropdown.tsx @@ -0,0 +1,130 @@ +import React, {Component} from 'react' +import uuid from 'uuid' +import classnames from 'classnames' + +import {ClickOutside} from 'src/shared/components/ClickOutside' +import FancyScrollbar from 'src/shared/components/FancyScrollbar' + +import {ColorNumber} from 'src/types/colors' +import {LINE_COLOR_SCALES} from 'src/shared/constants/graphColorPalettes' +import {ErrorHandling} from 'src/shared/decorators/errors' + +interface Props { + onChoose: (colors: ColorNumber[]) => void + stretchToFit?: boolean + disabled?: boolean + selected: ColorNumber[] +} + +interface State { + expanded: boolean +} + +@ErrorHandling +export default class ColorScaleDropdown extends Component { + public static defaultProps: Partial = { + disabled: false, + stretchToFit: false, + } + + constructor(props) { + super(props) + + this.state = { + expanded: false, + } + } + + public render() { + const {expanded} = this.state + const {selected} = this.props + + return ( + +
+
+
+
{selected[0].name}
+ +
+ {expanded && this.renderMenu} +
+ + ) + } + + private get renderMenu(): JSX.Element { + const {selected} = this.props + + return ( +
+ + {LINE_COLOR_SCALES.map(colorScale => ( +
+
+ {colorScale.name} +
+ ))} + +
+ ) + } + + private get buttonClassName(): string { + const {disabled} = this.props + const {expanded} = this.state + + return classnames('btn btn-sm btn-default color-dropdown--toggle', { + active: expanded, + 'color-dropdown__disabled': disabled, + }) + } + + private get dropdownClassName(): string { + const {stretchToFit} = this.props + const {expanded} = this.state + + return classnames('color-dropdown', { + open: expanded, + 'color-dropdown--stretch': stretchToFit, + }) + } + + private handleToggleMenu = (): void => { + const {disabled} = this.props + + if (disabled) { + return + } + this.setState({expanded: !this.state.expanded}) + } + + private handleClickOutside = (): void => { + this.setState({expanded: false}) + } + + private handleDropdownClick = colorScale => (): void => { + this.props.onChoose(colorScale) + this.setState({expanded: false}) + } + + private generateGradientStyle = colors => ({ + background: `linear-gradient(to right, ${colors[0].hex} 0%,${ + colors[1].hex + } 50%,${colors[2].hex} 100%)`, + }) +} From 4fc92a3f21a80740b35876ea9cbcebcd939e32f7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:50:44 -0700 Subject: [PATCH 12/51] Convert DatabaseDropdown to TypeScript --- ...tabaseDropdown.js => DatabaseDropdown.tsx} | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) rename ui/src/shared/components/{DatabaseDropdown.js => DatabaseDropdown.tsx} (70%) diff --git a/ui/src/shared/components/DatabaseDropdown.js b/ui/src/shared/components/DatabaseDropdown.tsx similarity index 70% rename from ui/src/shared/components/DatabaseDropdown.js rename to ui/src/shared/components/DatabaseDropdown.tsx index 725b339f03..d401945ed7 100644 --- a/ui/src/shared/components/DatabaseDropdown.js +++ b/ui/src/shared/components/DatabaseDropdown.tsx @@ -1,26 +1,43 @@ import React, {Component} from 'react' -import PropTypes from 'prop-types' -import Dropdown from 'shared/components/Dropdown' +import Dropdown from 'src/shared/components/Dropdown' -import {showDatabases} from 'shared/apis/metaQuery' -import parsers from 'shared/parsing' +import {showDatabases} from 'src/shared/apis/metaQuery' +import parsers from 'src/shared/parsing' +import {Source} from 'src/types/sources' import {ErrorHandling} from 'src/shared/decorators/errors' const {databases: showDatabasesParser} = parsers +interface Database { + text: string +} + +interface Props { + database: string + onSelectDatabase: (database: Database) => void + onStartEdit: () => void + onErrorThrown: (error: string) => void + source: Source +} + +interface State { + databases: Database[] +} + @ErrorHandling -class DatabaseDropdown extends Component { +class DatabaseDropdown extends Component { constructor(props) { super(props) + this.state = { databases: [], } } - componentDidMount() { - this._getDatabases() + public componentDidMount() { + this.getDatabasesAsync() } - render() { + public render() { const {databases} = this.state const {database, onSelectDatabase, onStartEdit} = this.props @@ -38,7 +55,7 @@ class DatabaseDropdown extends Component { ) } - _getDatabases = async () => { + private getDatabasesAsync = async () => { const {source, database, onSelectDatabase, onErrorThrown} = this.props const proxy = source.links.proxy try { @@ -62,18 +79,4 @@ class DatabaseDropdown extends Component { } } -const {func, shape, string} = PropTypes - -DatabaseDropdown.propTypes = { - database: string, - onSelectDatabase: func.isRequired, - onStartEdit: func, - onErrorThrown: func.isRequired, - source: shape({ - links: shape({ - proxy: string.isRequired, - }).isRequired, - }).isRequired, -} - export default DatabaseDropdown From f1e6320d92b59775377aafdccf3cc6f719e12309 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 15:50:56 -0700 Subject: [PATCH 13/51] Convert LineGraphColorSelector to TypeScript --- ...Selector.js => LineGraphColorSelector.tsx} | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) rename ui/src/shared/components/{LineGraphColorSelector.js => LineGraphColorSelector.tsx} (70%) diff --git a/ui/src/shared/components/LineGraphColorSelector.js b/ui/src/shared/components/LineGraphColorSelector.tsx similarity index 70% rename from ui/src/shared/components/LineGraphColorSelector.js rename to ui/src/shared/components/LineGraphColorSelector.tsx index 4e7ebacb8a..0f7bac8df2 100644 --- a/ui/src/shared/components/LineGraphColorSelector.js +++ b/ui/src/shared/components/LineGraphColorSelector.tsx @@ -1,24 +1,21 @@ import React, {Component} from 'react' -import PropTypes from 'prop-types' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' -import ColorScaleDropdown from 'shared/components/ColorScaleDropdown' +import ColorScaleDropdown from 'src/shared/components/ColorScaleDropdown' import {updateLineColors} from 'src/dashboards/actions/cellEditorOverlay' -import {colorsStringSchema} from 'shared/schemas' +import {ColorNumber} from 'src/types/colors' import {ErrorHandling} from 'src/shared/decorators/errors' +interface Props { + lineColors: ColorNumber[] + handleUpdateLineColors: (colors: ColorNumber[]) => void +} + @ErrorHandling -class LineGraphColorSelector extends Component { - handleSelectColors = colorScale => { - const {handleUpdateLineColors} = this.props - const {colors} = colorScale - - handleUpdateLineColors(colors) - } - - render() { +class LineGraphColorSelector extends Component { + public render() { const {lineColors} = this.props return ( @@ -32,13 +29,13 @@ class LineGraphColorSelector extends Component {
) } -} -const {func} = PropTypes + public handleSelectColors = colorScale => { + const {handleUpdateLineColors} = this.props + const {colors} = colorScale -LineGraphColorSelector.propTypes = { - lineColors: colorsStringSchema.isRequired, - handleUpdateLineColors: func.isRequired, + handleUpdateLineColors(colors) + } } const mapStateToProps = ({cellEditorOverlay: {lineColors}}) => ({ From f1addd9191c44733bc042ffbed62d27ec357b6ea Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 15:52:03 -0700 Subject: [PATCH 14/51] Make AlertsApp TS compliant --- ui/src/alerts/containers/AlertsApp.tsx | 139 +++++++++--------- .../components/CustomTimeRangeDropdown.tsx | 8 +- 2 files changed, 72 insertions(+), 75 deletions(-) diff --git a/ui/src/alerts/containers/AlertsApp.tsx b/ui/src/alerts/containers/AlertsApp.tsx index 03c4b1c880..5e0fa10ab5 100644 --- a/ui/src/alerts/containers/AlertsApp.tsx +++ b/ui/src/alerts/containers/AlertsApp.tsx @@ -1,22 +1,41 @@ import React, {PureComponent} from 'react' -import PropTypes from 'prop-types' -import SourceIndicator from 'shared/components/SourceIndicator' +import SourceIndicator from 'src/shared/components/SourceIndicator' import AlertsTable from 'src/alerts/components/AlertsTable' -import NoKapacitorError from 'shared/components/NoKapacitorError' -import CustomTimeRangeDropdown from 'shared/components/CustomTimeRangeDropdown' +import NoKapacitorError from 'src/shared/components/NoKapacitorError' +import CustomTimeRangeDropdown from 'src/shared/components/CustomTimeRangeDropdown' import {ErrorHandling} from 'src/shared/decorators/errors' import {getAlerts} from 'src/alerts/apis' -import AJAX from 'utils/ajax' +import AJAX from 'src/utils/ajax' import _ from 'lodash' import moment from 'moment' -import {timeRanges} from 'shared/data/timeRanges' +import {timeRanges} from 'src/shared/data/timeRanges' + +import {Source, TimeRange} from 'src/types' +import {Alert} from '../../types/alerts' + +interface Props { + source: Source + timeRange: TimeRange + isWidget: boolean + limit: number +} + +interface State { + loading: boolean + hasKapacitor: boolean + alerts: Alert[] + timeRange: TimeRange + limit: number + limitMultiplier: number + isAlertsMaxedOut: boolean +} @ErrorHandling -class AlertsApp extends PureComponent { +class AlertsApp extends PureComponent { constructor(props) { super(props) @@ -43,7 +62,7 @@ class AlertsApp extends PureComponent { } // TODO: show a loading screen until we figure out if there is a kapacitor and fetch the alerts - componentDidMount() { + public omponentDidMount() { const {source} = this.props AJAX({ url: source.links.kapacitors, @@ -59,13 +78,49 @@ class AlertsApp extends PureComponent { }) } - componentDidUpdate(prevProps, prevState) { + public componentDidUpdate(__, prevState) { if (!_.isEqual(prevState.timeRange, this.state.timeRange)) { this.fetchAlerts() } } + public render() { + const {isWidget, source} = this.props + const {loading, timeRange} = this.state - fetchAlerts = () => { + if (loading || !source) { + return
+ } + + return isWidget ? ( + this.renderSubComponents() + ) : ( +
+
+
+
+

Alert History

+
+
+ + +
+
+
+
+
+
+
{this.renderSubComponents()}
+
+
+
+
+ ) + } + + private fetchAlerts = (): void => { getAlerts( this.props.source.links.proxy, this.state.timeRange, @@ -112,13 +167,13 @@ class AlertsApp extends PureComponent { }) } - handleGetMoreAlerts = () => { + private handleGetMoreAlerts = (): void => { this.setState({limitMultiplier: this.state.limitMultiplier + 1}, () => { - this.fetchAlerts(this.state.limitMultiplier) + this.fetchAlerts() }) } - renderSubComponents = () => { + private renderSubComponents = (): JSX.Element => { const {source, isWidget, limit} = this.props const {isAlertsMaxedOut, alerts} = this.state @@ -137,65 +192,9 @@ class AlertsApp extends PureComponent { ) } - handleApplyTime = timeRange => { + private handleApplyTime = (timeRange: TimeRange) => { this.setState({timeRange}) } - - render() { - const {isWidget, source} = this.props - const {loading, timeRange} = this.state - - if (loading || !source) { - return
- } - - return isWidget ? ( - this.renderSubComponents() - ) : ( -
-
-
-
-

Alert History

-
-
- - -
-
-
-
-
-
-
{this.renderSubComponents()}
-
-
-
-
- ) - } -} - -const {bool, number, oneOfType, shape, string} = PropTypes - -AlertsApp.propTypes = { - source: shape({ - id: string.isRequired, - name: string.isRequired, - type: string, // 'influx-enterprise' - links: shape({ - proxy: string.isRequired, - }).isRequired, - }), - timeRange: shape({ - lower: string.isRequired, - upper: oneOfType([shape(), string]), - }), - isWidget: bool, - limit: number, } export default AlertsApp diff --git a/ui/src/shared/components/CustomTimeRangeDropdown.tsx b/ui/src/shared/components/CustomTimeRangeDropdown.tsx index c32a19cda6..2709d53a85 100644 --- a/ui/src/shared/components/CustomTimeRangeDropdown.tsx +++ b/ui/src/shared/components/CustomTimeRangeDropdown.tsx @@ -5,17 +5,15 @@ import classnames from 'classnames' import {ClickOutside} from 'src/shared/components/ClickOutside' import CustomTimeRange from 'src/shared/components/CustomTimeRange' import {ErrorHandling} from 'src/shared/decorators/errors' +import {TimeRange} from 'src/types' interface State { expanded: boolean } interface Props { - timeRange: { - upper?: string - lower: string - } - onApplyTimeRange: () => void + timeRange: TimeRange + onApplyTimeRange: (tr: TimeRange) => void } @ErrorHandling From fd18ec271487a1c17dd492cee17a86fbd26f75ae Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 16:02:16 -0700 Subject: [PATCH 15/51] Convert tableSizing to TS --- ui/src/alerts/constants/{tableSizing.js => tableSizing.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/src/alerts/constants/{tableSizing.js => tableSizing.ts} (100%) diff --git a/ui/src/alerts/constants/tableSizing.js b/ui/src/alerts/constants/tableSizing.ts similarity index 100% rename from ui/src/alerts/constants/tableSizing.js rename to ui/src/alerts/constants/tableSizing.ts From 3b2c63acb1b2b02b6af0c49e6699608d85b63ca3 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 16:34:43 -0700 Subject: [PATCH 16/51] Convert apis/index and queryUrlGenerator to TS --- ui/src/alerts/apis/index.js | 12 ----------- ui/src/alerts/apis/index.ts | 19 ++++++++++++++++ ui/src/utils/queryUrlGenerator.js | 20 ----------------- ui/src/utils/queryUrlGenerator.ts | 36 +++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 32 deletions(-) delete mode 100644 ui/src/alerts/apis/index.js create mode 100644 ui/src/alerts/apis/index.ts delete mode 100644 ui/src/utils/queryUrlGenerator.js create mode 100644 ui/src/utils/queryUrlGenerator.ts diff --git a/ui/src/alerts/apis/index.js b/ui/src/alerts/apis/index.js deleted file mode 100644 index 4c41ce09e0..0000000000 --- a/ui/src/alerts/apis/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import {proxy} from 'utils/queryUrlGenerator' - -export const getAlerts = (source, timeRange, limit) => - proxy({ - source, - query: `SELECT host, value, level, alertName FROM alerts WHERE time >= '${ - timeRange.lower - }' AND time <= '${timeRange.upper}' ORDER BY time desc ${ - limit ? `LIMIT ${limit}` : '' - }`, - db: 'chronograf', - }) diff --git a/ui/src/alerts/apis/index.ts b/ui/src/alerts/apis/index.ts new file mode 100644 index 0000000000..4222e7fe78 --- /dev/null +++ b/ui/src/alerts/apis/index.ts @@ -0,0 +1,19 @@ +import {proxy} from 'src/utils/queryUrlGenerator' +import {TimeRange} from '../../types' + +export const getAlerts = ( + source: string, + timeRange: TimeRange, + limit: number +) => { + const query = `SELECT host, value, level, alertName FROM alerts WHERE time >= '${ + timeRange.lower + }' AND time <= '${timeRange.upper}' ORDER BY time desc ${ + limit ? `LIMIT ${limit}` : '' + }` + return proxy({ + source, + query, + db: 'chronograf', + }) +} diff --git a/ui/src/utils/queryUrlGenerator.js b/ui/src/utils/queryUrlGenerator.js deleted file mode 100644 index 143ec378be..0000000000 --- a/ui/src/utils/queryUrlGenerator.js +++ /dev/null @@ -1,20 +0,0 @@ -import AJAX from 'utils/ajax' - -export const proxy = async ({source, query, db, rp, tempVars, resolution}) => { - try { - return await AJAX({ - method: 'POST', - url: source, - data: { - tempVars, - query, - resolution, - db, - rp, - }, - }) - } catch (error) { - console.error(error) - throw error - } -} diff --git a/ui/src/utils/queryUrlGenerator.ts b/ui/src/utils/queryUrlGenerator.ts new file mode 100644 index 0000000000..537e6a892f --- /dev/null +++ b/ui/src/utils/queryUrlGenerator.ts @@ -0,0 +1,36 @@ +import AJAX from 'src/utils/ajax' + +interface ProxyQuery { + source: string + query: string + db: string + rp?: string + tempVars?: string + resolution?: string +} + +export async function proxy({ + source, + query, + db, + rp, + tempVars, + resolution, +}: ProxyQuery) { + try { + return await AJAX({ + method: 'POST', + url: source, + data: { + tempVars, + query, + resolution, + db, + rp, + }, + }) + } catch (error) { + console.error(error) + throw error + } +} From 8ab7d1bc320cd1527f4178033db613ea95b72b65 Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Fri, 11 May 2018 16:36:51 -0700 Subject: [PATCH 17/51] Convert Alerts/index to TS --- ui/src/alerts/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/src/alerts/{index.js => index.ts} (100%) diff --git a/ui/src/alerts/index.js b/ui/src/alerts/index.ts similarity index 100% rename from ui/src/alerts/index.js rename to ui/src/alerts/index.ts From 0b851e6c131840ba4d7c3b7076e40eb9171d8728 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 11 May 2018 16:40:52 -0700 Subject: [PATCH 18/51] Remove JS file --- ui/src/shared/components/ColorDropdown.js | 115 ---------------------- 1 file changed, 115 deletions(-) delete mode 100644 ui/src/shared/components/ColorDropdown.js diff --git a/ui/src/shared/components/ColorDropdown.js b/ui/src/shared/components/ColorDropdown.js deleted file mode 100644 index 71ac90bb31..0000000000 --- a/ui/src/shared/components/ColorDropdown.js +++ /dev/null @@ -1,115 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' - -import classnames from 'classnames' -import OnClickOutside from 'shared/components/OnClickOutside' -import FancyScrollbar from 'shared/components/FancyScrollbar' -import {ErrorHandling} from 'src/shared/decorators/errors' -import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' - -@ErrorHandling -class ColorDropdown extends Component { - constructor(props) { - super(props) - - this.state = { - visible: false, - } - } - - handleToggleMenu = () => { - const {disabled} = this.props - - if (disabled) { - return - } - this.setState({visible: !this.state.visible}) - } - - handleClickOutside = () => { - this.setState({visible: false}) - } - - handleColorClick = color => () => { - this.props.onChoose(color) - this.setState({visible: false}) - } - - render() { - const {visible} = this.state - const {colors, selected, disabled, stretchToFit} = this.props - - const dropdownClassNames = classnames('color-dropdown', { - open: visible, - 'color-dropdown--stretch': stretchToFit, - }) - const toggleClassNames = classnames( - 'btn btn-sm btn-default color-dropdown--toggle', - {active: visible, 'color-dropdown__disabled': disabled} - ) - - return ( -
-
-
-
{selected.name}
- -
- {visible ? ( -
- - {colors.map((color, i) => ( -
- - {color.name} -
- ))} -
-
- ) : null} -
- ) - } -} - -const {arrayOf, bool, func, shape, string} = PropTypes - -ColorDropdown.propTypes = { - selected: shape({ - hex: string.isRequired, - name: string.isRequired, - }).isRequired, - onChoose: func.isRequired, - colors: arrayOf( - shape({ - hex: string.isRequired, - name: string.isRequired, - }).isRequired - ).isRequired, - stretchToFit: bool, - disabled: bool, -} - -export default OnClickOutside(ColorDropdown) From 83ea4ea10d8979aed5cba7a3cb70aa50dab9d97a Mon Sep 17 00:00:00 2001 From: ebb-tide Date: Sat, 12 May 2018 15:34:27 -0700 Subject: [PATCH 19/51] Complete missing return types --- ui/src/alerts/components/SearchBar.tsx | 2 +- ui/src/alerts/containers/AlertsApp.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/alerts/components/SearchBar.tsx b/ui/src/alerts/components/SearchBar.tsx index 9906c8aaa4..ce27e9364f 100644 --- a/ui/src/alerts/components/SearchBar.tsx +++ b/ui/src/alerts/components/SearchBar.tsx @@ -40,7 +40,7 @@ class SearchBar extends PureComponent { ) } - private handleSearch = () => { + private handleSearch = (): void => { this.props.onSearch(this.state.searchTerm) } diff --git a/ui/src/alerts/containers/AlertsApp.tsx b/ui/src/alerts/containers/AlertsApp.tsx index 5e0fa10ab5..20b7218346 100644 --- a/ui/src/alerts/containers/AlertsApp.tsx +++ b/ui/src/alerts/containers/AlertsApp.tsx @@ -192,7 +192,7 @@ class AlertsApp extends PureComponent { ) } - private handleApplyTime = (timeRange: TimeRange) => { + private handleApplyTime = (timeRange: TimeRange): void => { this.setState({timeRange}) } } From e91fd7c0fc6c84820de7e556dc57f860c9545184 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 11:10:01 -0700 Subject: [PATCH 20/51] Convert Header to ts --- ui/src/data_explorer/containers/Header.js | 64 ----------------------- 1 file changed, 64 deletions(-) delete mode 100644 ui/src/data_explorer/containers/Header.js diff --git a/ui/src/data_explorer/containers/Header.js b/ui/src/data_explorer/containers/Header.js deleted file mode 100644 index 3cb90bacd2..0000000000 --- a/ui/src/data_explorer/containers/Header.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import {withRouter} from 'react-router' - -import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown' -import TimeRangeDropdown from 'shared/components/TimeRangeDropdown' -import SourceIndicator from 'shared/components/SourceIndicator' -import GraphTips from 'shared/components/GraphTips' - -const {func, number, shape, string} = PropTypes - -const Header = ({ - timeRange, - autoRefresh, - showWriteForm, - onManualRefresh, - onChooseTimeRange, - onChooseAutoRefresh, -}) => ( -
-
-
-

Data Explorer

-
-
- - -
- - Write Data -
- - -
-
-
-) - -Header.propTypes = { - onChooseAutoRefresh: func.isRequired, - onChooseTimeRange: func.isRequired, - onManualRefresh: func.isRequired, - autoRefresh: number.isRequired, - showWriteForm: func.isRequired, - timeRange: shape({ - lower: string, - upper: string, - }).isRequired, -} - -export default withRouter(Header) From 7114ac741fe445b9906d42b388a766019f9a52f1 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 11:17:45 -0700 Subject: [PATCH 21/51] WIP typescripty --- ui/src/data_explorer/containers/Header.tsx | 65 +++++++++++++++++++ .../data/{groupByTimes.js => groupByTimes.ts} | 0 ui/src/data_explorer/{index.js => index.ts} | 0 .../reducers/{index.js => index.ts} | 0 .../{queryConfigs.js => queryConfigs.ts} | 0 .../reducers/{timeRange.js => timeRange.ts} | 2 +- .../data_explorer/reducers/{ui.js => ui.ts} | 11 +++- 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 ui/src/data_explorer/containers/Header.tsx rename ui/src/data_explorer/data/{groupByTimes.js => groupByTimes.ts} (100%) rename ui/src/data_explorer/{index.js => index.ts} (100%) rename ui/src/data_explorer/reducers/{index.js => index.ts} (100%) rename ui/src/data_explorer/reducers/{queryConfigs.js => queryConfigs.ts} (100%) rename ui/src/data_explorer/reducers/{timeRange.js => timeRange.ts} (86%) rename ui/src/data_explorer/reducers/{ui.js => ui.ts} (78%) diff --git a/ui/src/data_explorer/containers/Header.tsx b/ui/src/data_explorer/containers/Header.tsx new file mode 100644 index 0000000000..953edbd704 --- /dev/null +++ b/ui/src/data_explorer/containers/Header.tsx @@ -0,0 +1,65 @@ +import React, {PureComponent} from 'react' +import {withRouter, WithRouterProps} from 'react-router' + +import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown' +import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' +import SourceIndicator from 'src/shared/components/SourceIndicator' +import GraphTips from 'src/shared/components/GraphTips' +import {TimeRange} from 'src/types' + +// TODO: replace with actual queryTransitionFuncs +interface Props extends WithRouterProps { + onChooseAutoRefresh: () => void + onChooseTimeRange: () => void + onManualRefresh: () => void + autoRefresh: () => void + showWriteForm: () => void + timeRange: TimeRange +} + +class Header extends PureComponent { + public render() { + const { + timeRange, + autoRefresh, + showWriteForm, + onManualRefresh, + onChooseTimeRange, + onChooseAutoRefresh, + } = this.props + return ( +
+
+
+

Data Explorer

+
+
+ + +
+ + Write Data +
+ + +
+
+
+ ) + } +} + +export default withRouter(Header) diff --git a/ui/src/data_explorer/data/groupByTimes.js b/ui/src/data_explorer/data/groupByTimes.ts similarity index 100% rename from ui/src/data_explorer/data/groupByTimes.js rename to ui/src/data_explorer/data/groupByTimes.ts diff --git a/ui/src/data_explorer/index.js b/ui/src/data_explorer/index.ts similarity index 100% rename from ui/src/data_explorer/index.js rename to ui/src/data_explorer/index.ts diff --git a/ui/src/data_explorer/reducers/index.js b/ui/src/data_explorer/reducers/index.ts similarity index 100% rename from ui/src/data_explorer/reducers/index.js rename to ui/src/data_explorer/reducers/index.ts diff --git a/ui/src/data_explorer/reducers/queryConfigs.js b/ui/src/data_explorer/reducers/queryConfigs.ts similarity index 100% rename from ui/src/data_explorer/reducers/queryConfigs.js rename to ui/src/data_explorer/reducers/queryConfigs.ts diff --git a/ui/src/data_explorer/reducers/timeRange.js b/ui/src/data_explorer/reducers/timeRange.ts similarity index 86% rename from ui/src/data_explorer/reducers/timeRange.js rename to ui/src/data_explorer/reducers/timeRange.ts index 6029d36413..2a959614c5 100644 --- a/ui/src/data_explorer/reducers/timeRange.js +++ b/ui/src/data_explorer/reducers/timeRange.ts @@ -1,4 +1,4 @@ -import {timeRanges} from 'shared/data/timeRanges' +import {timeRanges} from 'src/shared/data/timeRanges' const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h') diff --git a/ui/src/data_explorer/reducers/ui.js b/ui/src/data_explorer/reducers/ui.ts similarity index 78% rename from ui/src/data_explorer/reducers/ui.js rename to ui/src/data_explorer/reducers/ui.ts index 6a044d3a6e..1a791dd92a 100644 --- a/ui/src/data_explorer/reducers/ui.js +++ b/ui/src/data_explorer/reducers/ui.ts @@ -1,8 +1,15 @@ +interface DataExplorerState { + queryIDs: ReadonlyArray +} + const initialState = { queryIDs: [], } -export default function ui(state = initialState, action) { +const ui = ( + state: DataExplorerState = initialState, + action +): DataExplorerState => { switch (action.type) { // there is an additional reducer for this same action in the queryConfig reducer case 'DE_ADD_QUERY': { @@ -27,3 +34,5 @@ export default function ui(state = initialState, action) { return state } + +export default ui From f204be23263c86d79443494920587e3dcf6fb7f5 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 11:21:36 -0700 Subject: [PATCH 22/51] Type the data explorer reducer --- ui/src/data_explorer/reducers/ui.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ui/src/data_explorer/reducers/ui.ts b/ui/src/data_explorer/reducers/ui.ts index 1a791dd92a..f9bb24bba8 100644 --- a/ui/src/data_explorer/reducers/ui.ts +++ b/ui/src/data_explorer/reducers/ui.ts @@ -2,13 +2,29 @@ interface DataExplorerState { queryIDs: ReadonlyArray } +interface ActionAddQuery { + type: 'DE_ADD_QUERY' + payload: { + queryID: string + } +} + +interface ActionDeleteQuery { + type: 'DE_DELETE_QUERY' + payload: { + queryID: string + } +} + +type Action = ActionAddQuery | ActionDeleteQuery + const initialState = { queryIDs: [], } const ui = ( state: DataExplorerState = initialState, - action + action: Action ): DataExplorerState => { switch (action.type) { // there is an additional reducer for this same action in the queryConfig reducer From 2e9689b1706eabaf1b26cf09cdaf52d0f68db89d Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 11:30:26 -0700 Subject: [PATCH 23/51] Convert time range reducer --- ui/src/data_explorer/reducers/timeRange.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ui/src/data_explorer/reducers/timeRange.ts b/ui/src/data_explorer/reducers/timeRange.ts index 2a959614c5..3bf1d1f727 100644 --- a/ui/src/data_explorer/reducers/timeRange.ts +++ b/ui/src/data_explorer/reducers/timeRange.ts @@ -1,4 +1,5 @@ import {timeRanges} from 'src/shared/data/timeRanges' +import {TimeRange} from 'src/types' const {lower, upper} = timeRanges.find(tr => tr.lower === 'now() - 1h') @@ -7,7 +8,18 @@ const initialState = { lower, } -export default function timeRange(state = initialState, action) { +type State = Readonly + +interface ActionSetTimeRange { + type: 'DE_SET_TIME_RANGE' + payload: { + bounds: TimeRange + } +} + +type Action = ActionSetTimeRange + +const timeRange = (state: State = initialState, action: Action): State => { switch (action.type) { case 'DE_SET_TIME_RANGE': { const {bounds} = action.payload @@ -17,3 +29,5 @@ export default function timeRange(state = initialState, action) { } return state } + +export default timeRange From 528cf2a67ee9f6fc1b0211789a44bd73cfb7bd0f Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 11:41:22 -0700 Subject: [PATCH 24/51] Add back editRawText transition --- ui/src/utils/defaultQueryConfig.ts | 3 +- ui/src/utils/queryTransitions.ts | 11 +- .../reducers/queryConfig.test.js | 170 +++++++++++++++--- 3 files changed, 155 insertions(+), 29 deletions(-) diff --git a/ui/src/utils/defaultQueryConfig.ts b/ui/src/utils/defaultQueryConfig.ts index 43b853b6e2..52f962cc12 100644 --- a/ui/src/utils/defaultQueryConfig.ts +++ b/ui/src/utils/defaultQueryConfig.ts @@ -1,10 +1,11 @@ import uuid from 'uuid' import {NULL_STRING} from 'src/shared/constants/queryFillOptions' +import {QueryConfig} from 'src/types' const defaultQueryConfig = ( {id, isKapacitorRule = false} = {id: uuid.v4()} -) => { +): QueryConfig => { const queryConfig = { id, database: null, diff --git a/ui/src/utils/queryTransitions.ts b/ui/src/utils/queryTransitions.ts index 751d355010..ee264487d6 100644 --- a/ui/src/utils/queryTransitions.ts +++ b/ui/src/utils/queryTransitions.ts @@ -8,16 +8,23 @@ import { } from 'src/shared/reducers/helpers/fields' import { + Tag, Field, GroupBy, Namespace, - QueryConfig, - Tag, TagValues, TimeShift, + QueryConfig, ApplyFuncsToFieldArgs, } from 'src/types' +export const editRawText = ( + query: QueryConfig, + rawText: string +): QueryConfig => { + return {...query, rawText} +} + export const chooseNamespace = ( query: QueryConfig, namespace: Namespace, diff --git a/ui/test/data_explorer/reducers/queryConfig.test.js b/ui/test/data_explorer/reducers/queryConfig.test.js index 24b9cfcb8b..b102ed8fce 100644 --- a/ui/test/data_explorer/reducers/queryConfig.test.js +++ b/ui/test/data_explorer/reducers/queryConfig.test.js @@ -9,6 +9,7 @@ import { groupByTime, toggleField, removeFuncs, + editRawText, updateRawQuery, editQueryStatus, chooseNamespace, @@ -24,12 +25,21 @@ import {LINEAR, NULL_STRING} from 'shared/constants/queryFillOptions' const fakeAddQueryAction = (panelID, queryID) => { return { type: 'DE_ADD_QUERY', - payload: {panelID, queryID}, + payload: { + panelID, + queryID, + }, } } function buildInitialState(queryID, params) { - return Object.assign({}, defaultQueryConfig({id: queryID}), params) + return Object.assign( + {}, + defaultQueryConfig({ + id: queryID, + }), + params + ) } describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { @@ -39,7 +49,9 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const state = reducer({}, fakeAddQueryAction('blah', queryID)) const actual = state[queryID] - const expected = defaultQueryConfig({id: queryID}) + const expected = defaultQueryConfig({ + id: queryID, + }) expect(actual).toEqual(expected) }) @@ -143,7 +155,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { expect(newState[queryID].fields.length).toBe(2) expect(newState[queryID].fields[1].alias).toEqual('mean_f2') expect(newState[queryID].fields[1].args).toEqual([ - {value: 'f2', type: 'field'}, + { + value: 'f2', + type: 'field', + }, ]) expect(newState[queryID].fields[1].value).toEqual('mean') }) @@ -164,7 +179,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { expect(newState[queryID].fields[1].value).toBe('mean') expect(newState[queryID].fields[1].alias).toBe('mean_f2') expect(newState[queryID].fields[1].args).toEqual([ - {value: 'f2', type: 'field'}, + { + value: 'f2', + type: 'field', + }, ]) expect(newState[queryID].fields[1].type).toBe('func') }) @@ -175,7 +193,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const newState = reducer( state, - toggleField(queryID, {value: 'fk1', type: 'field'}) + toggleField(queryID, { + value: 'fk1', + type: 'field', + }) ) expect(newState[queryID].fields.length).toBe(1) @@ -185,8 +206,14 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { describe('DE_APPLY_FUNCS_TO_FIELD', () => { it('applies new functions to a field', () => { - const f1 = {value: 'f1', type: 'field'} - const f2 = {value: 'f2', type: 'field'} + const f1 = { + value: 'f1', + type: 'field', + } + const f2 = { + value: 'f2', + type: 'field', + } const initialState = { [queryID]: { @@ -194,40 +221,100 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { database: 'db1', measurement: 'm1', fields: [ - {value: 'fn1', type: 'func', args: [f1], alias: `fn1_${f1.value}`}, - {value: 'fn1', type: 'func', args: [f2], alias: `fn1_${f2.value}`}, - {value: 'fn2', type: 'func', args: [f1], alias: `fn2_${f1.value}`}, + { + value: 'fn1', + type: 'func', + args: [f1], + alias: `fn1_${f1.value}`, + }, + { + value: 'fn1', + type: 'func', + args: [f2], + alias: `fn1_${f2.value}`, + }, + { + value: 'fn2', + type: 'func', + args: [f1], + alias: `fn2_${f1.value}`, + }, ], }, } const action = applyFuncsToField(queryID, { - field: {value: 'f1', type: 'field'}, + field: { + value: 'f1', + type: 'field', + }, funcs: [ - {value: 'fn3', type: 'func', args: []}, - {value: 'fn4', type: 'func', args: []}, + { + value: 'fn3', + type: 'func', + args: [], + }, + { + value: 'fn4', + type: 'func', + args: [], + }, ], }) const nextState = reducer(initialState, action) expect(nextState[queryID].fields).toEqual([ - {value: 'fn3', type: 'func', args: [f1], alias: `fn3_${f1.value}`}, - {value: 'fn4', type: 'func', args: [f1], alias: `fn4_${f1.value}`}, - {value: 'fn1', type: 'func', args: [f2], alias: `fn1_${f2.value}`}, + { + value: 'fn3', + type: 'func', + args: [f1], + alias: `fn3_${f1.value}`, + }, + { + value: 'fn4', + type: 'func', + args: [f1], + alias: `fn4_${f1.value}`, + }, + { + value: 'fn1', + type: 'func', + args: [f2], + alias: `fn1_${f2.value}`, + }, ]) }) }) describe('DE_REMOVE_FUNCS', () => { it('removes all functions and group by time when one field has no funcs applied', () => { - const f1 = {value: 'f1', type: 'field'} - const f2 = {value: 'f2', type: 'field'} + const f1 = { + value: 'f1', + type: 'field', + } + const f2 = { + value: 'f2', + type: 'field', + } const fields = [ - {value: 'fn1', type: 'func', args: [f1], alias: `fn1_${f1.value}`}, - {value: 'fn1', type: 'func', args: [f2], alias: `fn1_${f2.value}`}, + { + value: 'fn1', + type: 'func', + args: [f1], + alias: `fn1_${f1.value}`, + }, + { + value: 'fn1', + type: 'func', + args: [f2], + alias: `fn1_${f2.value}`, + }, ] - const groupBy = {time: '1m', tags: []} + const groupBy = { + time: '1m', + tags: [], + } const initialState = { [queryID]: { @@ -320,7 +407,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { measurement: 'm1', fields: [], tags: {}, - groupBy: {tags: [], time: null}, + groupBy: { + tags: [], + time: null, + }, }, } const action = groupByTag(queryID, 'k1') @@ -341,7 +431,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { measurement: 'm1', fields: [], tags: {}, - groupBy: {tags: ['k1'], time: null}, + groupBy: { + tags: ['k1'], + time: null, + }, }, } const action = groupByTag(queryID, 'k1') @@ -389,7 +482,14 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const initialState = { [queryID]: buildInitialState(queryID), } - const expected = defaultQueryConfig({id: queryID}, {rawText: 'hello'}) + const expected = defaultQueryConfig( + { + id: queryID, + }, + { + rawText: 'hello', + } + ) const action = updateQueryConfig(expected) const nextState = reducer(initialState, action) @@ -476,11 +576,29 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { [queryID]: buildInitialState(queryID), } - const shift = {quantity: 1, unit: 'd', duration: '1d'} + const shift = { + quantity: 1, + unit: 'd', + duration: '1d', + } const action = timeShift(queryID, shift) const nextState = reducer(initialState, action) expect(nextState[queryID].shifts).toEqual([shift]) }) }) + + describe('DE_EDIT_RAW_TEXT', () => { + it('can edit the raw text', () => { + const initialState = { + [queryID]: buildInitialState(queryID), + } + + const rawText = 'im the raw text' + const action = editRawText(queryID, rawText) + const nextState = reducer(initialState, action) + + expect(nextState[queryID].rawText).toEqual(rawText) + }) + }) }) From 2b78be167aa1cb32a9c5487a3df9e137ff22b1e6 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 12:51:07 -0700 Subject: [PATCH 25/51] Convert data_explorer/actions/index to ts --- ui/src/data_explorer/actions/view/index.js | 167 -------- ui/src/data_explorer/actions/view/index.ts | 394 ++++++++++++++++++ ui/src/utils/defaultQueryConfig.ts | 7 +- ...ueryConfig.test.js => queryConfig.test.ts} | 52 +-- 4 files changed, 422 insertions(+), 198 deletions(-) delete mode 100644 ui/src/data_explorer/actions/view/index.js create mode 100644 ui/src/data_explorer/actions/view/index.ts rename ui/test/data_explorer/reducers/{queryConfig.test.js => queryConfig.test.ts} (94%) diff --git a/ui/src/data_explorer/actions/view/index.js b/ui/src/data_explorer/actions/view/index.js deleted file mode 100644 index a0071e9fe9..0000000000 --- a/ui/src/data_explorer/actions/view/index.js +++ /dev/null @@ -1,167 +0,0 @@ -import uuid from 'uuid' - -import {getQueryConfigAndStatus} from 'shared/apis' - -import {errorThrown} from 'shared/actions/errors' - -export const addQuery = (queryID = uuid.v4()) => ({ - type: 'DE_ADD_QUERY', - payload: { - queryID, - }, -}) - -export const deleteQuery = queryID => ({ - type: 'DE_DELETE_QUERY', - payload: { - queryID, - }, -}) - -export const toggleField = (queryID, fieldFunc) => ({ - type: 'DE_TOGGLE_FIELD', - payload: { - queryID, - fieldFunc, - }, -}) - -export const groupByTime = (queryID, time) => ({ - type: 'DE_GROUP_BY_TIME', - payload: { - queryID, - time, - }, -}) - -export const fill = (queryID, value) => ({ - type: 'DE_FILL', - payload: { - queryID, - value, - }, -}) - -export const removeFuncs = (queryID, fields, groupBy) => ({ - type: 'DE_REMOVE_FUNCS', - payload: { - queryID, - fields, - groupBy, - }, -}) - -export const applyFuncsToField = (queryID, fieldFunc, groupBy) => ({ - type: 'DE_APPLY_FUNCS_TO_FIELD', - payload: { - queryID, - fieldFunc, - groupBy, - }, -}) - -export const chooseTag = (queryID, tag) => ({ - type: 'DE_CHOOSE_TAG', - payload: { - queryID, - tag, - }, -}) - -export const chooseNamespace = (queryID, {database, retentionPolicy}) => ({ - type: 'DE_CHOOSE_NAMESPACE', - payload: { - queryID, - database, - retentionPolicy, - }, -}) - -export const chooseMeasurement = (queryID, measurement) => ({ - type: 'DE_CHOOSE_MEASUREMENT', - payload: { - queryID, - measurement, - }, -}) - -export const editRawText = (queryID, rawText) => ({ - type: 'DE_EDIT_RAW_TEXT', - payload: { - queryID, - rawText, - }, -}) - -export const setTimeRange = bounds => ({ - type: 'DE_SET_TIME_RANGE', - payload: { - bounds, - }, -}) - -export const groupByTag = (queryID, tagKey) => ({ - type: 'DE_GROUP_BY_TAG', - payload: { - queryID, - tagKey, - }, -}) - -export const toggleTagAcceptance = queryID => ({ - type: 'DE_TOGGLE_TAG_ACCEPTANCE', - payload: { - queryID, - }, -}) - -export const updateRawQuery = (queryID, text) => ({ - type: 'DE_UPDATE_RAW_QUERY', - payload: { - queryID, - text, - }, -}) - -export const updateQueryConfig = config => ({ - type: 'DE_UPDATE_QUERY_CONFIG', - payload: { - config, - }, -}) - -export const addInitialField = (queryID, field, groupBy) => ({ - type: 'DE_ADD_INITIAL_FIELD', - payload: { - queryID, - field, - groupBy, - }, -}) - -export const editQueryStatus = (queryID, status) => ({ - type: 'DE_EDIT_QUERY_STATUS', - payload: { - queryID, - status, - }, -}) - -export const timeShift = (queryID, shift) => ({ - type: 'DE_TIME_SHIFT', - payload: { - queryID, - shift, - }, -}) - -// Async actions -export const editRawTextAsync = (url, id, text) => async dispatch => { - try { - const {data} = await getQueryConfigAndStatus(url, [{query: text, id}]) - const config = data.queries.find(q => q.id === id) - dispatch(updateQueryConfig(config.queryConfig)) - } catch (error) { - dispatch(errorThrown(error)) - } -} diff --git a/ui/src/data_explorer/actions/view/index.ts b/ui/src/data_explorer/actions/view/index.ts new file mode 100644 index 0000000000..c78d4321c6 --- /dev/null +++ b/ui/src/data_explorer/actions/view/index.ts @@ -0,0 +1,394 @@ +import uuid from 'uuid' + +import {getQueryConfigAndStatus} from 'src/shared/apis' + +import {errorThrown} from 'src/shared/actions/errors' +import { + QueryConfig, + Status, + Field, + GroupBy, + Tag, + TimeRange, + TimeShift, + ApplyFuncsToFieldArgs, +} from 'src/types' + +export type Action = + | ActionAddQuery + | ActionDeleteQuery + | ActionToggleField + | ActionGroupByTime + | ActionFill + | ActionRemoveFuncs + | ActionApplyFuncsToField + | ActionChooseTag + | ActionChooseNamspace + | ActionChooseMeasurement + | ActionEditRawText + | ActionSetTimeRange + | ActionGroupByTime + | ActionToggleField + | ActionUpdateRawQuery + | ActionQueryConfig + | ActionTimeShift + +interface ActionAddQuery { + type: 'DE_ADD_QUERY' + payload: { + queryID: string + } +} + +export const addQuery = (queryID: string = uuid.v4()): ActionAddQuery => ({ + type: 'DE_ADD_QUERY', + payload: { + queryID, + }, +}) + +interface ActionDeleteQuery { + type: 'DE_DELETE_QUERY' + payload: { + queryID: string + } +} + +export const deleteQuery = (queryID: string): ActionDeleteQuery => ({ + type: 'DE_DELETE_QUERY', + payload: { + queryID, + }, +}) + +interface ActionToggleField { + type: 'DE_DELETE_QUERY' + payload: { + queryID: string + } +} + +export const toggleField = (queryID, fieldFunc) => ({ + type: 'DE_TOGGLE_FIELD', + payload: { + queryID, + fieldFunc, + }, +}) + +interface ActionGroupByTime { + type: 'DE_GROUP_BY_TIME' + payload: { + queryID: string + time: string + } +} + +export const groupByTime = ( + queryID: string, + time: string +): ActionGroupByTime => ({ + type: 'DE_GROUP_BY_TIME', + payload: { + queryID, + time, + }, +}) + +interface ActionFill { + type: 'DE_FILL' + payload: { + queryID: string + value: string + } +} + +export const fill = (queryID: string, value: string): ActionFill => ({ + type: 'DE_FILL', + payload: { + queryID, + value, + }, +}) + +interface ActionRemoveFuncs { + type: 'DE_REMOVE_FUNCS' + payload: { + queryID: string + fields: Field[] + groupBy: GroupBy + } +} + +export const removeFuncs = ( + queryID: string, + fields: Field[], + groupBy: GroupBy +): ActionRemoveFuncs => ({ + type: 'DE_REMOVE_FUNCS', + payload: { + queryID, + fields, + groupBy, + }, +}) + +interface ActionApplyFuncsToField { + type: 'DE_APPLY_FUNCS_TO_FIELD' + payload: { + queryID: string + fieldFunc: ApplyFuncsToFieldArgs + groupBy: GroupBy + } +} + +export const applyFuncsToField = ( + queryID: string, + fieldFunc: ApplyFuncsToFieldArgs, + groupBy?: GroupBy +): ActionApplyFuncsToField => ({ + type: 'DE_APPLY_FUNCS_TO_FIELD', + payload: { + queryID, + fieldFunc, + groupBy, + }, +}) + +interface ActionChooseTag { + type: 'DE_CHOOSE_TAG' + payload: { + queryID: string + tag: Tag + } +} + +export const chooseTag = (queryID: string, tag: Tag): ActionChooseTag => ({ + type: 'DE_CHOOSE_TAG', + payload: { + queryID, + tag, + }, +}) + +interface ActionChooseNamspace { + type: 'DE_CHOOSE_NAMESPACE' + payload: { + queryID: string + database: string + retentionPolicy: string + } +} + +interface DBRP { + database: string + retentionPolicy: string +} + +export const chooseNamespace = ( + queryID: string, + {database, retentionPolicy}: DBRP +): ActionChooseNamspace => ({ + type: 'DE_CHOOSE_NAMESPACE', + payload: { + queryID, + database, + retentionPolicy, + }, +}) + +interface ActionChooseMeasurement { + type: 'DE_CHOOSE_MEASUREMENT' + payload: { + queryID: string + measurement: string + } +} + +export const chooseMeasurement = ( + queryID: string, + measurement: string +): ActionChooseMeasurement => ({ + type: 'DE_CHOOSE_MEASUREMENT', + payload: { + queryID, + measurement, + }, +}) + +interface ActionEditRawText { + type: 'DE_EDIT_RAW_TEXT' + payload: { + queryID: string + rawText: string + } +} + +export const editRawText = ( + queryID: string, + rawText: string +): ActionEditRawText => ({ + type: 'DE_EDIT_RAW_TEXT', + payload: { + queryID, + rawText, + }, +}) + +interface ActionSetTimeRange { + type: 'DE_SET_TIME_RANGE' + payload: { + bounds: TimeRange + } +} + +export const setTimeRange = (bounds: TimeRange): ActionSetTimeRange => ({ + type: 'DE_SET_TIME_RANGE', + payload: { + bounds, + }, +}) + +interface ActionGroupByTag { + type: 'DE_GROUP_BY_TAG' + payload: { + queryID: string + tagKey: string + } +} + +export const groupByTag = ( + queryID: string, + tagKey: string +): ActionGroupByTag => ({ + type: 'DE_GROUP_BY_TAG', + payload: { + queryID, + tagKey, + }, +}) + +interface ActionToggleTagAcceptance { + type: 'DE_TOGGLE_TAG_ACCEPTANCE' + payload: { + queryID: string + } +} + +export const toggleTagAcceptance = ( + queryID: string +): ActionToggleTagAcceptance => ({ + type: 'DE_TOGGLE_TAG_ACCEPTANCE', + payload: { + queryID, + }, +}) + +interface ActionUpdateRawQuery { + type: 'DE_UPDATE_RAW_QUERY' + payload: { + queryID: string + text: string + } +} + +export const updateRawQuery = ( + queryID: string, + text: string +): ActionUpdateRawQuery => ({ + type: 'DE_UPDATE_RAW_QUERY', + payload: { + queryID, + text, + }, +}) + +interface ActionQueryConfig { + type: 'DE_UPDATE_QUERY_CONFIG' + payload: { + config: QueryConfig + } +} + +export const updateQueryConfig = (config: QueryConfig): ActionQueryConfig => ({ + type: 'DE_UPDATE_QUERY_CONFIG', + payload: { + config, + }, +}) + +interface ActionAddInitialField { + type: 'DE_ADD_INITIAL_FIELD' + payload: { + queryID: string + field: Field + groupBy?: GroupBy + } +} + +export const addInitialField = ( + queryID: string, + field: Field, + groupBy: GroupBy +): ActionAddInitialField => ({ + type: 'DE_ADD_INITIAL_FIELD', + payload: { + queryID, + field, + groupBy, + }, +}) + +interface ActionEditQueryStatus { + type: 'DE_EDIT_QUERY_STATUS' + payload: { + queryID: string + status: Status + } +} + +export const editQueryStatus = ( + queryID: string, + status: Status +): ActionEditQueryStatus => ({ + type: 'DE_EDIT_QUERY_STATUS', + payload: { + queryID, + status, + }, +}) + +interface ActionTimeShift { + type: 'DE_TIME_SHIFT' + payload: { + queryID: string + shift: TimeShift + } +} + +export const timeShift = ( + queryID: string, + shift: TimeShift +): ActionTimeShift => ({ + type: 'DE_TIME_SHIFT', + payload: { + queryID, + shift, + }, +}) + +// Async actions +export const editRawTextAsync = (url, id, text) => async dispatch => { + try { + const {data} = await getQueryConfigAndStatus(url, [ + { + query: text, + id, + }, + ]) + const config = data.queries.find(q => q.id === id) + dispatch(updateQueryConfig(config.queryConfig)) + } catch (error) { + dispatch(errorThrown(error)) + } +} diff --git a/ui/src/utils/defaultQueryConfig.ts b/ui/src/utils/defaultQueryConfig.ts index 52f962cc12..8b6576ab28 100644 --- a/ui/src/utils/defaultQueryConfig.ts +++ b/ui/src/utils/defaultQueryConfig.ts @@ -3,8 +3,13 @@ import uuid from 'uuid' import {NULL_STRING} from 'src/shared/constants/queryFillOptions' import {QueryConfig} from 'src/types' +interface DefaultQueryArgs { + id?: string + isKapacitorRule?: boolean +} + const defaultQueryConfig = ( - {id, isKapacitorRule = false} = {id: uuid.v4()} + {id, isKapacitorRule = false}: DefaultQueryArgs = {id: uuid.v4()} ): QueryConfig => { const queryConfig = { id, diff --git a/ui/test/data_explorer/reducers/queryConfig.test.js b/ui/test/data_explorer/reducers/queryConfig.test.ts similarity index 94% rename from ui/test/data_explorer/reducers/queryConfig.test.js rename to ui/test/data_explorer/reducers/queryConfig.test.ts index b102ed8fce..57600027ad 100644 --- a/ui/test/data_explorer/reducers/queryConfig.test.js +++ b/ui/test/data_explorer/reducers/queryConfig.test.ts @@ -1,6 +1,6 @@ import reducer from 'src/data_explorer/reducers/queryConfigs' -import defaultQueryConfig from 'utils/defaultQueryConfig' +import defaultQueryConfig from 'src/utils/defaultQueryConfig' import { fill, timeShift, @@ -20,9 +20,9 @@ import { toggleTagAcceptance, } from 'src/data_explorer/actions/view' -import {LINEAR, NULL_STRING} from 'shared/constants/queryFillOptions' +import {LINEAR, NULL_STRING} from 'src/shared/constants/queryFillOptions' -const fakeAddQueryAction = (panelID, queryID) => { +const fakeAddQueryAction = (panelID: string, queryID: string) => { return { type: 'DE_ADD_QUERY', payload: { @@ -32,18 +32,17 @@ const fakeAddQueryAction = (panelID, queryID) => { } } -function buildInitialState(queryID, params) { - return Object.assign( - {}, - defaultQueryConfig({ +function buildInitialState(queryID, params?) { + return { + ...defaultQueryConfig({ id: queryID, }), - params - ) + params, + } } describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { - const queryID = 123 + const queryID = '123' it('can add a query', () => { const state = reducer({}, fakeAddQueryAction('blah', queryID)) @@ -93,14 +92,13 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { }) ) const three = reducer(two, chooseMeasurement(queryID, 'disk')) + const field = { + value: 'a great field', + type: 'field', + } + const groupBy = {} - state = reducer( - three, - addInitialField(queryID, { - value: 'a great field', - type: 'field', - }) - ) + state = reducer(three, addInitialField(queryID, field, groupBy)) }) describe('choosing a new namespace', () => { @@ -252,12 +250,10 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { { value: 'fn3', type: 'func', - args: [], }, { value: 'fn4', type: 'func', - args: [], }, ], }) @@ -482,14 +478,8 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const initialState = { [queryID]: buildInitialState(queryID), } - const expected = defaultQueryConfig( - { - id: queryID, - }, - { - rawText: 'hello', - } - ) + const id = {id: queryID} + const expected = defaultQueryConfig(id) const action = updateQueryConfig(expected) const nextState = reducer(initialState, action) @@ -513,12 +503,12 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const initialState = { [queryID]: buildInitialState(queryID), } - const status = 'your query was sweet' + const status = {success: 'Your query was very nice'} const action = editQueryStatus(queryID, status) const nextState = reducer(initialState, action) - expect(nextState[queryID].status).toBe(status) + expect(nextState[queryID].status).toEqual(status) }) describe('DE_FILL', () => { @@ -577,10 +567,12 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { } const shift = { - quantity: 1, + quantity: '1', unit: 'd', duration: '1d', + label: 'label', } + const action = timeShift(queryID, shift) const nextState = reducer(initialState, action) From 578badef1f68d87641c89ec79894db36bfedc8d0 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 13:50:59 -0700 Subject: [PATCH 26/51] Convert MOAR to TS --- ui/src/data_explorer/actions/view/index.ts | 15 ++++- .../data_explorer/containers/DataExplorer.tsx | 6 +- ui/src/data_explorer/containers/Header.tsx | 10 ++-- ui/src/data_explorer/reducers/queryConfigs.ts | 42 ++++++-------- .../containers/DataExplorer.test.tsx | 8 +++ .../reducers/queryConfig.test.ts | 55 ++++++++++--------- 6 files changed, 74 insertions(+), 62 deletions(-) diff --git a/ui/src/data_explorer/actions/view/index.ts b/ui/src/data_explorer/actions/view/index.ts index c78d4321c6..b19797327b 100644 --- a/ui/src/data_explorer/actions/view/index.ts +++ b/ui/src/data_explorer/actions/view/index.ts @@ -32,8 +32,13 @@ export type Action = | ActionUpdateRawQuery | ActionQueryConfig | ActionTimeShift + | ActionToggleTagAcceptance + | ActionToggleField + | ActionGroupByTag + | ActionEditQueryStatus + | ActionAddInitialField -interface ActionAddQuery { +export interface ActionAddQuery { type: 'DE_ADD_QUERY' payload: { queryID: string @@ -62,13 +67,17 @@ export const deleteQuery = (queryID: string): ActionDeleteQuery => ({ }) interface ActionToggleField { - type: 'DE_DELETE_QUERY' + type: 'DE_TOGGLE_FIELD' payload: { queryID: string + fieldFunc: Field } } -export const toggleField = (queryID, fieldFunc) => ({ +export const toggleField = ( + queryID: string, + fieldFunc: Field +): ActionToggleField => ({ type: 'DE_TOGGLE_FIELD', payload: { queryID, diff --git a/ui/src/data_explorer/containers/DataExplorer.tsx b/ui/src/data_explorer/containers/DataExplorer.tsx index e3581e86a8..62653a3a7b 100644 --- a/ui/src/data_explorer/containers/DataExplorer.tsx +++ b/ui/src/data_explorer/containers/DataExplorer.tsx @@ -32,7 +32,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors' interface Props { source: Source queryConfigs: QueryConfig[] - queryConfigActions: any // TODO: actually type these + queryConfigActions: any autoRefresh: number handleChooseAutoRefresh: () => void router?: InjectedRouter @@ -161,8 +161,8 @@ export class DataExplorer extends PureComponent { this.setState({showWriteForm: true}) } - private handleChooseTimeRange = (bounds: TimeRange): void => { - this.props.setTimeRange(bounds) + private handleChooseTimeRange = (timeRange: TimeRange): void => { + this.props.setTimeRange(timeRange) } private get selectedDatabase(): string { diff --git a/ui/src/data_explorer/containers/Header.tsx b/ui/src/data_explorer/containers/Header.tsx index 953edbd704..82cfc31130 100644 --- a/ui/src/data_explorer/containers/Header.tsx +++ b/ui/src/data_explorer/containers/Header.tsx @@ -1,5 +1,4 @@ import React, {PureComponent} from 'react' -import {withRouter, WithRouterProps} from 'react-router' import AutoRefreshDropdown from 'src/shared/components/AutoRefreshDropdown' import TimeRangeDropdown from 'src/shared/components/TimeRangeDropdown' @@ -7,13 +6,12 @@ import SourceIndicator from 'src/shared/components/SourceIndicator' import GraphTips from 'src/shared/components/GraphTips' import {TimeRange} from 'src/types' -// TODO: replace with actual queryTransitionFuncs -interface Props extends WithRouterProps { +interface Props { onChooseAutoRefresh: () => void - onChooseTimeRange: () => void onManualRefresh: () => void - autoRefresh: () => void + onChooseTimeRange: (timeRange: TimeRange) => void showWriteForm: () => void + autoRefresh: number timeRange: TimeRange } @@ -62,4 +60,4 @@ class Header extends PureComponent { } } -export default withRouter(Header) +export default Header diff --git a/ui/src/data_explorer/reducers/queryConfigs.ts b/ui/src/data_explorer/reducers/queryConfigs.ts index da678e2166..7049452534 100644 --- a/ui/src/data_explorer/reducers/queryConfigs.ts +++ b/ui/src/data_explorer/reducers/queryConfigs.ts @@ -1,6 +1,9 @@ import _ from 'lodash' import defaultQueryConfig from 'src/utils/defaultQueryConfig' +import {QueryConfig} from 'src/types' +import {Action} from 'src/data_explorer/actions/view' + import { fill, timeShift, @@ -18,7 +21,11 @@ import { toggleTagAcceptance, } from 'src/utils/queryTransitions' -const queryConfigs = (state = {}, action) => { +interface State { + [queryID: string]: Readonly +} + +const queryConfigs = (state: State = {}, action: Action): State => { switch (action.type) { case 'DE_CHOOSE_NAMESPACE': { const {queryID, database, retentionPolicy} = action.payload @@ -27,9 +34,7 @@ const queryConfigs = (state = {}, action) => { retentionPolicy, }) - return Object.assign({}, state, { - [queryID]: Object.assign(nextQueryConfig, {rawText: null}), - }) + return {...state, [queryID]: {...nextQueryConfig, rawText: null}} } case 'DE_CHOOSE_MEASUREMENT': { @@ -71,36 +76,31 @@ const queryConfigs = (state = {}, action) => { const {queryID, rawText} = action.payload const nextQueryConfig = editRawText(state[queryID], rawText) - return Object.assign({}, state, { + return { + ...state, [queryID]: nextQueryConfig, - }) + } } case 'DE_GROUP_BY_TIME': { const {queryID, time} = action.payload const nextQueryConfig = groupByTime(state[queryID], time) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) + return {...state, [queryID]: nextQueryConfig} } case 'DE_TOGGLE_TAG_ACCEPTANCE': { const {queryID} = action.payload const nextQueryConfig = toggleTagAcceptance(state[queryID]) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) + return {...state, [queryID]: nextQueryConfig} } case 'DE_TOGGLE_FIELD': { const {queryID, fieldFunc} = action.payload const nextQueryConfig = toggleField(state[queryID], fieldFunc) - return Object.assign({}, state, { - [queryID]: {...nextQueryConfig, rawText: null}, - }) + return {...state, [queryID]: {...nextQueryConfig, rawText: null}} } case 'DE_APPLY_FUNCS_TO_FIELD': { @@ -111,26 +111,20 @@ const queryConfigs = (state = {}, action) => { groupBy ) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) + return {...state, [queryID]: nextQueryConfig} } case 'DE_CHOOSE_TAG': { const {queryID, tag} = action.payload const nextQueryConfig = chooseTag(state[queryID], tag) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) + return {...state, [queryID]: nextQueryConfig} } case 'DE_GROUP_BY_TAG': { const {queryID, tagKey} = action.payload const nextQueryConfig = groupByTag(state[queryID], tagKey) - return Object.assign({}, state, { - [queryID]: nextQueryConfig, - }) + return {...state, [queryID]: nextQueryConfig} } case 'DE_FILL': { diff --git a/ui/test/data_explorer/containers/DataExplorer.test.tsx b/ui/test/data_explorer/containers/DataExplorer.test.tsx index 5abeec4f46..1dae485306 100644 --- a/ui/test/data_explorer/containers/DataExplorer.test.tsx +++ b/ui/test/data_explorer/containers/DataExplorer.test.tsx @@ -16,6 +16,14 @@ const queryConfigActions = { editRawTextAsync: () => {}, addInitialField: () => {}, editQueryStatus: () => {}, + deleteQuery: () => {}, + fill: () => {}, + removeFuncs: () => {}, + editRawText: () => {}, + setTimeRange: () => {}, + updateRawQuery: () => {}, + updateQueryConfig: () => {}, + timeShift: () => {}, } const setup = () => { diff --git a/ui/test/data_explorer/reducers/queryConfig.test.ts b/ui/test/data_explorer/reducers/queryConfig.test.ts index 57600027ad..12ae5b6f09 100644 --- a/ui/test/data_explorer/reducers/queryConfig.test.ts +++ b/ui/test/data_explorer/reducers/queryConfig.test.ts @@ -18,15 +18,15 @@ import { addInitialField, updateQueryConfig, toggleTagAcceptance, + ActionAddQuery, } from 'src/data_explorer/actions/view' import {LINEAR, NULL_STRING} from 'src/shared/constants/queryFillOptions' -const fakeAddQueryAction = (panelID: string, queryID: string) => { +const fakeAddQueryAction = (queryID: string): ActionAddQuery => { return { type: 'DE_ADD_QUERY', payload: { - panelID, queryID, }, } @@ -37,7 +37,7 @@ function buildInitialState(queryID, params?) { ...defaultQueryConfig({ id: queryID, }), - params, + ...params, } } @@ -45,7 +45,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { const queryID = '123' it('can add a query', () => { - const state = reducer({}, fakeAddQueryAction('blah', queryID)) + const state = reducer({}, fakeAddQueryAction(queryID)) const actual = state[queryID] const expected = defaultQueryConfig({ @@ -57,7 +57,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { describe('choosing db, rp, and measurement', () => { let state beforeEach(() => { - state = reducer({}, fakeAddQueryAction('any', queryID)) + state = reducer({}, fakeAddQueryAction(queryID)) }) it('sets the db and rp', () => { @@ -83,7 +83,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { describe('a query has measurements and fields', () => { let state beforeEach(() => { - const one = reducer({}, fakeAddQueryAction('any', queryID)) + const one = reducer({}, fakeAddQueryAction(queryID)) const two = reducer( one, chooseNamespace(queryID, { @@ -214,8 +214,8 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { } const initialState = { - [queryID]: { - id: 123, + [queryID]: buildInitialState(queryID, { + id: '123', database: 'db1', measurement: 'm1', fields: [ @@ -238,7 +238,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { alias: `fn2_${f1.value}`, }, ], - }, + }), } const action = applyFuncsToField(queryID, { @@ -313,13 +313,13 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { } const initialState = { - [queryID]: { - id: 123, + [queryID]: buildInitialState(queryID, { + id: '123', database: 'db1', measurement: 'm1', fields, groupBy, - }, + }), } const action = removeFuncs(queryID, fields, groupBy) @@ -343,6 +343,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { }, }), } + const action = chooseTag(queryID, { key: 'k1', value: 'v1', @@ -397,8 +398,8 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { describe('DE_GROUP_BY_TAG', () => { it('adds a tag key/value to the query', () => { const initialState = { - [queryID]: { - id: 123, + [queryID]: buildInitialState(queryID, { + id: '123', database: 'db1', measurement: 'm1', fields: [], @@ -407,7 +408,7 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { tags: [], time: null, }, - }, + }), } const action = groupByTag(queryID, 'k1') @@ -420,19 +421,21 @@ describe('Chronograf.Reducers.DataExplorer.queryConfigs', () => { }) it('removes a tag if the given tag key is already in the GROUP BY list', () => { - const initialState = { - [queryID]: { - id: 123, - database: 'db1', - measurement: 'm1', - fields: [], - tags: {}, - groupBy: { - tags: ['k1'], - time: null, - }, + const query = { + id: '123', + database: 'db1', + measurement: 'm1', + fields: [], + tags: {}, + groupBy: { + tags: ['k1'], + time: null, }, } + + const initialState = { + [queryID]: buildInitialState(queryID, query), + } const action = groupByTag(queryID, 'k1') const nextState = reducer(initialState, action) From 365230342ea92da5aa2ee29addb1e8d53b330c60 Mon Sep 17 00:00:00 2001 From: Andrew Watkins Date: Fri, 11 May 2018 14:43:14 -0700 Subject: [PATCH 27/51] Convert QueryEditor to ts --- .../data_explorer/components/QueryEditor.js | 98 ------------------- ui/src/data_explorer/constants/index.js | 84 ---------------- 2 files changed, 182 deletions(-) delete mode 100644 ui/src/data_explorer/components/QueryEditor.js delete mode 100644 ui/src/data_explorer/constants/index.js diff --git a/ui/src/data_explorer/components/QueryEditor.js b/ui/src/data_explorer/components/QueryEditor.js deleted file mode 100644 index 220f5b6090..0000000000 --- a/ui/src/data_explorer/components/QueryEditor.js +++ /dev/null @@ -1,98 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' - -import Dropdown from 'shared/components/Dropdown' -import {QUERY_TEMPLATES} from 'src/data_explorer/constants' -import QueryStatus from 'shared/components/QueryStatus' -import {ErrorHandling} from 'src/shared/decorators/errors' - -@ErrorHandling -class QueryEditor extends Component { - constructor(props) { - super(props) - this.state = { - value: this.props.query, - } - } - - componentWillReceiveProps(nextProps) { - if (this.props.query !== nextProps.query) { - this.setState({value: nextProps.query}) - } - } - - handleKeyDown = e => { - const {value} = this.state - - if (e.key === 'Escape') { - e.preventDefault() - this.setState({value}) - } - - if (e.key === 'Enter') { - e.preventDefault() - this.handleUpdate() - } - } - - handleChange = () => { - this.setState({value: this.editor.value}) - } - - handleUpdate = () => { - this.props.onUpdate(this.state.value) - } - - handleChooseMetaQuery = template => { - this.setState({value: template.query}) - } - - render() { - const { - config: {status}, - } = this.props - const {value} = this.state - - return ( -
-