Allow tags to be filtered with regex

pull/10616/head
Brandon Farmer 2018-06-06 15:10:58 -07:00
parent f8e7cfa92e
commit bae5b110a4
6 changed files with 60 additions and 80 deletions

View File

@ -51,7 +51,7 @@ export enum ActionTypes {
SetSearchTerm = 'LOGS_SET_SEARCH_TERM', SetSearchTerm = 'LOGS_SET_SEARCH_TERM',
AddFilter = 'LOGS_ADD_FILTER', AddFilter = 'LOGS_ADD_FILTER',
RemoveFilter = 'LOGS_REMOVE_FILTER', RemoveFilter = 'LOGS_REMOVE_FILTER',
SetFilterOperator = 'LOGS_SET_FILTER_OPERATOR', ChangeFilter = 'LOGS_CHANGE_FILTER',
} }
export interface AddFilterAction { export interface AddFilterAction {
@ -61,11 +61,12 @@ export interface AddFilterAction {
} }
} }
export interface SetFilterOperatorAction { export interface ChangeFilterAction {
type: ActionTypes.SetFilterOperator type: ActionTypes.ChangeFilter
payload: { payload: {
id: string id: string
operator: string operator: string
value: string
} }
} }
@ -159,7 +160,7 @@ export type Action =
| SetSearchTerm | SetSearchTerm
| AddFilterAction | AddFilterAction
| RemoveFilterAction | RemoveFilterAction
| SetFilterOperatorAction | ChangeFilterAction
const getTimeRange = (state: State): TimeRange | null => const getTimeRange = (state: State): TimeRange | null =>
getDeep<TimeRange | null>(state, 'logs.timeRange', null) getDeep<TimeRange | null>(state, 'logs.timeRange', null)
@ -182,9 +183,9 @@ const getSearchTerm = (state: State): string | null =>
const getFilters = (state: State): Filter[] => const getFilters = (state: State): Filter[] =>
getDeep<Filter[]>(state, 'logs.filters', []) getDeep<Filter[]>(state, 'logs.filters', [])
export const setFilterOperator = (id: string, operator: string) => ({ export const changeFilter = (id: string, operator: string, value: string) => ({
type: ActionTypes.SetFilterOperator, type: ActionTypes.ChangeFilter,
payload: {id, operator}, payload: {id, operator, value},
}) })
export const setSource = (source: Source): SetSourceAction => ({ export const setSource = (source: Source): SetSourceAction => ({

View File

@ -7,12 +7,13 @@ import {ClickOutside} from 'src/shared/components/ClickOutside'
interface Props { interface Props {
filter: Filter filter: Filter
onDelete: (id: string) => void onDelete: (id: string) => void
onChangeOperator: (id: string, newOperator: string) => void onChangeFilter: (id: string, newOperator: string, newValue: string) => void
onChangeValue: (id: string, newValue: string) => void
} }
interface State { interface State {
editing: boolean editing: boolean
value: string
operator: string
} }
class LogsFilter extends PureComponent<Props, State> { class LogsFilter extends PureComponent<Props, State> {
@ -21,14 +22,12 @@ class LogsFilter extends PureComponent<Props, State> {
this.state = { this.state = {
editing: false, editing: false,
value: this.props.filter.value,
operator: this.props.filter.operator,
} }
} }
public render() { public render() {
const {
filter: {id},
onDelete,
} = this.props
const {editing} = this.state const {editing} = this.state
return ( return (
@ -45,7 +44,7 @@ class LogsFilter extends PureComponent<Props, State> {
} }
private handleClickOutside = (): void => { private handleClickOutside = (): void => {
this.setState({editing: false}) this.stopEditing()
} }
private handleStartEdit = (): void => { private handleStartEdit = (): void => {
@ -76,8 +75,9 @@ class LogsFilter extends PureComponent<Props, State> {
} }
private get renderEditor(): JSX.Element { private get renderEditor(): JSX.Element {
const {operator, value} = this.state
const { const {
filter: {key, operator, value}, filter: {key},
} = this.props } = this.props
return ( return (
@ -106,56 +106,36 @@ class LogsFilter extends PureComponent<Props, State> {
} }
private handleOperatorInput = (e: ChangeEvent<HTMLInputElement>): void => { private handleOperatorInput = (e: ChangeEvent<HTMLInputElement>): void => {
const { const operator = getDeep(e, 'target.value', '').trim()
filter: {id},
onChangeOperator,
} = this.props
const cleanValue = this.enforceOperatorChars(e.target.value) this.setState({operator})
onChangeOperator(id, cleanValue)
} }
private handleValueInput = (e: ChangeEvent<HTMLInputElement>): void => { private handleValueInput = (e: ChangeEvent<HTMLInputElement>): void => {
const { const value = getDeep(e, 'target.value', '').trim()
filter: {id}, this.setState({value})
onChangeValue,
} = this.props
onChangeValue(id, e.target.value)
}
private enforceOperatorChars = text => {
return text
.split('')
.filter(t => ['!', '~', `=`].includes(t))
.join('')
} }
private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => { private handleKeyDown = (e: KeyboardEvent<HTMLInputElement>): void => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
e.preventDefault() e.preventDefault()
this.setState({editing: false}) this.stopEditing()
} }
} }
private handleToggleOperator = () => { private stopEditing(): void {
const id = getDeep(this.props, 'filter.id', '') const id = getDeep(this.props, 'filter.id', '')
const {operator, value} = this.state
let nextOperator = '==' let state = {}
if (this.operator === '==') { if (['!=', '==', '=~'].includes(operator) && value !== '') {
nextOperator = '!=' this.props.onChangeFilter(id, operator, value)
} else {
const {filter} = this.props
state = {operator: filter.operator, value: filter.value}
} }
this.props.onChangeOperator(id, nextOperator) this.setState({...state, editing: false})
}
private get toggleOperatorText(): string {
return this.operator === '==' ? '!=' : '=='
}
private get operator(): string {
return getDeep(this.props, 'filter.operator', '')
} }
} }

View File

@ -6,7 +6,7 @@ interface Props {
numResults: number numResults: number
filters: Filter[] filters: Filter[]
onDelete: (id: string) => void onDelete: (id: string) => void
onFilterOperatorChange: (id: string, operator: string) => void onFilterChange: (id: string, operator: string, value: string) => void
} }
class LogsFilters extends PureComponent<Props> { class LogsFilters extends PureComponent<Props> {
@ -31,22 +31,10 @@ class LogsFilters extends PureComponent<Props> {
key={filter.id} key={filter.id}
filter={filter} filter={filter}
onDelete={this.props.onDelete} onDelete={this.props.onDelete}
onChangeOperator={this.props.onFilterOperatorChange} onChangeFilter={this.props.onFilterChange}
onChangeValue={this.handleChangeFilterValue}
/> />
)) ))
} }
private handleChangeFilterValue = (id: string, value: string): void => {
// const {filters, onUpdateFilters} = this.props
// const filteredFilters = filters.map(filter => {
// if (filter.id === id) {
// return {...filter, value}
// }
// return filter
// })
// onUpdateFilters(filteredFilters)
}
} }
export default LogsFilters export default LogsFilters

View File

@ -10,7 +10,7 @@ import {
setSearchTermAsync, setSearchTermAsync,
addFilter, addFilter,
removeFilter, removeFilter,
setFilterOperator, changeFilter,
} from 'src/logs/actions' } from 'src/logs/actions'
import {getSourcesAsync} from 'src/shared/actions/sources' import {getSourcesAsync} from 'src/shared/actions/sources'
import LogViewerHeader from 'src/logs/components/LogViewerHeader' import LogViewerHeader from 'src/logs/components/LogViewerHeader'
@ -38,7 +38,7 @@ interface Props {
setSearchTermAsync: (searchTerm: string) => void setSearchTermAsync: (searchTerm: string) => void
addFilter: (filter: Filter) => void addFilter: (filter: Filter) => void
removeFilter: (id: string) => void removeFilter: (id: string) => void
setFilterOperator: (id: string, operator: string) => void changeFilter: (id: string, operator: string, value: string) => void
timeRange: TimeRange timeRange: TimeRange
histogramData: object[] histogramData: object[]
tableData: { tableData: {
@ -105,7 +105,7 @@ class LogsPage extends PureComponent<Props, State> {
numResults={count} numResults={count}
filters={filters || []} filters={filters || []}
onDelete={this.handleFilterDelete} onDelete={this.handleFilterDelete}
onFilterOperatorChange={this.handleFilterOperatorChange} onFilterChange={this.handleFilterChange}
/> />
<LogsTable <LogsTable
data={this.props.tableData} data={this.props.tableData}
@ -147,7 +147,6 @@ class LogsPage extends PureComponent<Props, State> {
id: uuid.v4(), id: uuid.v4(),
key: selection.key, key: selection.key,
value: selection.tag, value: selection.tag,
enabled: true,
operator: '==', operator: '==',
}) })
this.props.executeQueriesAsync() this.props.executeQueriesAsync()
@ -215,8 +214,12 @@ class LogsPage extends PureComponent<Props, State> {
this.props.executeQueriesAsync() this.props.executeQueriesAsync()
} }
private handleFilterOperatorChange = (id: string, operator: string) => { private handleFilterChange = (
this.props.setFilterOperator(id, operator) id: string,
operator: string,
value: string
) => {
this.props.changeFilter(id, operator, value)
this.props.executeQueriesAsync() this.props.executeQueriesAsync()
} }
@ -274,7 +277,7 @@ const mapDispatchToProps = {
setSearchTermAsync, setSearchTermAsync,
addFilter, addFilter,
removeFilter, removeFilter,
setFilterOperator, changeFilter,
} }
export default connect(mapStateToProps, mapDispatchToProps)(LogsPage) export default connect(mapStateToProps, mapDispatchToProps)(LogsPage)

View File

@ -4,7 +4,7 @@ import {
Action, Action,
RemoveFilterAction, RemoveFilterAction,
AddFilterAction, AddFilterAction,
SetFilterOperatorAction, ChangeFilterAction,
} from 'src/logs/actions' } from 'src/logs/actions'
import {LogsState} from 'src/types/logs' import {LogsState} from 'src/types/logs'
@ -40,15 +40,15 @@ const addFilter = (state: LogsState, action: AddFilterAction): LogsState => {
return {...state, filters: [..._.get(state, 'filters', []), filter]} return {...state, filters: [..._.get(state, 'filters', []), filter]}
} }
const setFilterOperator = ( const changeFilter = (
state: LogsState, state: LogsState,
action: SetFilterOperatorAction action: ChangeFilterAction
): LogsState => { ): LogsState => {
const {id, operator} = action.payload const {id, operator, value} = action.payload
const mappedFilters = _.map(_.get(state, 'filters', []), f => { const mappedFilters = _.map(_.get(state, 'filters', []), f => {
if (f.id === id) { if (f.id === id) {
return {...f, operator} return {...f, operator, value}
} }
return f return f
}) })
@ -84,8 +84,8 @@ export default (state: LogsState = defaultState, action: Action) => {
return addFilter(state, action) return addFilter(state, action)
case ActionTypes.RemoveFilter: case ActionTypes.RemoveFilter:
return removeFilter(state, action) return removeFilter(state, action)
case ActionTypes.SetFilterOperator: case ActionTypes.ChangeFilter:
return setFilterOperator(state, action) return changeFilter(state, action)
default: default:
return state return state
} }

View File

@ -99,13 +99,21 @@ const operatorMapping = (operator: string): string => {
} }
} }
const valueMapping = (operator: string, value): string => {
if (operator === '=~') {
return `${new RegExp(value)}`
} else {
return `'${value}'`
}
}
export const filtersClause = (filters: Filter[]): string => { export const filtersClause = (filters: Filter[]): string => {
return _.map( return _.map(
filters, filters,
(filter: Filter) => (filter: Filter) =>
`"${keyMapping(filter.key)}" ${operatorMapping(filter.operator)} '${ `"${keyMapping(filter.key)}" ${operatorMapping(
filter.value filter.operator
}'` )} ${valueMapping(filter.operator, filter.value)}`
).join(' AND ') ).join(' AND ')
} }