diff --git a/public/app/features/explore/Logs/Logs.test.tsx b/public/app/features/explore/Logs/Logs.test.tsx index f2eb2779f97..a8f70e47270 100644 --- a/public/app/features/explore/Logs/Logs.test.tsx +++ b/public/app/features/explore/Logs/Logs.test.tsx @@ -16,12 +16,22 @@ import { } from '@grafana/data'; import { organizeFieldsTransformer } from '@grafana/data/src/transformations/transformers/organize'; import { config } from '@grafana/runtime'; +import store from 'app/core/store'; import { extractFieldsTransformer } from 'app/features/transformers/extractFields/extractFields'; import { Logs } from './Logs'; import { visualisationTypeKey } from './utils/logs'; import { getMockElasticFrame, getMockLokiFrame } from './utils/testMocks.test'; +jest.mock('app/core/store', () => { + return { + getBool: jest.fn(), + getObject: jest.fn((_a, b) => b), + get: jest.fn(), + set: jest.fn(), + }; +}); + const reportInteraction = jest.fn(); jest.mock('@grafana/runtime', () => ({ ...jest.requireActual('@grafana/runtime'), @@ -379,7 +389,13 @@ describe('Logs', () => { it('should call reportInteraction on permalinkClick', async () => { const panelState = { logs: { id: 'not-included' } }; - setup({ loading: false, panelState }); + const rows = [ + makeLog({ uid: '1', rowId: 'id1', timeEpochMs: 4 }), + makeLog({ uid: '2', rowId: 'id2', timeEpochMs: 3 }), + makeLog({ uid: '3', rowId: 'id3', timeEpochMs: 2 }), + makeLog({ uid: '4', rowId: 'id3', timeEpochMs: 1 }), + ]; + setup({ loading: false, panelState, logRows: rows }); const row = screen.getAllByRole('row'); await userEvent.hover(row[0]); @@ -396,7 +412,13 @@ describe('Logs', () => { it('should call createAndCopyShortLink on permalinkClick - logs', async () => { const panelState: Partial = { logs: { id: 'not-included', visualisationType: 'logs' } }; - setup({ loading: false, panelState }); + const rows = [ + makeLog({ uid: '1', rowId: 'id1', timeEpochMs: 1 }), + makeLog({ uid: '2', rowId: 'id2', timeEpochMs: 1 }), + makeLog({ uid: '3', rowId: 'id3', timeEpochMs: 2 }), + makeLog({ uid: '4', rowId: 'id3', timeEpochMs: 2 }), + ]; + setup({ loading: false, panelState, logRows: rows }); const row = screen.getAllByRole('row'); await userEvent.hover(row[0]); @@ -411,6 +433,34 @@ describe('Logs', () => { ); expect(createAndCopyShortLink).toHaveBeenCalledWith(expect.stringMatching('visualisationType%22:%22logs')); }); + + it('should call createAndCopyShortLink on permalinkClick - with infinite scrolling', async () => { + const featureToggleValue = config.featureToggles.logsInfiniteScrolling; + config.featureToggles.logsInfiniteScrolling = true; + const rows = [ + makeLog({ uid: '1', rowId: 'id1', timeEpochMs: 1 }), + makeLog({ uid: '2', rowId: 'id2', timeEpochMs: 1 }), + makeLog({ uid: '3', rowId: 'id3', timeEpochMs: 2 }), + makeLog({ uid: '4', rowId: 'id3', timeEpochMs: 2 }), + ]; + + const panelState: Partial = { logs: { id: 'not-included', visualisationType: 'logs' } }; + setup({ loading: false, panelState, logRows: rows }); + + const row = screen.getAllByRole('row'); + await userEvent.hover(row[3]); + + const linkButton = screen.getByLabelText('Copy shortlink'); + await userEvent.click(linkButton); + + expect(createAndCopyShortLink).toHaveBeenCalledWith( + expect.stringMatching( + 'range%22:%7B%22from%22:%222019-01-01T10:00:00.000Z%22,%22to%22:%221970-01-01T00:00:00.002Z%22%7D' + ) + ); + expect(createAndCopyShortLink).toHaveBeenCalledWith(expect.stringMatching('visualisationType%22:%22logs')); + config.featureToggles.logsInfiniteScrolling = featureToggleValue; + }); }); describe('with table visualisation', () => { @@ -441,17 +491,23 @@ describe('Logs', () => { }); it('should use default state from localstorage - table', async () => { + const oldGet = store.get; + store.get = jest.fn().mockReturnValue('table'); localStorage.setItem(visualisationTypeKey, 'table'); setup({}); const table = await screen.findByTestId('logRowsTable'); expect(table).toBeInTheDocument(); + store.get = oldGet; }); it('should use default state from localstorage - logs', async () => { + const oldGet = store.get; + store.get = jest.fn().mockReturnValue('logs'); localStorage.setItem(visualisationTypeKey, 'logs'); setup({}); const table = await screen.findByTestId('logRows'); expect(table).toBeInTheDocument(); + store.get = oldGet; }); it('should change visualisation to table on toggle (elastic)', async () => { diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index b53337db900..c5faf08dc5a 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -437,6 +437,16 @@ class UnthemedLogs extends PureComponent { }; }; + getPreviousLog(row: LogRowModel, allLogs: LogRowModel[]): LogRowModel | null { + for (let i = allLogs.indexOf(row) - 1; i >= 0; i--) { + if (allLogs[i].timeEpochMs > row.timeEpochMs) { + return allLogs[i]; + } + } + + return null; + } + getPermalinkRange(row: LogRowModel) { const range = { from: new Date(this.props.absoluteRange.from).toISOString(), @@ -449,7 +459,7 @@ class UnthemedLogs extends PureComponent { // With infinite scrolling, the time range of the log line can be after the absolute range or beyond the request line limit, so we need to adjust // Look for the previous sibling log, and use its timestamp const allLogs = this.props.logRows.filter((logRow) => logRow.dataFrame.refId === row.dataFrame.refId); - const prevLog = allLogs[allLogs.indexOf(row) - 1]; + const prevLog = this.getPreviousLog(row, allLogs); if (row.timeEpochMs > this.props.absoluteRange.to && !prevLog) { // Because there's no sibling and the current `to` is oldest than the log, we have no reference we can use for the interval