mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Logs: Add show context to dashboard panel (#80403)
* Logs: Add show context to dashboard panel * add prop to enable show context toggle * update test * adjust tests * add query targets as a dependency * extract `useDatasourcesFromTargets` hook * add tests * remove comment
This commit is contained in:
parent
e3745b5fb8
commit
4e474161a1
@ -24,15 +24,16 @@ title: LogsPanelCfg kind
|
||||
|
||||
### Options
|
||||
|
||||
| Property | Type | Required | Default | Description |
|
||||
|----------------------|---------|----------|---------|---------------------------------------------------------------|
|
||||
| `dedupStrategy` | string | **Yes** | | Possible values are: `none`, `exact`, `numbers`, `signature`. |
|
||||
| `enableLogDetails` | boolean | **Yes** | | |
|
||||
| `prettifyLogMessage` | boolean | **Yes** | | |
|
||||
| `showCommonLabels` | boolean | **Yes** | | |
|
||||
| `showLabels` | boolean | **Yes** | | |
|
||||
| `showTime` | boolean | **Yes** | | |
|
||||
| `sortOrder` | string | **Yes** | | Possible values are: `Descending`, `Ascending`. |
|
||||
| `wrapLogMessage` | boolean | **Yes** | | |
|
||||
| Property | Type | Required | Default | Description |
|
||||
|------------------------|---------|----------|---------|---------------------------------------------------------------|
|
||||
| `dedupStrategy` | string | **Yes** | | Possible values are: `none`, `exact`, `numbers`, `signature`. |
|
||||
| `enableLogDetails` | boolean | **Yes** | | |
|
||||
| `prettifyLogMessage` | boolean | **Yes** | | |
|
||||
| `showCommonLabels` | boolean | **Yes** | | |
|
||||
| `showLabels` | boolean | **Yes** | | |
|
||||
| `showLogContextToggle` | boolean | **Yes** | | |
|
||||
| `showTime` | boolean | **Yes** | | |
|
||||
| `sortOrder` | string | **Yes** | | Possible values are: `Descending`, `Ascending`. |
|
||||
| `wrapLogMessage` | boolean | **Yes** | | |
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@ export interface Options {
|
||||
prettifyLogMessage: boolean;
|
||||
showCommonLabels: boolean;
|
||||
showLabels: boolean;
|
||||
showLogContextToggle: boolean;
|
||||
showTime: boolean;
|
||||
sortOrder: common.LogsSortOrder;
|
||||
wrapLogMessage: boolean;
|
||||
|
@ -1,11 +1,42 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { ComponentProps } from 'react';
|
||||
import { DatasourceSrvMock, MockDataSourceApi } from 'test/mocks/datasource_srv';
|
||||
|
||||
import { LoadingState, createDataFrame, FieldType, LogsSortOrder } from '@grafana/data';
|
||||
import { LoadingState, createDataFrame, FieldType, LogsSortOrder, CoreApp } from '@grafana/data';
|
||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||
|
||||
import { LogsPanel } from './LogsPanel';
|
||||
|
||||
type LogsPanelProps = ComponentProps<typeof LogsPanel>;
|
||||
type LogRowContextModalProps = ComponentProps<typeof LogRowContextModal>;
|
||||
|
||||
const logRowContextModalMock = jest.fn().mockReturnValue(<div>LogRowContextModal</div>);
|
||||
jest.mock('app/features/logs/components/log-context/LogRowContextModal', () => ({
|
||||
LogRowContextModal: (props: LogRowContextModalProps) => logRowContextModalMock(props),
|
||||
}));
|
||||
|
||||
const defaultDs = new MockDataSourceApi('default datasource', { data: ['default data'] });
|
||||
const noShowContextDs = new MockDataSourceApi('no-show-context');
|
||||
const showContextDs = new MockDataSourceApi('show-context') as MockDataSourceApi & { getLogRowContext: jest.Mock };
|
||||
|
||||
const datasourceSrv = new DatasourceSrvMock(defaultDs, {
|
||||
'no-show-context': noShowContextDs,
|
||||
'show-context': showContextDs,
|
||||
});
|
||||
const getDataSourceSrvMock = jest.fn().mockReturnValue(datasourceSrv);
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => getDataSourceSrvMock(),
|
||||
}));
|
||||
|
||||
const hasLogsContextSupport = jest.fn().mockImplementation((ds) => {
|
||||
return ds.name === 'show-context';
|
||||
});
|
||||
jest.mock('@grafana/data', () => ({
|
||||
...jest.requireActual('@grafana/data'),
|
||||
hasLogsContextSupport: (ds: MockDataSourceApi) => hasLogsContextSupport(ds),
|
||||
}));
|
||||
|
||||
describe('LogsPanel', () => {
|
||||
describe('when returned series include common labels', () => {
|
||||
@ -33,35 +64,37 @@ describe('LogsPanel', () => {
|
||||
}),
|
||||
];
|
||||
|
||||
it('shows common labels when showCommonLabels is set to true', () => {
|
||||
it('shows common labels when showCommonLabels is set to true', async () => {
|
||||
setup({ data: { series: seriesWithCommonLabels }, options: { showCommonLabels: true } });
|
||||
|
||||
expect(screen.getByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/common_app/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/common_job/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/common_app/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/common_job/i)).toBeInTheDocument();
|
||||
});
|
||||
it('shows common labels on top when descending sort order', () => {
|
||||
it('shows common labels on top when descending sort order', async () => {
|
||||
const { container } = setup({
|
||||
data: { series: seriesWithCommonLabels },
|
||||
options: { showCommonLabels: true, sortOrder: LogsSortOrder.Descending },
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(container.firstChild?.childNodes[0].textContent).toMatch(/^Common labels:common_appcommon_job/);
|
||||
});
|
||||
it('shows common labels on bottom when ascending sort order', () => {
|
||||
it('shows common labels on bottom when ascending sort order', async () => {
|
||||
const { container } = setup({
|
||||
data: { series: seriesWithCommonLabels },
|
||||
options: { showCommonLabels: true, sortOrder: LogsSortOrder.Ascending },
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(container.firstChild?.childNodes[0].textContent).toMatch(/Common labels:common_appcommon_job$/);
|
||||
});
|
||||
it('does not show common labels when showCommonLabels is set to false', () => {
|
||||
it('does not show common labels when showCommonLabels is set to false', async () => {
|
||||
setup({ data: { series: seriesWithCommonLabels }, options: { showCommonLabels: false } });
|
||||
|
||||
expect(screen.queryByText(/common labels:/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/common_app/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/common_job/i)).not.toBeInTheDocument();
|
||||
await waitFor(async () => {
|
||||
expect(screen.queryByText(/common labels:/i)).toBeNull();
|
||||
expect(screen.queryByText(/common_app/i)).toBeNull();
|
||||
expect(screen.queryByText(/common_job/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when returned series does not include common labels', () => {
|
||||
@ -84,15 +117,139 @@ describe('LogsPanel', () => {
|
||||
],
|
||||
}),
|
||||
];
|
||||
it('shows (no common labels) when showCommonLabels is set to true', () => {
|
||||
it('shows (no common labels) when showCommonLabels is set to true', async () => {
|
||||
setup({ data: { series: seriesWithoutCommonLabels }, options: { showCommonLabels: true } });
|
||||
expect(screen.getByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/(no common labels)/i)).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findByText(/common labels:/i)).toBeInTheDocument();
|
||||
expect(await screen.findByText(/(no common labels)/i)).toBeInTheDocument();
|
||||
});
|
||||
it('does not show common labels when showCommonLabels is set to false', () => {
|
||||
it('does not show common labels when showCommonLabels is set to false', async () => {
|
||||
setup({ data: { series: seriesWithoutCommonLabels }, options: { showCommonLabels: false } });
|
||||
expect(screen.queryByText(/common labels:/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/(no common labels)/i)).not.toBeInTheDocument();
|
||||
await waitFor(async () => {
|
||||
expect(screen.queryByText(/common labels:/i)).toBeNull();
|
||||
expect(screen.queryByText(/(no common labels)/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('log context', () => {
|
||||
const series = [
|
||||
createDataFrame({
|
||||
refId: 'A',
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
values: ['2019-04-26T09:28:11.352440161Z', '2019-04-26T14:42:50.991981292Z'],
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: FieldType.string,
|
||||
values: ['logline text'],
|
||||
labels: {
|
||||
app: 'common_app',
|
||||
job: 'common_job',
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
showContextDs.getLogRowContext = jest.fn().mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it('should not show the toggle if the datasource does not support show context', async () => {
|
||||
setup({
|
||||
data: {
|
||||
series,
|
||||
options: { showCommonLabels: false },
|
||||
request: {
|
||||
app: CoreApp.Dashboard,
|
||||
targets: [{ refId: 'A', datasource: { uid: 'no-show-context' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(screen.getByText(/logline text/i));
|
||||
expect(screen.queryByLabelText(/show context/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the toggle if the datasource does support show context', async () => {
|
||||
setup({
|
||||
data: {
|
||||
series,
|
||||
options: { showCommonLabels: false },
|
||||
request: {
|
||||
app: CoreApp.Dashboard,
|
||||
targets: [{ refId: 'A', datasource: { uid: 'show-context' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(screen.getByText(/logline text/i));
|
||||
expect(screen.getByLabelText(/show context/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show the toggle if the datasource does support show context but the app is not Dashboard', async () => {
|
||||
setup({
|
||||
data: {
|
||||
series,
|
||||
options: { showCommonLabels: false },
|
||||
request: {
|
||||
app: CoreApp.CloudAlerting,
|
||||
targets: [{ refId: 'A', datasource: { uid: 'show-context' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(screen.getByText(/logline text/i));
|
||||
expect(screen.queryByLabelText(/show context/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the mocked `LogRowContextModal` after click', async () => {
|
||||
setup({
|
||||
data: {
|
||||
series,
|
||||
options: { showCommonLabels: false },
|
||||
request: {
|
||||
app: CoreApp.Dashboard,
|
||||
targets: [{ refId: 'A', datasource: { uid: 'show-context' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(screen.getByText(/logline text/i));
|
||||
await userEvent.click(screen.getByLabelText(/show context/i));
|
||||
expect(screen.getByText(/LogRowContextModal/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call `getLogRowContext` if the user clicks the show context toggle', async () => {
|
||||
setup({
|
||||
data: {
|
||||
series,
|
||||
options: { showCommonLabels: false },
|
||||
request: {
|
||||
app: CoreApp.Dashboard,
|
||||
targets: [{ refId: 'A', datasource: { uid: 'show-context' } }],
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitFor(async () => {
|
||||
await userEvent.hover(screen.getByText(/logline text/i));
|
||||
await userEvent.click(screen.getByLabelText(/show context/i));
|
||||
|
||||
const getRowContextCb = logRowContextModalMock.mock.calls[0][0].getRowContext;
|
||||
getRowContextCb();
|
||||
expect(showContextDs.getLogRowContext).toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -11,9 +11,13 @@ import {
|
||||
DataHoverClearEvent,
|
||||
DataHoverEvent,
|
||||
CoreApp,
|
||||
DataQueryResponse,
|
||||
LogRowContextOptions,
|
||||
hasLogsContextSupport,
|
||||
} from '@grafana/data';
|
||||
import { CustomScrollbar, useStyles2, usePanelContext } from '@grafana/ui';
|
||||
import { getFieldLinksForExplore } from 'app/features/explore/utils/links';
|
||||
import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal';
|
||||
import { PanelDataErrorView } from 'app/features/panel/components/PanelDataErrorView';
|
||||
|
||||
import { LogLabels } from '../../../features/logs/components/LogLabels';
|
||||
@ -21,6 +25,7 @@ import { LogRows } from '../../../features/logs/components/LogRows';
|
||||
import { dataFrameToLogsModel, dedupLogRows, COMMON_LABELS } from '../../../features/logs/logsModel';
|
||||
|
||||
import { Options } from './types';
|
||||
import { useDatasourcesFromTargets } from './useDatasourcesFromTargets';
|
||||
|
||||
interface LogsPanelProps extends PanelProps<Options> {}
|
||||
|
||||
@ -37,14 +42,18 @@ export const LogsPanel = ({
|
||||
sortOrder,
|
||||
dedupStrategy,
|
||||
enableLogDetails,
|
||||
showLogContextToggle,
|
||||
},
|
||||
title,
|
||||
id,
|
||||
}: LogsPanelProps) => {
|
||||
const isAscending = sortOrder === LogsSortOrder.Ascending;
|
||||
const style = useStyles2(getStyles);
|
||||
const [scrollTop, setScrollTop] = useState(0);
|
||||
const logsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [contextRow, setContextRow] = useState<LogRowModel | null>(null);
|
||||
const [closeCallback, setCloseCallback] = useState<(() => void) | null>(null);
|
||||
|
||||
const dataSourcesMap = useDatasourcesFromTargets(data.request?.targets);
|
||||
|
||||
const { eventBus } = usePanelContext();
|
||||
const onLogRowHover = useCallback(
|
||||
@ -64,6 +73,58 @@ export const LogsPanel = ({
|
||||
[eventBus]
|
||||
);
|
||||
|
||||
const onCloseContext = useCallback(() => {
|
||||
setContextRow(null);
|
||||
if (closeCallback) {
|
||||
closeCallback();
|
||||
}
|
||||
}, [closeCallback]);
|
||||
|
||||
const onOpenContext = useCallback((row: LogRowModel, onClose: () => void) => {
|
||||
setContextRow(row);
|
||||
setCloseCallback(onClose);
|
||||
}, []);
|
||||
|
||||
const showContextToggle = useCallback(
|
||||
(row: LogRowModel): boolean => {
|
||||
if (
|
||||
!row.dataFrame.refId ||
|
||||
!dataSourcesMap ||
|
||||
(!showLogContextToggle &&
|
||||
data.request?.app !== CoreApp.Dashboard &&
|
||||
data.request?.app !== CoreApp.PanelEditor &&
|
||||
data.request?.app !== CoreApp.PanelViewer)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const dataSource = dataSourcesMap.get(row.dataFrame.refId);
|
||||
return hasLogsContextSupport(dataSource);
|
||||
},
|
||||
[dataSourcesMap, showLogContextToggle, data.request?.app]
|
||||
);
|
||||
|
||||
const getLogRowContext = useCallback(
|
||||
async (row: LogRowModel, origRow: LogRowModel, options: LogRowContextOptions): Promise<DataQueryResponse> => {
|
||||
if (!origRow.dataFrame.refId || !dataSourcesMap) {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
const query = data.request?.targets[0];
|
||||
if (!query) {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
const dataSource = dataSourcesMap.get(origRow.dataFrame.refId);
|
||||
if (!hasLogsContextSupport(dataSource)) {
|
||||
return Promise.resolve({ data: [] });
|
||||
}
|
||||
|
||||
return dataSource.getLogRowContext(row, options, query);
|
||||
},
|
||||
[data.request?.targets, dataSourcesMap]
|
||||
);
|
||||
|
||||
// Important to memoize stuff here, as panel rerenders a lot for example when resizing.
|
||||
const [logRows, deduplicatedRows, commonLabels] = useMemo(() => {
|
||||
const logs = data
|
||||
@ -102,28 +163,42 @@ export const LogsPanel = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHide scrollTop={scrollTop}>
|
||||
<div className={style.container} ref={logsContainerRef}>
|
||||
{showCommonLabels && !isAscending && renderCommonLabels()}
|
||||
<LogRows
|
||||
logRows={logRows}
|
||||
deduplicatedRows={deduplicatedRows}
|
||||
dedupStrategy={dedupStrategy}
|
||||
showLabels={showLabels}
|
||||
showTime={showTime}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
prettifyLogMessage={prettifyLogMessage}
|
||||
timeZone={timeZone}
|
||||
getFieldLinks={getFieldLinks}
|
||||
<>
|
||||
{contextRow && (
|
||||
<LogRowContextModal
|
||||
open={contextRow !== null}
|
||||
row={contextRow}
|
||||
onClose={onCloseContext}
|
||||
getRowContext={(row, options) => getLogRowContext(row, contextRow, options)}
|
||||
logsSortOrder={sortOrder}
|
||||
enableLogDetails={enableLogDetails}
|
||||
previewLimit={isAscending ? logRows.length : undefined}
|
||||
onLogRowHover={onLogRowHover}
|
||||
app={CoreApp.Dashboard}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
)}
|
||||
<CustomScrollbar autoHide scrollTop={scrollTop}>
|
||||
<div className={style.container} ref={logsContainerRef}>
|
||||
{showCommonLabels && !isAscending && renderCommonLabels()}
|
||||
<LogRows
|
||||
logRows={logRows}
|
||||
showContextToggle={showContextToggle}
|
||||
deduplicatedRows={deduplicatedRows}
|
||||
dedupStrategy={dedupStrategy}
|
||||
showLabels={showLabels}
|
||||
showTime={showTime}
|
||||
wrapLogMessage={wrapLogMessage}
|
||||
prettifyLogMessage={prettifyLogMessage}
|
||||
timeZone={timeZone}
|
||||
getFieldLinks={getFieldLinks}
|
||||
logsSortOrder={sortOrder}
|
||||
enableLogDetails={enableLogDetails}
|
||||
previewLimit={isAscending ? logRows.length : undefined}
|
||||
onLogRowHover={onLogRowHover}
|
||||
app={CoreApp.Dashboard}
|
||||
onOpenContext={onOpenContext}
|
||||
/>
|
||||
{showCommonLabels && isAscending && renderCommonLabels()}
|
||||
</div>
|
||||
</CustomScrollbar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -26,14 +26,15 @@ composableKinds: PanelCfg: {
|
||||
version: [0, 0]
|
||||
schema: {
|
||||
Options: {
|
||||
showLabels: bool
|
||||
showCommonLabels: bool
|
||||
showTime: bool
|
||||
wrapLogMessage: bool
|
||||
prettifyLogMessage: bool
|
||||
enableLogDetails: bool
|
||||
sortOrder: common.LogsSortOrder
|
||||
dedupStrategy: common.LogsDedupStrategy
|
||||
showLabels: bool
|
||||
showCommonLabels: bool
|
||||
showTime: bool
|
||||
showLogContextToggle: bool
|
||||
wrapLogMessage: bool
|
||||
prettifyLogMessage: bool
|
||||
enableLogDetails: bool
|
||||
sortOrder: common.LogsSortOrder
|
||||
dedupStrategy: common.LogsDedupStrategy
|
||||
} @cuetsy(kind="interface")
|
||||
}
|
||||
}]
|
||||
|
@ -16,6 +16,7 @@ export interface Options {
|
||||
prettifyLogMessage: boolean;
|
||||
showCommonLabels: boolean;
|
||||
showLabels: boolean;
|
||||
showLogContextToggle: boolean;
|
||||
showTime: boolean;
|
||||
sortOrder: common.LogsSortOrder;
|
||||
wrapLogMessage: boolean;
|
||||
|
@ -0,0 +1,44 @@
|
||||
// CustomHook.test.js
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { MockDataSourceApi, DatasourceSrvMock } from 'test/mocks/datasource_srv';
|
||||
|
||||
import { useDatasourcesFromTargets } from './useDatasourcesFromTargets'; // Update the path accordingly
|
||||
|
||||
const defaultDs = new MockDataSourceApi('default datasource', { data: ['default data'] });
|
||||
const ds1 = new MockDataSourceApi('dataSource1');
|
||||
const ds2 = new MockDataSourceApi('dataSource2') as MockDataSourceApi;
|
||||
|
||||
const datasourceSrv = new DatasourceSrvMock(defaultDs, {
|
||||
dataSource1: ds1,
|
||||
dataSource2: ds2,
|
||||
});
|
||||
const getDataSourceSrvMock = jest.fn().mockReturnValue(datasourceSrv);
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getDataSourceSrv: () => getDataSourceSrvMock(),
|
||||
}));
|
||||
|
||||
describe('useDatasourcesFromTargets', () => {
|
||||
it('returns an empty map when targets are not provided', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDatasourcesFromTargets(undefined));
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.size).toBe(0);
|
||||
});
|
||||
|
||||
it('fetches and returns the data sources map', async () => {
|
||||
const mockTargets = [
|
||||
{ refId: '1', datasource: { uid: 'dataSource1' } },
|
||||
{ refId: '2', datasource: { uid: 'dataSource2' } },
|
||||
];
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook(() => useDatasourcesFromTargets(mockTargets));
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.size).toBe(2);
|
||||
expect(result.current.get('1')).toEqual(ds1);
|
||||
expect(result.current.get('2')).toEqual(ds2);
|
||||
});
|
||||
});
|
31
public/app/plugins/panel/logs/useDatasourcesFromTargets.ts
Normal file
31
public/app/plugins/panel/logs/useDatasourcesFromTargets.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourceApi } from '@grafana/data';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
|
||||
export const useDatasourcesFromTargets = (targets: DataQuery[] | undefined): Map<string, DataSourceApi> => {
|
||||
const [dataSourcesMap, setDataSourcesMap] = useState(new Map<string, DataSourceApi>());
|
||||
|
||||
useAsync(async () => {
|
||||
if (!targets) {
|
||||
setDataSourcesMap(new Map<string, DataSourceApi>());
|
||||
return;
|
||||
}
|
||||
|
||||
const raw = await Promise.all(
|
||||
targets
|
||||
.filter((target) => !!target.datasource?.uid)
|
||||
.map((target) =>
|
||||
getDataSourceSrv()
|
||||
.get(target.datasource?.uid)
|
||||
.then((ds) => ({ key: target.refId, ds }))
|
||||
)
|
||||
);
|
||||
|
||||
setDataSourcesMap(new Map<string, DataSourceApi>(raw.map(({ key, ds }) => [key, ds])));
|
||||
}, [targets]);
|
||||
|
||||
return dataSourcesMap;
|
||||
};
|
Loading…
Reference in New Issue
Block a user