From 6bbed0cadd88b287f9ac4f130d82f086193d56aa Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 May 2018 15:58:09 -0700 Subject: [PATCH] Introduce component for individual filters --- ui/src/logs/components/LogsFilter.tsx | 94 ++++++++++++++++++++++++ ui/src/logs/components/LogsFilterBar.tsx | 47 ++++++++---- ui/src/logs/containers/LogsPage.tsx | 12 ++- ui/src/style/pages/logs-viewer.scss | 49 +++++++++++- 4 files changed, 185 insertions(+), 17 deletions(-) create mode 100644 ui/src/logs/components/LogsFilter.tsx diff --git a/ui/src/logs/components/LogsFilter.tsx b/ui/src/logs/components/LogsFilter.tsx new file mode 100644 index 0000000000..686ad49667 --- /dev/null +++ b/ui/src/logs/components/LogsFilter.tsx @@ -0,0 +1,94 @@ +import React, {PureComponent} from 'react' +import classnames from 'classnames' +import {Filter} from 'src/logs/containers/LogsPage' + +interface Props { + filter: Filter + onDelete: (id: string) => () => void + onToggleStatus: (id: string) => () => void + onToggleOperator: (id: string) => () => void +} + +interface State { + expanded: boolean +} + +class LogsFilter extends PureComponent { + constructor(props: Props) { + super(props) + + this.state = { + expanded: false, + } + } + + public render() { + const { + filter: {id}, + onDelete, + } = this.props + const {expanded} = this.state + + return ( +
  • + {this.label} +
    + {expanded && this.renderTooltip} +
  • + ) + } + + private get label(): JSX.Element { + const { + filter: {key, operator, value}, + } = this.props + + return ( + {`${key} ${operator} ${value}`} + ) + } + + private get className(): string { + const {expanded} = this.state + const { + filter: {enabled}, + } = this.props + + return classnames('logs-viewer--filter', { + active: expanded, + disabled: !enabled, + }) + } + + private handleMouseEnter = (): void => { + this.setState({expanded: true}) + } + + private handleMouseLeave = (): void => { + this.setState({expanded: false}) + } + + private get renderTooltip(): JSX.Element { + const { + filter: {id, enabled, operator}, + onDelete, + onToggleStatus, + onToggleOperator, + } = this.props + + const toggleStatusText = enabled ? 'Disable' : 'Enable' + const toggleOperatorText = operator === '==' ? '!=' : '==' + + return ( + + ) + } +} + +export default LogsFilter diff --git a/ui/src/logs/components/LogsFilterBar.tsx b/ui/src/logs/components/LogsFilterBar.tsx index c3f6aa8e79..64feb89acd 100644 --- a/ui/src/logs/components/LogsFilterBar.tsx +++ b/ui/src/logs/components/LogsFilterBar.tsx @@ -1,5 +1,6 @@ import React, {PureComponent} from 'react' import {Filter} from 'src/logs/containers/LogsPage' +import FilterBlock from 'src/logs/components/LogsFilter' interface Props { numResults: number @@ -25,31 +26,25 @@ class LogsFilters extends PureComponent { const {filters} = this.props return filters.map(filter => ( -
  • - - {filter.key} - {filter.operator} - {filter.value} - -
  • + )) } private handleDeleteFilter = (id: string) => (): void => { const {filters, onUpdateFilters} = this.props - const filteredFilters = filters.map( - filter => (filter.id === id ? null : filter) - ) + const filteredFilters = filters.filter(filter => filter.id !== id) onUpdateFilters(filteredFilters) } - private handleToggleFilter = (id: string) => (): void => { + private handleToggleFilterStatus = (id: string) => (): void => { const {filters, onUpdateFilters} = this.props const filteredFilters = filters.map(filter => { @@ -62,6 +57,28 @@ class LogsFilters extends PureComponent { onUpdateFilters(filteredFilters) } + + private handleToggleFilterOperator = (id: string) => (): void => { + const {filters, onUpdateFilters} = this.props + + const filteredFilters = filters.map(filter => { + if (filter.id === id) { + return {...filter, operator: this.toggleOperator(filter.operator)} + } + + return filter + }) + + onUpdateFilters(filteredFilters) + } + + private toggleOperator = (op: string): string => { + if (op === '==') { + return '!=' + } + + return '==' + } } export default LogsFilters diff --git a/ui/src/logs/containers/LogsPage.tsx b/ui/src/logs/containers/LogsPage.tsx index db4eff76eb..e4f6c389da 100644 --- a/ui/src/logs/containers/LogsPage.tsx +++ b/ui/src/logs/containers/LogsPage.tsx @@ -34,13 +34,23 @@ interface State { filters: Filter[] } +const DUMMY_FILTERS = [ + { + id: '0', + key: 'host', + value: 'prod1-rsavage.local', + operator: '==', + enabled: true, + }, +] + class LogsPage extends PureComponent { constructor(props: Props) { super(props) this.state = { searchString: '', - filters: [], + filters: DUMMY_FILTERS, } } diff --git a/ui/src/style/pages/logs-viewer.scss b/ui/src/style/pages/logs-viewer.scss index dab491dbe8..152741f046 100644 --- a/ui/src/style/pages/logs-viewer.scss +++ b/ui/src/style/pages/logs-viewer.scss @@ -94,6 +94,7 @@ $logs-viewer-gutter: 60px; } .logs-viewer--filter { + position: relative; font-size: 12px; display: flex; align-items: center; @@ -103,8 +104,20 @@ $logs-viewer-gutter: 60px; border-radius: 4px; background-color: $g5-pepper; color: $g13-mist; - font-weight: 500; + font-weight: 600; margin: 2px; + + &.disabled { + background-color: $g4-onyx; + color: $g9-mountain; + font-style: italic; + } + + &.active { + background-color: $g6-smoke; + color: $g15-platinum; + } + } .logs-viewer--filter-remove { @@ -144,4 +157,38 @@ $logs-viewer-gutter: 60px; background-color: $c-dreamsicle; } } +} + +.logs-viewer--filter-tooltip { + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: stretch; + border-radius: $radius; + z-index: 9999; + @extend %drop-shadow; + background-color: $g4-onyx; + margin: 0; + padding: 0; + list-style: none; + overflow: hidden; + + > li { + height: 26px; + line-height: 26px; + padding: 0 8px; + font-size: 13px; + font-weight: 600; + color: $g11-sidewalk; + transition: background-color 0.25s ease, color 0.25s ease; + + &:hover { + cursor: pointer; + background-color: $g5-pepper; + color: $g18-cloud; + } + } } \ No newline at end of file