Explore: Fix deferred rendering of logs (#20110)

Remove artificial delay before first 100 rows were rendered and defer just the rest.
This commit is contained in:
Andrej Ocenas 2019-10-31 11:06:11 +01:00 committed by GitHub
parent 8b672c8aed
commit 2715d6535f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 30 deletions

View File

@ -0,0 +1,126 @@
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,
};
};

View File

@ -8,8 +8,8 @@ import { withTheme } from '../../themes/index';
import { getLogRowStyles } from './getLogRowStyles';
import memoizeOne from 'memoize-one';
const PREVIEW_LIMIT = 100;
const RENDER_LIMIT = 500;
export const PREVIEW_LIMIT = 100;
export const RENDER_LIMIT = 500;
export interface Props extends Themeable {
data: LogsModel;
@ -19,48 +19,42 @@ export interface Props extends Themeable {
showLabels: boolean;
timeZone: TimeZone;
deduplicatedData?: LogsModel;
rowLimit?: number;
onClickLabel?: (label: string, value: string) => void;
getRowContext?: (row: LogRowModel, options?: any) => Promise<any>;
rowLimit?: number;
previewLimit?: number;
}
interface State {
deferLogs: boolean;
renderAll: boolean;
}
class UnThemedLogRows extends PureComponent<Props, State> {
deferLogsTimer: number | null = null;
renderAllTimer: number | null = null;
static defaultProps = {
previewLimit: PREVIEW_LIMIT,
rowLimit: RENDER_LIMIT,
};
state: State = {
deferLogs: true,
renderAll: false,
};
componentDidMount() {
// Staged rendering
if (this.state.deferLogs) {
const { data } = this.props;
const rowCount = data && data.rows ? data.rows.length : 0;
// Render all right away if not too far over the limit
const renderAll = rowCount <= PREVIEW_LIMIT * 2;
this.deferLogsTimer = window.setTimeout(() => this.setState({ deferLogs: false, renderAll }), rowCount);
}
}
componentDidUpdate(prevProps: Props, prevState: State) {
// Staged rendering
if (prevState.deferLogs && !this.state.deferLogs && !this.state.renderAll) {
const { data, previewLimit } = this.props;
const rowCount = data ? data.rows.length : 0;
// Render all right away if not too far over the limit
const renderAll = rowCount <= previewLimit! * 2;
if (renderAll) {
this.setState({ renderAll });
} else {
this.renderAllTimer = window.setTimeout(() => this.setState({ renderAll: true }), 2000);
}
}
componentWillUnmount() {
if (this.deferLogsTimer) {
clearTimeout(this.deferLogsTimer);
}
if (this.renderAllTimer) {
clearTimeout(this.renderAllTimer);
}
@ -82,8 +76,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onClickLabel,
rowLimit,
theme,
previewLimit,
} = this.props;
const { deferLogs, renderAll } = this.state;
const { renderAll } = this.state;
const dedupedData = deduplicatedData ? deduplicatedData : data;
const hasData = data && data.rows && data.rows.length > 0;
const hasLabel = hasData && dedupedData && dedupedData.hasUniqueLabels ? true : false;
@ -94,10 +89,9 @@ class UnThemedLogRows extends PureComponent<Props, State> {
// Staged rendering
const processedRows = dedupedData ? dedupedData.rows : [];
const firstRows = processedRows.slice(0, PREVIEW_LIMIT);
const renderLimit = rowLimit || RENDER_LIMIT;
const rowCount = Math.min(processedRows.length, renderLimit);
const lastRows = processedRows.slice(PREVIEW_LIMIT, rowCount);
const firstRows = processedRows.slice(0, previewLimit!);
const rowCount = Math.min(processedRows.length, rowLimit!);
const lastRows = processedRows.slice(previewLimit!, rowCount);
// React profiler becomes unusable if we pass all rows to all rows and their labels, using getter instead
const getRows = this.makeGetRows(processedRows);
@ -107,7 +101,6 @@ class UnThemedLogRows extends PureComponent<Props, State> {
return (
<div className={cx([logsRows])}>
{hasData &&
!deferLogs && // Only inject highlighterExpression in the first set for performance reasons
firstRows.map((row, index) => (
<LogRow
key={row.uid}
@ -123,7 +116,6 @@ class UnThemedLogRows extends PureComponent<Props, State> {
/>
))}
{hasData &&
!deferLogs &&
renderAll &&
lastRows.map((row, index) => (
<LogRow
@ -138,7 +130,7 @@ class UnThemedLogRows extends PureComponent<Props, State> {
onClickLabel={onClickLabel}
/>
))}
{hasData && deferLogs && <span>Rendering {rowCount} rows...</span>}
{hasData && !renderAll && <span>Rendering {rowCount - previewLimit!} rows...</span>}
</div>
);
}