Initial duel load of log viewer data

pull/10616/head
Brandon Farmer 2018-07-10 15:05:59 -07:00
parent 0e0a263993
commit b1bb27bbee
12 changed files with 698 additions and 94 deletions

View File

@ -9,6 +9,8 @@ import {
buildHistogramQueryConfig, buildHistogramQueryConfig,
buildTableQueryConfig, buildTableQueryConfig,
buildLogQuery, buildLogQuery,
buildForwardLogQuery,
buildBackwardLogQuery,
parseHistogramQueryResponse, parseHistogramQueryResponse,
} from 'src/logs/utils' } from 'src/logs/utils'
import { import {
@ -21,8 +23,11 @@ import {
// getLogConfig as getLogConfigAJAX, // getLogConfig as getLogConfigAJAX,
// updateLogConfig as updateLogConfigAJAX, // updateLogConfig as updateLogConfigAJAX,
} from 'src/logs/api' } from 'src/logs/api'
import serverLogData from 'src/logs/data/serverLogData'
import {LogsState, Filter, TableData, LogConfig} from 'src/types/logs' import {LogsState, Filter, TableData, LogConfig} from 'src/types/logs'
const INITIAL_LIMIT = 1000
const defaultTableData: TableData = { const defaultTableData: TableData = {
columns: [ columns: [
'time', 'time',
@ -61,7 +66,40 @@ export enum ActionTypes {
DecrementQueryCount = 'LOGS_DECREMENT_QUERY_COUNT', DecrementQueryCount = 'LOGS_DECREMENT_QUERY_COUNT',
ConcatMoreLogs = 'LOGS_CONCAT_MORE_LOGS', ConcatMoreLogs = 'LOGS_CONCAT_MORE_LOGS',
SetConfig = 'SET_CONFIG', SetConfig = 'SET_CONFIG',
SetTableRelativeTime = 'SET_TABLE_RELATIVE_TIME',
SetTableCustomTime = 'SET_TABLE_CUSTOM_TIME',
SetTableForwardData = 'SET_TABLE_FORWARD_DATA',
SetTableBackwardData = 'SET_TABLE_BACKWARD_DATA',
} }
export interface SetTableForwardDataAction {
type: ActionTypes.SetTableForwardData
payload: {
data: TableData
}
}
export interface SetTableBackwardDataAction {
type: ActionTypes.SetTableBackwardData
payload: {
data: TableData
}
}
export interface SetTableRelativeTimeAction {
type: ActionTypes.SetTableRelativeTime
payload: {
time: number
}
}
export interface SetTableCustomTimeAction {
type: ActionTypes.SetTableCustomTime
payload: {
time: string
}
}
export interface ConcatMoreLogsAction { export interface ConcatMoreLogsAction {
type: ActionTypes.ConcatMoreLogs type: ActionTypes.ConcatMoreLogs
payload: { payload: {
@ -194,6 +232,10 @@ export type Action =
| IncrementQueryCountAction | IncrementQueryCountAction
| ConcatMoreLogsAction | ConcatMoreLogsAction
| SetConfigsAction | SetConfigsAction
| SetTableCustomTimeAction
| SetTableRelativeTimeAction
| SetTableForwardDataAction
| SetTableBackwardDataAction
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)
@ -216,6 +258,131 @@ 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', [])
const getTableSelectedTime = (state: State): string => {
const custom = getDeep<string>(state, 'logs.tableTime.custom', '')
if (!_.isEmpty(custom)) {
return custom
}
const relative = getDeep<number>(state, 'logs.tableTime.relative', 0)
return moment()
.subtract(relative, 'seconds')
.toISOString()
}
export const setTableCustomTime = (time: string): SetTableCustomTimeAction => ({
type: ActionTypes.SetTableCustomTime,
payload: {time},
})
export const setTableRelativeTime = (
time: number
): SetTableRelativeTimeAction => ({
type: ActionTypes.SetTableRelativeTime,
payload: {time},
})
export const setTableForwardData = (
data: TableData
): SetTableForwardDataAction => ({
type: ActionTypes.SetTableForwardData,
payload: {data},
})
export const setTableBackwardData = (
data: TableData
): SetTableBackwardDataAction => ({
type: ActionTypes.SetTableBackwardData,
payload: {data},
})
export const executeTableForwardQueryAsync = () => async (
dispatch,
getState: GetState
) => {
const state = getState()
const time = getTableSelectedTime(state)
const queryConfig = getTableQueryConfig(state)
const namespace = getNamespace(state)
const proxyLink = getProxyLink(state)
const searchTerm = getSearchTerm(state)
const filters = getFilters(state)
if (!_.every([queryConfig, time, namespace, proxyLink])) {
return
}
try {
dispatch(incrementQueryCount())
const query = buildForwardLogQuery(time, queryConfig, filters, searchTerm)
const response = await executeQueryAsync(
proxyLink,
namespace,
`${query} ORDER BY time ASC LIMIT ${INITIAL_LIMIT}`
)
const series = getDeep(response, 'results.0.series.0', defaultTableData)
const result = {
columns: series.columns,
values: _.reverse(series.values),
}
dispatch(setTableForwardData(result))
} finally {
dispatch(decrementQueryCount())
}
}
export const executeTableBackwardQueryAsync = () => async (
dispatch,
getState: GetState
) => {
const state = getState()
const time = getTableSelectedTime(state)
const queryConfig = getTableQueryConfig(state)
const namespace = getNamespace(state)
const proxyLink = getProxyLink(state)
const searchTerm = getSearchTerm(state)
const filters = getFilters(state)
if (!_.every([queryConfig, time, namespace, proxyLink])) {
return
}
try {
dispatch(incrementQueryCount())
const query = buildBackwardLogQuery(time, queryConfig, filters, searchTerm)
const response = await executeQueryAsync(
proxyLink,
namespace,
`${query} ORDER BY time DESC LIMIT ${INITIAL_LIMIT}`
)
const series = getDeep(response, 'results.0.series.0', defaultTableData)
dispatch(setTableBackwardData(series))
} finally {
dispatch(decrementQueryCount())
}
}
export const setTableCustomTimeAsync = (time: string) => async dispatch => {
await dispatch(setTableCustomTime(time))
await dispatch(executeTableQueryAsync())
}
export const setTableRelativeTimeAsync = (time: number) => async dispatch => {
await dispatch(setTableRelativeTime(time))
await dispatch(executeTableQueryAsync())
}
export const changeFilter = (id: string, operator: string, value: string) => ({ export const changeFilter = (id: string, operator: string, value: string) => ({
type: ActionTypes.ChangeFilter, type: ActionTypes.ChangeFilter,
payload: {id, operator, value}, payload: {id, operator, value},
@ -271,44 +438,11 @@ export const executeHistogramQueryAsync = () => async (
} }
} }
const setTableData = (series: TableData): SetTableData => ({ export const executeTableQueryAsync = () => async (dispatch): Promise<void> => {
type: ActionTypes.SetTableData, await Promise.all([
payload: {data: {columns: series.columns, values: series.values}}, dispatch(executeTableForwardQueryAsync()),
}) dispatch(executeTableBackwardQueryAsync()),
])
export const executeTableQueryAsync = () => async (
dispatch,
getState: GetState
): Promise<void> => {
const state = getState()
const queryConfig = getTableQueryConfig(state)
const timeRange = getTimeRange(state)
const namespace = getNamespace(state)
const proxyLink = getProxyLink(state)
const searchTerm = getSearchTerm(state)
const filters = getFilters(state)
if (!_.every([queryConfig, timeRange, namespace, proxyLink])) {
return
}
try {
dispatch(incrementQueryCount())
const query = buildLogQuery(timeRange, queryConfig, filters, searchTerm)
const response = await executeQueryAsync(
proxyLink,
namespace,
`${query} ORDER BY time DESC LIMIT 1000`
)
const series = getDeep(response, 'results.0.series.0', defaultTableData)
dispatch(setTableData(series))
} finally {
dispatch(decrementQueryCount())
}
} }
export const decrementQueryCount = () => ({ export const decrementQueryCount = () => ({

View File

@ -323,6 +323,7 @@ class LogsTable extends Component<Props, State> {
} }
private loadMoreRows = async () => { private loadMoreRows = async () => {
return
const data = getValuesFromData(this.props.data) const data = getValuesFromData(this.props.data)
const {timeRange} = this.props const {timeRange} = this.props
const lastTime = getDeep( const lastTime = getDeep(

View File

@ -0,0 +1,158 @@
import React, {Component, MouseEvent} from 'react'
import classnames from 'classnames'
import moment from 'moment'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import timePoints from 'src/logs/data/timePoints'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {ClickOutside} from 'src/shared/components/ClickOutside'
import CustomSingularTime from 'src/shared/components/CustomSingularTime'
interface Props {
customTime?: string
relativeTime?: number
onChooseCustomTime: (time: string) => void
onChooseRelativeTime: (time: number) => void
}
interface State {
isOpen: boolean
isTimeSelectorOpen: boolean
}
const dateFormat = 'YYYY-MM-DD HH:mm'
const format = t => moment(t.replace(/\'/g, '')).format(dateFormat)
@ErrorHandling
class TimeRangeDropdown extends Component<Props, State> {
constructor(props) {
super(props)
this.state = {
isOpen: false,
isTimeSelectorOpen: false,
}
}
public render() {
const {isTimeSelectorOpen} = this.state
return (
<ClickOutside onClickOutside={this.handleClickOutside}>
<div className="time-range-dropdown" style={{display: 'inline'}}>
<div className={this.dropdownClassName}>
<div
className="btn btn-sm btn-default dropdown-toggle"
onClick={this.toggleMenu}
>
<span className="icon clock" />
<span className="dropdown-selected">{this.timeInputValue}</span>
<span className="caret" />
</div>
<ul className="dropdown-menu">
<FancyScrollbar
autoHide={false}
autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
>
<div>
<li className="dropdown-header">Absolute Time</li>
<li
className={
isTimeSelectorOpen
? 'active dropdown-item custom-timerange'
: 'dropdown-item custom-timerange'
}
>
<a href="#" onClick={this.handleOpenCustomTime}>
Date Picker
</a>
</li>
</div>
<li className="dropdown-header">Relative Time</li>
{timePoints.map(point => {
return (
<li className="dropdown-item" key={`pot-${point.value}`}>
<a
href="#"
onClick={this.handleSelection}
data-value={point.value}
>
{point.text}
</a>
</li>
)
})}
</FancyScrollbar>
</ul>
</div>
{isTimeSelectorOpen ? (
<ClickOutside onClickOutside={this.handleCloseCustomTime}>
<div className="custom-time--overlay">
<CustomSingularTime
onSelected={this.handleCustomSelection}
time={this.props.customTime}
/>
</div>
</ClickOutside>
) : null}
</div>
</ClickOutside>
)
}
private get dropdownClassName(): string {
const {isOpen} = this.state
const absoluteTimeRange = !!this.props.customTime
return classnames('dropdown', {
'dropdown-290': absoluteTimeRange,
'dropdown-120': !absoluteTimeRange,
open: isOpen,
})
}
private handleCustomSelection = (time: string) => {
this.handleCloseCustomTime()
this.props.onChooseCustomTime(time)
this.setState({isOpen: false})
}
private handleSelection = (e: MouseEvent<HTMLAnchorElement>) => {
e.preventDefault()
const {dataset} = e.target as HTMLAnchorElement
this.props.onChooseRelativeTime(+dataset.value)
this.setState({isOpen: false})
}
private get timeInputValue(): string {
if (!this.props.customTime) {
const point = timePoints.find(p => p.value === this.props.relativeTime)
if (point) {
return point.text
}
return 'None'
}
return format(this.props.customTime)
}
private handleClickOutside = () => {
this.setState({isOpen: false})
}
private toggleMenu = () => {
this.setState({isOpen: !this.state.isOpen})
}
private handleCloseCustomTime = () => {
this.setState({isTimeSelectorOpen: false})
}
private handleOpenCustomTime = () => {
this.setState({isTimeSelectorOpen: true})
}
}
export default TimeRangeDropdown

View File

@ -8,7 +8,7 @@ import timeRanges from 'src/logs/data/timeRanges'
import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index' import {DROPDOWN_MENU_MAX_HEIGHT} from 'src/shared/constants/index'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {ClickOutside} from 'src/shared/components/ClickOutside' import {ClickOutside} from 'src/shared/components/ClickOutside'
import CustomSingularTime from 'src/shared/components/CustomSingularTime' import CustomTimeRange from 'src/shared/components/CustomTimeRange'
import {TimeRange} from 'src/types' import {TimeRange} from 'src/types'
@ -23,8 +23,6 @@ interface Props {
} }
onChooseTimeRange: (timeRange: TimeRange) => void onChooseTimeRange: (timeRange: TimeRange) => void
preventCustomTimeRange?: boolean
page?: string
} }
interface State { interface State {
@ -36,10 +34,6 @@ interface State {
@ErrorHandling @ErrorHandling
class TimeRangeDropdown extends Component<Props, State> { class TimeRangeDropdown extends Component<Props, State> {
public static defaultProps = {
page: 'default',
}
constructor(props) { constructor(props) {
super(props) super(props)
const {lower, upper} = props.selected const {lower, upper} = props.selected
@ -56,7 +50,7 @@ class TimeRangeDropdown extends Component<Props, State> {
} }
public render() { public render() {
const {selected, preventCustomTimeRange} = this.props const {selected} = this.props
const {customTimeRange, isCustomTimeRangeOpen} = this.state const {customTimeRange, isCustomTimeRangeOpen} = this.state
return ( return (
@ -79,25 +73,21 @@ class TimeRangeDropdown extends Component<Props, State> {
autoHeight={true} autoHeight={true}
maxHeight={DROPDOWN_MENU_MAX_HEIGHT} maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
> >
{preventCustomTimeRange ? null : ( <div>
<div> <li className="dropdown-header">Absolute Time</li>
<li className="dropdown-header">Absolute Time</li> <li
<li className={
className={ isCustomTimeRangeOpen
isCustomTimeRangeOpen ? 'active dropdown-item custom-timerange'
? 'active dropdown-item custom-timerange' : 'dropdown-item custom-timerange'
: 'dropdown-item custom-timerange' }
} >
> <a href="#" onClick={this.showCustomTimeRange}>
<a href="#" onClick={this.showCustomTimeRange}> Date Picker
Date Picker </a>
</a> </li>
</li> </div>
</div> <li className="dropdown-header">Relative Time</li>
)}
<li className="dropdown-header">
{preventCustomTimeRange ? '' : 'Relative '}Time
</li>
{timeRanges.map(item => { {timeRanges.map(item => {
return ( return (
<li className="dropdown-item" key={item.menuOption}> <li className="dropdown-item" key={item.menuOption}>
@ -113,11 +103,13 @@ class TimeRangeDropdown extends Component<Props, State> {
{isCustomTimeRangeOpen ? ( {isCustomTimeRangeOpen ? (
<ClickOutside onClickOutside={this.handleCloseCustomTimeRange}> <ClickOutside onClickOutside={this.handleCloseCustomTimeRange}>
<div className="custom-time--overlay"> <div className="custom-time--overlay">
<CustomSingularTime <CustomTimeRange
time={customTimeRange.lower} onApplyTimeRange={this.handleApplyCustomTimeRange}
onSelected={this.handleApplyCustomTimeRange} timeRange={customTimeRange}
onClose={this.handleCloseCustomTimeRange} onClose={this.handleCloseCustomTimeRange}
isVisible={isCustomTimeRangeOpen}
timeInterval={300} timeInterval={300}
page="default"
/> />
</div> </div>
</ClickOutside> </ClickOutside>
@ -129,9 +121,7 @@ class TimeRangeDropdown extends Component<Props, State> {
private get dropdownClassName(): string { private get dropdownClassName(): string {
const {isOpen} = this.state const {isOpen} = this.state
const {lower, upper} = _.get(this.props, 'selected', {upper: '', lower: ''}) const {lower, upper} = _.get(this.props, 'selected', {upper: '', lower: ''})
const absoluteTimeRange = !_.isEmpty(lower) && !_.isEmpty(upper) const absoluteTimeRange = !_.isEmpty(lower) && !_.isEmpty(upper)
return classnames('dropdown', { return classnames('dropdown', {

View File

@ -5,6 +5,8 @@ import {connect} from 'react-redux'
import {AutoSizer} from 'react-virtualized' import {AutoSizer} from 'react-virtualized'
import { import {
setTableCustomTimeAsync,
setTableRelativeTimeAsync,
getSourceAndPopulateNamespacesAsync, getSourceAndPopulateNamespacesAsync,
setTimeRangeAsync, setTimeRangeAsync,
setNamespaceAsync, setNamespaceAsync,
@ -26,14 +28,10 @@ import OptionsOverlay from 'src/logs/components/OptionsOverlay'
import SearchBar from 'src/logs/components/LogsSearchBar' import SearchBar from 'src/logs/components/LogsSearchBar'
import FilterBar from 'src/logs/components/LogsFilterBar' import FilterBar from 'src/logs/components/LogsFilterBar'
import LogsTable from 'src/logs/components/LogsTable' import LogsTable from 'src/logs/components/LogsTable'
import PointInTimeDropDown from 'src/logs/components/PointInTimeDropDown'
import {getDeep} from 'src/utils/wrappers' import {getDeep} from 'src/utils/wrappers'
import {colorForSeverity} from 'src/logs/utils/colors' import {colorForSeverity} from 'src/logs/utils/colors'
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology' import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
import {
orderTableColumns,
filterTableColumns,
} from 'src/dashboards/utils/tableGraph'
import {SeverityFormatOptions} from 'src/logs/constants' import {SeverityFormatOptions} from 'src/logs/constants'
import {Source, Namespace, TimeRange} from 'src/types' import {Source, Namespace, TimeRange} from 'src/types'
@ -46,6 +44,7 @@ import {
LogConfig, LogConfig,
TableData, TableData,
} from 'src/types/logs' } from 'src/types/logs'
import {applyChangesToTableData} from 'src/logs/utils/table'
interface Props { interface Props {
sources: Source[] sources: Source[]
@ -59,6 +58,8 @@ interface Props {
changeZoomAsync: (timeRange: TimeRange) => void changeZoomAsync: (timeRange: TimeRange) => void
executeQueriesAsync: () => void executeQueriesAsync: () => void
setSearchTermAsync: (searchTerm: string) => void setSearchTermAsync: (searchTerm: string) => void
setTableRelativeTime: (time: number) => void
setTableCustomTime: (time: string) => void
fetchMoreAsync: (queryTimeEnd: string, lastTime: number) => Promise<void> fetchMoreAsync: (queryTimeEnd: string, lastTime: number) => Promise<void>
addFilter: (filter: Filter) => void addFilter: (filter: Filter) => void
removeFilter: (id: string) => void removeFilter: (id: string) => void
@ -73,6 +74,14 @@ interface Props {
queryCount: number queryCount: number
logConfig: LogConfig logConfig: LogConfig
logConfigLink: string logConfigLink: string
tableInfiniteData: {
forward: TableData
backward: TableData
}
tableTime: {
custom: string
relative: number
}
} }
interface State { interface State {
@ -134,7 +143,7 @@ class LogsPage extends PureComponent<Props, State> {
public render() { public render() {
const {liveUpdating} = this.state const {liveUpdating} = this.state
const {searchTerm, filters, queryCount, timeRange} = this.props const {searchTerm, filters, queryCount, timeRange, tableTime} = this.props
return ( return (
<> <>
@ -142,6 +151,17 @@ class LogsPage extends PureComponent<Props, State> {
{this.header} {this.header}
<div className="page-contents logs-viewer"> <div className="page-contents logs-viewer">
<LogsGraphContainer>{this.chart}</LogsGraphContainer> <LogsGraphContainer>{this.chart}</LogsGraphContainer>
<div style={{height: '50px', position: 'relative'}}>
<div style={{position: 'absolute', right: '10px', top: '10px'}}>
<span style={{marginRight: '10px'}}>Go to </span>
<PointInTimeDropDown
customTime={tableTime.custom}
relativeTime={tableTime.relative}
onChooseCustomTime={this.handleChooseCustomTime}
onChooseRelativeTime={this.handleChooseRelativeTime}
/>
</div>
</div>
<SearchBar <SearchBar
searchString={searchTerm} searchString={searchTerm}
onSearch={this.handleSubmitSearch} onSearch={this.handleSubmitSearch}
@ -174,21 +194,28 @@ class LogsPage extends PureComponent<Props, State> {
) )
} }
private get tableData(): TableData { private handleChooseCustomTime = (time: string) => {
const {tableData} = this.props this.props.setTableCustomTime(time)
const tableColumns = this.tableColumns }
const columns = _.get(tableData, 'columns', [])
const values = _.get(tableData, 'values', [])
const data = [columns, ...values]
const filteredData = filterTableColumns(data, tableColumns) private handleChooseRelativeTime = (time: number) => {
const orderedData = orderTableColumns(filteredData, tableColumns) this.props.setTableRelativeTime(time)
const updatedColumns: string[] = _.get(orderedData, '0', []) }
const updatedValues = _.slice(orderedData, 1)
private get tableData(): TableData {
const forwardData = applyChangesToTableData(
this.props.tableInfiniteData.forward,
this.tableColumns
)
const backwardData = applyChangesToTableData(
this.props.tableInfiniteData.backward,
this.tableColumns
)
return { return {
columns: updatedColumns, columns: forwardData.columns,
values: updatedValues, values: [...forwardData.values, ...backwardData.values],
} }
} }
@ -436,6 +463,8 @@ const mapStateToProps = ({
filters, filters,
queryCount, queryCount,
logConfig, logConfig,
tableTime,
tableInfiniteData,
}, },
}) => ({ }) => ({
sources, sources,
@ -449,7 +478,9 @@ const mapStateToProps = ({
filters, filters,
queryCount, queryCount,
logConfig, logConfig,
tableTime,
logConfigLink: logViewer, logConfigLink: logViewer,
tableInfiniteData,
}) })
const mapDispatchToProps = { const mapDispatchToProps = {
@ -464,6 +495,8 @@ const mapDispatchToProps = {
removeFilter, removeFilter,
changeFilter, changeFilter,
fetchMoreAsync, fetchMoreAsync,
setTableCustomTime: setTableCustomTimeAsync,
setTableRelativeTime: setTableRelativeTimeAsync,
getConfig: getLogConfigAsync, getConfig: getLogConfigAsync,
updateConfig: updateLogConfigAsync, updateConfig: updateLogConfigAsync,
} }

View File

@ -0,0 +1,100 @@
export default {
columns: [
{
name: 'severity',
position: 1,
encodings: [
{
type: 'visibility',
value: 'visible',
},
{
type: 'label',
value: 'icon',
},
{
type: 'label',
value: 'text',
},
],
},
{
name: 'timestamp',
position: 2,
encodings: [
{
type: 'visibility',
value: 'visible',
},
],
},
{
name: 'message',
position: 3,
encodings: [
{
type: 'visibility',
value: 'visible',
},
],
},
{
name: 'facility',
position: 4,
encodings: [
{
type: 'visibility',
value: 'visible',
},
],
},
{
name: 'time',
position: 0,
encodings: [
{
type: 'visibility',
value: 'hidden',
},
],
},
{
name: 'procid',
position: 5,
encodings: [
{
type: 'visibility',
value: 'visible',
},
{
type: 'displayName',
value: 'Proc ID',
},
],
},
{
name: 'host',
position: 7,
encodings: [
{
type: 'visibility',
value: 'visible',
},
],
},
{
name: 'appname',
position: 6,
encodings: [
{
type: 'visibility',
value: 'visible',
},
{
type: 'displayName',
value: 'Application',
},
],
},
],
}

View File

@ -0,0 +1,26 @@
export default [
{
text: '1 minute ago',
value: 60,
},
{
text: '5 minute ago',
value: 300,
},
{
text: '10 minute ago',
value: 600,
},
{
text: '30 minute ago',
value: 1800,
},
{
text: '1 hour ago',
value: 3600,
},
{
text: '3 hour ago',
value: 10800,
},
]

View File

@ -13,7 +13,21 @@ import {
} from 'src/logs/actions' } from 'src/logs/actions'
import {SeverityFormatOptions} from 'src/logs/constants' import {SeverityFormatOptions} from 'src/logs/constants'
import {LogsState} from 'src/types/logs' import {LogsState, TableData} from 'src/types/logs'
const defaultTableData: TableData = {
columns: [
'time',
'severity',
'timestamp',
'facility',
'procid',
'application',
'host',
'message',
],
values: [],
}
const defaultState: LogsState = { const defaultState: LogsState = {
currentSource: null, currentSource: null,
@ -32,6 +46,11 @@ const defaultState: LogsState = {
severityFormat: SeverityFormatOptions.dotText, severityFormat: SeverityFormatOptions.dotText,
severityLevelColors: [], severityLevelColors: [],
}, },
tableTime: {},
tableInfiniteData: {
forward: defaultTableData,
backward: defaultTableData,
},
} }
const removeFilter = ( const removeFilter = (
@ -135,11 +154,31 @@ export default (state: LogsState = defaultState, action: Action) => {
return {...state, tableQueryConfig: action.payload.queryConfig} return {...state, tableQueryConfig: action.payload.queryConfig}
case ActionTypes.SetTableData: case ActionTypes.SetTableData:
return {...state, tableData: action.payload.data} return {...state, tableData: action.payload.data}
case ActionTypes.SetTableForwardData:
return {
...state,
tableInfiniteData: {
...state.tableInfiniteData,
forward: action.payload.data,
},
}
case ActionTypes.SetTableBackwardData:
return {
...state,
tableInfiniteData: {
...state.tableInfiniteData,
backward: action.payload.data,
},
}
case ActionTypes.ChangeZoom: case ActionTypes.ChangeZoom:
return {...state, timeRange: action.payload.timeRange} return {...state, timeRange: action.payload.timeRange}
case ActionTypes.SetSearchTerm: case ActionTypes.SetSearchTerm:
const {searchTerm} = action.payload const {searchTerm} = action.payload
return {...state, searchTerm} return {...state, searchTerm}
case ActionTypes.SetTableCustomTime:
return {...state, tableTime: {custom: action.payload.time}}
case ActionTypes.SetTableRelativeTime:
return {...state, tableTime: {relative: action.payload.time}}
case ActionTypes.AddFilter: case ActionTypes.AddFilter:
return addFilter(state, action) return addFilter(state, action)
case ActionTypes.RemoveFilter: case ActionTypes.RemoveFilter:

View File

@ -115,6 +115,99 @@ export const filtersClause = (filters: Filter[]): string => {
).join(' AND ') ).join(' AND ')
} }
export function buildInfiniteWhereClause({
lower,
upper,
tags,
areTagsAccepted,
}: QueryConfig): string {
const timeClauses = []
if (lower) {
timeClauses.push(`time >= '${lower}'`)
}
if (upper) {
timeClauses.push(`time < '${upper}'`)
}
const tagClauses = _.keys(tags).map(k => {
const operator = areTagsAccepted ? '=' : '!='
if (tags[k].length > 1) {
const joinedOnOr = tags[k]
.map(v => `"${k}"${operator}'${v}'`)
.join(' OR ')
return `(${joinedOnOr})`
}
return `"${k}"${operator}'${tags[k]}'`
})
const subClauses = timeClauses.concat(tagClauses)
if (!subClauses.length) {
return ''
}
return ` WHERE ${subClauses.join(' AND ')}`
}
export function buildGeneralLogQuery(
condition: string,
config: QueryConfig,
filters: Filter[],
searchTerm: string | null = null
) {
const {groupBy, fill = NULL_STRING} = config
const select = buildSelect(config, '')
const dimensions = buildGroupBy(groupBy)
const fillClause = groupBy.time ? buildFill(fill) : ''
if (!_.isEmpty(searchTerm)) {
condition = `${condition} AND message =~ ${new RegExp(searchTerm)}`
}
if (!_.isEmpty(filters)) {
condition = `${condition} AND ${filtersClause(filters)}`
}
return `${select}${condition}${dimensions}${fillClause}`
}
export function buildBackwardLogQuery(
upper: string,
config: QueryConfig,
filters: Filter[],
searchTerm: string | null = null
) {
const {tags, areTagsAccepted} = config
const condition = buildInfiniteWhereClause({
upper,
tags,
areTagsAccepted,
})
return buildGeneralLogQuery(condition, config, filters, searchTerm)
}
export function buildForwardLogQuery(
lower: string,
config: QueryConfig,
filters: Filter[],
searchTerm: string | null = null
) {
const {tags, areTagsAccepted} = config
const condition = buildInfiniteWhereClause({
lower,
tags,
areTagsAccepted,
})
return buildGeneralLogQuery(condition, config, filters, searchTerm)
}
export function buildLogQuery( export function buildLogQuery(
timeRange: TimeRange, timeRange: TimeRange,
config: QueryConfig, config: QueryConfig,

View File

@ -3,6 +3,10 @@ import moment from 'moment'
import {getDeep} from 'src/utils/wrappers' import {getDeep} from 'src/utils/wrappers'
import {TableData, LogsTableColumn, SeverityFormat} from 'src/types/logs' import {TableData, LogsTableColumn, SeverityFormat} from 'src/types/logs'
import {SeverityFormatOptions} from 'src/logs/constants' import {SeverityFormatOptions} from 'src/logs/constants'
import {
orderTableColumns,
filterTableColumns,
} from 'src/dashboards/utils/tableGraph'
export const ROW_HEIGHT = 26 export const ROW_HEIGHT = 26
const CHAR_WIDTH = 9 const CHAR_WIDTH = 9
@ -119,3 +123,22 @@ export const getMessageWidth = (
return calculatedWidth - CHAR_WIDTH return calculatedWidth - CHAR_WIDTH
} }
export const applyChangesToTableData = (
tableData: TableData,
tableColumns: LogsTableColumn[]
): TableData => {
const columns = _.get(tableData, 'columns', [])
const values = _.get(tableData, 'values', [])
const data = [columns, ...values]
const filteredData = filterTableColumns(data, tableColumns)
const orderedData = orderTableColumns(filteredData, tableColumns)
const updatedColumns: string[] = _.get(orderedData, '0', [])
const updatedValues = _.slice(orderedData, 1)
return {
columns: updatedColumns,
values: updatedValues,
}
}

View File

@ -3,10 +3,8 @@ import rome from 'rome'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {formatTimeRange} from 'src/shared/utils/time' import {formatTimeRange} from 'src/shared/utils/time'
import {TimeRange} from 'src/types'
interface Props { interface Props {
onSelected: (timeRange: TimeRange) => void onSelected: (time: string) => void
time: string time: string
timeInterval?: number timeInterval?: number
onClose?: () => void onClose?: () => void
@ -66,6 +64,7 @@ class CustomSingularTime extends Component<Props, State> {
</div> </div>
</div> </div>
<div <div
style={{marginTop: '10px'}}
className="custom-time--apply btn btn-sm btn-primary" className="custom-time--apply btn btn-sm btn-primary"
onClick={this.handleClick} onClick={this.handleClick}
> >
@ -85,8 +84,8 @@ class CustomSingularTime extends Component<Props, State> {
private handleClick = () => { private handleClick = () => {
const date = this.calendar.getDate() const date = this.calendar.getDate()
if (date) { if (date) {
const lower = date.toISOString() const time = date.toISOString()
this.props.onSelected({lower, upper: 'now()'}) this.props.onSelected(time)
} }
if (this.props.onClose) { if (this.props.onClose) {

View File

@ -32,6 +32,14 @@ export interface LogsState {
filters: Filter[] filters: Filter[]
queryCount: number queryCount: number
logConfig: LogConfig logConfig: LogConfig
tableInfiniteData: {
forward: TableData
backward: TableData
}
tableTime: {
custom?: string
relative?: string
}
} }
export interface LogConfig { export interface LogConfig {