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