Loki: Open log context in new tab (#79723)

* pass `getLogRowContextQuery` to component

* reset `appliedContextFilters` in `getLogRowContextQuery`

* open show context in new tab

* open window before crafting url

* only open in new tab if getRowContextQuery is set

* only open `about:blank`

* change conditional
This commit is contained in:
Sven Grossmann 2023-12-20 22:10:29 +01:00 committed by GitHub
parent d195c3807e
commit f05cbe589a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 70 additions and 10 deletions

View File

@ -791,6 +791,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
onClickFilterLabel={onClickFilterLabel}
onClickFilterOutLabel={onClickFilterOutLabel}
showContextToggle={showContextToggle}
getRowContextQuery={getRowContextQuery}
showLabels={showLabels}
showTime={showTime}
enableLogDetails={true}

View File

@ -3,10 +3,20 @@ import { debounce } from 'lodash';
import memoizeOne from 'memoize-one';
import React, { PureComponent, MouseEvent } from 'react';
import { Field, LinkModel, LogRowModel, LogsSortOrder, dateTimeFormat, CoreApp, DataFrame } from '@grafana/data';
import {
Field,
LinkModel,
LogRowModel,
LogsSortOrder,
dateTimeFormat,
CoreApp,
DataFrame,
LogRowContextOptions,
} from '@grafana/data';
import { reportInteraction } from '@grafana/runtime';
import { TimeZone } from '@grafana/schema';
import { DataQuery, TimeZone } from '@grafana/schema';
import { withTheme2, Themeable2, Icon, Tooltip } from '@grafana/ui';
import { LokiQuery } from 'app/plugins/datasource/loki/types';
import { checkLogsError, escapeUnescapedString } from '../utils';
@ -39,6 +49,11 @@ interface Props extends Themeable2 {
onClickHideField?: (key: string) => void;
onLogRowHover?: (row?: LogRowModel) => void;
onOpenContext: (row: LogRowModel, onClose: () => void) => void;
getRowContextQuery?: (
row: LogRowModel,
options?: LogRowContextOptions,
origQuery?: LokiQuery
) => Promise<DataQuery | null>;
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
styles: LogRowStyles;
permalinkedRowId?: string;
@ -202,6 +217,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
forceEscape,
app,
styles,
getRowContextQuery,
} = this.props;
const { showDetails, showingContext, permalinked } = this.state;
const levelStyles = getLogLevelStyles(theme, row.logLevel);
@ -276,6 +292,7 @@ class UnThemedLogRow extends PureComponent<Props, State> {
<LogRowMessage
row={processedRow}
showContextToggle={showContextToggle}
getRowContextQuery={getRowContextQuery}
wrapLogMessage={wrapLogMessage}
prettifyLogMessage={prettifyLogMessage}
onOpenContext={this.onOpenContext}

View File

@ -1,7 +1,9 @@
import React, { FocusEvent, SyntheticEvent, useCallback } from 'react';
import { LogRowModel } from '@grafana/data';
import { LogRowContextOptions, LogRowModel, getDefaultTimeRange, locationUtil, urlUtil } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { ClipboardButton, IconButton } from '@grafana/ui';
import { getConfig } from 'app/core/config';
import { LogRowStyles } from './getLogRowStyles';
@ -10,6 +12,7 @@ interface Props {
row: LogRowModel;
showContextToggle?: (row: LogRowModel) => boolean;
onOpenContext: (row: LogRowModel) => void;
getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>;
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
@ -32,17 +35,39 @@ export const LogRowMenuCell = React.memo(
styles,
mouseIsOver,
onBlur,
getRowContextQuery,
}: Props) => {
const shouldShowContextToggle = showContextToggle ? showContextToggle(row) : false;
const onLogRowClick = useCallback((e: SyntheticEvent) => {
e.stopPropagation();
}, []);
const onShowContextClick = useCallback(
(e: SyntheticEvent<HTMLElement, Event>) => {
e.stopPropagation();
async (event: SyntheticEvent<HTMLButtonElement, MouseEvent>) => {
event.stopPropagation();
// if ctrl or meta key is pressed, open query in new Explore tab
if (
getRowContextQuery &&
(event.nativeEvent.ctrlKey || event.nativeEvent.metaKey || event.nativeEvent.shiftKey)
) {
const win = window.open('about:blank');
const query = await getRowContextQuery(row);
if (query && win) {
const url = urlUtil.renderUrl(locationUtil.assureBaseUrl(`${getConfig().appSubUrl}explore`), {
left: JSON.stringify({
datasource: query.datasource,
queries: [query],
range: getDefaultTimeRange(),
}),
});
win.location = url;
return;
}
win?.close();
}
onOpenContext(row);
},
[onOpenContext, row]
[onOpenContext, getRowContextQuery, row]
);
/**
* For better accessibility support, we listen to the onBlur event here (to hide this component), and

View File

@ -1,7 +1,8 @@
import React, { useMemo } from 'react';
import Highlighter from 'react-highlight-words';
import { CoreApp, findHighlightChunksInText, LogRowModel } from '@grafana/data';
import { CoreApp, findHighlightChunksInText, LogRowContextOptions, LogRowModel } from '@grafana/data';
import { DataQuery } from '@grafana/schema';
import { LogMessageAnsi } from './LogMessageAnsi';
import { LogRowMenuCell } from './LogRowMenuCell';
@ -16,6 +17,7 @@ interface Props {
app?: CoreApp;
showContextToggle?: (row: LogRowModel) => boolean;
onOpenContext: (row: LogRowModel) => void;
getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>;
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
onPinLine?: (row: LogRowModel) => void;
onUnpinLine?: (row: LogRowModel) => void;
@ -77,6 +79,7 @@ export const LogRowMessage = React.memo((props: Props) => {
pinned,
mouseIsOver,
onBlur,
getRowContextQuery,
} = props;
const { hasAnsi, raw } = row;
const restructuredEntry = useMemo(() => restructureLog(raw, prettifyLogMessage), [raw, prettifyLogMessage]);
@ -100,6 +103,7 @@ export const LogRowMessage = React.memo((props: Props) => {
logText={restructuredEntry}
row={row}
showContextToggle={showContextToggle}
getRowContextQuery={getRowContextQuery}
onOpenContext={onOpenContext}
onPermalinkClick={onPermalinkClick}
onPinLine={onPinLine}

View File

@ -11,8 +11,10 @@ import {
LogsSortOrder,
CoreApp,
DataFrame,
LogRowContextOptions,
} from '@grafana/data';
import { config } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import { withTheme2, Themeable2 } from '@grafana/ui';
import { PopoverMenu } from '../../explore/Logs/PopoverMenu';
@ -50,6 +52,7 @@ export interface Props extends Themeable2 {
onUnpinLine?: (row: LogRowModel) => void;
onLogRowHover?: (row?: LogRowModel) => void;
onOpenContext?: (row: LogRowModel, onClose: () => void) => void;
getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>;
onPermalinkClick?: (row: LogRowModel) => Promise<void>;
permalinkedRowId?: string;
scrollIntoView?: (element: HTMLElement) => void;

View File

@ -130,18 +130,22 @@ describe('LogContextProvider', () => {
direction: LogRowContextQueryDirection.Backward,
});
expect(query.expr).toBe('{bar="baz"}');
expect(logContextProvider.getInitContextFilters).toHaveBeenCalled();
});
it('should not call getInitContextFilters if appliedContextFilters', async () => {
it('should also call getInitContextFilters if appliedContextFilters is set', async () => {
logContextProvider.getInitContextFilters = jest
.fn()
.mockResolvedValue([{ value: 'baz', enabled: true, fromParser: false, label: 'bar' }]);
logContextProvider.appliedContextFilters = [
{ value: 'baz', enabled: true, fromParser: false, label: 'bar' },
{ value: 'abc', enabled: true, fromParser: false, label: 'xyz' },
];
const query = await logContextProvider.getLogRowContextQuery(defaultLogRow, {
await logContextProvider.getLogRowContextQuery(defaultLogRow, {
limit: 10,
direction: LogRowContextQueryDirection.Backward,
});
expect(query.expr).toBe('{bar="baz",xyz="abc"}');
expect(logContextProvider.getInitContextFilters).toHaveBeenCalled();
});
});

View File

@ -77,7 +77,13 @@ export class LogContextProvider {
options?: LogRowContextOptions,
origQuery?: LokiQuery
): Promise<LokiQuery> => {
// FIXME: This is a hack to make sure that the context query is created with
// the correct set of filters. The whole `appliedContextFilters` property
// should be revisted.
const cachedFilters = this.appliedContextFilters;
this.appliedContextFilters = [];
const { query } = await this.getQueryAndRange(row, options, origQuery);
this.appliedContextFilters = cachedFilters;
return query;
};