From f05cbe589a62f7597a6f4a622ee6344549dda549 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Wed, 20 Dec 2023 22:10:29 +0100 Subject: [PATCH] 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 --- public/app/features/explore/Logs/Logs.tsx | 1 + .../app/features/logs/components/LogRow.tsx | 21 ++++++++++-- .../logs/components/LogRowMenuCell.tsx | 33 ++++++++++++++++--- .../logs/components/LogRowMessage.tsx | 6 +++- .../app/features/logs/components/LogRows.tsx | 3 ++ .../loki/LogContextProvider.test.ts | 10 ++++-- .../datasource/loki/LogContextProvider.ts | 6 ++++ 7 files changed, 70 insertions(+), 10 deletions(-) diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 293ca610a56..8ce7e9d54aa 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -791,6 +791,7 @@ class UnthemedLogs extends PureComponent { onClickFilterLabel={onClickFilterLabel} onClickFilterOutLabel={onClickFilterOutLabel} showContextToggle={showContextToggle} + getRowContextQuery={getRowContextQuery} showLabels={showLabels} showTime={showTime} enableLogDetails={true} diff --git a/public/app/features/logs/components/LogRow.tsx b/public/app/features/logs/components/LogRow.tsx index 04d61a3cc5c..970b7615c80 100644 --- a/public/app/features/logs/components/LogRow.tsx +++ b/public/app/features/logs/components/LogRow.tsx @@ -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; onPermalinkClick?: (row: LogRowModel) => Promise; styles: LogRowStyles; permalinkedRowId?: string; @@ -202,6 +217,7 @@ class UnThemedLogRow extends PureComponent { 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 { boolean; onOpenContext: (row: LogRowModel) => void; + getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise; onPermalinkClick?: (row: LogRowModel) => Promise; 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) => { - e.stopPropagation(); + async (event: SyntheticEvent) => { + 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 diff --git a/public/app/features/logs/components/LogRowMessage.tsx b/public/app/features/logs/components/LogRowMessage.tsx index fd2cf4a7abe..cfef53686c9 100644 --- a/public/app/features/logs/components/LogRowMessage.tsx +++ b/public/app/features/logs/components/LogRowMessage.tsx @@ -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; onPermalinkClick?: (row: LogRowModel) => Promise; 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} diff --git a/public/app/features/logs/components/LogRows.tsx b/public/app/features/logs/components/LogRows.tsx index c04217e3ab9..bebe6f04fac 100644 --- a/public/app/features/logs/components/LogRows.tsx +++ b/public/app/features/logs/components/LogRows.tsx @@ -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; onPermalinkClick?: (row: LogRowModel) => Promise; permalinkedRowId?: string; scrollIntoView?: (element: HTMLElement) => void; diff --git a/public/app/plugins/datasource/loki/LogContextProvider.test.ts b/public/app/plugins/datasource/loki/LogContextProvider.test.ts index 1aa9aad86ca..24650006511 100644 --- a/public/app/plugins/datasource/loki/LogContextProvider.test.ts +++ b/public/app/plugins/datasource/loki/LogContextProvider.test.ts @@ -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(); }); }); diff --git a/public/app/plugins/datasource/loki/LogContextProvider.ts b/public/app/plugins/datasource/loki/LogContextProvider.ts index 02506ab82fc..561013a0ac3 100644 --- a/public/app/plugins/datasource/loki/LogContextProvider.ts +++ b/public/app/plugins/datasource/loki/LogContextProvider.ts @@ -77,7 +77,13 @@ export class LogContextProvider { options?: LogRowContextOptions, origQuery?: LokiQuery ): Promise => { + // 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; };