feat(logs/overlay): Add log options overlay (#969)

feat(logs/overlay): Add log options overlay

Adds the log options overlay components in preparation for showing log
config for table columns.
pull/10616/head
Delmer 2018-10-10 15:32:26 -04:00 committed by GitHub
parent 610faf18e7
commit 4a9f0959b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1181 additions and 174 deletions

View File

@ -0,0 +1,217 @@
import React, {Component, ChangeEvent} from 'react'
import {findDOMNode} from 'react-dom'
import {
DragSourceSpec,
DropTargetConnector,
DragSourceMonitor,
DragSource,
DropTarget,
DragSourceConnector,
ConnectDragSource,
ConnectDropTarget,
ConnectDragPreview,
} from 'react-dnd'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {LogsTableColumn} from 'src/types/logs'
const columnType = 'column'
interface Props {
internalName: string
displayName: string
visible: boolean
index: number
id: string
key: string
onUpdateColumn: (column: LogsTableColumn) => void
isDragging?: boolean
connectDragSource?: ConnectDragSource
connectDropTarget?: ConnectDropTarget
connectDragPreview?: ConnectDragPreview
onMoveColumn: (dragIndex: number, hoverIndex: number) => void
}
const columnSource: DragSourceSpec<Props> = {
beginDrag(props) {
return {
id: props.id,
index: props.index,
}
},
}
const columnTarget = {
hover(props, monitor, component) {
const dragIndex = monitor.getItem().index
const hoverIndex = props.index
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return
}
// Determine rectangle on screen
const domNode = findDOMNode(component) as Element
const hoverBoundingRect = domNode.getBoundingClientRect()
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
// Determine mouse position
const clientOffset = monitor.getClientOffset()
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return
}
// Time to actually perform the action
props.onMoveColumn(dragIndex, hoverIndex)
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
monitor.getItem().index = hoverIndex
},
}
function ColumnDropTarget(dropColumnType, dropColumnTarget, dropHandler) {
return target =>
DropTarget(dropColumnType, dropColumnTarget, dropHandler)(target) as any
}
function ColumnDragSource(dragColumnType, dragColumnSource, dragHandler) {
return target =>
DragSource(dragColumnType, dragColumnSource, dragHandler)(target) as any
}
@ErrorHandling
@ColumnDropTarget(columnType, columnTarget, (connect: DropTargetConnector) => ({
connectDropTarget: connect.dropTarget(),
}))
@ColumnDragSource(
columnType,
columnSource,
(connect: DragSourceConnector, monitor: DragSourceMonitor) => ({
connectDragSource: connect.dragSource(),
connectDragPreview: connect.dragPreview(),
isDragging: monitor.isDragging(),
})
)
export default class DraggableColumn extends Component<Props> {
constructor(props) {
super(props)
this.handleColumnRename = this.handleColumnRename.bind(this)
this.handleToggleVisible = this.handleToggleVisible.bind(this)
}
public render(): JSX.Element | null {
const {
internalName,
displayName,
connectDragPreview,
connectDropTarget,
visible,
} = this.props
return connectDragPreview(
connectDropTarget(
<div className={this.columnClassName}>
<div className={this.labelClassName}>
{this.dragHandle}
{this.visibilityToggle}
<div className="customizable-field--name">{internalName}</div>
</div>
<input
className="form-control input-sm customizable-field--input"
type="text"
spellCheck={false}
id="internalName"
value={displayName}
onChange={this.handleColumnRename}
placeholder={`Rename ${internalName}`}
disabled={!visible}
/>
</div>
)
)
}
private get dragHandle(): JSX.Element {
const {connectDragSource} = this.props
return connectDragSource(
<div className="customizable-field--drag">
<span className="hamburger" />
</div>
)
}
private get visibilityToggle(): JSX.Element {
const {visible, internalName} = this.props
if (visible) {
return (
<div
className="customizable-field--visibility"
onClick={this.handleToggleVisible}
title={`Click to HIDE ${internalName}`}
>
<span className="icon eye-open" />
</div>
)
}
return (
<div
className="customizable-field--visibility"
onClick={this.handleToggleVisible}
title={`Click to SHOW ${internalName}`}
>
<span className="icon eye-closed" />
</div>
)
}
private get labelClassName(): string {
const {visible} = this.props
if (visible) {
return 'customizable-field--label'
}
return 'customizable-field--label__hidden'
}
private get columnClassName(): string {
const {isDragging} = this.props
if (isDragging) {
return 'customizable-field dragging'
}
return 'customizable-field'
}
private handleColumnRename = (e: ChangeEvent<HTMLInputElement>): void => {
const {onUpdateColumn, internalName, visible} = this.props
onUpdateColumn({internalName, displayName: e.target.value, visible})
}
private handleToggleVisible = (): void => {
const {onUpdateColumn, internalName, displayName, visible} = this.props
onUpdateColumn({internalName, displayName, visible: !visible})
}
}

View File

@ -0,0 +1,48 @@
import React, {Component} from 'react'
import {DragDropContext} from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'
import DraggableColumn from 'src/logs/components/draggable_column/DraggableColumn'
import {LogsTableColumn} from 'src/types/logs'
interface Props {
columns: LogsTableColumn[]
onMoveColumn: (dragIndex: number, hoverIndex: number) => void
onUpdateColumn: (column: LogsTableColumn) => void
}
class ColumnsOptions extends Component<Props> {
public render() {
const {columns} = this.props
return (
<>
<label className="form-label">Table Columns</label>
<div className="logs-options--columns">
{columns.map((c, i) => this.getDraggableColumn(c, i))}
</div>
</>
)
}
private getDraggableColumn(column: LogsTableColumn, i: number): JSX.Element {
const {onMoveColumn, onUpdateColumn} = this.props
if (column.internalName !== 'time') {
return (
<DraggableColumn
key={column.internalName}
index={i}
id={column.internalName}
internalName={column.internalName}
displayName={column.displayName}
visible={column.visible}
onUpdateColumn={onUpdateColumn}
onMoveColumn={onMoveColumn}
/>
)
}
}
}
export default DragDropContext(HTML5Backend)(ColumnsOptions)

View File

@ -0,0 +1,201 @@
import React, {Component} from 'react'
import _ from 'lodash'
import {Button, ComponentStatus} from 'src/clockface'
import Container from 'src/clockface/components/overlays/OverlayContainer'
import Heading from 'src/clockface/components/overlays/OverlayHeading'
import Body from 'src/clockface/components/overlays/OverlayBody'
import SeverityOptions from 'src/logs/components/options_overlay/SeverityOptions'
import ColumnsOptions from 'src/logs/components/options_overlay/ColumnsOptions'
import {
SeverityLevelColor,
SeverityColor,
SeverityFormat,
LogsTableColumn,
SeverityLevelOptions,
LogConfig,
} from 'src/types/logs'
import {DEFAULT_SEVERITY_LEVELS} from 'src/logs/constants'
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
severityLevelColors: SeverityLevelColor[]
columns: LogsTableColumn[]
severityFormat: SeverityFormat
onDismissOverlay: () => void
onSave: (config: Partial<LogConfig>) => Promise<void>
}
interface State {
workingLevelColumns: SeverityLevelColor[]
workingColumns: LogsTableColumn[]
workingFormat: SeverityFormat
}
@ErrorHandling
class OptionsOverlay extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
workingLevelColumns: this.props.severityLevelColors,
workingColumns: this.props.columns,
workingFormat: this.props.severityFormat,
}
}
public shouldComponentUpdate(__, nextState: State) {
const isColorsDifferent = !_.isEqual(
nextState.workingLevelColumns,
this.state.workingLevelColumns
)
const isFormatDifferent = !_.isEqual(
nextState.workingFormat,
this.state.workingFormat
)
const isColumnsDifferent = !_.isEqual(
nextState.workingColumns,
this.state.workingColumns
)
if (isColorsDifferent || isFormatDifferent || isColumnsDifferent) {
return true
}
return false
}
public render() {
const {workingLevelColumns, workingColumns, workingFormat} = this.state
return (
<Container maxWidth={800}>
<Heading title="Configure Log Viewer">
{this.overlayActionButtons}
</Heading>
<Body>
<div className="row">
<div className="col-sm-5">
<SeverityOptions
severityLevelColors={workingLevelColumns}
onReset={this.handleResetSeverityLevels}
onChangeSeverityLevel={this.handleChangeSeverityLevel}
severityFormat={workingFormat}
onChangeSeverityFormat={this.handleChangeSeverityFormat}
/>
</div>
<div className="col-sm-7">
<ColumnsOptions
columns={workingColumns}
onMoveColumn={this.handleMoveColumn}
onUpdateColumn={this.handleUpdateColumn}
/>
</div>
</div>
</Body>
</Container>
)
}
private get overlayActionButtons(): JSX.Element {
const {onDismissOverlay} = this.props
return (
<div className="btn-group--right">
<Button text="Cancel" onClick={onDismissOverlay} />
<Button
onClick={this.handleSave}
status={this.isSaveDisabled}
text="Save"
/>
</div>
)
}
private get isSaveDisabled(): ComponentStatus {
const {workingLevelColumns, workingColumns, workingFormat} = this.state
const {severityLevelColors, columns, severityFormat} = this.props
const severityChanged = !_.isEqual(workingLevelColumns, severityLevelColors)
const columnsChanged = !_.isEqual(workingColumns, columns)
const formatChanged = !_.isEqual(workingFormat, severityFormat)
if (severityChanged || columnsChanged || formatChanged) {
return ComponentStatus.Default
}
return ComponentStatus.Disabled
}
private handleSave = async () => {
const {onDismissOverlay, onSave} = this.props
const {workingLevelColumns, workingFormat, workingColumns} = this.state
await onSave({
tableColumns: workingColumns,
severityFormat: workingFormat,
severityLevelColors: workingLevelColumns,
})
onDismissOverlay()
}
private handleResetSeverityLevels = (): void => {
const defaults = _.map(DEFAULT_SEVERITY_LEVELS, (color, level) => {
return {level: SeverityLevelOptions[level], color}
})
this.setState({workingLevelColumns: defaults})
}
private handleChangeSeverityLevel = (
severityLevel: string,
override: SeverityColor
): void => {
const workingLevelColumns = this.state.workingLevelColumns.map(config => {
if (config.level === severityLevel) {
return {...config, color: override.name}
}
return config
})
this.setState({workingLevelColumns})
}
private handleChangeSeverityFormat = (format: SeverityFormat) => {
this.setState({workingFormat: format})
}
private handleMoveColumn = (dragIndex, hoverIndex) => {
const {workingColumns} = this.state
const draggedField = workingColumns[dragIndex]
const columnsRemoved = _.concat(
_.slice(workingColumns, 0, dragIndex),
_.slice(workingColumns, dragIndex + 1)
)
const columnsAdded = _.concat(
_.slice(columnsRemoved, 0, hoverIndex),
[draggedField],
_.slice(columnsRemoved, hoverIndex)
)
this.setState({workingColumns: columnsAdded})
}
private handleUpdateColumn = (column: LogsTableColumn) => {
const workingColumns = this.state.workingColumns.map(wc => {
if (wc.internalName === column.internalName) {
return column
}
return wc
})
this.setState({workingColumns})
}
}
export default OptionsOverlay

View File

@ -0,0 +1,67 @@
// Libraries
import React, {PureComponent} from 'react'
// Components
import {Radio, ButtonShape} from 'src/clockface'
// Constants
import {SeverityFormatOptions} from 'src/types/logs'
// Types
import {SeverityFormat} from 'src/types/logs'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
interface Props {
format: SeverityFormat
onChangeFormat: (format: SeverityFormat) => void
}
@ErrorHandling
class SeverityColumnFormat extends PureComponent<Props> {
constructor(props: Props) {
super(props)
}
public render() {
const {format, onChangeFormat} = this.props
return (
<div className="graph-options-group">
<label className="form-label">Severity Format</label>
<Radio shape={ButtonShape.StretchToFit}>
<Radio.Button
active={format === SeverityFormatOptions.Dot}
id="severity-format-option--dot"
value={SeverityFormatOptions.Dot}
onClick={onChangeFormat}
titleText="Show only a dot in the severity column"
>
Dot
</Radio.Button>
<Radio.Button
active={format === SeverityFormatOptions.DotText}
id="severity-format-option--dot-text"
value={SeverityFormatOptions.DotText}
onClick={onChangeFormat}
titleText="Show both a dot and the severity name in the severity column"
>
Dot + Text
</Radio.Button>
<Radio.Button
active={format === SeverityFormatOptions.Text}
id="severity-format-option--text"
value={SeverityFormatOptions.Text}
onClick={onChangeFormat}
titleText="Show only the severity name in the severity column"
>
Text
</Radio.Button>
</Radio>
</div>
)
}
}
export default SeverityColumnFormat

View File

@ -0,0 +1,65 @@
import React, {SFC} from 'react'
import uuid from 'uuid'
import {Button, IconFont, ButtonShape} from 'src/clockface'
import ColorDropdown from 'src/shared/components/color_dropdown/ColorDropdown'
import SeverityColumnFormat from 'src/logs/components/options_overlay/SeverityColumnFormat'
import {
SeverityLevelColor,
SeverityColor,
SeverityFormat,
SeverityColorValues,
} from 'src/types/logs'
interface Props {
severityLevelColors: SeverityLevelColor[]
onReset: () => void
onChangeSeverityLevel: (severity: string, override: SeverityColor) => void
severityFormat: SeverityFormat
onChangeSeverityFormat: (format: SeverityFormat) => void
}
const SeverityConfig: SFC<Props> = ({
severityLevelColors,
onReset,
onChangeSeverityLevel,
severityFormat,
onChangeSeverityFormat,
}) => (
<>
<label className="form-label">Severity Colors</label>
<div className="logs-options--color-list">
{severityLevelColors.map(lc => {
const color = {name: lc.color, hex: SeverityColorValues[lc.color]}
return (
<div key={uuid.v4()} className="logs-options--color-row">
<div className="logs-options--color-column">
<div className="logs-options--color-label">{lc.level}</div>
</div>
<div className="logs-options--color-column">
<ColorDropdown
selected={color}
onChoose={onChangeSeverityLevel}
stretchToFit={true}
severityLevel={lc.level}
/>
</div>
</div>
)
})}
</div>
<Button
shape={ButtonShape.Default}
icon={IconFont.Refresh}
onClick={onReset}
text="Reset to Defaults"
/>
<SeverityColumnFormat
format={severityFormat}
onChangeFormat={onChangeSeverityFormat}
/>
</>
)
export default SeverityConfig

View File

@ -0,0 +1,44 @@
/*
Styles for Logs Viewer Options Overlay
----------------------------------------------------------------------------
*/
.logs-options--color-list {
display: flex;
flex-direction: column;
align-items: stretch;
}
.logs-options--color-row {
display: flex;
height: 30px;
align-items: stretch;
margin-bottom: 4px;
}
.logs-options--color-column {
width: 50%;
}
.logs-options--color-label {
height: 30px;
border-radius: $radius;
background-color: $g3-castle;
padding: 0 11px;
line-height: 30px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: $g13-mist;
font-weight: 500;
font-size: 13px;
margin-right: 4px;
@include no-user-select();
}
// Not very clean way of slightly darkening the disabled state
// of draggable columns in the overlay
.logs-options--columns .customizable-field--label__hidden {
background-color: $g3-castle;
}

View File

@ -1,4 +1,9 @@
import {LogViewerView, ViewType, ViewShape} from 'src/types/v2/dashboards'
import {
SeverityColorValues,
SeverityColorOptions,
SeverityLevelOptions,
} from 'src/types/logs'
export const NOW = 0
export const DEFAULT_TRUNCATION = true
@ -11,3 +16,97 @@ export const EMPTY_VIEW_PROPERTIES: LogViewerView = {
type: ViewType.LogViewer,
shape: ViewShape.ChronografV2,
}
export const DEFAULT_SEVERITY_LEVELS = {
[SeverityLevelOptions.Emerg]: SeverityColorOptions.Ruby,
[SeverityLevelOptions.Alert]: SeverityColorOptions.Fire,
[SeverityLevelOptions.Crit]: SeverityColorOptions.Curacao,
[SeverityLevelOptions.Err]: SeverityColorOptions.Tiger,
[SeverityLevelOptions.Warning]: SeverityColorOptions.Pineapple,
[SeverityLevelOptions.Notice]: SeverityColorOptions.Rainforest,
[SeverityLevelOptions.Info]: SeverityColorOptions.Star,
[SeverityLevelOptions.Debug]: SeverityColorOptions.Wolf,
}
export const SEVERITY_COLORS = [
{
hex: SeverityColorValues[SeverityColorOptions.Ruby],
name: SeverityColorOptions.Ruby,
},
{
hex: SeverityColorValues[SeverityColorOptions.Fire],
name: SeverityColorOptions.Fire,
},
{
hex: SeverityColorValues[SeverityColorOptions.Curacao],
name: SeverityColorOptions.Curacao,
},
{
hex: SeverityColorValues[SeverityColorOptions.Tiger],
name: SeverityColorOptions.Tiger,
},
{
hex: SeverityColorValues[SeverityColorOptions.Pineapple],
name: SeverityColorOptions.Pineapple,
},
{
hex: SeverityColorValues[SeverityColorOptions.Thunder],
name: SeverityColorOptions.Thunder,
},
{
hex: SeverityColorValues[SeverityColorOptions.Sulfur],
name: SeverityColorOptions.Sulfur,
},
{
hex: SeverityColorValues[SeverityColorOptions.Viridian],
name: SeverityColorOptions.Viridian,
},
{
hex: SeverityColorValues[SeverityColorOptions.Rainforest],
name: SeverityColorOptions.Rainforest,
},
{
hex: SeverityColorValues[SeverityColorOptions.Honeydew],
name: SeverityColorOptions.Honeydew,
},
{
hex: SeverityColorValues[SeverityColorOptions.Ocean],
name: SeverityColorOptions.Ocean,
},
{
hex: SeverityColorValues[SeverityColorOptions.Pool],
name: SeverityColorOptions.Pool,
},
{
hex: SeverityColorValues[SeverityColorOptions.Laser],
name: SeverityColorOptions.Laser,
},
{
hex: SeverityColorValues[SeverityColorOptions.Planet],
name: SeverityColorOptions.Planet,
},
{
hex: SeverityColorValues[SeverityColorOptions.Star],
name: SeverityColorOptions.Star,
},
{
hex: SeverityColorValues[SeverityColorOptions.Comet],
name: SeverityColorOptions.Comet,
},
{
hex: SeverityColorValues[SeverityColorOptions.Graphite],
name: SeverityColorOptions.Graphite,
},
{
hex: SeverityColorValues[SeverityColorOptions.Wolf],
name: SeverityColorOptions.Wolf,
},
{
hex: SeverityColorValues[SeverityColorOptions.Mist],
name: SeverityColorOptions.Mist,
},
{
hex: SeverityColorValues[SeverityColorOptions.Pearl],
name: SeverityColorOptions.Pearl,
},
]

View File

@ -23,7 +23,7 @@ export const defaultState: LogsState = {
id: null,
link: null,
tableColumns: [],
severityFormat: SeverityFormatOptions.dotText,
severityFormat: SeverityFormatOptions.DotText,
severityLevelColors: [],
isTruncated: DEFAULT_TRUNCATION,
},

View File

@ -212,8 +212,8 @@ describe('Logs.Config', () => {
const severityFormatDotText = generateColumnFormatConfig(viewDotText)
const severityFormatDot = generateColumnFormatConfig(viewColumnDot)
expect(severityFormatDotText).toBe(SeverityFormatOptions.dotText)
expect(severityFormatDot).toBe(SeverityFormatOptions.dot)
expect(severityFormatDotText).toBe(SeverityFormatOptions.DotText)
expect(severityFormatDot).toBe(SeverityFormatOptions.Dot)
})
it('sorts columns by column position', () => {
@ -256,12 +256,12 @@ describe('Logs.Config', () => {
const expectedColors = [
{
level: SeverityLevelOptions.emerg,
color: SeverityColorOptions.pineapple,
level: SeverityLevelOptions.Emerg,
color: SeverityColorOptions.Pineapple,
},
{
level: SeverityLevelOptions.err,
color: SeverityColorOptions.fire,
level: SeverityLevelOptions.Err,
color: SeverityColorOptions.Fire,
},
]
@ -285,15 +285,15 @@ describe('Logs.Config', () => {
{internalName: 'appname', displayName: 'Application', visible: true},
{internalName: 'host', displayName: '', visible: true},
],
severityFormat: SeverityFormatOptions.dotText,
severityFormat: SeverityFormatOptions.DotText,
severityLevelColors: [
{
level: SeverityLevelOptions.alert,
color: SeverityColorOptions.pearl,
level: SeverityLevelOptions.Alert,
color: SeverityColorOptions.Pearl,
},
{
level: SeverityLevelOptions.warning,
color: SeverityColorOptions.wolf,
level: SeverityLevelOptions.Warning,
color: SeverityColorOptions.Wolf,
},
],
}
@ -325,8 +325,8 @@ describe('Logs.Config', () => {
})
it('generates label settings from view column settings', () => {
const severityFormatDotText = SeverityFormatOptions.dotText
const severityFormatDot = SeverityFormatOptions.dot
const severityFormatDotText = SeverityFormatOptions.DotText
const severityFormatDot = SeverityFormatOptions.Dot
const settingsDotText = generateViewColumnSeverityLabels(
severityFormatDotText
@ -357,20 +357,20 @@ describe('Logs.Config', () => {
it('generates color settings from severityLevelColors', () => {
const severityLevelColors = [
{
level: SeverityLevelOptions.emerg,
color: SeverityColorOptions.pearl,
level: SeverityLevelOptions.Emerg,
color: SeverityColorOptions.Pearl,
},
{
level: SeverityLevelOptions.alert,
color: SeverityColorOptions.mist,
level: SeverityLevelOptions.Alert,
color: SeverityColorOptions.Mist,
},
{
level: SeverityLevelOptions.crit,
color: SeverityColorOptions.wolf,
level: SeverityLevelOptions.Crit,
color: SeverityColorOptions.Wolf,
},
{
level: SeverityLevelOptions.err,
color: SeverityColorOptions.graphite,
level: SeverityLevelOptions.Err,
color: SeverityColorOptions.Graphite,
},
]
@ -381,22 +381,22 @@ describe('Logs.Config', () => {
{
type: 'color',
name: 'emerg',
value: SeverityColorOptions.pearl,
value: SeverityColorOptions.Pearl,
},
{
type: 'color',
name: 'alert',
value: SeverityColorOptions.mist,
value: SeverityColorOptions.Mist,
},
{
type: 'color',
name: 'crit',
value: SeverityColorOptions.wolf,
value: SeverityColorOptions.Wolf,
},
{
type: 'color',
name: 'err',
value: SeverityColorOptions.graphite,
value: SeverityColorOptions.Graphite,
},
]
@ -415,12 +415,12 @@ describe('Logs.Config', () => {
displayName: '',
visible: true,
}
const severityFormat = SeverityFormatOptions.dotText
const severityFormat = SeverityFormatOptions.DotText
const severityLevelColors = [
{level: SeverityLevelOptions.emerg, color: SeverityColorOptions.pearl},
{level: SeverityLevelOptions.alert, color: SeverityColorOptions.mist},
{level: SeverityLevelOptions.crit, color: SeverityColorOptions.wolf},
{level: SeverityLevelOptions.err, color: SeverityColorOptions.graphite},
{level: SeverityLevelOptions.Emerg, color: SeverityColorOptions.Pearl},
{level: SeverityLevelOptions.Alert, color: SeverityColorOptions.Mist},
{level: SeverityLevelOptions.Crit, color: SeverityColorOptions.Wolf},
{level: SeverityLevelOptions.Err, color: SeverityColorOptions.Graphite},
]
const settingsSeverity = generateViewColumnSettings(
tableColumnSeverity,
@ -452,22 +452,22 @@ describe('Logs.Config', () => {
{
type: 'color',
name: 'emerg',
value: SeverityColorOptions.pearl,
value: SeverityColorOptions.Pearl,
},
{
type: 'color',
name: 'alert',
value: SeverityColorOptions.mist,
value: SeverityColorOptions.Mist,
},
{
type: 'color',
name: 'crit',
value: SeverityColorOptions.wolf,
value: SeverityColorOptions.Wolf,
},
{
type: 'color',
name: 'err',
value: SeverityColorOptions.graphite,
value: SeverityColorOptions.Graphite,
},
]
@ -497,15 +497,15 @@ describe('Logs.Config', () => {
{internalName: 'appname', displayName: 'Application', visible: true},
{internalName: 'host', displayName: '', visible: true},
],
severityFormat: SeverityFormatOptions.dotText,
severityFormat: SeverityFormatOptions.DotText,
severityLevelColors: [
{
level: SeverityLevelOptions.alert,
color: SeverityColorOptions.pearl,
level: SeverityLevelOptions.Alert,
color: SeverityColorOptions.Pearl,
},
{
level: SeverityLevelOptions.warning,
color: SeverityColorOptions.wolf,
level: SeverityLevelOptions.Warning,
color: SeverityColorOptions.Wolf,
},
],
}

View File

@ -73,11 +73,11 @@ export const generateColumnConfig = (
const settings: LogsTableColumn = column.settings.reduce(
(acc, e) => {
if (
e.type === ColumnSettingTypes.visibility &&
e.value === ColumnSettingVisibilityOptions.visible
e.type === ColumnSettingTypes.Visibility &&
e.value === ColumnSettingVisibilityOptions.Visible
) {
acc.visible = true
} else if (e.type === ColumnSettingTypes.display) {
} else if (e.type === ColumnSettingTypes.Display) {
acc.displayName = e.value
}
return acc
@ -94,22 +94,22 @@ export const generateColumnFormatConfig = (
let hasIcon = false
column.settings.forEach(e => {
if (e.type === ColumnSettingTypes.label) {
if (e.value === ColumnSettingLabelOptions.icon) {
if (e.type === ColumnSettingTypes.Label) {
if (e.value === ColumnSettingLabelOptions.Icon) {
hasIcon = true
}
if (e.value === ColumnSettingLabelOptions.text) {
if (e.value === ColumnSettingLabelOptions.Text) {
hasText = true
}
}
})
if (hasText && hasIcon) {
return SeverityFormatOptions.dotText
return SeverityFormatOptions.DotText
} else if (hasText) {
return SeverityFormatOptions.text
return SeverityFormatOptions.Text
} else {
return SeverityFormatOptions.dot
return SeverityFormatOptions.Dot
}
}
@ -117,15 +117,19 @@ export const generateColumnColorsConfig = (
column: LogViewerColumn
): SeverityLevelColor[] => {
const colors = column.settings.filter(
e => e.type === ColumnSettingTypes.color
e => e.type === ColumnSettingTypes.Color
)
return colors.map(c => {
const level: SeverityLevelOptions = SeverityLevelOptions[c.name]
const color: SeverityColorOptions = SeverityColorOptions[c.value]
const level: SeverityLevelOptions = SeverityLevelOptions[capitalize(c.name)]
const color: SeverityColorOptions =
SeverityColorOptions[capitalize(c.value)]
return {level, color}
})
}
const capitalize = (word: string): string =>
word.charAt(0).toUpperCase() + word.slice(1)
export const uiToServerConfig = (config: LogConfig): View => {
const properties: LogViewerView = generateViewProperties(config)
@ -174,19 +178,19 @@ export const generateViewColumns = (
if (tableColumn.visible) {
settings.push({
type: ColumnSettingTypes.visibility,
value: ColumnSettingVisibilityOptions.visible,
type: ColumnSettingTypes.Visibility,
value: ColumnSettingVisibilityOptions.Visible,
})
} else {
settings.push({
type: ColumnSettingTypes.visibility,
value: ColumnSettingVisibilityOptions.hidden,
type: ColumnSettingTypes.Visibility,
value: ColumnSettingVisibilityOptions.Hidden,
})
}
if (!_.isEmpty(tableColumn.displayName)) {
settings.push({
type: ColumnSettingTypes.display,
type: ColumnSettingTypes.Display,
value: tableColumn.displayName,
})
}
@ -215,18 +219,18 @@ export const generateViewColumnSeverityLabels = (
format: SeverityFormat
): LogViewerColumnSetting[] => {
switch (format) {
case SeverityFormatOptions.dot:
case SeverityFormatOptions.Dot:
return [
{type: ColumnSettingTypes.label, value: ColumnSettingLabelOptions.icon},
{type: ColumnSettingTypes.Label, value: ColumnSettingLabelOptions.Icon},
]
case SeverityFormatOptions.text:
case SeverityFormatOptions.Text:
return [
{type: ColumnSettingTypes.label, value: ColumnSettingLabelOptions.text},
{type: ColumnSettingTypes.Label, value: ColumnSettingLabelOptions.Text},
]
case SeverityFormatOptions.dotText:
case SeverityFormatOptions.DotText:
return [
{type: ColumnSettingTypes.label, value: ColumnSettingLabelOptions.icon},
{type: ColumnSettingTypes.label, value: ColumnSettingLabelOptions.text},
{type: ColumnSettingTypes.Label, value: ColumnSettingLabelOptions.Icon},
{type: ColumnSettingTypes.Label, value: ColumnSettingLabelOptions.Text},
]
}
return null
@ -236,6 +240,6 @@ export const generateViewColumnSeverityColors = (
levelColors: SeverityLevelColor[]
): LogViewerColumnSetting[] => {
return levelColors.map(({color, level}) => {
return {type: ColumnSettingTypes.color, value: color, name: level}
return {type: ColumnSettingTypes.Color, value: color, name: level}
})
}

View File

@ -15,19 +15,19 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'message',
value: 'seq_!@.#',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'message',
value: 'TERMS',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'message',
value: '/api/search',
operator: Operator.LIKE,
operator: Operator.Like,
},
]
@ -43,25 +43,25 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'severity',
value: 'info',
operator: Operator.EQUAL,
operator: Operator.Equal,
},
{
id: isUUID,
key: 'message',
value: ':TERMS',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'host',
value: 'del.local',
operator: Operator.NOT_EQUAL,
operator: Operator.NotEqual,
},
{
id: isUUID,
key: 'message',
value: 'foo:',
operator: Operator.LIKE,
operator: Operator.Like,
},
]
@ -77,19 +77,19 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'message',
value: '/api/search',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'message',
value: 'status_bad',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
{
id: isUUID,
key: 'message',
value: '@123!',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
]
@ -105,13 +105,13 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'message',
value: '/api/search status:200',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'message',
value: 'a success',
operator: Operator.LIKE,
operator: Operator.Like,
},
]
@ -127,13 +127,13 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'message',
value: '/api/search status:200',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
{
id: isUUID,
key: 'message',
value: 'a success',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
]
@ -149,25 +149,25 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'severity',
value: '4\\d{2}',
operator: Operator.EQUAL,
operator: Operator.Equal,
},
{
id: isUUID,
key: 'message',
value: 'NOT FOUND',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
{
id: isUUID,
key: 'message',
value: 'some "quote"',
operator: Operator.LIKE,
operator: Operator.Like,
},
{
id: isUUID,
key: 'message',
value: 'thing',
operator: Operator.NOT_LIKE,
operator: Operator.NotLike,
},
]
@ -183,7 +183,7 @@ describe('Logs.searchToFilters', () => {
id: isUUID,
key: 'message',
value: "some 'quote'",
operator: Operator.LIKE,
operator: Operator.Like,
},
]

View File

@ -16,31 +16,31 @@ const APP_NAME = 'appname'
export const createRule = (
part: TermPart,
type: TermType = TermType.INCLUDE
type: TermType = TermType.Include
): TermRule => ({
type,
pattern: getPattern(type, part),
})
const getPattern = (type: TermType, phrase: TermPart): RegExp => {
const {ATTRIBUTE, COLON, EXCLUSION} = TermPart
const PHRASE = `(${ATTRIBUTE}${COLON})?${phrase}`
const {Attribute, Colon, Exclusion} = TermPart
const phrasePattern = `(${Attribute}${Colon})?${phrase}`
switch (type) {
case TermType.EXCLUDE:
return new RegExp(`^${EXCLUSION}${PHRASE}`)
case TermType.Exclude:
return new RegExp(`^${Exclusion}${phrasePattern}`)
default:
return new RegExp(`^${PHRASE}`)
return new RegExp(`^${phrasePattern}`)
}
}
export const LOG_SEARCH_TERMS: TermRule[] = [
createRule(TermPart.SINGLE_QUOTED, TermType.EXCLUDE),
createRule(TermPart.DOUBLE_QUOTED, TermType.EXCLUDE),
createRule(TermPart.SINGLE_QUOTED),
createRule(TermPart.DOUBLE_QUOTED),
createRule(TermPart.UNQUOTED_WORD, TermType.EXCLUDE),
createRule(TermPart.UNQUOTED_WORD),
createRule(TermPart.SingleQuoted, TermType.Exclude),
createRule(TermPart.DoubleQuoted, TermType.Exclude),
createRule(TermPart.SingleQuoted),
createRule(TermPart.DoubleQuoted),
createRule(TermPart.UnquotedWord, TermType.Exclude),
createRule(TermPart.UnquotedWord),
]
export const searchToFilters = (searchTerm: string): Filter[] => {
@ -116,9 +116,9 @@ const termToOp = (term: Term): Operator => {
switch (term.attribute) {
case MESSAGE_KEY:
case APP_NAME:
return handleOpExclusion(term, Operator.LIKE, Operator.NOT_LIKE)
return handleOpExclusion(term, Operator.Like, Operator.NotLike)
default:
return handleOpExclusion(term, Operator.EQUAL, Operator.NOT_EQUAL)
return handleOpExclusion(term, Operator.Equal, Operator.NotEqual)
}
}
@ -128,9 +128,9 @@ const handleOpExclusion = (
exclusion: Operator
): Operator => {
switch (term.type) {
case TermType.EXCLUDE:
case TermType.Exclude:
return exclusion
case TermType.INCLUDE:
case TermType.Include:
return inclusion
}
}

View File

@ -0,0 +1,141 @@
import React, {Component, MouseEvent} from 'react'
import classnames from 'classnames'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import FancyScrollbar from 'src/shared/components/fancy_scrollbar/FancyScrollbar'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
import {SEVERITY_COLORS} from 'src/logs/constants'
import {SeverityColor, SeverityColorOptions} from 'src/types/logs'
interface Props {
selected: SeverityColor
disabled?: boolean
stretchToFit?: boolean
onChoose: (severityLevel: string, colors: SeverityColor) => void
severityLevel: string
}
interface State {
expanded: boolean
}
@ErrorHandling
export default class ColorDropdown extends Component<Props, State> {
public static defaultProps: Partial<Props> = {
stretchToFit: false,
disabled: false,
}
constructor(props) {
super(props)
this.state = {
expanded: false,
}
}
public render() {
const {expanded} = this.state
const {selected} = this.props
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div className={this.dropdownClassNames}>
<div
className={this.buttonClassNames}
onClick={this.handleToggleMenu}
>
<div
className="color-dropdown--swatch"
style={{backgroundColor: selected.hex}}
/>
<div className="color-dropdown--name">{selected.name}</div>
<span className="caret" />
</div>
{expanded && this.renderMenu}
</div>
</ClickOutside>
)
}
private get dropdownClassNames(): string {
const {stretchToFit} = this.props
const {expanded} = this.state
return classnames('color-dropdown', {
open: expanded,
'color-dropdown--stretch': stretchToFit,
})
}
private get buttonClassNames(): string {
const {disabled} = this.props
const {expanded} = this.state
return classnames('btn btn-sm btn-default color-dropdown--toggle', {
active: expanded,
'color-dropdown__disabled': disabled,
})
}
private get renderMenu(): JSX.Element {
const {selected} = this.props
return (
<div className="color-dropdown--menu">
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
{SEVERITY_COLORS.map((color, i) => (
<div
className={classnames('color-dropdown--item', {
active: color.name === selected.name,
})}
data-tag-key={color.name}
data-tag-value={color.hex}
key={i}
onClick={this.handleColorClick}
title={color.name}
>
<span
className="color-dropdown--swatch"
style={{backgroundColor: color.hex}}
/>
<span className="color-dropdown--name">{color.name}</span>
</div>
))}
</FancyScrollbar>
</div>
)
}
private handleToggleMenu = (): void => {
const {disabled} = this.props
if (disabled) {
return
}
this.setState({expanded: !this.state.expanded})
}
private handleClickOutside = (): void => {
this.setState({expanded: false})
}
private handleColorClick = (e: MouseEvent<HTMLElement>): void => {
const target = e.target as HTMLElement
const hex = target.dataset.tagValue || target.parentElement.dataset.tagValue
const nameString =
target.dataset.tagKey || target.parentElement.dataset.tagKey
const name = SeverityColorOptions[nameString]
const color: SeverityColor = {name, hex}
this.props.onChoose(this.props.severityLevel, color)
this.setState({expanded: false})
}
}

View File

@ -0,0 +1,119 @@
/*
Color Dropdown
------------------------------------------------------------------------------
*/
$color-dropdown--circle: 14px;
$color-dropdown--bar: 104px;
$color-dropdown--bar-height: 10px;
$color-dropdown--left-padding: 11px;
$color-dropdown--name-padding: 20px;
.color-dropdown {
width: 140px;
height: 30px;
position: relative;
}
.color-dropdown.color-dropdown--stretch {
width: 100%;
}
.color-dropdown--toggle {
width: 100%;
position: relative;
}
.color-dropdown--toggle span.caret {
font-style: normal !important;
position: absolute;
top: 50%;
right: 11px;
transform: translateY(-50%);
}
.color-dropdown--menu {
position: absolute;
top: 30px;
left: 0;
z-index: 5;
width: 100%;
border-radius: 4px;
box-shadow: 0 2px 5px 0.6px fade-out($g0-obsidian, 0.7);
@include gradient-h($g0-obsidian, $g2-kevlar);
}
.color-dropdown--item {
@include no-user-select();
width: 100%;
height: 28px;
position: relative;
color: $g11-sidewalk;
transition: color 0.25s ease, background-color 0.25s ease;
&:hover {
background-color: $g4-onyx;
color: $g18-cloud;
}
&:hover,
&:hover > * {
cursor: pointer !important;
}
&.active {
background-color: $g3-castle;
color: $g15-platinum;
}
&:first-child {
border-radius: 4px 4px 0 0;
}
&:last-child {
border-radius: 0 0 4px 4px;
}
}
.color-dropdown--swatch,
.color-dropdown--swatches,
.color-dropdown--name {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.color-dropdown--swatch {
width: $color-dropdown--circle;
height: $color-dropdown--circle;
border-radius: 50%;
left: $color-dropdown--left-padding;
}
.color-dropdown--swatches {
width: $color-dropdown--bar;
height: $color-dropdown--bar-height;
border-radius: $color-dropdown--bar-height / 2;
left: $color-dropdown--left-padding;
}
.color-dropdown--name {
text-align: left;
right: $color-dropdown--name-padding;
left: $color-dropdown--circle + $color-dropdown--name-padding;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 600;
text-transform: capitalize;
.color-dropdown--swatches + & {
left: $color-dropdown--bar + $color-dropdown--name-padding;
}
}
.color-dropdown
.color-dropdown--menu
.fancy-scroll--container
.fancy-scroll--track-v
.fancy-scroll--thumb-v {
@include gradient-v($g9-mountain, $g7-graphite);
}
.color-dropdown--toggle.color-dropdown__disabled {
color: $g7-graphite;
font-style: italic;
cursor: not-allowed;
}
.color-dropdown--toggle.color-dropdown__disabled > .color-dropdown--swatch {
background-color: $g7-graphite !important;
}

View File

@ -22,6 +22,7 @@
// Components
// TODO: Import these styles into their respective components instead of this stylesheet
@import 'src/page_layout/PageLayout';
@import 'src/shared/components/color_dropdown/color-dropdown';
@import 'src/shared/components/index_views/IndexList';
@import 'src/shared/components/dropdown_auto_refresh/AutoRefreshDropdown';
@import 'src/shared/components/profile_page/ProfilePage';
@ -56,3 +57,4 @@
@import 'src/logs/containers/logs_page/logs-viewer';
@import 'src/logs/components/loading_status/loading-status';
@import 'src/logs/components/logs_filter_bar/logs-filter-bar';
@import 'src/logs/components/options_overlay/logs-viewer-options';

View File

@ -1,5 +1,4 @@
import {QueryConfig, Namespace, Source} from 'src/types'
import {FieldOption} from 'src/types/v2/dashboards'
export enum SearchStatus {
@ -44,6 +43,7 @@ export interface LogConfig {
isTruncated: boolean
}
// Severity Colors
export interface SeverityLevelColor {
level: SeverityLevelOptions
color: SeverityColorOptions
@ -60,84 +60,84 @@ export type LogsTableColumn = FieldOption
// Log Severity
export enum SeverityLevelOptions {
emerg = 'emerg',
alert = 'alert',
crit = 'crit',
err = 'err',
warning = 'warning',
notice = 'notice',
info = 'info',
debug = 'debug',
Emerg = 'emerg',
Alert = 'alert',
Crit = 'crit',
Err = 'err',
Warning = 'warning',
Notice = 'notice',
Info = 'info',
Debug = 'debug',
}
export enum SeverityFormatOptions {
dot = 'dot',
dotText = 'dotText',
text = 'text',
Dot = 'dot',
DotText = 'dotText',
Text = 'text',
}
export enum SeverityColorOptions {
ruby = 'ruby',
fire = 'fire',
curacao = 'curacao',
tiger = 'tiger',
pineapple = 'pineapple',
thunder = 'thunder',
sulfur = 'sulfur',
viridian = 'viridian',
rainforest = 'rainforest',
honeydew = 'honeydew',
ocean = 'ocean',
pool = 'pool',
laser = 'laser',
planet = 'planet',
star = 'star',
comet = 'comet',
graphite = 'graphite',
wolf = 'wolf',
mist = 'mist',
pearl = 'pearl',
Ruby = 'ruby',
Fire = 'fire',
Curacao = 'curacao',
Tiger = 'tiger',
Pineapple = 'pineapple',
Thunder = 'thunder',
Sulfur = 'sulfur',
Viridian = 'viridian',
Rainforest = 'rainforest',
Honeydew = 'honeydew',
Ocean = 'ocean',
Pool = 'pool',
Laser = 'laser',
Planet = 'planet',
Star = 'star',
Comet = 'comet',
Graphite = 'graphite',
Wolf = 'wolf',
Mist = 'mist',
Pearl = 'pearl',
}
export const SeverityColorValues = {
[SeverityColorOptions.ruby]: '#BF3D5E',
[SeverityColorOptions.fire]: '#DC4E58',
[SeverityColorOptions.curacao]: '#F95F53',
[SeverityColorOptions.tiger]: '#F48D38',
[SeverityColorOptions.pineapple]: '#FFB94A',
[SeverityColorOptions.thunder]: '#FFD255',
[SeverityColorOptions.sulfur]: '#FFE480',
[SeverityColorOptions.viridian]: '#32B08C',
[SeverityColorOptions.rainforest]: '#4ED8A0',
[SeverityColorOptions.honeydew]: '#7CE490',
[SeverityColorOptions.ocean]: '#4591ED',
[SeverityColorOptions.pool]: '#22ADF6',
[SeverityColorOptions.laser]: '#00C9FF',
[SeverityColorOptions.planet]: '#513CC6',
[SeverityColorOptions.star]: '#7A65F2',
[SeverityColorOptions.comet]: '#9394FF',
[SeverityColorOptions.graphite]: '#545667',
[SeverityColorOptions.wolf]: '#8E91A1',
[SeverityColorOptions.mist]: '#BEC2CC',
[SeverityColorOptions.pearl]: '#E7E8EB',
[SeverityColorOptions.Ruby]: '#BF3D5E',
[SeverityColorOptions.Fire]: '#DC4E58',
[SeverityColorOptions.Curacao]: '#F95F53',
[SeverityColorOptions.Tiger]: '#F48D38',
[SeverityColorOptions.Pineapple]: '#FFB94A',
[SeverityColorOptions.Thunder]: '#FFD255',
[SeverityColorOptions.Sulfur]: '#FFE480',
[SeverityColorOptions.Viridian]: '#32B08C',
[SeverityColorOptions.Rainforest]: '#4ED8A0',
[SeverityColorOptions.Honeydew]: '#7CE490',
[SeverityColorOptions.Ocean]: '#4591ED',
[SeverityColorOptions.Pool]: '#22ADF6',
[SeverityColorOptions.Laser]: '#00C9FF',
[SeverityColorOptions.Planet]: '#513CC6',
[SeverityColorOptions.Star]: '#7A65F2',
[SeverityColorOptions.Comet]: '#9394FF',
[SeverityColorOptions.Graphite]: '#545667',
[SeverityColorOptions.Wolf]: '#8E91A1',
[SeverityColorOptions.Mist]: '#BEC2CC',
[SeverityColorOptions.Pearl]: '#E7E8EB',
}
// Log Column Settings
export enum ColumnSettingTypes {
visibility = 'visibility',
display = 'displayName',
label = 'label',
color = 'color',
Visibility = 'visibility',
Display = 'displayName',
Label = 'label',
Color = 'color',
}
export enum ColumnSettingLabelOptions {
text = 'text',
icon = 'icon',
Text = 'text',
Icon = 'icon',
}
export enum ColumnSettingVisibilityOptions {
visible = 'visible',
hidden = 'hidden',
Visible = 'visible',
Hidden = 'hidden',
}
// Time
@ -174,29 +174,29 @@ export interface TermRule {
}
export enum TermType {
EXCLUDE,
INCLUDE,
Exclude,
Include,
}
export enum TermPart {
EXCLUSION = '-',
SINGLE_QUOTED = "'([^']+)'",
DOUBLE_QUOTED = '"([^"]+)"',
ATTRIBUTE = '(\\w+(?=\\:))',
COLON = '(?::)',
UNQUOTED_WORD = '([\\S]+)',
Exclusion = '-',
SingleQuoted = "'([^']+)'",
DoubleQuoted = '"([^"]+)"',
Attribute = '(\\w+(?=\\:))',
Colon = '(?::)',
UnquotedWord = '([\\S]+)',
}
export enum Operator {
NOT_LIKE = '!~',
LIKE = '=~',
EQUAL = '==',
NOT_EQUAL = '!=',
NotLike = '!~',
Like = '=~',
Equal = '==',
NotEqual = '!=',
}
export enum MatchType {
NONE = 'no-match',
MATCH = 'match',
None = 'no-match',
Match = 'match',
}
export interface MatchSection {