Convert dashboard QueryMaker & children to TypeScript

Co-authored-by: Jared Scheib <jared.scheib@gmail.com>
pull/10616/head
Iris Scholten 2018-04-27 17:11:18 -07:00
parent 691805dc48
commit 39278205d7
13 changed files with 336 additions and 293 deletions

View File

@ -31,9 +31,15 @@ import {
TEMP_VAR_DASHBOARD_TIME,
} from 'src/shared/constants'
import {getCellTypeColors} from 'src/dashboards/constants/cellEditor'
import {TimeRange, Source, QueryConfig} from 'src/types'
import {Status} from 'src/types/query'
import {Cell, CellQuery, Legend} from 'src/types/dashboard'
import {
TimeRange,
Source,
QueryConfig,
Cell,
CellQuery,
Legend,
Status,
} from 'src/types'
import {ErrorHandling} from 'src/shared/decorators/errors'
const staticLegend: Legend = {

View File

@ -1,22 +1,44 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import _ from 'lodash'
import {QueryConfig, Source, SourceLinks, TimeRange} from 'src/types'
import {CellEditorOverlayActions} from 'src/types/dashboard'
import EmptyQuery from 'src/shared/components/EmptyQuery'
import QueryTabList from 'src/shared/components/QueryTabList'
import QueryTextArea from 'src/dashboards/components/QueryTextArea'
import SchemaExplorer from 'src/shared/components/SchemaExplorer'
import {buildQuery} from 'utils/influxql'
import {TYPE_QUERY_CONFIG} from 'src/dashboards/constants'
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
import {buildQuery} from 'src/utils/influxql'
import {TYPE_QUERY_CONFIG, TEMPLATE_RANGE} from 'src/dashboards/constants'
const TEMPLATE_RANGE = {upper: null, lower: TEMP_VAR_DASHBOARD_TIME}
const rawTextBinder = (links, id, action) => text =>
action(links.queries, id, text)
const buildText = q =>
const rawTextBinder = (
links: SourceLinks,
id: string,
action: (linksQueries: string, id: string, text: string) => void
) => (text: string) => action(links.queries, id, text)
const buildText = (q: QueryConfig): string =>
q.rawText || buildQuery(TYPE_QUERY_CONFIG, q.range || TEMPLATE_RANGE, q) || ''
const QueryMaker = ({
interface Template {
tempVar: string
}
interface Props {
source: Source
queries: QueryConfig[]
timeRange: TimeRange
actions: CellEditorOverlayActions
setActiveQueryIndex: () => void
onDeleteQuery: () => void
activeQueryIndex: number
activeQuery: QueryConfig
onAddQuery: () => void
templates: Template[]
initialGroupByTime: string
}
const QueryMaker: SFC<Props> = ({
source,
actions,
queries,
@ -69,43 +91,4 @@ const QueryMaker = ({
</div>
)
const {arrayOf, func, number, shape, string} = PropTypes
QueryMaker.propTypes = {
source: shape({
links: shape({
queries: string.isRequired,
}).isRequired,
}).isRequired,
queries: arrayOf(shape({})).isRequired,
timeRange: shape({
upper: string,
lower: string,
}).isRequired,
actions: shape({
chooseNamespace: func.isRequired,
chooseMeasurement: func.isRequired,
chooseTag: func.isRequired,
groupByTag: func.isRequired,
toggleField: func.isRequired,
groupByTime: func.isRequired,
toggleTagAcceptance: func.isRequired,
fill: func,
applyFuncsToField: func.isRequired,
editRawTextAsync: func.isRequired,
addInitialField: func.isRequired,
}).isRequired,
setActiveQueryIndex: func.isRequired,
onDeleteQuery: func.isRequired,
activeQueryIndex: number,
activeQuery: shape({}),
onAddQuery: func.isRequired,
templates: arrayOf(
shape({
tempVar: string.isRequired,
})
).isRequired,
initialGroupByTime: string.isRequired,
}
export default QueryMaker

View File

@ -3,6 +3,7 @@ import {
DEFAULT_FIX_FIRST_COLUMN,
} from 'src/shared/constants/tableGraph'
import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph'
import {TEMP_VAR_DASHBOARD_TIME} from 'src/shared/constants'
export const UNTITLED_CELL_LINE = 'Untitled Line Graph'
export const UNTITLED_CELL_STACKED = 'Untitled Stacked Gracph'
@ -151,3 +152,4 @@ export const TYPE_QUERY_CONFIG = 'queryConfig'
export const TYPE_SHIFTED = 'shifted queryConfig'
export const TYPE_IFQL = 'ifql'
export const DASHBOARD_NAME_MAX_LENGTH = 50
export const TEMPLATE_RANGE = {upper: null, lower: TEMP_VAR_DASHBOARD_TIME}

View File

@ -1,14 +1,45 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import FunctionSelector from 'shared/components/FunctionSelector'
import {firstFieldName} from 'shared/reducers/helpers/fields'
import FunctionSelector from 'src/shared/components/FunctionSelector'
import {firstFieldName} from 'src/shared/reducers/helpers/fields'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Field {
type: string
value: string
}
interface FuncArg {
value: string
type: string
}
interface FieldFunc extends Field {
args: FuncArg[]
}
interface ApplyFuncsToFieldArgs {
field: Field
funcs: FuncArg[]
}
interface Props {
fieldFuncs: FieldFunc[]
isSelected: boolean
onToggleField: (field: Field) => void
onApplyFuncsToField: (args: ApplyFuncsToFieldArgs) => void
isKapacitorRule: boolean
funcs: string[]
isDisabled: boolean
}
interface State {
isOpen: boolean
}
@ErrorHandling
class FieldListItem extends Component {
class FieldListItem extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
@ -16,60 +47,10 @@ class FieldListItem extends Component {
}
}
toggleFunctionsMenu = e => {
if (e) {
e.stopPropagation()
}
const {isDisabled} = this.props
if (isDisabled) {
return
}
this.setState({isOpen: !this.state.isOpen})
}
close = () => {
this.setState({isOpen: false})
}
handleToggleField = () => {
const {onToggleField} = this.props
const value = this._getFieldName()
onToggleField({value, type: 'field'})
this.close()
}
handleApplyFunctions = selectedFuncs => {
const {onApplyFuncsToField} = this.props
const fieldName = this._getFieldName()
const field = {value: fieldName, type: 'field'}
onApplyFuncsToField({
field,
funcs: selectedFuncs.map(this._makeFunc),
})
this.close()
}
_makeFunc = value => ({
value,
type: 'func',
})
_getFieldName = () => {
const {fieldFuncs} = this.props
const fieldFunc = _.head(fieldFuncs)
return _.get(fieldFunc, 'type') === 'field'
? _.get(fieldFunc, 'value')
: firstFieldName(_.get(fieldFunc, 'args'))
}
render() {
public render() {
const {isKapacitorRule, isSelected, funcs} = this.props
const {isOpen} = this.state
const fieldName = this._getFieldName()
const fieldName = this.getFieldName()
let fieldFuncsLabel
const num = funcs.length
@ -121,29 +102,56 @@ class FieldListItem extends Component {
</div>
)
}
}
const {string, shape, func, arrayOf, bool} = PropTypes
private toggleFunctionsMenu = (e: MouseEvent<HTMLElement>) => {
if (e) {
e.stopPropagation()
}
const {isDisabled} = this.props
if (isDisabled) {
return
}
FieldListItem.propTypes = {
fieldFuncs: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
alias: string,
args: arrayOf(
shape({
type: string.isRequired,
value: string.isRequired,
})
),
this.setState({isOpen: !this.state.isOpen})
}
private close = (): void => {
this.setState({isOpen: false})
}
private handleToggleField = (): void => {
const {onToggleField} = this.props
const value = this.getFieldName()
onToggleField({value, type: 'field'})
this.close()
}
private handleApplyFunctions = (selectedFuncs: string[]) => {
const {onApplyFuncsToField} = this.props
const fieldName = this.getFieldName()
const field: Field = {value: fieldName, type: 'field'}
onApplyFuncsToField({
field,
funcs: selectedFuncs.map(val => this.makeFuncArg(val)),
})
).isRequired,
isSelected: bool.isRequired,
onToggleField: func.isRequired,
onApplyFuncsToField: func.isRequired,
isKapacitorRule: bool.isRequired,
funcs: arrayOf(string.isRequired).isRequired,
isDisabled: bool,
this.close()
}
private makeFuncArg = (value: string): FuncArg => ({
value,
type: 'func',
})
private getFieldName = (): string => {
const {fieldFuncs} = this.props
const fieldFunc = _.head(fieldFuncs)
return _.get(fieldFunc, 'type') === 'field'
? _.get(fieldFunc, 'value')
: firstFieldName(_.get(fieldFunc, 'args'))
}
}
export default FieldListItem

View File

@ -1,21 +1,35 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import {withRouter} from 'react-router'
import {Location} from 'history'
import groupByTimeOptions from 'src/data_explorer/data/groupByTimes'
import Dropdown from 'shared/components/Dropdown'
import Dropdown from 'src/shared/components/Dropdown'
import {AUTO_GROUP_BY} from 'shared/constants'
import {AUTO_GROUP_BY} from 'src/shared/constants'
const isInRuleBuilder = pathname => pathname.includes('alert-rules')
interface GroupByTimeOption {
defaultTimeBound: string
seconds: number
menuOption: string
}
const getOptions = pathname =>
interface Props {
location?: Location
selected: string
onChooseGroupByTime: () => void
isDisabled: boolean
}
const isInRuleBuilder = (pathname: string): boolean =>
pathname.includes('alert-rules')
const getOptions = (pathname: string): GroupByTimeOption[] =>
isInRuleBuilder(pathname)
? groupByTimeOptions.filter(({menuOption}) => menuOption !== AUTO_GROUP_BY)
: groupByTimeOptions
const GroupByTimeDropdown = ({
const GroupByTimeDropdown: SFC<Props> = ({
selected,
onChooseGroupByTime,
location: {pathname},
@ -38,15 +52,4 @@ const GroupByTimeDropdown = ({
</div>
)
const {bool, func, string, shape} = PropTypes
GroupByTimeDropdown.propTypes = {
location: shape({
pathname: string.isRequired,
}).isRequired,
selected: string,
onChooseGroupByTime: func.isRequired,
isDisabled: bool,
}
export default withRouter(GroupByTimeDropdown)

View File

@ -1,23 +1,23 @@
import React, {Component} from 'react'
import React, {PureComponent} from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
import QueryOptions from 'shared/components/QueryOptions'
import QueryOptions from 'src/shared/components/QueryOptions'
import FieldListItem from 'src/data_explorer/components/FieldListItem'
import FancyScrollbar from 'shared/components/FancyScrollbar'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {showFieldKeys} from 'shared/apis/metaQuery'
import showFieldKeysParser from 'shared/parsing/showFieldKeys'
import {showFieldKeys} from 'src/shared/apis/metaQuery'
import showFieldKeysParser from 'src/shared/parsing/showFieldKeys'
import {
functionNames,
numFunctions,
getFieldsWithName,
getFuncsByFieldName,
} from 'shared/reducers/helpers/fields'
} from 'src/shared/reducers/helpers/fields'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ErrorHandling
class FieldList extends Component {
class FieldList extends PureComponent {
constructor(props) {
super(props)
this.state = {
@ -25,7 +25,7 @@ class FieldList extends Component {
}
}
componentDidMount() {
public componentDidMount() {
const {database, measurement} = this.props.query
if (!database || !measurement) {
return
@ -34,7 +34,7 @@ class FieldList extends Component {
this._getFields()
}
componentDidUpdate(prevProps) {
public componentDidUpdate(prevProps) {
const {querySource, query} = this.props
const {database, measurement, retentionPolicy} = query
const {
@ -58,15 +58,15 @@ class FieldList extends Component {
this._getFields()
}
handleGroupByTime = groupBy => {
public handleGroupByTime = groupBy => {
this.props.onGroupByTime(groupBy.menuOption)
}
handleFill = fill => {
public handleFill = fill => {
this.props.onFill(fill)
}
handleToggleField = field => {
public handleToggleField = field => {
const {
query,
onToggleField,
@ -90,7 +90,7 @@ class FieldList extends Component {
onToggleField(field)
}
handleApplyFuncs = fieldFunc => {
public handleApplyFuncs = fieldFunc => {
const {
query,
removeFuncs,
@ -113,11 +113,11 @@ class FieldList extends Component {
applyFuncsToField(fieldFunc, groupBy)
}
handleTimeShift = shift => {
public handleTimeShift = shift => {
this.props.onTimeShift(shift)
}
_getFields = () => {
public _getFields = () => {
const {database, measurement, retentionPolicy} = this.props.query
const {source} = this.context
const {querySource} = this.props
@ -142,7 +142,7 @@ class FieldList extends Component {
})
}
render() {
public render() {
const {
query: {database, measurement, fields = [], groupBy, fill, shifts},
isQuerySupportedByExplorer,

View File

@ -1,18 +1,42 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
import React, {
PureComponent,
FocusEvent,
ChangeEvent,
KeyboardEvent,
} from 'react'
import Dropdown from 'src/shared/components/Dropdown'
import {NULL_STRING, NUMBER} from 'shared/constants/queryFillOptions'
import {NULL_STRING, NUMBER} from 'src/shared/constants/queryFillOptions'
import queryFills from 'shared/data/queryFills'
import queryFills from 'src/shared/data/queryFills'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
onChooseFill: (text: string) => void
value: string
size?: string
theme?: string
isDisabled?: boolean
}
interface Item {
type: string
text: string
}
interface State {
selected: Item
currentNumberValue: string
resetNumberValue: string
}
@ErrorHandling
class FillQuery extends Component {
class FillQuery extends PureComponent<Props, State> {
private numberInput: HTMLElement
constructor(props) {
super(props)
const isNumberValue = !isNaN(Number(props.value))
const isNumberValue: boolean = !isNaN(Number(props.value))
this.state = isNumberValue
? {
@ -27,65 +51,13 @@ class FillQuery extends Component {
}
}
handleDropdown = item => {
if (item.text === NUMBER) {
this.setState({selected: item}, () => {
this.numberInput.focus()
})
} else {
this.setState({selected: item}, () => {
this.props.onChooseFill(item.text)
})
}
public static defaultProps: Partial<Props> = {
size: 'sm',
theme: 'blue',
value: NULL_STRING,
}
handleInputBlur = e => {
const nextNumberValue = e.target.value
? e.target.value
: this.state.resetNumberValue || '0'
this.setState({
currentNumberValue: nextNumberValue,
resetNumberValue: nextNumberValue,
})
this.props.onChooseFill(nextNumberValue)
}
handleInputChange = e => {
const currentNumberValue = e.target.value
this.setState({currentNumberValue})
}
handleKeyDown = e => {
if (e.key === 'Enter') {
this.numberInput.blur()
}
}
handleKeyUp = e => {
if (e.key === 'Escape') {
this.setState({currentNumberValue: this.state.resetNumberValue}, () => {
this.numberInput.blur()
})
}
}
getColor = theme => {
switch (theme) {
case 'BLUE':
return 'plutonium'
case 'GREEN':
return 'malachite'
case 'PURPLE':
return 'astronaut'
default:
return 'plutonium'
}
}
render() {
public render() {
const {size, theme, isDisabled} = this.props
const {selected, currentNumberValue} = this.state
@ -120,22 +92,64 @@ class FillQuery extends Component {
</div>
)
}
}
const {bool, func, string} = PropTypes
private handleDropdown = (item: Item): void => {
if (item.text === NUMBER) {
this.setState({selected: item}, () => {
this.numberInput.focus()
})
} else {
this.setState({selected: item}, () => {
this.props.onChooseFill(item.text)
})
}
}
FillQuery.defaultProps = {
size: 'sm',
theme: 'blue',
value: NULL_STRING,
}
private handleInputBlur = (e: FocusEvent<HTMLInputElement>): void => {
const nextNumberValue = e.target.value
? e.target.value
: this.state.resetNumberValue || '0'
FillQuery.propTypes = {
onChooseFill: func.isRequired,
value: string,
size: string,
theme: string,
isDisabled: bool,
this.setState({
currentNumberValue: nextNumberValue,
resetNumberValue: nextNumberValue,
})
this.props.onChooseFill(nextNumberValue)
}
private handleInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
const currentNumberValue = e.target.value
this.setState({currentNumberValue})
}
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
if (e.key === 'Enter') {
this.numberInput.blur()
}
}
private handleKeyUp = (e: KeyboardEvent<HTMLInputElement>): void => {
if (e.key === 'Escape') {
this.setState({currentNumberValue: this.state.resetNumberValue}, () => {
this.numberInput.blur()
})
}
}
private getColor = (theme: string): string => {
switch (theme) {
case 'BLUE':
return 'plutonium'
case 'GREEN':
return 'malachite'
case 'PURPLE':
return 'astronaut'
default:
return 'plutonium'
}
}
}
export default FillQuery

View File

@ -1,10 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, {SFC} from 'react'
import {GroupBy, TimeShift} from 'src/types'
import GroupByTimeDropdown from 'src/data_explorer/components/GroupByTimeDropdown'
import TimeShiftDropdown from 'src/shared/components/TimeShiftDropdown'
import FillQuery from 'shared/components/FillQuery'
import FillQuery from 'src/shared/components/FillQuery'
const QueryOptions = ({
interface Props {
fill: string
onFill: () => void
groupBy: GroupBy
shift: TimeShift
onGroupByTime: () => void
isKapacitorRule: boolean
onTimeShift: () => void
isDisabled: boolean
}
const QueryOptions: SFC<Props> = ({
fill,
shift,
onFill,
@ -33,21 +46,4 @@ const QueryOptions = ({
</div>
)
const {bool, func, shape, string} = PropTypes
QueryOptions.propTypes = {
fill: string,
onFill: func.isRequired,
groupBy: shape({
time: string,
}).isRequired,
shift: shape({
label: string,
}),
onGroupByTime: func.isRequired,
isKapacitorRule: bool.isRequired,
onTimeShift: func.isRequired,
isDisabled: bool,
}
export default QueryOptions

View File

@ -1,6 +1,9 @@
import React from 'react'
import PropTypes from 'prop-types'
import {QueryConfig, Source} from 'src/types'
import {CellEditorOverlayActions} from 'src/types/dashboard'
import DatabaseList from 'src/shared/components/DatabaseList'
import MeasurementList from 'src/shared/components/MeasurementList'
import FieldList from 'src/shared/components/FieldList'

View File

@ -1,28 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Dropdown from 'shared/components/Dropdown'
import {TIME_SHIFTS} from 'shared/constants/timeShift'
const TimeShiftDropdown = ({selected, onChooseTimeShift, isDisabled}) => (
<div className="group-by-time">
<label className="group-by-time--label">Compare:</label>
<Dropdown
className="group-by-time--dropdown"
buttonColor="btn-info"
items={TIME_SHIFTS}
onChoose={onChooseTimeShift}
selected={selected || 'none'}
disabled={isDisabled}
/>
</div>
)
const {bool, func, string} = PropTypes
TimeShiftDropdown.propTypes = {
selected: string,
onChooseTimeShift: func.isRequired,
isDisabled: bool,
}
export default TimeShiftDropdown

View File

@ -0,0 +1,30 @@
import React, {SFC} from 'react'
import Dropdown from 'src/shared/components/Dropdown'
import {TIME_SHIFTS} from 'src/shared/constants/timeShift'
interface Props {
selected: string
onChooseTimeShift: () => void
isDisabled: boolean
}
const TimeShiftDropdown: SFC<Props> = ({
selected,
onChooseTimeShift,
isDisabled,
}) => (
<div className="group-by-time">
<label className="group-by-time--label">Compare:</label>
<Dropdown
className="group-by-time--dropdown"
buttonColor="btn-info"
items={TIME_SHIFTS}
onChoose={onChooseTimeShift}
selected={selected || 'none'}
disabled={isDisabled}
/>
</div>
)
export default TimeShiftDropdown

View File

@ -1,5 +1,6 @@
import {QueryConfig} from 'src/types'
import {ColorString} from 'src/types/colors'
interface Axis {
bounds: [string, string]
label: string
@ -75,3 +76,19 @@ export interface Template {
tempVar: string
values: TemplateValue[]
}
export interface CellEditorOverlayActions {
chooseNamespace: () => void
chooseMeasurement: () => void
applyFuncsToField: () => void
chooseTag: () => void
groupByTag: () => void
toggleField: () => void
groupByTime: () => void
toggleTagAcceptance: () => void
fill: () => void
editRawTextAsync: () => void
addInitialField: () => void
removeFuncs: () => void
timeShift: () => void
}

View File

@ -1,7 +1,8 @@
import {AuthLinks, Organization, Role, User, Me} from './auth'
import {QueryConfig, TimeRange} from './query'
import {Template, Cell, CellQuery, Legend} from './dashboard'
import {GroupBy, QueryConfig, Status, TimeRange, TimeShift} from './query'
import {AlertRule, Kapacitor, Task} from './kapacitor'
import {Source} from './sources'
import {Source, SourceLinks} from './sources'
import {DropdownAction, DropdownItem} from './shared'
export {
@ -10,10 +11,18 @@ export {
Role,
User,
Organization,
Template,
Cell,
CellQuery,
Legend,
Status,
QueryConfig,
TimeShift,
GroupBy,
AlertRule,
Kapacitor,
QueryConfig,
Source,
SourceLinks,
DropdownAction,
DropdownItem,
TimeRange,