mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Log Context: Add button to open the context query in a split view (#66777)
* add show context button * improve type definition * change to default `maxLines` * remove context query * add provider to tests * add test for split view button * improve documentation * add tests for `getLogRowContextQuery` * refactor LogsContainer functions * fix spelling * add `contextQuery` as state * fix tests * fix lint * do not use callIfDefined * make button secondary
This commit is contained in:
@@ -2759,9 +2759,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"]
|
||||
],
|
||||
"public/app/features/explore/LogsContainer.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/explore/LogsMetaRow.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
|
||||
@@ -131,6 +131,11 @@ export interface DataSourceWithLogsContextSupport<TQuery extends DataQuery = Dat
|
||||
*/
|
||||
getLogRowContext: (row: LogRowModel, options?: LogRowContextOptions, query?: TQuery) => Promise<DataQueryResponse>;
|
||||
|
||||
/**
|
||||
* Retrieve the context query object for a given log row. This is currently used to open LogContext queries in a split view.
|
||||
*/
|
||||
getLogRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions, query?: TQuery) => Promise<TQuery | null>;
|
||||
|
||||
/**
|
||||
* This method can be used to show "context" button based on runtime conditions (for example row model data or plugin settings, etc.)
|
||||
*/
|
||||
|
||||
@@ -79,6 +79,7 @@ interface Props extends Themeable2 {
|
||||
onStartScanning?: () => void;
|
||||
onStopScanning?: () => void;
|
||||
getRowContext?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<any>;
|
||||
getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>;
|
||||
getLogRowContextUi?: (row: LogRowModel, runContextQuery?: () => void) => React.ReactNode;
|
||||
getFieldLinks: (field: Field, rowIndex: number, dataFrame: DataFrame) => Array<LinkModel<Field>>;
|
||||
addResultsToCache: () => void;
|
||||
@@ -377,6 +378,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
|
||||
exploreId,
|
||||
getRowContext,
|
||||
getLogRowContextUi,
|
||||
getRowContextQuery,
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
@@ -412,6 +414,7 @@ class UnthemedLogs extends PureComponent<Props, State> {
|
||||
row={contextRow}
|
||||
onClose={this.onCloseContext}
|
||||
getRowContext={getRowContext}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
getLogRowContextUi={getLogRowContextUi}
|
||||
logsSortOrder={logsSortOrder}
|
||||
timeZone={timeZone}
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
DataFrame,
|
||||
SupplementaryQueryType,
|
||||
DataQueryResponse,
|
||||
LogRowContextOptions,
|
||||
DataSourceWithLogsContextSupport,
|
||||
DataSourceApi,
|
||||
} from '@grafana/data';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { Collapse } from '@grafana/ui';
|
||||
import { StoreState } from 'app/types';
|
||||
import { ExploreId, ExploreItemState } from 'app/types/explore';
|
||||
@@ -49,28 +53,45 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
|
||||
updateTimeRange({ exploreId, absoluteRange });
|
||||
};
|
||||
|
||||
getLogRowContext = async (row: LogRowModel, options?: any): Promise<DataQueryResponse | []> => {
|
||||
private getQuery(
|
||||
logsQueries: DataQuery[] | undefined,
|
||||
row: LogRowModel,
|
||||
datasourceInstance: DataSourceApi<DataQuery> & DataSourceWithLogsContextSupport<DataQuery>
|
||||
) {
|
||||
// we need to find the query, and we need to be very sure that it's a query
|
||||
// from this datasource
|
||||
return (logsQueries ?? []).find(
|
||||
(q) => q.refId === row.dataFrame.refId && q.datasource != null && q.datasource.type === datasourceInstance.type
|
||||
);
|
||||
}
|
||||
|
||||
getLogRowContext = async (row: LogRowModel, options?: LogRowContextOptions): Promise<DataQueryResponse | []> => {
|
||||
const { datasourceInstance, logsQueries } = this.props;
|
||||
|
||||
if (hasLogsContextSupport(datasourceInstance)) {
|
||||
// we need to find the query, and we need to be very sure that
|
||||
// it's a query from this datasource
|
||||
const query = (logsQueries ?? []).find(
|
||||
(q) => q.refId === row.dataFrame.refId && q.datasource != null && q.datasource.type === datasourceInstance.type
|
||||
);
|
||||
const query = this.getQuery(logsQueries, row, datasourceInstance);
|
||||
return datasourceInstance.getLogRowContext(row, options, query);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
getLogRowContextQuery = async (row: LogRowModel, options?: LogRowContextOptions): Promise<DataQuery | null> => {
|
||||
const { datasourceInstance, logsQueries } = this.props;
|
||||
|
||||
if (hasLogsContextSupport(datasourceInstance) && datasourceInstance.getLogRowContextQuery) {
|
||||
const query = this.getQuery(logsQueries, row, datasourceInstance);
|
||||
return datasourceInstance.getLogRowContextQuery(row, options, query);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
getLogRowContextUi = (row: LogRowModel, runContextQuery?: () => void): React.ReactNode => {
|
||||
const { datasourceInstance, logsQueries } = this.props;
|
||||
|
||||
if (hasLogsContextUiSupport(datasourceInstance) && datasourceInstance.getLogRowContextUi) {
|
||||
const query = (logsQueries ?? []).find(
|
||||
(q) => q.refId === row.dataFrame.refId && q.datasource != null && q.datasource.type === datasourceInstance.type
|
||||
);
|
||||
const query = this.getQuery(logsQueries, row, datasourceInstance);
|
||||
return datasourceInstance.getLogRowContextUi(row, runContextQuery, query);
|
||||
}
|
||||
|
||||
@@ -174,6 +195,7 @@ class LogsContainer extends PureComponent<LogsContainerProps> {
|
||||
scanRange={range.raw}
|
||||
showContextToggle={this.showContextToggle}
|
||||
getRowContext={this.getLogRowContext}
|
||||
getRowContextQuery={this.getLogRowContextQuery}
|
||||
getLogRowContextUi={this.getLogRowContextUi}
|
||||
getFieldLinks={this.getFieldLinks}
|
||||
addResultsToCache={() => addResultsToCache(exploreId)}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { render } from 'test/redux-rtl';
|
||||
|
||||
import { createLogRow } from '../__mocks__/logRow';
|
||||
|
||||
@@ -9,6 +10,18 @@ import { LogRowContextModal } from './LogRowContextModal';
|
||||
|
||||
const getRowContext = jest.fn().mockResolvedValue({ data: { fields: [], rows: [] } });
|
||||
|
||||
const dispatchMock = jest.fn();
|
||||
jest.mock('app/types', () => ({
|
||||
...jest.requireActual('app/types'),
|
||||
useDispatch: () => dispatchMock,
|
||||
}));
|
||||
|
||||
const splitOpen = Symbol('splitOpen');
|
||||
jest.mock('app/features/explore/state/main', () => ({
|
||||
...jest.requireActual('app/features/explore/state/main'),
|
||||
splitOpen: () => splitOpen,
|
||||
}));
|
||||
|
||||
const row = createLogRow({ uid: '1' });
|
||||
|
||||
const timeZone = 'UTC';
|
||||
@@ -24,12 +37,20 @@ describe('LogRowContextModal', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should not render modal when it is closed', () => {
|
||||
render(
|
||||
<LogRowContextModal row={row} open={false} onClose={() => {}} getRowContext={getRowContext} timeZone={timeZone} />
|
||||
);
|
||||
it('should not render modal when it is closed', async () => {
|
||||
act(() => {
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={false}
|
||||
onClose={() => {}}
|
||||
getRowContext={getRowContext}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Log context')).not.toBeInTheDocument();
|
||||
await waitFor(() => expect(screen.queryByText('Log context')).not.toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should render modal when it is open', async () => {
|
||||
@@ -48,12 +69,20 @@ describe('LogRowContextModal', () => {
|
||||
await waitFor(() => expect(screen.queryByText('Log context')).toBeInTheDocument());
|
||||
});
|
||||
|
||||
it('should call getRowContext on open and change of row', () => {
|
||||
render(
|
||||
<LogRowContextModal row={row} open={false} onClose={() => {}} getRowContext={getRowContext} timeZone={timeZone} />
|
||||
);
|
||||
it('should call getRowContext on open and change of row', async () => {
|
||||
act(() => {
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={false}
|
||||
onClose={() => {}}
|
||||
getRowContext={getRowContext}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(getRowContext).not.toHaveBeenCalled();
|
||||
await waitFor(() => expect(getRowContext).not.toHaveBeenCalled());
|
||||
});
|
||||
it('should call getRowContext on open', async () => {
|
||||
act(() => {
|
||||
@@ -97,4 +126,102 @@ describe('LogRowContextModal', () => {
|
||||
|
||||
await waitFor(() => expect(getRowContext).toHaveBeenCalledTimes(4));
|
||||
});
|
||||
|
||||
it('should show a split view button', async () => {
|
||||
const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
|
||||
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={true}
|
||||
onClose={() => {}}
|
||||
getRowContext={getRowContext}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: /open in split view/i,
|
||||
})
|
||||
).toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
|
||||
it('should not show a split view button', async () => {
|
||||
render(
|
||||
<LogRowContextModal row={row} open={true} onClose={() => {}} getRowContext={getRowContext} timeZone={timeZone} />
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole('button', {
|
||||
name: /open in split view/i,
|
||||
})
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call getRowContextQuery', async () => {
|
||||
const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={true}
|
||||
onClose={() => {}}
|
||||
getRowContext={getRowContext}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => expect(getRowContextQuery).toHaveBeenCalledTimes(1));
|
||||
});
|
||||
|
||||
it('should close modal', async () => {
|
||||
const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
|
||||
const onClose = jest.fn();
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
getRowContext={getRowContext}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
|
||||
const splitViewButton = await screen.findByRole('button', {
|
||||
name: /open in split view/i,
|
||||
});
|
||||
|
||||
await userEvent.click(splitViewButton);
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should dispatch splitOpen', async () => {
|
||||
const getRowContextQuery = jest.fn().mockResolvedValue({ datasource: { uid: 'test-uid' } });
|
||||
const onClose = jest.fn();
|
||||
|
||||
render(
|
||||
<LogRowContextModal
|
||||
row={row}
|
||||
open={true}
|
||||
onClose={onClose}
|
||||
getRowContext={getRowContext}
|
||||
getRowContextQuery={getRowContextQuery}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
);
|
||||
|
||||
const splitViewButton = await screen.findByRole('button', {
|
||||
name: /open in split view/i,
|
||||
});
|
||||
|
||||
await userEvent.click(splitViewButton);
|
||||
|
||||
await waitFor(() => expect(dispatchMock).toHaveBeenCalledWith(splitOpen));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useAsyncFn } from 'react-use';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { useAsync, useAsyncFn } from 'react-use';
|
||||
|
||||
import {
|
||||
DataQueryResponse,
|
||||
@@ -12,13 +12,16 @@ import {
|
||||
LogsDedupStrategy,
|
||||
LogsSortOrder,
|
||||
SelectableValue,
|
||||
rangeUtil,
|
||||
} from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { TimeZone } from '@grafana/schema';
|
||||
import { LoadingBar, Modal, useTheme2 } from '@grafana/ui';
|
||||
import { DataQuery, TimeZone } from '@grafana/schema';
|
||||
import { Button, LoadingBar, Modal, useTheme2 } from '@grafana/ui';
|
||||
import { dataFrameToLogsModel } from 'app/core/logsModel';
|
||||
import store from 'app/core/store';
|
||||
import { splitOpen } from 'app/features/explore/state/main';
|
||||
import { SETTINGS_KEYS } from 'app/features/explore/utils/logs';
|
||||
import { useDispatch } from 'app/types';
|
||||
|
||||
import { LogRows } from '../LogRows';
|
||||
|
||||
@@ -104,6 +107,8 @@ interface LogRowContextModalProps {
|
||||
timeZone: TimeZone;
|
||||
onClose: () => void;
|
||||
getRowContext: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQueryResponse>;
|
||||
|
||||
getRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions) => Promise<DataQuery | null>;
|
||||
logsSortOrder?: LogsSortOrder | null;
|
||||
runContextQuery?: () => void;
|
||||
getLogRowContextUi?: DataSourceWithLogsContextSupport['getLogRowContextUi'];
|
||||
@@ -113,10 +118,11 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
||||
row,
|
||||
open,
|
||||
logsSortOrder,
|
||||
timeZone,
|
||||
getLogRowContextUi,
|
||||
getRowContextQuery,
|
||||
onClose,
|
||||
getRowContext,
|
||||
timeZone,
|
||||
}) => {
|
||||
const scrollElement = React.createRef<HTMLDivElement>();
|
||||
const entryElement = React.createRef<HTMLTableRowElement>();
|
||||
@@ -125,12 +131,28 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
||||
// first.
|
||||
const preEntryElement = React.createRef<HTMLTableRowElement>();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
const [context, setContext] = useState<{ after: LogRowModel[]; before: LogRowModel[] }>({ after: [], before: [] });
|
||||
const [limit, setLimit] = useState<number>(LoadMoreOptions[0].value!);
|
||||
const [loadingWidth, setLoadingWidth] = useState(0);
|
||||
const [loadMoreOption, setLoadMoreOption] = useState<SelectableValue<number>>(LoadMoreOptions[0]);
|
||||
const [contextQuery, setContextQuery] = useState<DataQuery | null>(null);
|
||||
|
||||
const getFullTimeRange = useCallback(() => {
|
||||
const { before, after } = context;
|
||||
const allRows = [...before, row, ...after].sort((a, b) => a.timeEpochMs - b.timeEpochMs);
|
||||
const first = allRows[0];
|
||||
const last = allRows[allRows.length - 1];
|
||||
return rangeUtil.convertRawToRange(
|
||||
{
|
||||
from: first.timeUtc,
|
||||
to: last.timeUtc,
|
||||
},
|
||||
'utc'
|
||||
);
|
||||
}, [context, row]);
|
||||
|
||||
const onChangeLimitOption = (option: SelectableValue<number>) => {
|
||||
setLoadMoreOption(option);
|
||||
@@ -215,6 +237,11 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
||||
}
|
||||
}, [scrollElement]);
|
||||
|
||||
useAsync(async () => {
|
||||
const contextQuery = getRowContextQuery ? await getRowContextQuery(row) : null;
|
||||
setContextQuery(contextQuery);
|
||||
}, [getRowContextQuery, row]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={open}
|
||||
@@ -300,6 +327,25 @@ export const LogRowContextModal: React.FunctionComponent<LogRowContextModalProps
|
||||
Showing {context.before.length} lines {logsSortOrder === LogsSortOrder.Descending ? 'after' : 'before'} match.
|
||||
</div>
|
||||
</div>
|
||||
{contextQuery?.datasource?.uid && (
|
||||
<Modal.ButtonRow>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
dispatch(
|
||||
splitOpen({
|
||||
queries: [contextQuery],
|
||||
range: getFullTimeRange(),
|
||||
datasourceUid: contextQuery.datasource!.uid!,
|
||||
})
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Open in split view
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -71,6 +71,28 @@ describe('LogContextProvider', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLogRowContextQuery', () => {
|
||||
it('should call getInitContextFilters if no appliedContextFilters', async () => {
|
||||
const query = await logContextProvider.getLogRowContextQuery(defaultLogRow, {
|
||||
limit: 10,
|
||||
direction: LogRowContextQueryDirection.Backward,
|
||||
});
|
||||
expect(query.expr).toBe('{bar="baz"}');
|
||||
});
|
||||
|
||||
it('should not call getInitContextFilters if appliedContextFilters', async () => {
|
||||
logContextProvider.appliedContextFilters = [
|
||||
{ value: 'baz', enabled: true, fromParser: false, label: 'bar' },
|
||||
{ value: 'abc', enabled: true, fromParser: false, label: 'xyz' },
|
||||
];
|
||||
const query = await logContextProvider.getLogRowContextQuery(defaultLogRow, {
|
||||
limit: 10,
|
||||
direction: LogRowContextQueryDirection.Backward,
|
||||
});
|
||||
expect(query.expr).toBe('{bar="baz",xyz="abc"}');
|
||||
});
|
||||
});
|
||||
|
||||
describe('prepareLogRowContextQueryTarget', () => {
|
||||
describe('query with no parser', () => {
|
||||
const query = {
|
||||
|
||||
@@ -33,13 +33,9 @@ export class LogContextProvider {
|
||||
this.appliedContextFilters = [];
|
||||
}
|
||||
|
||||
getLogRowContext = async (
|
||||
row: LogRowModel,
|
||||
options?: LogRowContextOptions,
|
||||
origQuery?: DataQuery
|
||||
): Promise<{ data: DataFrame[] }> => {
|
||||
private async getQueryAndRange(row: LogRowModel, options?: LogRowContextOptions, origQuery?: DataQuery) {
|
||||
const direction = (options && options.direction) || LogRowContextQueryDirection.Backward;
|
||||
const limit = (options && options.limit) || 10;
|
||||
const limit = (options && options.limit) || this.datasource.maxLines;
|
||||
|
||||
// This happens only on initial load, when user haven't applied any filters yet
|
||||
// We need to get the initial filters from the row labels
|
||||
@@ -48,7 +44,27 @@ export class LogContextProvider {
|
||||
this.appliedContextFilters = filters;
|
||||
}
|
||||
|
||||
const { query, range } = await this.prepareLogRowContextQueryTarget(row, limit, direction, origQuery);
|
||||
return await this.prepareLogRowContextQueryTarget(row, limit, direction, origQuery);
|
||||
}
|
||||
|
||||
getLogRowContextQuery = async (
|
||||
row: LogRowModel,
|
||||
options?: LogRowContextOptions,
|
||||
origQuery?: DataQuery
|
||||
): Promise<LokiQuery> => {
|
||||
const { query } = await this.getQueryAndRange(row, options, origQuery);
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
getLogRowContext = async (
|
||||
row: LogRowModel,
|
||||
options?: LogRowContextOptions,
|
||||
origQuery?: DataQuery
|
||||
): Promise<{ data: DataFrame[] }> => {
|
||||
const direction = (options && options.direction) || LogRowContextQueryDirection.Backward;
|
||||
|
||||
const { query, range } = await this.getQueryAndRange(row, options, origQuery);
|
||||
|
||||
const processResults = (result: DataQueryResponse): DataQueryResponse => {
|
||||
const frames: DataFrame[] = result.data;
|
||||
@@ -101,6 +117,7 @@ export class LogContextProvider {
|
||||
refId: `${REF_ID_STARTER_LOG_ROW_CONTEXT}${row.dataFrame.refId || ''}`,
|
||||
maxLines: limit,
|
||||
direction: queryDirection,
|
||||
datasource: { uid: this.datasource.uid, type: this.datasource.type },
|
||||
};
|
||||
|
||||
const fieldCache = new FieldCache(row.dataFrame);
|
||||
|
||||
@@ -657,6 +657,14 @@ export class LokiDatasource
|
||||
return await this.logContextProvider.getLogRowContext(row, options, origQuery);
|
||||
};
|
||||
|
||||
getLogRowContextQuery = async (
|
||||
row: LogRowModel,
|
||||
options?: LogRowContextOptions,
|
||||
origQuery?: DataQuery
|
||||
): Promise<DataQuery> => {
|
||||
return await this.logContextProvider.getLogRowContextQuery(row, options, origQuery);
|
||||
};
|
||||
|
||||
getLogRowContextUi(row: LogRowModel, runContextQuery: () => void, origQuery: DataQuery): React.ReactNode {
|
||||
return this.logContextProvider.getLogRowContextUi(row, runContextQuery, origQuery);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user