Merge pull request #3593 from influxdata/log-viewer/dynamic-message-width
Message column width changes with window sizepull/3613/head
commit
5616ff9ec3
|
@ -63,7 +63,11 @@ export const saveToLocalStorage = ({
|
|||
const appPersisted = {app: {persisted}}
|
||||
const dashTimeV1 = {ranges: normalizer(ranges)}
|
||||
|
||||
const minimalLogs = _.omit(logs, ['tableData', 'histogramData'])
|
||||
const minimalLogs = _.omit(logs, [
|
||||
'tableData',
|
||||
'histogramData',
|
||||
'queryCount',
|
||||
])
|
||||
|
||||
window.localStorage.setItem(
|
||||
'state',
|
||||
|
@ -75,7 +79,7 @@ export const saveToLocalStorage = ({
|
|||
dataExplorer,
|
||||
dataExplorerQueryConfigs,
|
||||
script,
|
||||
logs: {...minimalLogs, histogramData: [], tableData: {}},
|
||||
logs: {...minimalLogs, histogramData: [], tableData: {}, queryCount: 0},
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
|
|
|
@ -52,6 +52,16 @@ export enum ActionTypes {
|
|||
AddFilter = 'LOGS_ADD_FILTER',
|
||||
RemoveFilter = 'LOGS_REMOVE_FILTER',
|
||||
ChangeFilter = 'LOGS_CHANGE_FILTER',
|
||||
IncrementQueryCount = 'LOGS_INCREMENT_QUERY_COUNT',
|
||||
DecrementQueryCount = 'LOGS_DECREMENT_QUERY_COUNT',
|
||||
}
|
||||
|
||||
export interface IncrementQueryCountAction {
|
||||
type: ActionTypes.IncrementQueryCount
|
||||
}
|
||||
|
||||
export interface DecrementQueryCountAction {
|
||||
type: ActionTypes.DecrementQueryCount
|
||||
}
|
||||
|
||||
export interface AddFilterAction {
|
||||
|
@ -161,6 +171,8 @@ export type Action =
|
|||
| AddFilterAction
|
||||
| RemoveFilterAction
|
||||
| ChangeFilterAction
|
||||
| DecrementQueryCountAction
|
||||
| IncrementQueryCountAction
|
||||
|
||||
const getTimeRange = (state: State): TimeRange | null =>
|
||||
getDeep<TimeRange | null>(state, 'logs.timeRange', null)
|
||||
|
@ -257,9 +269,26 @@ export const executeTableQueryAsync = () => async (
|
|||
}
|
||||
}
|
||||
|
||||
export const decrementQueryCount = () => ({
|
||||
type: ActionTypes.DecrementQueryCount,
|
||||
})
|
||||
|
||||
export const incrementQueryCount = () => ({
|
||||
type: ActionTypes.IncrementQueryCount,
|
||||
})
|
||||
|
||||
export const executeQueriesAsync = () => async dispatch => {
|
||||
dispatch(executeHistogramQueryAsync())
|
||||
dispatch(executeTableQueryAsync())
|
||||
dispatch(incrementQueryCount())
|
||||
try {
|
||||
await Promise.all([
|
||||
dispatch(executeHistogramQueryAsync()),
|
||||
dispatch(executeTableQueryAsync()),
|
||||
])
|
||||
} catch (ex) {
|
||||
console.error('Could not make query requests')
|
||||
} finally {
|
||||
dispatch(decrementQueryCount())
|
||||
}
|
||||
}
|
||||
|
||||
export const setSearchTermAsync = (searchTerm: string) => async dispatch => {
|
||||
|
|
|
@ -99,7 +99,16 @@ class LogViewerHeader extends PureComponent<Props> {
|
|||
return ''
|
||||
}
|
||||
|
||||
return this.sourceDropDownItems[0].text
|
||||
const id = _.get(this.props, 'currentSource.id', '')
|
||||
const currentItem = _.find(this.sourceDropDownItems, item => {
|
||||
return item.id === id
|
||||
})
|
||||
|
||||
if (currentItem) {
|
||||
return currentItem.text
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
private get selectedNamespace(): string {
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
import {Filter} from 'src/types/logs'
|
||||
import FilterBlock from 'src/logs/components/LogsFilter'
|
||||
import QueryResults from 'src/logs/components/QueryResults'
|
||||
|
||||
interface Props {
|
||||
numResults: number
|
||||
filters: Filter[]
|
||||
queryCount: number
|
||||
onDelete: (id: string) => void
|
||||
onFilterChange: (id: string, operator: string, value: string) => void
|
||||
}
|
||||
|
||||
class LogsFilters extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {numResults} = this.props
|
||||
const {numResults, queryCount} = this.props
|
||||
|
||||
return (
|
||||
<div className="logs-viewer--filter-bar">
|
||||
<label className="logs-viewer--results-text">
|
||||
Query returned <strong>{numResults} Events</strong>
|
||||
</label>
|
||||
<QueryResults count={numResults} queryCount={queryCount} />
|
||||
<ul className="logs-viewer--filters">{this.renderFilters}</ul>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -26,7 +26,7 @@ class LogsSearchBar extends PureComponent<Props, State> {
|
|||
<div className="logs-viewer--search-input">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search logs using Keywords or Regular Expressions..."
|
||||
placeholder="Search logs using keywords or regular expressions..."
|
||||
value={searchTerm}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleInputKeyDown}
|
||||
|
|
|
@ -7,9 +7,7 @@ import {getDeep} from 'src/utils/wrappers'
|
|||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
|
||||
const ROW_HEIGHT = 26
|
||||
const ROW_CHAR_LIMIT = 100
|
||||
const CHAR_WIDTH = 7
|
||||
|
||||
const CHAR_WIDTH = 9
|
||||
interface Props {
|
||||
data: {
|
||||
columns: string[]
|
||||
|
@ -46,11 +44,14 @@ class LogsTable extends Component<Props, State> {
|
|||
}
|
||||
|
||||
private grid: React.RefObject<Grid>
|
||||
private headerGrid: React.RefObject<Grid>
|
||||
private currentMessageWidth: number | null
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
this.grid = React.createRef()
|
||||
this.headerGrid = React.createRef()
|
||||
|
||||
this.state = {
|
||||
scrollTop: 0,
|
||||
|
@ -61,6 +62,15 @@ class LogsTable extends Component<Props, State> {
|
|||
|
||||
public componentDidUpdate() {
|
||||
this.grid.current.recomputeGridSize()
|
||||
this.headerGrid.current.recomputeGridSize()
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
window.addEventListener('resize', this.handleWindowResize)
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleWindowResize)
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
@ -75,6 +85,7 @@ class LogsTable extends Component<Props, State> {
|
|||
<AutoSizer>
|
||||
{({width}) => (
|
||||
<Grid
|
||||
ref={this.headerGrid}
|
||||
height={ROW_HEIGHT}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
rowCount={1}
|
||||
|
@ -120,6 +131,12 @@ class LogsTable extends Component<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private handleWindowResize = () => {
|
||||
this.currentMessageWidth = null
|
||||
this.grid.current.recomputeGridSize()
|
||||
this.headerGrid.current.recomputeGridSize()
|
||||
}
|
||||
|
||||
private handleHeaderScroll = ({scrollLeft}) => this.setState({scrollLeft})
|
||||
|
||||
private handleScrollbarScroll = (e: MouseEvent<JSX.Element>) => {
|
||||
|
@ -127,15 +144,62 @@ class LogsTable extends Component<Props, State> {
|
|||
this.handleScroll(target)
|
||||
}
|
||||
|
||||
private get widthMapping() {
|
||||
return {
|
||||
timestamp: 160,
|
||||
procid: 80,
|
||||
facility: 120,
|
||||
severity: 22,
|
||||
severity_1: 120,
|
||||
host: 300,
|
||||
}
|
||||
}
|
||||
|
||||
private get messageWidth() {
|
||||
if (this.currentMessageWidth) {
|
||||
return this.currentMessageWidth
|
||||
}
|
||||
|
||||
const columns = getDeep<string[]>(this.props, 'data.columns', [])
|
||||
const otherWidth = columns.reduce((acc, col) => {
|
||||
if (col === 'message' || col === 'time') {
|
||||
return acc
|
||||
}
|
||||
|
||||
return acc + _.get(this.widthMapping, col, 200)
|
||||
}, 0)
|
||||
|
||||
const calculatedWidth = window.innerWidth - (otherWidth + 180)
|
||||
this.currentMessageWidth = Math.max(100 * CHAR_WIDTH, calculatedWidth)
|
||||
|
||||
return this.currentMessageWidth - CHAR_WIDTH
|
||||
}
|
||||
|
||||
private getColumnWidth = ({index}: {index: number}) => {
|
||||
const column = getDeep<string>(this.props, `data.columns.${index + 1}`, '')
|
||||
|
||||
switch (column) {
|
||||
case 'message':
|
||||
return this.messageWidth
|
||||
default:
|
||||
return _.get(this.widthMapping, column, 200)
|
||||
}
|
||||
}
|
||||
|
||||
private get rowCharLimit(): number {
|
||||
return Math.floor(this.messageWidth / CHAR_WIDTH)
|
||||
}
|
||||
|
||||
private get columns(): string[] {
|
||||
return getDeep<string[]>(this.props, 'data.columns', [])
|
||||
}
|
||||
|
||||
private calculateMessageHeight = (index: number): number => {
|
||||
const columnIndex = this.props.data.columns.indexOf('message')
|
||||
const height =
|
||||
(Math.floor(
|
||||
this.props.data.values[index][columnIndex].length / ROW_CHAR_LIMIT
|
||||
) +
|
||||
1) *
|
||||
ROW_HEIGHT
|
||||
return height
|
||||
const columnIndex = this.columns.indexOf('message')
|
||||
const value = getDeep(this.props, `data.values.${index}.${columnIndex}`, '')
|
||||
const lines = Math.round(value.length / this.rowCharLimit + 0.25)
|
||||
|
||||
return Math.max(lines, 1) * (ROW_HEIGHT - 14) + 14
|
||||
}
|
||||
|
||||
private calculateTotalHeight = (): number => {
|
||||
|
@ -181,27 +245,6 @@ class LogsTable extends Component<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
private getColumnWidth = ({index}: {index: number}) => {
|
||||
const column = getDeep<string>(this.props, `data.columns.${index + 1}`, '')
|
||||
|
||||
switch (column) {
|
||||
case 'message':
|
||||
return ROW_CHAR_LIMIT * CHAR_WIDTH
|
||||
case 'timestamp':
|
||||
return 160
|
||||
case 'procid':
|
||||
return 80
|
||||
case 'facility':
|
||||
return 120
|
||||
case 'severity_1':
|
||||
return 80
|
||||
case 'severity':
|
||||
return 22
|
||||
default:
|
||||
return 200
|
||||
}
|
||||
}
|
||||
|
||||
private header(key: string): string {
|
||||
return getDeep<string>(
|
||||
{
|
||||
|
@ -251,7 +294,9 @@ class LogsTable extends Component<Props, State> {
|
|||
value = moment(+value / 1000000).format('YYYY/MM/DD HH:mm:ss')
|
||||
break
|
||||
case 'message':
|
||||
value = _.replace(value, '\\n', '')
|
||||
if (value.indexOf(' ') > this.rowCharLimit - 5) {
|
||||
value = _.truncate(value, {length: this.rowCharLimit - 5})
|
||||
}
|
||||
break
|
||||
case 'severity':
|
||||
value = (
|
||||
|
@ -283,6 +328,8 @@ class LogsTable extends Component<Props, State> {
|
|||
data-tag-key={column}
|
||||
data-tag-value={value}
|
||||
onClick={this.handleTagClick}
|
||||
data-index={rowIndex}
|
||||
onMouseOver={this.handleMouseEnter}
|
||||
className="logs-viewer--clickable"
|
||||
>
|
||||
{value}
|
||||
|
@ -293,7 +340,9 @@ class LogsTable extends Component<Props, State> {
|
|||
|
||||
return (
|
||||
<div
|
||||
className={classnames('logs-viewer--cell', {highlight: highlightRow})}
|
||||
className={classnames(`logs-viewer--cell ${column}--cell`, {
|
||||
highlight: highlightRow,
|
||||
})}
|
||||
key={key}
|
||||
style={style}
|
||||
onMouseOver={this.handleMouseEnter}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import React, {PureComponent} from 'react'
|
||||
|
||||
interface Props {
|
||||
count: number
|
||||
queryCount: number
|
||||
}
|
||||
|
||||
class QueryResults extends PureComponent<Props> {
|
||||
public render() {
|
||||
const {count} = this.props
|
||||
if (this.isPending) {
|
||||
return <label className="logs-viewer--results-text">Querying ...</label>
|
||||
}
|
||||
|
||||
return (
|
||||
<label className="logs-viewer--results-text">
|
||||
Query returned <strong>{count} Events</strong>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
private get isPending(): boolean {
|
||||
const {queryCount} = this.props
|
||||
return queryCount > 0
|
||||
}
|
||||
}
|
||||
|
||||
export default QueryResults
|
|
@ -47,6 +47,7 @@ interface Props {
|
|||
}
|
||||
searchTerm: string
|
||||
filters: Filter[]
|
||||
queryCount: number
|
||||
}
|
||||
|
||||
interface State {
|
||||
|
@ -88,7 +89,7 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
|
||||
public render() {
|
||||
const {liveUpdating} = this.state
|
||||
const {searchTerm, filters} = this.props
|
||||
const {searchTerm, filters, queryCount} = this.props
|
||||
|
||||
const count = getDeep(this.props, 'tableData.values.length', 0)
|
||||
|
||||
|
@ -106,6 +107,7 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
filters={filters || []}
|
||||
onDelete={this.handleFilterDelete}
|
||||
onFilterChange={this.handleFilterChange}
|
||||
queryCount={queryCount}
|
||||
/>
|
||||
<LogsTable
|
||||
data={this.props.tableData}
|
||||
|
@ -119,13 +121,19 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
)
|
||||
}
|
||||
|
||||
private get isSpecificTimeRange(): boolean {
|
||||
return !!getDeep(this.props, 'timeRange.upper', false)
|
||||
}
|
||||
|
||||
private startUpdating = () => {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
|
||||
this.interval = setInterval(this.handleInterval, 10000)
|
||||
this.setState({liveUpdating: true})
|
||||
if (!this.isSpecificTimeRange) {
|
||||
this.interval = setInterval(this.handleInterval, 10000)
|
||||
this.setState({liveUpdating: true})
|
||||
}
|
||||
}
|
||||
|
||||
private handleScrollToTop = () => {
|
||||
|
@ -180,7 +188,7 @@ class LogsPage extends PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<LogViewerHeader
|
||||
liveUpdating={liveUpdating}
|
||||
liveUpdating={liveUpdating && !this.isSpecificTimeRange}
|
||||
availableSources={sources}
|
||||
timeRange={timeRange}
|
||||
onChooseSource={this.handleChooseSource}
|
||||
|
@ -254,6 +262,7 @@ const mapStateToProps = ({
|
|||
tableData,
|
||||
searchTerm,
|
||||
filters,
|
||||
queryCount,
|
||||
},
|
||||
}) => ({
|
||||
sources,
|
||||
|
@ -265,6 +274,7 @@ const mapStateToProps = ({
|
|||
tableData,
|
||||
searchTerm,
|
||||
filters,
|
||||
queryCount,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = {
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
RemoveFilterAction,
|
||||
AddFilterAction,
|
||||
ChangeFilterAction,
|
||||
DecrementQueryCountAction,
|
||||
IncrementQueryCountAction,
|
||||
} from 'src/logs/actions'
|
||||
import {LogsState} from 'src/types/logs'
|
||||
|
||||
|
@ -19,6 +21,7 @@ const defaultState: LogsState = {
|
|||
histogramData: [],
|
||||
searchTerm: null,
|
||||
filters: [],
|
||||
queryCount: 0,
|
||||
}
|
||||
|
||||
const removeFilter = (
|
||||
|
@ -56,6 +59,22 @@ const changeFilter = (
|
|||
return {...state, filters: mappedFilters}
|
||||
}
|
||||
|
||||
const decrementQueryCount = (
|
||||
state: LogsState,
|
||||
__: DecrementQueryCountAction
|
||||
) => {
|
||||
const {queryCount} = state
|
||||
return {...state, queryCount: Math.max(queryCount - 1, 0)}
|
||||
}
|
||||
|
||||
const incrementQueryCount = (
|
||||
state: LogsState,
|
||||
__: IncrementQueryCountAction
|
||||
) => {
|
||||
const {queryCount} = state
|
||||
return {...state, queryCount: queryCount + 1}
|
||||
}
|
||||
|
||||
export default (state: LogsState = defaultState, action: Action) => {
|
||||
switch (action.type) {
|
||||
case ActionTypes.SetSource:
|
||||
|
@ -86,6 +105,10 @@ export default (state: LogsState = defaultState, action: Action) => {
|
|||
return removeFilter(state, action)
|
||||
case ActionTypes.ChangeFilter:
|
||||
return changeFilter(state, action)
|
||||
case ActionTypes.IncrementQueryCount:
|
||||
return incrementQueryCount(state, action)
|
||||
case ActionTypes.DecrementQueryCount:
|
||||
return decrementQueryCount(state, action)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
|
|
@ -155,7 +155,7 @@ const computeSeconds = (range: TimeRange) => {
|
|||
|
||||
const createGroupBy = (range: TimeRange) => {
|
||||
const seconds = computeSeconds(range)
|
||||
const time = `${Math.floor(seconds / BIN_COUNT)}s`
|
||||
const time = `${Math.max(Math.floor(seconds / BIN_COUNT), 1)}s`
|
||||
const tags = []
|
||||
|
||||
return {time, tags}
|
||||
|
|
|
@ -242,6 +242,10 @@ $logs-viewer-gutter: 60px;
|
|||
}
|
||||
}
|
||||
|
||||
.message--cell {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
// Table Cell Styles
|
||||
.logs-viewer--cell {
|
||||
font-size: 12px;
|
||||
|
|
|
@ -18,4 +18,5 @@ export interface LogsState {
|
|||
tableData: object[]
|
||||
searchTerm: string | null
|
||||
filters: Filter[]
|
||||
queryCount: number
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue