Logs: Add tracking for permalinking (#70334)

* add permalink tracking

* move location override to beforeAll

* add `logLevel` to tracking
This commit is contained in:
Sven Grossmann
2023-06-20 14:55:51 +02:00
committed by GitHub
parent 5c4571f231
commit 90b621be20
4 changed files with 111 additions and 3 deletions
@@ -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<string, unknown> | 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<ComponentProps<typeof Logs>>, 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'
);
});
});
});
@@ -368,6 +368,12 @@ class UnthemedLogs extends PureComponent<Props, State> {
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) => {
@@ -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<string, unknown> | undefined) =>
reportInteraction(interactionName, properties),
}));
const theme = createTheme();
const styles = getLogRowStyles(theme);
const setup = (propOverrides?: Partial<ComponentProps<typeof LogRow>>, rowOverrides?: Partial<LogRowModel>) => {
@@ -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');
@@ -125,7 +125,9 @@ class UnThemedLogRow extends PureComponent<Props, State> {
}
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<Props, State> {
}
// 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 });
}
};