Refactor MeasurementList
parent
5f26de0d96
commit
52fcc8e95f
|
@ -1,6 +1,5 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import _ from 'lodash'
|
||||
|
||||
import {showMeasurements} from 'src/shared/apis/metaQuery'
|
||||
|
@ -8,7 +7,8 @@ import showMeasurementsParser from 'src/shared/parsing/showMeasurements'
|
|||
|
||||
import {Query, Source} from 'src/types'
|
||||
|
||||
import TagList from 'src/data_explorer/components/TagList'
|
||||
import MeasurementListFilter from 'src/shared/components/MeasurementListFilter'
|
||||
import MeasurementListItem from 'src/shared/components/MeasurementListItem'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
|
||||
interface Props {
|
||||
|
@ -23,6 +23,7 @@ interface Props {
|
|||
interface State {
|
||||
measurements: string[]
|
||||
filterText: string
|
||||
filtered: string[]
|
||||
}
|
||||
|
||||
const {shape, string} = PropTypes
|
||||
|
@ -31,6 +32,7 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
filtered: [],
|
||||
measurements: [],
|
||||
filterText: '',
|
||||
}
|
||||
|
@ -38,6 +40,8 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
this.handleEscape = this.handleEscape.bind(this)
|
||||
this.handleFilterText = this.handleFilterText.bind(this)
|
||||
this.handleAcceptReject = this.handleAcceptReject.bind(this)
|
||||
this.handleFilterMeasuremet = this.handleFilterMeasuremet.bind(this)
|
||||
this.handleChoosemeasurement = this.handleChoosemeasurement.bind(this)
|
||||
}
|
||||
|
||||
public static defaultProps: Partial<Props> = {
|
||||
|
@ -57,7 +61,7 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
return
|
||||
}
|
||||
|
||||
this._getMeasurements()
|
||||
this.getMeasurements()
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
|
@ -74,16 +78,24 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
return
|
||||
}
|
||||
|
||||
this._getMeasurements()
|
||||
this.getMeasurements()
|
||||
}
|
||||
|
||||
handleFilterText(e) {
|
||||
e.stopPropagation()
|
||||
const filterText = e.target.value
|
||||
this.setState({
|
||||
filterText: e.target.value,
|
||||
filterText,
|
||||
filtered: this.handleFilterMeasuremet(filterText),
|
||||
})
|
||||
}
|
||||
|
||||
handleFilterMeasuremet(filter) {
|
||||
return this.state.measurements.filter(m =>
|
||||
m.toLowerCase().includes(filter.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
handleEscape(e) {
|
||||
if (e.key !== 'Escape') {
|
||||
return
|
||||
|
@ -99,122 +111,69 @@ class MeasurementList extends PureComponent<Props, State> {
|
|||
this.props.onToggleTagAcceptance()
|
||||
}
|
||||
|
||||
handleChoosemeasurement(measurement) {
|
||||
return () => this.props.onChooseMeasurement(measurement)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {query, querySource, onChooseTag, onGroupByTag} = this.props
|
||||
const {database, areTagsAccepted} = query
|
||||
const {filtered} = this.state
|
||||
|
||||
return (
|
||||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Measurements & Tags</span>
|
||||
{this.props.query.database
|
||||
? <div className="query-builder--filter">
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="Filter"
|
||||
type="text"
|
||||
value={this.state.filterText}
|
||||
onChange={this.handleFilterText}
|
||||
onKeyUp={this.handleEscape}
|
||||
spellCheck={false}
|
||||
autoComplete="false"
|
||||
/>
|
||||
<span className="icon search" />
|
||||
</div>
|
||||
: null}
|
||||
{database &&
|
||||
<MeasurementListFilter
|
||||
onEscape={this.handleEscape}
|
||||
onFilterText={this.handleFilterText}
|
||||
filterText={this.state.filterText}
|
||||
/>}
|
||||
</div>
|
||||
{this.renderList()}
|
||||
{database
|
||||
? <div className="query-builder--list">
|
||||
<FancyScrollbar>
|
||||
{filtered.map(measurement =>
|
||||
<MeasurementListItem
|
||||
query={query}
|
||||
key={measurement}
|
||||
measurement={measurement}
|
||||
querySource={querySource}
|
||||
onChooseTag={onChooseTag}
|
||||
onGroupByTag={onGroupByTag}
|
||||
areTagsAccepted={areTagsAccepted}
|
||||
onAcceptReject={this.handleAcceptReject}
|
||||
isActive={measurement === query.measurement}
|
||||
numTagsActive={Object.keys(query.tags).length}
|
||||
onChooseMeasurement={this.handleChoosemeasurement}
|
||||
/>
|
||||
)}
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
: <div className="query-builder--list-empty">
|
||||
<span>
|
||||
No <strong>Database</strong> selected
|
||||
</span>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderList() {
|
||||
if (!this.props.query.database) {
|
||||
return (
|
||||
<div className="query-builder--list-empty">
|
||||
<span>
|
||||
No <strong>Database</strong> selected
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const filterText = this.state.filterText.toLowerCase()
|
||||
const measurements = this.state.measurements.filter(m =>
|
||||
m.toLowerCase().includes(filterText)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="query-builder--list">
|
||||
<FancyScrollbar>
|
||||
{measurements.map(measurement => {
|
||||
const isActive = measurement === this.props.query.measurement
|
||||
const numTagsActive = Object.keys(this.props.query.tags).length
|
||||
|
||||
return (
|
||||
<div
|
||||
key={measurement}
|
||||
onClick={
|
||||
isActive
|
||||
? () => {}
|
||||
: () => this.props.onChooseMeasurement(measurement)
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={classnames('query-builder--list-item', {
|
||||
active: isActive,
|
||||
})}
|
||||
data-test={`query-builder-list-item-measurement-${measurement}`}
|
||||
>
|
||||
<span>
|
||||
<div className="query-builder--caret icon caret-right" />
|
||||
{measurement}
|
||||
</span>
|
||||
{isActive && numTagsActive >= 1
|
||||
? <div
|
||||
className={classnames('flip-toggle', {
|
||||
flipped: this.props.query.areTagsAccepted,
|
||||
})}
|
||||
onClick={this.handleAcceptReject}
|
||||
>
|
||||
<div className="flip-toggle--container">
|
||||
<div className="flip-toggle--front">!=</div>
|
||||
<div className="flip-toggle--back">=</div>
|
||||
</div>
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
{isActive
|
||||
? <TagList
|
||||
query={this.props.query}
|
||||
querySource={this.props.querySource}
|
||||
onChooseTag={this.props.onChooseTag}
|
||||
onGroupByTag={this.props.onGroupByTag}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</FancyScrollbar>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
_getMeasurements() {
|
||||
async getMeasurements() {
|
||||
const {source} = this.context
|
||||
const {querySource} = this.props
|
||||
const {querySource, query} = this.props
|
||||
|
||||
const proxy =
|
||||
_.get(querySource, ['links', 'proxy'], null) || source.links.proxy
|
||||
const proxy = _.get(querySource, ['links', 'proxy'], source.links.proxy)
|
||||
|
||||
showMeasurements(proxy, this.props.query.database).then(resp => {
|
||||
const {errors, measurementSets} = showMeasurementsParser(resp.data)
|
||||
if (errors.length) {
|
||||
// TODO: display errors in the UI.
|
||||
return console.error('InfluxDB returned error(s): ', errors)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
measurements: measurementSets[0].measurements,
|
||||
})
|
||||
})
|
||||
try {
|
||||
const {data} = await showMeasurements(proxy, query.database)
|
||||
const {measurementSets} = showMeasurementsParser(data)
|
||||
const measurements = measurementSets[0].measurements
|
||||
this.setState({measurements, filtered: measurements})
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React, {SFC} from 'react'
|
||||
|
||||
interface Props {
|
||||
filterText: string
|
||||
onEscape: (e: React.KeyboardEvent<HTMLInputElement>) => void
|
||||
onFilterText: (e: React.InputHTMLAttributes<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const MeasurementListFilter: SFC<Props> = ({
|
||||
onEscape,
|
||||
onFilterText,
|
||||
filterText,
|
||||
}) =>
|
||||
<div className="query-builder--filter">
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="Filter"
|
||||
type="text"
|
||||
value={filterText}
|
||||
onChange={onFilterText}
|
||||
onKeyUp={onEscape}
|
||||
spellCheck={false}
|
||||
autoComplete="false"
|
||||
/>
|
||||
<span className="icon search" />
|
||||
</div>
|
||||
|
||||
export default MeasurementListFilter
|
|
@ -0,0 +1,64 @@
|
|||
import React, {SFC} from 'react'
|
||||
import classnames from 'classnames'
|
||||
import TagList from 'src/data_explorer/components/TagList'
|
||||
|
||||
import {Query, Source} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
query: Query
|
||||
querySource: Source
|
||||
isActive: boolean
|
||||
measurement: string
|
||||
numTagsActive: number
|
||||
areTagsAccepted: boolean
|
||||
onChooseTag: () => void
|
||||
onGroupByTag: () => void
|
||||
onAcceptReject: () => void
|
||||
onChooseMeasurement: (measurement: string) => () => void
|
||||
}
|
||||
|
||||
const noop = () => {}
|
||||
|
||||
const MeasurementListItem: SFC<Props> = ({
|
||||
query,
|
||||
isActive,
|
||||
querySource,
|
||||
measurement,
|
||||
onChooseTag,
|
||||
onGroupByTag,
|
||||
numTagsActive,
|
||||
onAcceptReject,
|
||||
areTagsAccepted,
|
||||
onChooseMeasurement,
|
||||
}) =>
|
||||
<div
|
||||
key={measurement}
|
||||
onClick={isActive ? noop : onChooseMeasurement(measurement)}
|
||||
>
|
||||
<div className={classnames('query-builder--list-item', {active: isActive})}>
|
||||
<span>
|
||||
<div className="query-builder--caret icon caret-right" />
|
||||
{measurement}
|
||||
</span>
|
||||
{isActive &&
|
||||
numTagsActive >= 1 &&
|
||||
<div
|
||||
className={classnames('flip-toggle', {flipped: areTagsAccepted})}
|
||||
onClick={onAcceptReject}
|
||||
>
|
||||
<div className="flip-toggle--container">
|
||||
<div className="flip-toggle--front">!=</div>
|
||||
<div className="flip-toggle--back">=</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
{isActive &&
|
||||
<TagList
|
||||
query={query}
|
||||
querySource={querySource}
|
||||
onChooseTag={onChooseTag}
|
||||
onGroupByTag={onGroupByTag}
|
||||
/>}
|
||||
</div>
|
||||
|
||||
export default MeasurementListItem
|
|
@ -14,7 +14,7 @@ const setup = (override = {}) => {
|
|||
...override,
|
||||
}
|
||||
|
||||
MeasurementList.prototype._getMeasurements = jest.fn(() => Promise.resolve())
|
||||
MeasurementList.prototype.getMeasurements = jest.fn(() => Promise.resolve())
|
||||
|
||||
const wrapper = shallow(<MeasurementList {...props} />, {
|
||||
context: {source},
|
||||
|
@ -41,67 +41,67 @@ describe('Shared.Components.MeasurementList', () => {
|
|||
describe('lifecycle methods', () => {
|
||||
describe('componentDidMount', () => {
|
||||
it('does not fire getMeasurements if there is no database', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance} = setup({query: {...query, database: ''}})
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidMount()
|
||||
expect(_getMeasurements).not.toHaveBeenCalled()
|
||||
expect(getMeasurements).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does fire _getMeasurements if there is a database', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
it('does fire getMeasurements if there is a database', () => {
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance} = setup()
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidMount()
|
||||
expect(_getMeasurements).toHaveBeenCalled()
|
||||
expect(getMeasurements).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('componentDidUpdate', () => {
|
||||
it('does not fire getMeasurements if there is no database', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance, props} = setup({query: {...query, database: ''}})
|
||||
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidUpdate(props)
|
||||
|
||||
expect(_getMeasurements).not.toHaveBeenCalled()
|
||||
expect(getMeasurements).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not fire _getMeasurements if the database does not change and the sources are equal', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
it('does not fire getMeasurements if the database does not change and the sources are equal', () => {
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance, props} = setup()
|
||||
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidUpdate(props)
|
||||
|
||||
expect(_getMeasurements).not.toHaveBeenCalled()
|
||||
expect(getMeasurements).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it fires _getMeasurements if there is a change in database', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
it('it fires getMeasurements if there is a change in database', () => {
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance, props} = setup()
|
||||
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidUpdate({
|
||||
...props,
|
||||
query: {...query, database: 'diffDb'},
|
||||
})
|
||||
|
||||
expect(_getMeasurements).toHaveBeenCalled()
|
||||
expect(getMeasurements).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it calls _getMeasurements if there is a change in source', () => {
|
||||
const _getMeasurements = jest.fn()
|
||||
it('it calls getMeasurements if there is a change in source', () => {
|
||||
const getMeasurements = jest.fn()
|
||||
const {instance, props} = setup()
|
||||
|
||||
instance._getMeasurements = _getMeasurements
|
||||
instance.getMeasurements = getMeasurements
|
||||
instance.componentDidUpdate({
|
||||
...props,
|
||||
querySource: {...source, id: 'newSource'},
|
||||
})
|
||||
|
||||
expect(_getMeasurements).toHaveBeenCalled()
|
||||
expect(getMeasurements).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue