Split Search Bar into its own component and type-ify AlertsTable
parent
1bcff7c1db
commit
bfbc9b654f
|
@ -1,34 +1,95 @@
|
||||||
import React, {Component} from 'react'
|
import React, {PureComponent} from 'react'
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import {Link} from 'react-router'
|
import {Link} from 'react-router'
|
||||||
import uuid from 'uuid'
|
import uuid from 'uuid'
|
||||||
|
|
||||||
import InfiniteScroll from 'shared/components/InfiniteScroll'
|
import InfiniteScroll from 'src/shared/components/InfiniteScroll'
|
||||||
|
import SearchBar from 'src/alerts/components/SearchBar'
|
||||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
import {ALERTS_TABLE} from 'src/alerts/constants/tableSizing'
|
import {ALERTS_TABLE} from 'src/alerts/constants/tableSizing'
|
||||||
|
import {Alert} from 'src/types/alerts'
|
||||||
|
import {Source} from 'src/types'
|
||||||
|
|
||||||
|
enum Direction {
|
||||||
|
ASC = 'asc',
|
||||||
|
DESC = 'desc',
|
||||||
|
NONE = 'none',
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
alerts: Alert[]
|
||||||
|
source: Source
|
||||||
|
shouldNotBeFilterable: boolean
|
||||||
|
limit: number
|
||||||
|
isAlertsMaxedOut: boolean
|
||||||
|
alertsCount: number
|
||||||
|
onGetMoreAlerts: () => void
|
||||||
|
}
|
||||||
|
interface State {
|
||||||
|
searchTerm: string
|
||||||
|
filteredAlerts: Alert[]
|
||||||
|
sortDirection: Direction
|
||||||
|
sortKey: string
|
||||||
|
}
|
||||||
|
|
||||||
@ErrorHandling
|
@ErrorHandling
|
||||||
class AlertsTable extends Component {
|
class AlertsTable extends PureComponent<Props, State> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
filteredAlerts: this.props.alerts,
|
filteredAlerts: this.props.alerts,
|
||||||
sortDirection: null,
|
sortDirection: Direction.NONE,
|
||||||
sortKey: null,
|
sortKey: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(newProps) {
|
public componentWillReceiveProps(newProps) {
|
||||||
this.filterAlerts(this.state.searchTerm, newProps.alerts)
|
this.filterAlerts(this.state.searchTerm, newProps.alerts)
|
||||||
}
|
}
|
||||||
|
|
||||||
filterAlerts = (searchTerm, newAlerts) => {
|
public render() {
|
||||||
|
const {
|
||||||
|
shouldNotBeFilterable,
|
||||||
|
limit,
|
||||||
|
onGetMoreAlerts,
|
||||||
|
isAlertsMaxedOut,
|
||||||
|
alertsCount,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
return shouldNotBeFilterable ? (
|
||||||
|
<div className="alerts-widget">
|
||||||
|
{this.renderTable()}
|
||||||
|
{limit && alertsCount ? (
|
||||||
|
<button
|
||||||
|
className="btn btn-sm btn-default btn-block"
|
||||||
|
onClick={onGetMoreAlerts}
|
||||||
|
disabled={isAlertsMaxedOut}
|
||||||
|
style={{marginBottom: '20px'}}
|
||||||
|
>
|
||||||
|
{isAlertsMaxedOut
|
||||||
|
? `All ${alertsCount} Alerts displayed`
|
||||||
|
: 'Load next 30 Alerts'}
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="panel">
|
||||||
|
<div className="panel-heading">
|
||||||
|
<h2 className="panel-title">{this.props.alerts.length} Alerts</h2>
|
||||||
|
{this.props.alerts.length ? (
|
||||||
|
<SearchBar onSearch={this.filterAlerts} />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="panel-body">{this.renderTable()}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterAlerts = (searchTerm: string, newAlerts?: Alert[]): void => {
|
||||||
const alerts = newAlerts || this.props.alerts
|
const alerts = newAlerts || this.props.alerts
|
||||||
const filterText = searchTerm.toLowerCase()
|
const filterText = searchTerm.toLowerCase()
|
||||||
const filteredAlerts = alerts.filter(({name, host, level}) => {
|
const filteredAlerts = alerts.filter(({name, host, level}) => {
|
||||||
|
@ -41,20 +102,22 @@ class AlertsTable extends Component {
|
||||||
this.setState({searchTerm, filteredAlerts})
|
this.setState({searchTerm, filteredAlerts})
|
||||||
}
|
}
|
||||||
|
|
||||||
changeSort = key => () => {
|
private changeSort = (key: string): (() => void) => (): void => {
|
||||||
// if we're using the key, reverse order; otherwise, set it with ascending
|
// if we're using the key, reverse order; otherwise, set it with ascending
|
||||||
if (this.state.sortKey === key) {
|
if (this.state.sortKey === key) {
|
||||||
const reverseDirection =
|
const reverseDirection: Direction =
|
||||||
this.state.sortDirection === 'asc' ? 'desc' : 'asc'
|
this.state.sortDirection === Direction.ASC
|
||||||
|
? Direction.DESC
|
||||||
|
: Direction.ASC
|
||||||
this.setState({sortDirection: reverseDirection})
|
this.setState({sortDirection: reverseDirection})
|
||||||
} else {
|
} else {
|
||||||
this.setState({sortKey: key, sortDirection: 'asc'})
|
this.setState({sortKey: key, sortDirection: Direction.ASC})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sortableClasses = key => {
|
private sortableClasses = (key: string): string => {
|
||||||
if (this.state.sortKey === key) {
|
if (this.state.sortKey === key) {
|
||||||
if (this.state.sortDirection === 'asc') {
|
if (this.state.sortDirection === Direction.ASC) {
|
||||||
return 'alert-history-table--th sortable-header sorting-ascending'
|
return 'alert-history-table--th sortable-header sorting-ascending'
|
||||||
}
|
}
|
||||||
return 'alert-history-table--th sortable-header sorting-descending'
|
return 'alert-history-table--th sortable-header sorting-descending'
|
||||||
|
@ -62,18 +125,22 @@ class AlertsTable extends Component {
|
||||||
return 'alert-history-table--th sortable-header'
|
return 'alert-history-table--th sortable-header'
|
||||||
}
|
}
|
||||||
|
|
||||||
sort = (alerts, key, direction) => {
|
private sort = (
|
||||||
|
alerts: Alert[],
|
||||||
|
key: string,
|
||||||
|
direction: Direction
|
||||||
|
): Alert[] => {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'asc':
|
case Direction.ASC:
|
||||||
return _.sortBy(alerts, e => e[key])
|
return _.sortBy<Alert>(alerts, e => e[key])
|
||||||
case 'desc':
|
case Direction.DESC:
|
||||||
return _.sortBy(alerts, e => e[key]).reverse()
|
return _.sortBy<Alert>(alerts, e => e[key]).reverse()
|
||||||
default:
|
default:
|
||||||
return alerts
|
return alerts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTable() {
|
private renderTable(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
source: {id},
|
source: {id},
|
||||||
} = this.props
|
} = this.props
|
||||||
|
@ -176,7 +243,7 @@ class AlertsTable extends Component {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTableEmpty() {
|
private renderTableEmpty(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
source: {id},
|
source: {id},
|
||||||
shouldNotBeFilterable,
|
shouldNotBeFilterable,
|
||||||
|
@ -206,110 +273,6 @@ class AlertsTable extends Component {
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
shouldNotBeFilterable,
|
|
||||||
limit,
|
|
||||||
onGetMoreAlerts,
|
|
||||||
isAlertsMaxedOut,
|
|
||||||
alertsCount,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
return shouldNotBeFilterable ? (
|
|
||||||
<div className="alerts-widget">
|
|
||||||
{this.renderTable()}
|
|
||||||
{limit && alertsCount ? (
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-default btn-block"
|
|
||||||
onClick={onGetMoreAlerts}
|
|
||||||
disabled={isAlertsMaxedOut}
|
|
||||||
style={{marginBottom: '20px'}}
|
|
||||||
>
|
|
||||||
{isAlertsMaxedOut
|
|
||||||
? `All ${alertsCount} Alerts displayed`
|
|
||||||
: 'Load next 30 Alerts'}
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="panel">
|
|
||||||
<div className="panel-heading">
|
|
||||||
<h2 className="panel-title">{this.props.alerts.length} Alerts</h2>
|
|
||||||
{this.props.alerts.length ? (
|
|
||||||
<SearchBar onSearch={this.filterAlerts} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div className="panel-body">{this.renderTable()}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ErrorHandling
|
|
||||||
class SearchBar extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
searchTerm: '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
|
||||||
const waitPeriod = 300
|
|
||||||
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearch = () => {
|
|
||||||
this.props.onSearch(this.state.searchTerm)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = e => {
|
|
||||||
this.setState({searchTerm: e.target.value}, this.handleSearch)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="search-widget" style={{width: '260px'}}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="form-control input-sm"
|
|
||||||
placeholder="Filter Alerts..."
|
|
||||||
onChange={this.handleChange}
|
|
||||||
value={this.state.searchTerm}
|
|
||||||
/>
|
|
||||||
<span className="icon search" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
|
||||||
|
|
||||||
AlertsTable.propTypes = {
|
|
||||||
alerts: arrayOf(
|
|
||||||
shape({
|
|
||||||
name: string,
|
|
||||||
time: string,
|
|
||||||
value: string,
|
|
||||||
host: string,
|
|
||||||
level: string,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
source: shape({
|
|
||||||
id: string.isRequired,
|
|
||||||
name: string.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
shouldNotBeFilterable: bool,
|
|
||||||
limit: number,
|
|
||||||
onGetMoreAlerts: func,
|
|
||||||
isAlertsMaxedOut: bool,
|
|
||||||
alertsCount: number,
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchBar.propTypes = {
|
|
||||||
onSearch: func.isRequired,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AlertsTable
|
export default AlertsTable
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import React, {PureComponent, ChangeEvent} from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSearch: (s: string) => void
|
||||||
|
}
|
||||||
|
interface State {
|
||||||
|
searchTerm: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@ErrorHandling
|
||||||
|
class SearchBar extends PureComponent<Props, State> {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
searchTerm: '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentWillMount() {
|
||||||
|
const waitPeriod = 300
|
||||||
|
this.handleSearch = _.debounce(this.handleSearch, waitPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return (
|
||||||
|
<div className="search-widget" style={{width: '260px'}}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control input-sm"
|
||||||
|
placeholder="Filter Alerts..."
|
||||||
|
onChange={this.handleChange}
|
||||||
|
value={this.state.searchTerm}
|
||||||
|
/>
|
||||||
|
<span className="icon search" />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearch = () => {
|
||||||
|
this.props.onSearch(this.state.searchTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
this.setState({searchTerm: e.target.value}, this.handleSearch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBar
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface Alert {
|
||||||
|
name: string
|
||||||
|
time: string
|
||||||
|
value: string
|
||||||
|
host: string
|
||||||
|
level: string
|
||||||
|
}
|
Loading…
Reference in New Issue