mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 01:23:32 -06:00
292 lines
9.3 KiB
TypeScript
292 lines
9.3 KiB
TypeScript
import { render, screen, waitFor } from '@testing-library/react';
|
|
import React, { PropsWithChildren } from 'react';
|
|
|
|
import { DataQueryRequest, dateTime, LoadingState, PanelData, toDataFrame } from '@grafana/data';
|
|
import { DataQuery } from '@grafana/schema';
|
|
import { mockDataSource } from 'app/features/alerting/unified/mocks';
|
|
|
|
import { DataSourceType } from '../../alerting/unified/utils/datasource';
|
|
|
|
import { filterPanelDataToQuery, Props, QueryEditorRow } from './QueryEditorRow';
|
|
|
|
const mockDS = mockDataSource({
|
|
name: 'test',
|
|
type: DataSourceType.Alertmanager,
|
|
});
|
|
jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => {
|
|
return {
|
|
getDataSourceSrv: () => ({
|
|
get: () => Promise.resolve(mockDS),
|
|
getList: () => {},
|
|
getInstanceSettings: () => mockDS,
|
|
}),
|
|
};
|
|
});
|
|
// Draggable fails to render in tests, so we mock it out
|
|
jest.mock('app/core/components/QueryOperationRow/QueryOperationRow', () => ({
|
|
QueryOperationRow: (props: PropsWithChildren) => <div>{props.children}</div>,
|
|
}));
|
|
|
|
function makePretendRequest(requestId: string, subRequests?: DataQueryRequest[]): DataQueryRequest {
|
|
return {
|
|
requestId,
|
|
// subRequests,
|
|
} as DataQueryRequest;
|
|
}
|
|
|
|
describe('filterPanelDataToQuery', () => {
|
|
const data: PanelData = {
|
|
state: LoadingState.Done,
|
|
series: [
|
|
toDataFrame({ refId: 'A', fields: [{ name: 'AAA' }], meta: {} }),
|
|
toDataFrame({ refId: 'B', fields: [{ name: 'B111' }], meta: {} }),
|
|
toDataFrame({ refId: 'B', fields: [{ name: 'B222' }], meta: {} }),
|
|
toDataFrame({ refId: 'B', fields: [{ name: 'B333' }], meta: {} }),
|
|
toDataFrame({ refId: 'C', fields: [{ name: 'CCCC' }], meta: { requestId: 'sub3' } }),
|
|
],
|
|
errors: [
|
|
{
|
|
refId: 'B',
|
|
message: 'Error!!',
|
|
},
|
|
],
|
|
request: makePretendRequest('111', [
|
|
makePretendRequest('sub1'),
|
|
makePretendRequest('sub2'),
|
|
makePretendRequest('sub3'),
|
|
]),
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
it('should not have an error unless the refId matches', () => {
|
|
const panelData = filterPanelDataToQuery(data, 'A');
|
|
expect(panelData?.series.length).toBe(1);
|
|
expect(panelData?.series[0].refId).toBe('A');
|
|
expect(panelData?.error).toBeUndefined();
|
|
expect(panelData?.errors).toBeUndefined();
|
|
});
|
|
|
|
it('should match the error to the query', () => {
|
|
const panelData = filterPanelDataToQuery(data, 'B');
|
|
expect(panelData?.series.length).toBe(3);
|
|
expect(panelData?.series[0].refId).toBe('B');
|
|
expect(panelData?.error!.refId).toBe('B');
|
|
expect(panelData?.errors![0].refId).toBe('B');
|
|
});
|
|
|
|
it('should include errors when missing data', () => {
|
|
const withError = {
|
|
series: [],
|
|
error: {
|
|
message: 'Error!!',
|
|
},
|
|
errors: [{ message: 'Error!!' }],
|
|
} as unknown as PanelData;
|
|
|
|
const panelData = filterPanelDataToQuery(withError, 'B');
|
|
expect(panelData).toBeDefined();
|
|
expect(panelData?.state).toBe(LoadingState.Error);
|
|
expect(panelData?.error).toBe(withError.error);
|
|
expect(panelData?.errors).toEqual(withError.errors);
|
|
});
|
|
|
|
it('should set the state to done if the frame has no errors', () => {
|
|
const withError = {
|
|
...data,
|
|
};
|
|
withError.state = LoadingState.Error;
|
|
|
|
const panelDataB = filterPanelDataToQuery(withError, 'B');
|
|
expect(panelDataB?.series.length).toBe(3);
|
|
expect(panelDataB?.series[0].refId).toBe('B');
|
|
expect(panelDataB?.state).toBe(LoadingState.Error);
|
|
|
|
const panelDataA = filterPanelDataToQuery(withError, 'A');
|
|
expect(panelDataA?.series.length).toBe(1);
|
|
expect(panelDataA?.series[0].refId).toBe('A');
|
|
expect(panelDataA?.state).toBe(LoadingState.Done);
|
|
});
|
|
|
|
it('should return error for query that returns no data, but another query does return data', () => {
|
|
const withError = {
|
|
...data,
|
|
state: LoadingState.Error,
|
|
error: {
|
|
message: 'Sad',
|
|
refId: 'Q',
|
|
},
|
|
};
|
|
|
|
const panelDataB = filterPanelDataToQuery(withError, 'Q');
|
|
expect(panelDataB?.series.length).toBe(0);
|
|
expect(panelDataB?.error?.refId).toBe('Q');
|
|
expect(panelDataB?.errors![0].refId).toBe('Q');
|
|
});
|
|
|
|
it('should not set the state to done if the frame is loading and has no errors', () => {
|
|
const loadingData: PanelData = {
|
|
state: LoadingState.Loading,
|
|
series: [
|
|
toDataFrame({ refId: 'A', fields: [{ name: 'AAA' }], meta: {} }),
|
|
toDataFrame({ refId: 'B', fields: [{ name: 'B111' }], meta: {} }),
|
|
],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
const panelDataB = filterPanelDataToQuery(loadingData, 'B');
|
|
expect(panelDataB?.state).toBe(LoadingState.Loading);
|
|
|
|
const panelDataA = filterPanelDataToQuery(loadingData, 'A');
|
|
expect(panelDataA?.state).toBe(LoadingState.Loading);
|
|
});
|
|
it('should keep the state in loading until all queries are finished, even if the current query has errored', () => {
|
|
const loadingData: PanelData = {
|
|
state: LoadingState.Loading,
|
|
series: [],
|
|
error: {
|
|
refId: 'A',
|
|
message: 'Error',
|
|
},
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
const panelDataA = filterPanelDataToQuery(loadingData, 'A');
|
|
expect(panelDataA?.state).toBe(LoadingState.Loading);
|
|
});
|
|
it('should keep the state in loading until all queries are finished, if another query has errored', () => {
|
|
const loadingData: PanelData = {
|
|
state: LoadingState.Loading,
|
|
series: [],
|
|
error: {
|
|
refId: 'B',
|
|
message: 'Error',
|
|
},
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
const panelDataA = filterPanelDataToQuery(loadingData, 'A');
|
|
expect(panelDataA?.state).toBe(LoadingState.Loading);
|
|
});
|
|
});
|
|
|
|
describe('frame results with warnings', () => {
|
|
const meta = {
|
|
notices: [
|
|
{
|
|
severity: 'warning',
|
|
text: 'Reduce operation is not needed. Input query or expression A is already reduced data.',
|
|
},
|
|
],
|
|
};
|
|
|
|
const dataWithWarnings: PanelData = {
|
|
state: LoadingState.Done,
|
|
series: [
|
|
toDataFrame({
|
|
refId: 'B',
|
|
fields: [{ name: 'B1' }],
|
|
meta,
|
|
}),
|
|
toDataFrame({
|
|
refId: 'B',
|
|
fields: [{ name: 'B2' }],
|
|
meta,
|
|
}),
|
|
],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
const dataWithoutWarnings: PanelData = {
|
|
state: LoadingState.Done,
|
|
series: [
|
|
toDataFrame({
|
|
refId: 'B',
|
|
fields: [{ name: 'B1' }],
|
|
meta: {},
|
|
}),
|
|
toDataFrame({
|
|
refId: 'B',
|
|
fields: [{ name: 'B2' }],
|
|
meta: {},
|
|
}),
|
|
],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
|
|
it('should show a warning badge and de-duplicate warning messages', () => {
|
|
// @ts-ignore: there are _way_ too many props to inject here :(
|
|
const editorRow = new QueryEditorRow({
|
|
data: dataWithWarnings,
|
|
query: {
|
|
refId: 'B',
|
|
},
|
|
});
|
|
|
|
const warningsComponent = editorRow.renderWarnings();
|
|
expect(warningsComponent).not.toBe(null);
|
|
|
|
render(warningsComponent!);
|
|
expect(screen.getByText('1 warning')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should not show a warning badge when there are no warnings', () => {
|
|
// @ts-ignore: there are _way_ too many props to inject here :(
|
|
const editorRow = new QueryEditorRow({
|
|
data: dataWithoutWarnings,
|
|
query: {
|
|
refId: 'B',
|
|
},
|
|
});
|
|
|
|
const warningsComponent = editorRow.renderWarnings();
|
|
expect(warningsComponent).toBe(null);
|
|
});
|
|
});
|
|
describe('QueryEditorRow', () => {
|
|
const props = (data: PanelData): Props<DataQuery> => ({
|
|
dataSource: mockDS,
|
|
query: { refId: 'B' },
|
|
data,
|
|
queries: [{ refId: 'B' }],
|
|
id: 'test',
|
|
onAddQuery: jest.fn(),
|
|
onRunQuery: jest.fn(),
|
|
onChange: jest.fn(),
|
|
onRemoveQuery: jest.fn(),
|
|
index: 0,
|
|
});
|
|
it('should display error message in corresponding panel', async () => {
|
|
const data = {
|
|
state: LoadingState.Error,
|
|
series: [],
|
|
errors: [{ message: 'Error!!', refId: 'B' }],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
render(<QueryEditorRow {...props(data)} />);
|
|
expect(await screen.findByText('Error!!')).toBeInTheDocument();
|
|
});
|
|
it('should display error message in corresponding panel if only error field is provided', async () => {
|
|
const data = {
|
|
state: LoadingState.Error,
|
|
series: [],
|
|
error: { message: 'Error!!', refId: 'B' },
|
|
errors: [],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
render(<QueryEditorRow {...props(data)} />);
|
|
expect(await screen.findByText('Error!!')).toBeInTheDocument();
|
|
});
|
|
it('should not display error message if error.refId doesnt match', async () => {
|
|
const data = {
|
|
state: LoadingState.Error,
|
|
series: [],
|
|
errors: [{ message: 'Error!!', refId: 'A' }],
|
|
timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-1d', to: 'now' } },
|
|
};
|
|
render(<QueryEditorRow {...props(data)} />);
|
|
await waitFor(() => {
|
|
expect(screen.queryByText('Error!!')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|