Merge pull request #3593 from influxdata/log-viewer/dynamic-message-width

Message column width changes with window size
pull/3613/head
Brandon Farmer 2018-06-08 11:07:44 -07:00 committed by GitHub
commit 5616ff9ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 206 additions and 49 deletions

View File

@ -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) {

View File

@ -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 => {

View File

@ -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 {

View File

@ -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>
)

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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 = {

View File

@ -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
}

View File

@ -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}

View File

@ -242,6 +242,10 @@ $logs-viewer-gutter: 60px;
}
}
.message--cell {
word-break: break-all;
}
// Table Cell Styles
.logs-viewer--cell {
font-size: 12px;

View File

@ -18,4 +18,5 @@ export interface LogsState {
tableData: object[]
searchTerm: string | null
filters: Filter[]
queryCount: number
}