Merge pull request #4317 from influxdata/feature/filter-protoboards-by-name
Add ability to filter protoboards by name in dashboard steppull/4318/head
commit
3e620a429c
|
@ -1,56 +0,0 @@
|
||||||
import React, {Component} from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
|
||||||
|
|
||||||
@ErrorHandling
|
|
||||||
class SearchBar extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
searchTerm: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
this.handleSearch = _.debounce(this.handleSearch, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearch = () => {
|
|
||||||
this.props.onSearch(this.state.searchTerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = () => {
|
|
||||||
this.setState({searchTerm: this.refs.searchInput.value}, this.handleSearch)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {placeholder, width} = this.props
|
|
||||||
return (
|
|
||||||
<div className="search-widget" style={{width: `${width}px`}}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control input-sm"
|
|
||||||
placeholder={placeholder}
|
|
||||||
ref="searchInput"
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
<span className="icon search" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {func, number, string} = PropTypes
|
|
||||||
|
|
||||||
SearchBar.defaultProps = {
|
|
||||||
width: 260,
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchBar.propTypes = {
|
|
||||||
width: number,
|
|
||||||
onSearch: func.isRequired,
|
|
||||||
placeholder: string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SearchBar
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Libraries
|
||||||
|
import React, {PureComponent, ChangeEvent} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// Decorators
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
width?: number
|
||||||
|
placeholder: string
|
||||||
|
onSearch: (searchTerm: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
searchTerm: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@ErrorHandling
|
||||||
|
class SearchBar extends PureComponent<Props, State> {
|
||||||
|
public static defaultProps: Partial<Props> = {
|
||||||
|
width: 260,
|
||||||
|
}
|
||||||
|
|
||||||
|
public debouncedHandleSearch: () => void
|
||||||
|
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
searchTerm: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillMount() {
|
||||||
|
this.debouncedHandleSearch = _.debounce(this.handleSearch, 50)
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const {placeholder, width} = this.props
|
||||||
|
return (
|
||||||
|
<div className="search-widget" style={{width: `${width}px`}}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control input-sm"
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
/>
|
||||||
|
<span className="icon search" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearch = () => {
|
||||||
|
this.props.onSearch(this.state.searchTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({searchTerm: e.target.value}, this.debouncedHandleSearch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBar
|
|
@ -2,4 +2,14 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-step--filter-controls {
|
||||||
|
margin-bottom: $ix-marg-c;
|
||||||
|
padding: 0 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,16 +4,23 @@ import React, {Component} from 'react'
|
||||||
// APIs
|
// APIs
|
||||||
import {getProtoBoards} from 'src/sources/apis'
|
import {getProtoBoards} from 'src/sources/apis'
|
||||||
|
|
||||||
// Components
|
// Decorators
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
import {isSearchMatch} from 'src/utils/searchMatch'
|
||||||
|
|
||||||
|
// Components
|
||||||
import GridSizer from 'src/reusable_ui/components/grid_sizer/GridSizer'
|
import GridSizer from 'src/reusable_ui/components/grid_sizer/GridSizer'
|
||||||
import CardSelectCard from 'src/reusable_ui/components/card_select/CardSelectCard'
|
import CardSelectCard from 'src/reusable_ui/components/card_select/CardSelectCard'
|
||||||
|
import SearchBar from 'src/hosts/components/SearchBar'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import {Protoboard} from 'src/types'
|
import {Protoboard} from 'src/types'
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
selected: object
|
selected: object
|
||||||
|
searchTerm: string
|
||||||
protoboards: Protoboard[]
|
protoboards: Protoboard[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +31,7 @@ class DashboardStep extends Component<{}, State> {
|
||||||
this.state = {
|
this.state = {
|
||||||
selected: {},
|
selected: {},
|
||||||
protoboards: [],
|
protoboards: [],
|
||||||
|
searchTerm: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +45,12 @@ class DashboardStep extends Component<{}, State> {
|
||||||
if (protoboards && protoboards.length) {
|
if (protoboards && protoboards.length) {
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-step">
|
<div className="dashboard-step">
|
||||||
|
<div className="dashboard-step--filter-controls">
|
||||||
|
<SearchBar
|
||||||
|
placeholder="Filter by name..."
|
||||||
|
onSearch={this.setSearchTerm}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<GridSizer>{this.dashboardCards}</GridSizer>
|
<GridSizer>{this.dashboardCards}</GridSizer>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -44,10 +58,16 @@ class DashboardStep extends Component<{}, State> {
|
||||||
return <div />
|
return <div />
|
||||||
}
|
}
|
||||||
|
|
||||||
private get dashboardCards() {
|
private setSearchTerm = searchTerm => {
|
||||||
const {selected, protoboards} = this.state
|
this.setState({searchTerm})
|
||||||
|
}
|
||||||
|
|
||||||
return protoboards.map((protoboard, i) => {
|
private get dashboardCards() {
|
||||||
|
const {selected, protoboards, searchTerm} = this.state
|
||||||
|
const filteredProtoboards = protoboards.filter(pb =>
|
||||||
|
isSearchMatch(pb.meta.name, searchTerm)
|
||||||
|
)
|
||||||
|
return filteredProtoboards.map((protoboard, i) => {
|
||||||
const {meta} = protoboard
|
const {meta} = protoboard
|
||||||
return (
|
return (
|
||||||
<CardSelectCard
|
<CardSelectCard
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export const isSearchMatch = (input: string, searchTerm: string): boolean =>
|
||||||
|
input.toLowerCase().includes(searchTerm.toLowerCase())
|
Loading…
Reference in New Issue