Merge pull request #5704 from influxdata/5528/filterFields

feat(ui): allow to filter fields in query builder
pull/5712/head
Pavel Závora 2021-03-27 09:32:26 +01:00 committed by GitHub
commit b86c173222
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 53 deletions

View File

@ -22,6 +22,7 @@
1. [#5688](https://github.com/influxdata/chronograf/pull/5688): Add UI variables to flux query execution.
1. [#5697](https://github.com/influxdata/chronograf/pull/5697): Allow to define dashboard variables using flux.
1. [#5700](https://github.com/influxdata/chronograf/pull/5700): Remove HipChat alerts.
1. [#5704](https://github.com/influxdata/chronograf/pull/5704): Allow to filter fields in Query Builder UI.
### Other

View File

@ -1,14 +1,13 @@
import React, {PureComponent, MouseEvent} from 'react'
import classnames from 'classnames'
import _ from 'lodash'
import FunctionSelector from 'src/shared/components/FunctionSelector'
import {firstFieldName} from 'src/shared/reducers/helpers/fields'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {ApplyFuncsToFieldArgs, Field, FieldFunc, FuncArg} from 'src/types'
interface Props {
fieldName: string
fieldFuncs: FieldFunc[]
isSelected: boolean
onToggleField: (field: Field) => void
@ -32,9 +31,14 @@ class FieldListItem extends PureComponent<Props, State> {
}
public render() {
const {isKapacitorRule, isSelected, funcs, isDisabled} = this.props
const {
isKapacitorRule,
isSelected,
fieldName,
funcs,
isDisabled,
} = this.props
const {isOpen} = this.state
const fieldName = this.getFieldName()
let fieldFuncsLabel
const num = funcs.length
@ -104,16 +108,14 @@ class FieldListItem extends PureComponent<Props, State> {
}
private handleToggleField = (): void => {
const {onToggleField} = this.props
const value = this.getFieldName()
const {onToggleField, fieldName} = this.props
onToggleField({value, type: 'field'})
onToggleField({value: fieldName, type: 'field'})
this.close()
}
private handleApplyFunctions = (selectedFuncs: string[]) => {
const {onApplyFuncsToField} = this.props
const fieldName = this.getFieldName()
const {onApplyFuncsToField, fieldName} = this.props
const field: Field = {value: fieldName, type: 'field'}
onApplyFuncsToField({
@ -127,15 +129,6 @@ class FieldListItem extends PureComponent<Props, State> {
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

@ -22,8 +22,10 @@ import {
numFunctions,
getFieldsWithName,
getFuncsByFieldName,
getFieldName,
} from 'src/shared/reducers/helpers/fields'
import {ErrorHandling} from 'src/shared/decorators/errors'
import QueryBuilderFilter from './QueryBuilderFilter'
interface GroupByOption extends GroupBy {
menuOption: string
@ -55,6 +57,7 @@ interface Props {
}
interface State {
filterText: string
fields: Field[]
}
@ -68,6 +71,7 @@ class FieldList extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
filterText: '',
fields: [],
}
}
@ -131,7 +135,13 @@ class FieldList extends PureComponent<Props, State> {
onGroupByTime={this.handleGroupByTime}
isDisabled={isDisabled}
/>
) : null}
) : (
<QueryBuilderFilter
filterText={this.state.filterText}
onEscape={this.handleEscapeFilter}
onFilterText={this.handleFilterText}
></QueryBuilderFilter>
)}
</div>
{noDBorMeas ? (
<div className="query-builder--list-empty">
@ -141,41 +151,85 @@ class FieldList extends PureComponent<Props, State> {
</div>
) : (
<div className="query-builder--list">
<FancyScrollbar>
{this.state.fields.map((fieldFunc, i) => {
const selectedFields = getFieldsWithName(
fieldFunc.value,
fields
)
<div>
<FancyScrollbar>
{this.state.fields.map((fieldFunc, i) => {
const selectedFields = getFieldsWithName(
fieldFunc.value,
fields
)
const fieldName = getFieldName(fieldFunc)
if (
this.state.filterText &&
!selectedFields.length &&
!fieldName
.toLowerCase()
.includes(this.state.filterText.toLowerCase())
) {
// do not render the item unless it is selected or matches filter
return
}
const funcs: FieldFunc[] = getFuncsByFieldName(
fieldFunc.value,
fields
)
const fieldFuncs = selectedFields.length
? selectedFields
: [fieldFunc]
const funcs: FieldFunc[] = getFuncsByFieldName(
fieldFunc.value,
fields
)
const fieldFuncs = selectedFields.length
? selectedFields
: [fieldFunc]
return (
<FieldListItem
key={i}
onToggleField={this.handleToggleField}
onApplyFuncsToField={this.handleApplyFuncs}
isSelected={!!selectedFields.length}
fieldFuncs={fieldFuncs}
funcs={functionNames(funcs)}
isKapacitorRule={isKapacitorRule}
isDisabled={isDisabled}
/>
)
})}
</FancyScrollbar>
return (
<FieldListItem
key={i}
onToggleField={this.handleToggleField}
onApplyFuncsToField={this.handleApplyFuncs}
isSelected={!!selectedFields.length}
fieldName={fieldName}
fieldFuncs={fieldFuncs}
funcs={functionNames(funcs)}
isKapacitorRule={isKapacitorRule}
isDisabled={isDisabled}
/>
)
})}
</FancyScrollbar>
</div>
</div>
)}
{hasAggregates ? (
<div
className="query-builder--heading"
style={{backgroundColor: 'transparent'}}
>
<QueryBuilderFilter
filterText={this.state.filterText}
onEscape={this.handleEscapeFilter}
onFilterText={this.handleFilterText}
></QueryBuilderFilter>
</div>
) : undefined}
</div>
)
}
private handleFilterText = (e: React.FormEvent<HTMLInputElement>) => {
e.stopPropagation()
const filterText = e.currentTarget.value
this.setState({
filterText,
})
}
private handleEscapeFilter = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key !== 'Escape') {
return
}
e.stopPropagation()
this.setState({
filterText: '',
})
}
private handleGroupByTime = (groupBy: GroupByOption): void => {
this.props.onGroupByTime(groupBy.menuOption)
}

View File

@ -9,7 +9,7 @@ import showMeasurementsParser from 'src/shared/parsing/showMeasurements'
import {QueryConfig, Source, Tag} from 'src/types'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import MeasurementListFilter from 'src/shared/components/MeasurementListFilter'
import QueryBuilderFilter from 'src/shared/components/QueryBuilderFilter'
import MeasurementListItem from 'src/shared/components/MeasurementListItem'
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -136,7 +136,7 @@ class MeasurementList extends PureComponent<Props, State> {
<div className="query-builder--heading">
<span>Measurements & Tags</span>
{database && (
<MeasurementListFilter
<QueryBuilderFilter
onEscape={this.handleEscape}
onFilterText={this.handleFilterText}
filterText={this.state.filterText}

View File

@ -6,7 +6,7 @@ interface Props {
onFilterText: (e: React.InputHTMLAttributes<HTMLInputElement>) => void
}
const MeasurementListFilter: FunctionComponent<Props> = ({
const QueryBuilderFilter: FunctionComponent<Props> = ({
onEscape,
onFilterText,
filterText,
@ -26,4 +26,4 @@ const MeasurementListFilter: FunctionComponent<Props> = ({
</div>
)
export default MeasurementListFilter
export default QueryBuilderFilter

View File

@ -87,3 +87,10 @@ export const removeField = (fieldName, fields) => {
return [...acc, f]
}, [])
}
// Gets field name out of the field func supplied
export function getFieldName(fieldFunc) {
return _.get(fieldFunc, 'type') === 'field'
? _.get(fieldFunc, 'value')
: firstFieldName(_.get(fieldFunc, 'args'))
}

View File

@ -1,7 +1,7 @@
import {shallow} from 'enzyme'
import React from 'react'
import MeasurementList from 'src/shared/components/MeasurementList'
import MeasurementListFilter from 'src/shared/components/MeasurementListFilter'
import QueryBuilderFilter from 'src/shared/components/QueryBuilderFilter'
import MeasurementListItem from 'src/shared/components/MeasurementListItem'
import {query, source} from 'test/resources'
@ -55,7 +55,7 @@ describe('Shared.Components.MeasurementList', () => {
it('renders <MeasurementListFilter/> to the page', () => {
const {wrapper} = setup()
const filter = wrapper.find(MeasurementListFilter)
const filter = wrapper.find(QueryBuilderFilter)
expect(filter.exists()).toBe(true)
})
@ -69,7 +69,7 @@ describe('Shared.Components.MeasurementList', () => {
const event = {target: {value: 'f'}, stopPropagation: () => {}}
wrapper.setState({filtered: measurements, measurements})
const filter = wrapper.find(MeasurementListFilter)
const filter = wrapper.find(QueryBuilderFilter)
const input = filter.dive().find('input')
input.simulate('change', event)
wrapper.update()