From 90b621be2078a95b1707b92ba0db8631886d39dc Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Tue, 20 Jun 2023 14:55:51 +0200 Subject: [PATCH] Logs: Add tracking for permalinking (#70334) * add permalink tracking * move location override to beforeAll * add `logLevel` to tracking --- .../app/features/explore/Logs/Logs.test.tsx | 80 +++++++++++++++++++ public/app/features/explore/Logs/Logs.tsx | 6 ++ .../features/logs/components/LogRow.test.tsx | 16 ++++ .../app/features/logs/components/LogRow.tsx | 12 ++- 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/public/app/features/explore/Logs/Logs.test.tsx b/public/app/features/explore/Logs/Logs.test.tsx index e79059e8c9c..7c1d813aa25 100644 --- a/public/app/features/explore/Logs/Logs.test.tsx +++ b/public/app/features/explore/Logs/Logs.test.tsx @@ -15,6 +15,34 @@ import { ExploreId } from 'app/types'; import { Logs } from './Logs'; +const reportInteraction = jest.fn(); +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: (interactionName: string, properties?: Record | undefined) => + reportInteraction(interactionName, properties), +})); + +const createAndCopyShortLink = jest.fn(); +jest.mock('app/core/utils/shortLinks', () => ({ + ...jest.requireActual('app/core/utils/shortLinks'), + createAndCopyShortLink: (url: string) => createAndCopyShortLink(url), +})); + +jest.mock('app/store/store', () => ({ + getState: jest.fn().mockReturnValue({ + explore: { + panes: { + left: { + datasource: 'id', + queries: [{ refId: 'A', expr: '', queryType: 'range', datasource: { type: 'loki', uid: 'id' } }], + range: { raw: { from: 'now-1h', to: 'now' } }, + }, + }, + }, + }), + dispatch: jest.fn(), +})); + const changePanelState = jest.fn(); jest.mock('../state/explorePane', () => ({ ...jest.requireActual('../state/explorePane'), @@ -24,10 +52,30 @@ jest.mock('../state/explorePane', () => ({ })); describe('Logs', () => { + let originalHref = window.location.href; + beforeEach(() => { jest.clearAllMocks(); }); + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + href: 'http://localhost:3000/explore?test', + }, + writable: true, + }); + }); + + afterAll(() => { + Object.defineProperty(window, 'location', { + value: { + href: originalHref, + }, + writable: true, + }); + }); + const getComponent = (partialProps?: Partial>, logs?: LogRowModel[]) => { const rows = [ makeLog({ uid: '1', timeEpochMs: 1 }), @@ -243,6 +291,38 @@ describe('Logs', () => { expect(scrollElementMock.scroll).not.toHaveBeenCalled(); }); + + it('should call reportInteraction on permalinkClick', async () => { + const panelState = { logs: { id: 'not-included' } }; + setup({ loading: false, panelState }); + + const row = screen.getAllByRole('row'); + await userEvent.hover(row[0]); + + const linkButtons = row[1].querySelectorAll('button'); + await userEvent.click(linkButtons[2]); + + expect(reportInteraction).toHaveBeenCalledWith('grafana_explore_logs_permalink_clicked', { + datasourceType: 'unknown', + logRowUid: '2', + logRowLevel: 'debug', + }); + }); + + it('should call createAndCopyShortLink on permalinkClick', async () => { + const panelState = { logs: { id: 'not-included' } }; + setup({ loading: false, panelState }); + + const row = screen.getAllByRole('row'); + await userEvent.hover(row[0]); + + const linkButtons = row[1].querySelectorAll('button'); + await userEvent.click(linkButtons[2]); + + expect(createAndCopyShortLink).toHaveBeenCalledWith( + 'http://localhost:3000/explore?left=%7B%22datasource%22:%22%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22expr%22:%22%22,%22queryType%22:%22range%22,%22datasource%22:%7B%22type%22:%22loki%22,%22uid%22:%22id%22%7D%7D%5D,%22range%22:%7B%22from%22:%222019-01-01T10:00:00.000Z%22,%22to%22:%222019-01-01T16:00:00.000Z%22%7D,%22panelsState%22:%7B%22logs%22:%7B%22id%22:%222%22%7D%7D%7D' + ); + }); }); }); diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index a1e1c799a19..e76b01064a9 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -368,6 +368,12 @@ class UnthemedLogs extends PureComponent { const baseUrl = /.*(?=\/explore)/.exec(`${window.location.href}`)![0]; const url = urlUtil.renderUrl(`${baseUrl}/explore`, { left: serializedState }); await createAndCopyShortLink(url); + + reportInteraction('grafana_explore_logs_permalink_clicked', { + datasourceType: row.datasourceType ?? 'unknown', + logRowUid: row.uid, + logRowLevel: row.logLevel, + }); }; scrollIntoView = (element: HTMLElement) => { diff --git a/public/app/features/logs/components/LogRow.test.tsx b/public/app/features/logs/components/LogRow.test.tsx index 71af96525b8..dd5a2ac3dba 100644 --- a/public/app/features/logs/components/LogRow.test.tsx +++ b/public/app/features/logs/components/LogRow.test.tsx @@ -9,6 +9,13 @@ import { LogRow } from './LogRow'; import { createLogRow } from './__mocks__/logRow'; import { getLogRowStyles } from './getLogRowStyles'; +const reportInteraction = jest.fn(); +jest.mock('@grafana/runtime', () => ({ + ...jest.requireActual('@grafana/runtime'), + reportInteraction: (interactionName: string, properties?: Record | undefined) => + reportInteraction(interactionName, properties), +})); + const theme = createTheme(); const styles = getLogRowStyles(theme); const setup = (propOverrides?: Partial>, rowOverrides?: Partial) => { @@ -52,6 +59,15 @@ describe('LogRow', () => { }); describe('with permalinking', () => { + it('reports via feature tracking when log line matches', () => { + const scrollIntoView = jest.fn(); + setup({ permalinkedRowId: 'log-row-id', scrollIntoView }); + expect(reportInteraction).toHaveBeenCalledWith('grafana_explore_logs_permalink_opened', { + logRowUid: 'log-row-id', + datasourceType: 'unknown', + }); + }); + it('highlights row with same permalink-id', () => { const { container } = setup({ permalinkedRowId: 'log-row-id' }); const row = container.querySelector('tr'); diff --git a/public/app/features/logs/components/LogRow.tsx b/public/app/features/logs/components/LogRow.tsx index f270e5f4c7a..f28a511779f 100644 --- a/public/app/features/logs/components/LogRow.tsx +++ b/public/app/features/logs/components/LogRow.tsx @@ -125,7 +125,9 @@ class UnThemedLogRow extends PureComponent { } scrollToLogRow = (prevState: State, mounted = false) => { - if (this.props.permalinkedRowId !== this.props.row.uid) { + const { row, permalinkedRowId, scrollIntoView } = this.props; + + if (permalinkedRowId !== row.uid) { // only set the new state if the row is not permalinked anymore or if the component was mounted. if (prevState.highlightBackround || mounted) { this.setState({ highlightBackround: false }); @@ -134,10 +136,14 @@ class UnThemedLogRow extends PureComponent { } // at this point this row is the permalinked row, so we need to scroll to it and highlight it if possible. - if (this.logLineRef.current && this.props.scrollIntoView) { - this.props.scrollIntoView(this.logLineRef.current); + if (this.logLineRef.current && scrollIntoView) { + scrollIntoView(this.logLineRef.current); } if (!this.state.highlightBackround) { + reportInteraction('grafana_explore_logs_permalink_opened', { + datasourceType: row.datasourceType ?? 'unknown', + logRowUid: row.uid, + }); this.setState({ highlightBackround: true }); } };