Commit 2715d653 by Andrej Ocenas Committed by GitHub

Explore: Fix deferred rendering of logs (#20110)

Remove artificial delay before first 100 rows were rendered and defer just the rest.
parent 8b672c8a
import React from 'react';
import { range } from 'lodash';
import { LogRows, PREVIEW_LIMIT } from './LogRows';
import { mount } from 'enzyme';
import { LogLevel, LogRowModel, LogsDedupStrategy } from '@grafana/data';
import { LogRow } from './LogRow';
describe('LogRows', () => {
it('renders rows', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
const wrapper = mount(
<LogRows
data={{
rows,
hasUniqueLabels: false,
}}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showTime={false}
showLabels={false}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(3);
expect(wrapper.contains('log message 1')).toBeTruthy();
expect(wrapper.contains('log message 2')).toBeTruthy();
expect(wrapper.contains('log message 3')).toBeTruthy();
});
it('renders rows only limited number of rows first', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
jest.useFakeTimers();
const wrapper = mount(
<LogRows
data={{
rows,
hasUniqueLabels: false,
}}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showTime={false}
showLabels={false}
timeZone={'utc'}
previewLimit={1}
/>
);
expect(wrapper.find(LogRow).length).toBe(1);
expect(wrapper.contains('log message 1')).toBeTruthy();
jest.runAllTimers();
wrapper.update();
expect(wrapper.find(LogRow).length).toBe(3);
expect(wrapper.contains('log message 1')).toBeTruthy();
expect(wrapper.contains('log message 2')).toBeTruthy();
expect(wrapper.contains('log message 3')).toBeTruthy();
jest.useRealTimers();
});
it('renders deduped rows if supplied', () => {
const rows: LogRowModel[] = [makeLog({ uid: '1' }), makeLog({ uid: '2' }), makeLog({ uid: '3' })];
const dedupedRows: LogRowModel[] = [makeLog({ uid: '4' }), makeLog({ uid: '5' })];
const wrapper = mount(
<LogRows
data={{
rows,
hasUniqueLabels: false,
}}
deduplicatedData={{
rows: dedupedRows,
hasUniqueLabels: false,
}}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showTime={false}
showLabels={false}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(2);
expect(wrapper.contains('log message 4')).toBeTruthy();
expect(wrapper.contains('log message 5')).toBeTruthy();
});
it('renders with default preview limit', () => {
// PREVIEW_LIMIT * 2 is there because otherwise we just render all rows
const rows: LogRowModel[] = range(PREVIEW_LIMIT * 2 + 1).map(num => makeLog({ uid: num.toString() }));
const wrapper = mount(
<LogRows
data={{
rows,
hasUniqueLabels: false,
}}
dedupStrategy={LogsDedupStrategy.none}
highlighterExpressions={[]}
showTime={false}
showLabels={false}
timeZone={'utc'}
/>
);
expect(wrapper.find(LogRow).length).toBe(100);
});
});
const makeLog = (overides: Partial<LogRowModel>): LogRowModel => {
const uid = overides.uid || '1';
const entry = `log message ${uid}`;
return {
uid,
logLevel: LogLevel.debug,
entry,
hasAnsi: false,
labels: {},
raw: entry,
timestamp: '',
timeFromNow: '',
timeEpochMs: 1,
timeLocal: '',
timeUtc: '',
...overides,
};
};
...@@ -8,8 +8,8 @@ import { withTheme } from '../../themes/index'; ...@@ -8,8 +8,8 @@ import { withTheme } from '../../themes/index';
import { getLogRowStyles } from './getLogRowStyles'; import { getLogRowStyles } from './getLogRowStyles';
import memoizeOne from 'memoize-one'; import memoizeOne from 'memoize-one';
const PREVIEW_LIMIT = 100; export const PREVIEW_LIMIT = 100;
const RENDER_LIMIT = 500; export const RENDER_LIMIT = 500;
export interface Props extends Themeable { export interface Props extends Themeable {
data: LogsModel; data: LogsModel;
...@@ -19,48 +19,42 @@ export interface Props extends Themeable { ...@@ -19,48 +19,42 @@ export interface Props extends Themeable {
showLabels: boolean; showLabels: boolean;
timeZone: TimeZone; timeZone: TimeZone;
deduplicatedData?: LogsModel; deduplicatedData?: LogsModel;
rowLimit?: number;
onClickLabel?: (label: string, value: string) => void; onClickLabel?: (label: string, value: string) => void;
getRowContext?: (row: LogRowModel, options?: any) => Promise<any>; getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
rowLimit?: number;
previewLimit?: number;
} }
interface State { interface State {
deferLogs: boolean;
renderAll: boolean; renderAll: boolean;
} }
class UnThemedLogRows extends PureComponent<Props, State> { class UnThemedLogRows extends PureComponent<Props, State> {
deferLogsTimer: number | null = null;
renderAllTimer: number | null = null; renderAllTimer: number | null = null;
static defaultProps = {
previewLimit: PREVIEW_LIMIT,
rowLimit: RENDER_LIMIT,
};
state: State = { state: State = {
deferLogs: true,
renderAll: false, renderAll: false,
}; };
componentDidMount() { componentDidMount() {
// Staged rendering // Staged rendering
if (this.state.deferLogs) { const { data, previewLimit } = this.props;
const { data } = this.props; const rowCount = data ? data.rows.length : 0;
const rowCount = data && data.rows ? data.rows.length : 0; // Render all right away if not too far over the limit
// Render all right away if not too far over the limit const renderAll = rowCount <= previewLimit! * 2;
const renderAll = rowCount <= PREVIEW_LIMIT * 2; if (renderAll) {
this.deferLogsTimer = window.setTimeout(() => this.setState({ deferLogs: false, renderAll }), rowCount); this.setState({ renderAll });
} } else {
}
componentDidUpdate(prevProps: Props, prevState: State) {
// Staged rendering
if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) {
this.renderAllTimer = window.setTimeout(() => this.setState({ renderAll: true }), 2000); this.renderAllTimer = window.setTimeout(() => this.setState({ renderAll: true }), 2000);
} }
} }
componentWillUnmount() { componentWillUnmount() {
if (this.deferLogsTimer) {
clearTimeout(this.deferLogsTimer);
}
if (this.renderAllTimer) { if (this.renderAllTimer) {
clearTimeout(this.renderAllTimer); clearTimeout(this.renderAllTimer);
} }
...@@ -82,8 +76,9 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -82,8 +76,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onClickLabel, onClickLabel,
rowLimit, rowLimit,
theme, theme,
previewLimit,
} = this.props; } = this.props;
const { deferLogs, renderAll } = this.state; const { renderAll } = this.state;
const dedupedData = deduplicatedData ? deduplicatedData : data; const dedupedData = deduplicatedData ? deduplicatedData : data;
const hasData = data && data.rows && data.rows.length > 0; const hasData = data && data.rows && data.rows.length > 0;
const hasLabel = hasData && dedupedData && dedupedData.hasUniqueLabels ? true : false; const hasLabel = hasData && dedupedData && dedupedData.hasUniqueLabels ? true : false;
...@@ -94,10 +89,9 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -94,10 +89,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
// Staged rendering // Staged rendering
const processedRows = dedupedData ? dedupedData.rows : []; const processedRows = dedupedData ? dedupedData.rows : [];
const firstRows = processedRows.slice(0, PREVIEW_LIMIT); const firstRows = processedRows.slice(0, previewLimit!);
const renderLimit = rowLimit || RENDER_LIMIT; const rowCount = Math.min(processedRows.length, rowLimit!);
const rowCount = Math.min(processedRows.length, renderLimit); const lastRows = processedRows.slice(previewLimit!, rowCount);
const lastRows = processedRows.slice(PREVIEW_LIMIT, rowCount);
// React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead // React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
const getRows = this.makeGetRows(processedRows); const getRows = this.makeGetRows(processedRows);
...@@ -107,7 +101,6 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -107,7 +101,6 @@ class UnThemedLogRows extends PureComponent<Props, State> {
return ( return (
<div className={cx([logsRows])}> <div className={cx([logsRows])}>
{hasData && {hasData &&
!deferLogs && // Only inject highlighterExpression in the first set for performance reasons
firstRows.map((row, index) => ( firstRows.map((row, index) => (
<LogRow <LogRow
key={row.uid} key={row.uid}
...@@ -123,7 +116,6 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -123,7 +116,6 @@ class UnThemedLogRows extends PureComponent<Props, State> {
/> />
))} ))}
{hasData && {hasData &&
!deferLogs &&
renderAll && renderAll &&
lastRows.map((row, index) => ( lastRows.map((row, index) => (
<LogRow <LogRow
...@@ -138,7 +130,7 @@ class UnThemedLogRows extends PureComponent<Props, State> { ...@@ -138,7 +130,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onClickLabel={onClickLabel} onClickLabel={onClickLabel}
/> />
))} ))}
{hasData && deferLogs && <span>Rendering {rowCount} rows...</span>} {hasData && !renderAll && <span>Rendering {rowCount - previewLimit!} rows...</span>}
</div> </div>
); );
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment