From 5a4465a3824efc16e8698df2651a85a99c3826ee Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 3 Dec 2019 13:02:44 +0100 Subject: [PATCH] Explore: Log message line wrapping options for logs (#20360) --- .../grafana-ui/src/components/Logs/LogRow.tsx | 4 + .../src/components/Logs/LogRowMessage.tsx | 8 +- .../src/components/Logs/LogRows.test.tsx | 4 + .../src/components/Logs/LogRows.tsx | 79 ++++++++++--------- .../src/components/Logs/getLogRowStyles.ts | 5 ++ public/app/features/explore/Logs.tsx | 15 +++- public/app/plugins/panel/logs/LogsPanel.tsx | 3 +- .../plugins/panel/logs/LogsPanelEditor.tsx | 15 +++- public/app/plugins/panel/logs/types.ts | 2 + 9 files changed, 95 insertions(+), 40 deletions(-) diff --git a/packages/grafana-ui/src/components/Logs/LogRow.tsx b/packages/grafana-ui/src/components/Logs/LogRow.tsx index 1451f9a86e6..771ff78534a 100644 --- a/packages/grafana-ui/src/components/Logs/LogRow.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRow.tsx @@ -22,6 +22,7 @@ interface Props extends Themeable { row: LogRowModel; showDuplicates: boolean; showTime: boolean; + wrapLogMessage: boolean; timeZone: TimeZone; allowDetails?: boolean; getRows: () => LogRowModel[]; @@ -93,6 +94,7 @@ class UnThemedLogRow extends PureComponent<Props, State> { showDuplicates, timeZone, showTime, + wrapLogMessage, theme, getFieldLinks, } = this.props; @@ -103,6 +105,7 @@ class UnThemedLogRow extends PureComponent<Props, State> { const showDetailsClassName = showDetails ? cx(['fa fa-chevron-down', styles.topVerticalAlign]) : cx(['fa fa-chevron-right', styles.topVerticalAlign]); + return ( <div className={style.logsRow}> {showDuplicates && ( @@ -141,6 +144,7 @@ class UnThemedLogRow extends PureComponent<Props, State> { updateLimit={updateLimit} context={context} showContext={showContext} + wrapLogMessage={wrapLogMessage} onToggleContext={this.toggleContext} /> </div> diff --git a/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx b/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx index 47402172fb5..bda60e6a0b9 100644 --- a/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRowMessage.tsx @@ -21,6 +21,7 @@ interface Props extends Themeable { row: LogRowModel; hasMoreContextRows?: HasMoreContextRows; showContext: boolean; + wrapLogMessage: boolean; errors?: LogRowContextQueryErrors; context?: LogRowContextRows; highlighterExpressions?: string[]; @@ -57,6 +58,10 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => { label: whiteSpacePreWrap; white-space: pre-wrap; `, + horizontalScroll: css` + label: verticalScroll; + white-space: nowrap; + `, }; }); @@ -76,6 +81,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> { updateLimit, context, showContext, + wrapLogMessage, onToggleContext, } = this.props; const {} = this.state; @@ -91,7 +97,7 @@ class UnThemedLogRowMessage extends PureComponent<Props, State> { const styles = getStyles(theme); return ( <div className={style.logsRowMessage}> - <div className={styles.positionRelative}> + <div className={cx(styles.positionRelative, { [styles.horizontalScroll]: !wrapLogMessage })}> {showContext && context && ( <LogRowContext row={row} diff --git a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx index e88eb2b78d0..fe7dd4c515a 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx @@ -14,6 +14,7 @@ describe('LogRows', () => { dedupStrategy={LogsDedupStrategy.none} highlighterExpressions={[]} showTime={false} + wrapLogMessage={true} timeZone={'utc'} /> ); @@ -33,6 +34,7 @@ describe('LogRows', () => { dedupStrategy={LogsDedupStrategy.none} highlighterExpressions={[]} showTime={false} + wrapLogMessage={true} timeZone={'utc'} previewLimit={1} /> @@ -61,6 +63,7 @@ describe('LogRows', () => { dedupStrategy={LogsDedupStrategy.none} highlighterExpressions={[]} showTime={false} + wrapLogMessage={true} timeZone={'utc'} /> ); @@ -79,6 +82,7 @@ describe('LogRows', () => { dedupStrategy={LogsDedupStrategy.none} highlighterExpressions={[]} showTime={false} + wrapLogMessage={true} timeZone={'utc'} /> ); diff --git a/packages/grafana-ui/src/components/Logs/LogRows.tsx b/packages/grafana-ui/src/components/Logs/LogRows.tsx index 323f98135f6..c7c55112049 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.tsx @@ -18,6 +18,7 @@ export interface Props extends Themeable { dedupStrategy: LogsDedupStrategy; highlighterExpressions?: string[]; showTime: boolean; + wrapLogMessage: boolean; timeZone: TimeZone; rowLimit?: number; allowDetails?: boolean; @@ -71,6 +72,7 @@ class UnThemedLogRows extends PureComponent<Props, State> { const { dedupStrategy, showTime, + wrapLogMessage, logRows, deduplicatedRows, highlighterExpressions, @@ -84,12 +86,14 @@ class UnThemedLogRows extends PureComponent<Props, State> { getFieldLinks, } = this.props; const { renderAll } = this.state; + const { logsRows, logsRowsHorizontalScroll } = getLogRowStyles(theme); const dedupedRows = deduplicatedRows ? deduplicatedRows : logRows; const hasData = logRows && logRows.length > 0; const dedupCount = dedupedRows ? dedupedRows.reduce((sum, row) => (row.duplicates ? sum + row.duplicates : sum), 0) : 0; const showDuplicates = dedupStrategy !== LogsDedupStrategy.none && dedupCount > 0; + const horizontalScrollWindow = wrapLogMessage ? '' : logsRowsHorizontalScroll; // Staged rendering const processedRows = dedupedRows ? dedupedRows : []; @@ -100,45 +104,48 @@ class UnThemedLogRows extends PureComponent<Props, State> { // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead const getRows = this.makeGetRows(processedRows); const getRowContext = this.props.getRowContext ? this.props.getRowContext : () => Promise.resolve([]); - const { logsRows } = getLogRowStyles(theme); return ( <div className={logsRows}> - {hasData && - firstRows.map((row, index) => ( - <LogRow - key={row.uid} - getRows={getRows} - getRowContext={getRowContext} - highlighterExpressions={highlighterExpressions} - row={row} - showDuplicates={showDuplicates} - showTime={showTime} - timeZone={timeZone} - allowDetails={allowDetails} - onClickFilterLabel={onClickFilterLabel} - onClickFilterOutLabel={onClickFilterOutLabel} - getFieldLinks={getFieldLinks} - /> - ))} - {hasData && - renderAll && - lastRows.map((row, index) => ( - <LogRow - key={row.uid} - getRows={getRows} - getRowContext={getRowContext} - row={row} - showDuplicates={showDuplicates} - showTime={showTime} - timeZone={timeZone} - allowDetails={allowDetails} - onClickFilterLabel={onClickFilterLabel} - onClickFilterOutLabel={onClickFilterOutLabel} - getFieldLinks={getFieldLinks} - /> - ))} - {hasData && !renderAll && <span>Rendering {rowCount - previewLimit!} rows...</span>} + <div className={horizontalScrollWindow}> + {hasData && + firstRows.map((row, index) => ( + <LogRow + key={row.uid} + getRows={getRows} + getRowContext={getRowContext} + highlighterExpressions={highlighterExpressions} + row={row} + showDuplicates={showDuplicates} + showTime={showTime} + wrapLogMessage={wrapLogMessage} + timeZone={timeZone} + allowDetails={allowDetails} + onClickFilterLabel={onClickFilterLabel} + onClickFilterOutLabel={onClickFilterOutLabel} + getFieldLinks={getFieldLinks} + /> + ))} + {hasData && + renderAll && + lastRows.map((row, index) => ( + <LogRow + key={row.uid} + getRows={getRows} + getRowContext={getRowContext} + row={row} + showDuplicates={showDuplicates} + showTime={showTime} + wrapLogMessage={wrapLogMessage} + timeZone={timeZone} + allowDetails={allowDetails} + onClickFilterLabel={onClickFilterLabel} + onClickFilterOutLabel={onClickFilterOutLabel} + getFieldLinks={getFieldLinks} + /> + ))} + {hasData && !renderAll && <span>Rendering {rowCount - previewLimit!} rows...</span>} + </div> </div> ); } diff --git a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts index b117ed28477..2463476b622 100644 --- a/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts +++ b/packages/grafana-ui/src/components/Logs/getLogRowStyles.ts @@ -62,6 +62,10 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo table-layout: fixed; width: 100%; `, + logsRowsHorizontalScroll: css` + label: logs-rows__horizontal-scroll; + overflow-y: scroll; + `, context: context, logsRow: css` label: logs-row; @@ -130,6 +134,7 @@ export const getLogRowStyles = stylesFactory((theme: GrafanaTheme, logLevel?: Lo display: table-cell; white-space: nowrap; width: 12.5em; + padding-right: 1em; `, logsRowMessage: css` label: logs-row__message; diff --git a/public/app/features/explore/Logs.tsx b/public/app/features/explore/Logs.tsx index fbd8f6d0810..764f9766e6d 100644 --- a/public/app/features/explore/Logs.tsx +++ b/public/app/features/explore/Logs.tsx @@ -57,11 +57,13 @@ interface Props { interface State { showTime: boolean; + wrapLogMessage: boolean; } export class Logs extends PureComponent<Props, State> { state = { showTime: true, + wrapLogMessage: true, }; onChangeDedup = (dedup: LogsDedupStrategy) => { @@ -81,6 +83,15 @@ export class Logs extends PureComponent<Props, State> { } }; + onChangewrapLogMessage = (event?: React.SyntheticEvent) => { + const target = event && (event.target as HTMLInputElement); + if (target) { + this.setState({ + wrapLogMessage: target.checked, + }); + } + }; + onToggleLogLevel = (hiddenRawLevels: string[]) => { const hiddenLogLevels: LogLevel[] = hiddenRawLevels.map(level => LogLevel[level as LogLevel]); this.props.onToggleLogLevel(hiddenLogLevels); @@ -123,7 +134,7 @@ export class Logs extends PureComponent<Props, State> { return null; } - const { showTime } = this.state; + const { showTime, wrapLogMessage } = this.state; const { dedupStrategy } = this.props; const hasData = logRows && logRows.length > 0; const dedupCount = dedupedRows @@ -164,6 +175,7 @@ export class Logs extends PureComponent<Props, State> { <div className="logs-panel-options"> <div className="logs-panel-controls"> <Switch label="Time" checked={showTime} onChange={this.onChangeTime} transparent /> + <Switch label="Wrap lines" checked={wrapLogMessage} onChange={this.onChangewrapLogMessage} transparent /> <ToggleButtonGroup label="Dedup" transparent={true}> {Object.keys(LogsDedupStrategy).map((dedupType: string, i) => ( <ToggleButton @@ -202,6 +214,7 @@ export class Logs extends PureComponent<Props, State> { onClickFilterLabel={onClickFilterLabel} onClickFilterOutLabel={onClickFilterOutLabel} showTime={showTime} + wrapLogMessage={wrapLogMessage} timeZone={timeZone} getFieldLinks={getFieldLinks} /> diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index bed64edde61..d9198ac4bc3 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -10,7 +10,7 @@ interface LogsPanelProps extends PanelProps<Options> {} export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({ data, timeZone, - options: { showTime, sortOrder }, + options: { showTime, wrapLogMessage, sortOrder }, width, }) => { if (!data) { @@ -31,6 +31,7 @@ export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({ dedupStrategy={LogsDedupStrategy.none} highlighterExpressions={[]} showTime={showTime} + wrapLogMessage={wrapLogMessage} timeZone={timeZone} allowDetails={true} /> diff --git a/public/app/plugins/panel/logs/LogsPanelEditor.tsx b/public/app/plugins/panel/logs/LogsPanelEditor.tsx index 83cc3a8485e..c27632e1cd6 100644 --- a/public/app/plugins/panel/logs/LogsPanelEditor.tsx +++ b/public/app/plugins/panel/logs/LogsPanelEditor.tsx @@ -20,13 +20,20 @@ export class LogsPanelEditor extends PureComponent<PanelEditorProps<Options>> { onOptionsChange({ ...options, showTime: !showTime }); }; + onTogglewrapLogMessage = () => { + const { options, onOptionsChange } = this.props; + const { wrapLogMessage } = options; + + onOptionsChange({ ...options, wrapLogMessage: !wrapLogMessage }); + }; + onShowValuesChange = (item: SelectableValue<SortOrder>) => { const { options, onOptionsChange } = this.props; onOptionsChange({ ...options, sortOrder: item.value }); }; render() { - const { showTime, sortOrder } = this.props.options; + const { showTime, wrapLogMessage, sortOrder } = this.props.options; const value = sortOrderOptions.filter(option => option.value === sortOrder)[0]; return ( @@ -34,6 +41,12 @@ export class LogsPanelEditor extends PureComponent<PanelEditorProps<Options>> { <PanelOptionsGrid> <PanelOptionsGroup title="Columns"> <Switch label="Time" labelClass="width-10" checked={showTime} onChange={this.onToggleTime} /> + <Switch + label="Wrap lines" + labelClass="width-10" + checked={wrapLogMessage} + onChange={this.onTogglewrapLogMessage} + /> <div className="gf-form"> <FormLabel>Order</FormLabel> <Select options={sortOrderOptions} value={value} onChange={this.onShowValuesChange} /> diff --git a/public/app/plugins/panel/logs/types.ts b/public/app/plugins/panel/logs/types.ts index a8cb953bd2f..46859ddce08 100644 --- a/public/app/plugins/panel/logs/types.ts +++ b/public/app/plugins/panel/logs/types.ts @@ -2,10 +2,12 @@ import { SortOrder } from 'app/core/utils/explore'; export interface Options { showTime: boolean; + wrapLogMessage: boolean; sortOrder: SortOrder; } export const defaults: Options = { showTime: true, + wrapLogMessage: true, sortOrder: SortOrder.Descending, };