From 2715d6535fd060a203ef5a11a5d6b84b89f7cca9 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Thu, 31 Oct 2019 11:06:11 +0100 Subject: [PATCH] Explore: Fix deferred rendering of logs (#20110) Remove artificial delay before first 100 rows were rendered and defer just the rest. --- .../src/components/Logs/LogRows.test.tsx | 126 ++++++++++++++++++ .../src/components/Logs/LogRows.tsx | 52 +++----- 2 files changed, 148 insertions(+), 30 deletions(-) create mode 100644 packages/grafana-ui/src/components/Logs/LogRows.test.tsx diff --git a/packages/grafana-ui/src/components/Logs/LogRows.test.tsx b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx new file mode 100644 index 00000000000..906aff2023f --- /dev/null +++ b/packages/grafana-ui/src/components/Logs/LogRows.test.tsx @@ -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( + + ); + + 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( + + ); + + 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( + + ); + + 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( + + ); + + expect(wrapper.find(LogRow).length).toBe(100); + }); +}); + +const makeLog = (overides: Partial): 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, + }; +}; diff --git a/packages/grafana-ui/src/components/Logs/LogRows.tsx b/packages/grafana-ui/src/components/Logs/LogRows.tsx index 0dc13e70c4a..66a427e7889 100644 --- a/packages/grafana-ui/src/components/Logs/LogRows.tsx +++ b/packages/grafana-ui/src/components/Logs/LogRows.tsx @@ -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; + rowLimit?: number; + previewLimit?: number; } interface State { - deferLogs: boolean; renderAll: boolean; } class UnThemedLogRows extends PureComponent { - 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 { 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 { // 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 { return (
{hasData && - !deferLogs && // Only inject highlighterExpression in the first set for performance reasons firstRows.map((row, index) => ( { /> ))} {hasData && - !deferLogs && renderAll && lastRows.map((row, index) => ( { onClickLabel={onClickLabel} /> ))} - {hasData && deferLogs && Rendering {rowCount} rows...} + {hasData && !renderAll && Rendering {rowCount - previewLimit!} rows...}
); }