Initial duel load of log viewer data
parent
0e0a263993
commit
b1bb27bbee
|
@ -9,6 +9,8 @@ import {
|
|||
buildHistogramQueryConfig,
|
||||
buildTableQueryConfig,
|
||||
buildLogQuery,
|
||||
buildForwardLogQuery,
|
||||
buildBackwardLogQuery,
|
||||
parseHistogramQueryResponse,
|
||||
} from 'src/logs/utils'
|
||||
import {
|
||||
|
@ -21,8 +23,11 @@ import {
|
|||
// getLogConfig as getLogConfigAJAX,
|
||||
// updateLogConfig as updateLogConfigAJAX,
|
||||
} from 'src/logs/api'
|
||||
import serverLogData from 'src/logs/data/serverLogData'
|
||||
import {LogsState, Filter, TableData, LogConfig} from 'src/types/logs'
|
||||
|
||||
const INITIAL_LIMIT = 1000
|
||||
|
||||
const defaultTableData: TableData = {
|
||||
columns: [
|
||||
'time',
|
||||
|
@ -61,7 +66,40 @@ export enum ActionTypes {
|
|||
DecrementQueryCount = 'LOGS_DECREMENT_QUERY_COUNT',
|
||||
ConcatMoreLogs = 'LOGS_CONCAT_MORE_LOGS',
|
||||
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 {
|
||||
type: ActionTypes.ConcatMoreLogs
|
||||
payload: {
|
||||
|
@ -194,6 +232,10 @@ export type Action =
|
|||
| IncrementQueryCountAction
|
||||
| ConcatMoreLogsAction
|
||||
| SetConfigsAction
|
||||
| SetTableCustomTimeAction
|
||||
| SetTableRelativeTimeAction
|
||||
| SetTableForwardDataAction
|
||||
| SetTableBackwardDataAction
|
||||
|
||||
const getTimeRange = (state: State): TimeRange | null =>
|
||||
getDeep<TimeRange | null>(state, 'logs.timeRange', null)
|
||||
|
@ -216,6 +258,131 @@ const getSearchTerm = (state: State): string | null =>
|
|||
const getFilters = (state: State): Filter[] =>
|
||||
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) => ({
|
||||
type: ActionTypes.ChangeFilter,
|
||||
payload: {id, operator, value},
|
||||
|
@ -271,44 +438,11 @@ export const executeHistogramQueryAsync = () => async (
|
|||
}
|
||||
}
|
||||
|
||||
const setTableData = (series: TableData): SetTableData => ({
|
||||
type: ActionTypes.SetTableData,
|
||||
payload: {data: {columns: series.columns, values: series.values}},
|
||||
})
|
||||
|
||||
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 executeTableQueryAsync = () => async (dispatch): Promise<void> => {
|
||||
await Promise.all([
|
||||
dispatch(executeTableForwardQueryAsync()),
|
||||
dispatch(executeTableBackwardQueryAsync()),
|
||||
])
|
||||
}
|
||||
|
||||
export const decrementQueryCount = () => ({
|
||||
|
|
|
@ -323,6 +323,7 @@ class LogsTable extends Component<Props, State> {
|
|||
}
|
||||
|
||||
private loadMoreRows = async () => {
|
||||
return
|
||||
const data = getValuesFromData(this.props.data)
|
||||
const {timeRange} = this.props
|
||||
const lastTime = getDeep(
|
||||
|
|
|
@ -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
|
|
@ -8,7 +8,7 @@ import timeRanges from 'src/logs/data/timeRanges'
|
|||
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'
|
||||
import CustomTimeRange from 'src/shared/components/CustomTimeRange'
|
||||
|
||||
import {TimeRange} from 'src/types'
|
||||
|
||||
|
@ -23,8 +23,6 @@ interface Props {
|
|||
}
|
||||
|
||||
onChooseTimeRange: (timeRange: TimeRange) => void
|
||||
preventCustomTimeRange?: boolean
|
||||
page?: string
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -36,10 +34,6 @@ interface State {
|
|||
|
||||
@ErrorHandling
|
||||
class TimeRangeDropdown extends Component<Props, State> {
|
||||
public static defaultProps = {
|
||||
page: 'default',
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
const {lower, upper} = props.selected
|
||||
|
@ -56,7 +50,7 @@ class TimeRangeDropdown extends Component<Props, State> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {selected, preventCustomTimeRange} = this.props
|
||||
const {selected} = this.props
|
||||
const {customTimeRange, isCustomTimeRangeOpen} = this.state
|
||||
|
||||
return (
|
||||
|
@ -79,25 +73,21 @@ class TimeRangeDropdown extends Component<Props, State> {
|
|||
autoHeight={true}
|
||||
maxHeight={DROPDOWN_MENU_MAX_HEIGHT}
|
||||
>
|
||||
{preventCustomTimeRange ? null : (
|
||||
<div>
|
||||
<li className="dropdown-header">Absolute Time</li>
|
||||
<li
|
||||
className={
|
||||
isCustomTimeRangeOpen
|
||||
? 'active dropdown-item custom-timerange'
|
||||
: 'dropdown-item custom-timerange'
|
||||
}
|
||||
>
|
||||
<a href="#" onClick={this.showCustomTimeRange}>
|
||||
Date Picker
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
)}
|
||||
<li className="dropdown-header">
|
||||
{preventCustomTimeRange ? '' : 'Relative '}Time
|
||||
</li>
|
||||
<div>
|
||||
<li className="dropdown-header">Absolute Time</li>
|
||||
<li
|
||||
className={
|
||||
isCustomTimeRangeOpen
|
||||
? 'active dropdown-item custom-timerange'
|
||||
: 'dropdown-item custom-timerange'
|
||||
}
|
||||
>
|
||||
<a href="#" onClick={this.showCustomTimeRange}>
|
||||
Date Picker
|
||||
</a>
|
||||
</li>
|
||||
</div>
|
||||
<li className="dropdown-header">Relative Time</li>
|
||||
{timeRanges.map(item => {
|
||||
return (
|
||||
<li className="dropdown-item" key={item.menuOption}>
|
||||
|
@ -113,11 +103,13 @@ class TimeRangeDropdown extends Component<Props, State> {
|
|||
{isCustomTimeRangeOpen ? (
|
||||
<ClickOutside onClickOutside={this.handleCloseCustomTimeRange}>
|
||||
<div className="custom-time--overlay">
|
||||
<CustomSingularTime
|
||||
time={customTimeRange.lower}
|
||||
onSelected={this.handleApplyCustomTimeRange}
|
||||
<CustomTimeRange
|
||||
onApplyTimeRange={this.handleApplyCustomTimeRange}
|
||||
timeRange={customTimeRange}
|
||||
onClose={this.handleCloseCustomTimeRange}
|
||||
isVisible={isCustomTimeRangeOpen}
|
||||
timeInterval={300}
|
||||
page="default"
|
||||
/>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
|
@ -129,9 +121,7 @@ class TimeRangeDropdown extends Component<Props, State> {
|
|||
|
||||
private get dropdownClassName(): string {
|
||||
const {isOpen} = this.state
|
||||
|
||||
const {lower, upper} = _.get(this.props, 'selected', {upper: '', lower: ''})
|
||||
|
||||
const absoluteTimeRange = !_.isEmpty(lower) && !_.isEmpty(upper)
|
||||
|
||||
return classnames('dropdown', {
|
||||
|
|
|
@ -5,6 +5,8 @@ import {connect} from 'react-redux'
|
|||
import {AutoSizer} from 'react-virtualized'
|
||||
|
||||
import {
|
||||
setTableCustomTimeAsync,
|
||||
setTableRelativeTimeAsync,
|
||||
getSourceAndPopulateNamespacesAsync,
|
||||
setTimeRangeAsync,
|
||||
setNamespaceAsync,
|
||||
|
@ -26,14 +28,10 @@ import OptionsOverlay from 'src/logs/components/OptionsOverlay'
|
|||
import SearchBar from 'src/logs/components/LogsSearchBar'
|
||||
import FilterBar from 'src/logs/components/LogsFilterBar'
|
||||
import LogsTable from 'src/logs/components/LogsTable'
|
||||
import PointInTimeDropDown from 'src/logs/components/PointInTimeDropDown'
|
||||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {colorForSeverity} from 'src/logs/utils/colors'
|
||||
import OverlayTechnology from 'src/reusable_ui/components/overlays/OverlayTechnology'
|
||||
import {
|
||||
orderTableColumns,
|
||||
filterTableColumns,
|
||||
} from 'src/dashboards/utils/tableGraph'
|
||||
|
||||
import {SeverityFormatOptions} from 'src/logs/constants'
|
||||
import {Source, Namespace, TimeRange} from 'src/types'
|
||||
|
||||
|
@ -46,6 +44,7 @@ import {
|
|||
LogConfig,
|
||||
TableData,
|
||||
} from 'src/types/logs'
|
||||
import {applyChangesToTableData} from 'src/logs/utils/table'
|
||||
|
||||
interface Props {
|
||||
sources: Source[]
|
||||
|
@ -59,6 +58,8 @@ interface Props {
|
|||
changeZoomAsync: (timeRange: TimeRange) => void
|
||||
executeQueriesAsync: () => void
|
||||
setSearchTermAsync: (searchTerm: string) => void
|
||||
setTableRelativeTime: (time: number) => void
|
||||
setTableCustomTime: (time: string) => void
|
||||
fetchMoreAsync: (queryTimeEnd: string, lastTime: number) => Promise<void>
|
||||
addFilter: (filter: Filter) => void
|
||||
removeFilter: (id: string) => void
|
||||
|
@ -73,6 +74,14 @@ interface Props {
|
|||
queryCount: number
|
||||
logConfig: LogConfig
|
||||
logConfigLink: string
|
||||
tableInfiniteData: {
|
||||
forward: TableData
|
||||
backward: TableData
|
||||
}
|
||||
tableTime: {
|
||||
custom: string
|
||||
relative: number
|
||||
}
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -134,7 +143,7 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
|
||||
public render() {
|
||||
const {liveUpdating} = this.state
|
||||
const {searchTerm, filters, queryCount, timeRange} = this.props
|
||||
const {searchTerm, filters, queryCount, timeRange, tableTime} = this.props
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -142,6 +151,17 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
{this.header}
|
||||
<div className="page-contents logs-viewer">
|
||||
<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
|
||||
searchString={searchTerm}
|
||||
onSearch={this.handleSubmitSearch}
|
||||
|
@ -174,21 +194,28 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private get tableData(): TableData {
|
||||
const {tableData} = this.props
|
||||
const tableColumns = this.tableColumns
|
||||
const columns = _.get(tableData, 'columns', [])
|
||||
const values = _.get(tableData, 'values', [])
|
||||
const data = [columns, ...values]
|
||||
private handleChooseCustomTime = (time: string) => {
|
||||
this.props.setTableCustomTime(time)
|
||||
}
|
||||
|
||||
const filteredData = filterTableColumns(data, tableColumns)
|
||||
const orderedData = orderTableColumns(filteredData, tableColumns)
|
||||
const updatedColumns: string[] = _.get(orderedData, '0', [])
|
||||
const updatedValues = _.slice(orderedData, 1)
|
||||
private handleChooseRelativeTime = (time: number) => {
|
||||
this.props.setTableRelativeTime(time)
|
||||
}
|
||||
|
||||
private get tableData(): TableData {
|
||||
const forwardData = applyChangesToTableData(
|
||||
this.props.tableInfiniteData.forward,
|
||||
this.tableColumns
|
||||
)
|
||||
|
||||
const backwardData = applyChangesToTableData(
|
||||
this.props.tableInfiniteData.backward,
|
||||
this.tableColumns
|
||||
)
|
||||
|
||||
return {
|
||||
columns: updatedColumns,
|
||||
values: updatedValues,
|
||||
columns: forwardData.columns,
|
||||
values: [...forwardData.values, ...backwardData.values],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,6 +463,8 @@ const mapStateToProps = ({
|
|||
filters,
|
||||
queryCount,
|
||||
logConfig,
|
||||
tableTime,
|
||||
tableInfiniteData,
|
||||
},
|
||||
}) => ({
|
||||
sources,
|
||||
|
@ -449,7 +478,9 @@ const mapStateToProps = ({
|
|||
filters,
|
||||
queryCount,
|
||||
logConfig,
|
||||
tableTime,
|
||||
logConfigLink: logViewer,
|
||||
tableInfiniteData,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
@ -464,6 +495,8 @@ const mapDispatchToProps = {
|
|||
removeFilter,
|
||||
changeFilter,
|
||||
fetchMoreAsync,
|
||||
setTableCustomTime: setTableCustomTimeAsync,
|
||||
setTableRelativeTime: setTableRelativeTimeAsync,
|
||||
getConfig: getLogConfigAsync,
|
||||
updateConfig: updateLogConfigAsync,
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
]
|
|
@ -13,7 +13,21 @@ import {
|
|||
} from 'src/logs/actions'
|
||||
|
||||
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 = {
|
||||
currentSource: null,
|
||||
|
@ -32,6 +46,11 @@ const defaultState: LogsState = {
|
|||
severityFormat: SeverityFormatOptions.dotText,
|
||||
severityLevelColors: [],
|
||||
},
|
||||
tableTime: {},
|
||||
tableInfiniteData: {
|
||||
forward: defaultTableData,
|
||||
backward: defaultTableData,
|
||||
},
|
||||
}
|
||||
|
||||
const removeFilter = (
|
||||
|
@ -135,11 +154,31 @@ export default (state: LogsState = defaultState, action: Action) => {
|
|||
return {...state, tableQueryConfig: action.payload.queryConfig}
|
||||
case ActionTypes.SetTableData:
|
||||
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:
|
||||
return {...state, timeRange: action.payload.timeRange}
|
||||
case ActionTypes.SetSearchTerm:
|
||||
const {searchTerm} = action.payload
|
||||
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:
|
||||
return addFilter(state, action)
|
||||
case ActionTypes.RemoveFilter:
|
||||
|
|
|
@ -115,6 +115,99 @@ export const filtersClause = (filters: Filter[]): string => {
|
|||
).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(
|
||||
timeRange: TimeRange,
|
||||
config: QueryConfig,
|
||||
|
|
|
@ -3,6 +3,10 @@ import moment from 'moment'
|
|||
import {getDeep} from 'src/utils/wrappers'
|
||||
import {TableData, LogsTableColumn, SeverityFormat} from 'src/types/logs'
|
||||
import {SeverityFormatOptions} from 'src/logs/constants'
|
||||
import {
|
||||
orderTableColumns,
|
||||
filterTableColumns,
|
||||
} from 'src/dashboards/utils/tableGraph'
|
||||
|
||||
export const ROW_HEIGHT = 26
|
||||
const CHAR_WIDTH = 9
|
||||
|
@ -119,3 +123,22 @@ export const getMessageWidth = (
|
|||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,8 @@ import rome from 'rome'
|
|||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
import {formatTimeRange} from 'src/shared/utils/time'
|
||||
|
||||
import {TimeRange} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
onSelected: (timeRange: TimeRange) => void
|
||||
onSelected: (time: string) => void
|
||||
time: string
|
||||
timeInterval?: number
|
||||
onClose?: () => void
|
||||
|
@ -66,6 +64,7 @@ class CustomSingularTime extends Component<Props, State> {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{marginTop: '10px'}}
|
||||
className="custom-time--apply btn btn-sm btn-primary"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
|
@ -85,8 +84,8 @@ class CustomSingularTime extends Component<Props, State> {
|
|||
private handleClick = () => {
|
||||
const date = this.calendar.getDate()
|
||||
if (date) {
|
||||
const lower = date.toISOString()
|
||||
this.props.onSelected({lower, upper: 'now()'})
|
||||
const time = date.toISOString()
|
||||
this.props.onSelected(time)
|
||||
}
|
||||
|
||||
if (this.props.onClose) {
|
||||
|
|
|
@ -32,6 +32,14 @@ export interface LogsState {
|
|||
filters: Filter[]
|
||||
queryCount: number
|
||||
logConfig: LogConfig
|
||||
tableInfiniteData: {
|
||||
forward: TableData
|
||||
backward: TableData
|
||||
}
|
||||
tableTime: {
|
||||
custom?: string
|
||||
relative?: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface LogConfig {
|
||||
|
|
Loading…
Reference in New Issue