|
|
|
|
@@ -0,0 +1,458 @@
|
|
|
|
|
import { fireEvent, render as rtlRender, screen } from '@testing-library/react';
|
|
|
|
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
|
|
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|
|
|
|
import { AppContext } from 'providers/App/App';
|
|
|
|
|
import { IAppContext } from 'providers/App/types';
|
|
|
|
|
import React, { MutableRefObject } from 'react';
|
|
|
|
|
import { QueryClient, QueryClientProvider, UseQueryResult } from 'react-query';
|
|
|
|
|
import { Provider } from 'react-redux';
|
|
|
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
|
|
|
import configureStore from 'redux-mock-store';
|
|
|
|
|
import thunk from 'redux-thunk';
|
|
|
|
|
import { SuccessResponse, Warning } from 'types/api';
|
|
|
|
|
import { Widgets } from 'types/api/dashboard/getAll';
|
|
|
|
|
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
|
|
|
|
import { EQueryType } from 'types/common/dashboard';
|
|
|
|
|
import { ROLES } from 'types/roles';
|
|
|
|
|
|
|
|
|
|
import { MenuItemKeys } from '../contants';
|
|
|
|
|
import WidgetHeader from '../index';
|
|
|
|
|
|
|
|
|
|
const TEST_WIDGET_TITLE = 'Test Widget';
|
|
|
|
|
const TABLE_WIDGET_TITLE = 'Table Widget';
|
|
|
|
|
const WIDGET_HEADER_SEARCH = 'widget-header-search';
|
|
|
|
|
const WIDGET_HEADER_SEARCH_INPUT = 'widget-header-search-input';
|
|
|
|
|
const TEST_WIDGET_TITLE_RESOLVED = 'Test Widget Title';
|
|
|
|
|
|
|
|
|
|
const mockStore = configureStore([thunk]);
|
|
|
|
|
const createMockStore = (): ReturnType<typeof mockStore> =>
|
|
|
|
|
mockStore({
|
|
|
|
|
app: {
|
|
|
|
|
role: 'ADMIN',
|
|
|
|
|
user: {
|
|
|
|
|
userId: 'test-user-id',
|
|
|
|
|
email: 'test@signoz.io',
|
|
|
|
|
name: 'TestUser',
|
|
|
|
|
},
|
|
|
|
|
isLoggedIn: true,
|
|
|
|
|
org: [],
|
|
|
|
|
},
|
|
|
|
|
globalTime: {
|
|
|
|
|
minTime: '2023-01-01T00:00:00Z',
|
|
|
|
|
maxTime: '2023-01-02T00:00:00Z',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const queryClient = new QueryClient({
|
|
|
|
|
defaultOptions: {
|
|
|
|
|
queries: {
|
|
|
|
|
refetchOnWindowFocus: false,
|
|
|
|
|
retry: false,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const createMockAppContext = (): Partial<IAppContext> => ({
|
|
|
|
|
user: {
|
|
|
|
|
accessJwt: '',
|
|
|
|
|
refreshJwt: '',
|
|
|
|
|
id: '',
|
|
|
|
|
email: '',
|
|
|
|
|
displayName: '',
|
|
|
|
|
createdAt: 0,
|
|
|
|
|
organization: '',
|
|
|
|
|
orgId: '',
|
|
|
|
|
role: 'ADMIN' as ROLES,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const render = (ui: React.ReactElement): ReturnType<typeof rtlRender> =>
|
|
|
|
|
rtlRender(
|
|
|
|
|
<MemoryRouter>
|
|
|
|
|
<QueryClientProvider client={queryClient}>
|
|
|
|
|
<Provider store={createMockStore()}>
|
|
|
|
|
<AppContext.Provider value={createMockAppContext() as IAppContext}>
|
|
|
|
|
{ui}
|
|
|
|
|
</AppContext.Provider>
|
|
|
|
|
</Provider>
|
|
|
|
|
</QueryClientProvider>
|
|
|
|
|
</MemoryRouter>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
jest.mock('hooks/queryBuilder/useCreateAlerts', () => ({
|
|
|
|
|
__esModule: true,
|
|
|
|
|
default: jest.fn(() => jest.fn()),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
jest.mock('hooks/dashboard/useGetResolvedText', () => {
|
|
|
|
|
// eslint-disable-next-line sonarjs/no-duplicate-string
|
|
|
|
|
const TEST_WIDGET_TITLE_RESOLVED = 'Test Widget Title';
|
|
|
|
|
return {
|
|
|
|
|
__esModule: true,
|
|
|
|
|
default: jest.fn(() => ({
|
|
|
|
|
truncatedText: TEST_WIDGET_TITLE_RESOLVED,
|
|
|
|
|
fullText: TEST_WIDGET_TITLE_RESOLVED,
|
|
|
|
|
})),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const mockWidget: Widgets = {
|
|
|
|
|
id: 'test-widget-id',
|
|
|
|
|
title: TEST_WIDGET_TITLE,
|
|
|
|
|
description: 'Test Description',
|
|
|
|
|
panelTypes: PANEL_TYPES.TIME_SERIES,
|
|
|
|
|
query: {
|
|
|
|
|
builder: {
|
|
|
|
|
queryData: [],
|
|
|
|
|
queryFormulas: [],
|
|
|
|
|
queryTraceOperator: [],
|
|
|
|
|
},
|
|
|
|
|
promql: [],
|
|
|
|
|
clickhouse_sql: [],
|
|
|
|
|
id: 'query-id',
|
|
|
|
|
queryType: 'builder' as EQueryType,
|
|
|
|
|
},
|
|
|
|
|
timePreferance: 'GLOBAL_TIME',
|
|
|
|
|
opacity: '',
|
|
|
|
|
nullZeroValues: '',
|
|
|
|
|
yAxisUnit: '',
|
|
|
|
|
fillSpans: false,
|
|
|
|
|
softMin: null,
|
|
|
|
|
softMax: null,
|
|
|
|
|
selectedLogFields: [],
|
|
|
|
|
selectedTracesFields: [],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const mockQueryResponse = ({
|
|
|
|
|
data: {
|
|
|
|
|
payload: {
|
|
|
|
|
data: {
|
|
|
|
|
result: [],
|
|
|
|
|
resultType: '',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
statusCode: 200,
|
|
|
|
|
message: 'success',
|
|
|
|
|
error: null,
|
|
|
|
|
},
|
|
|
|
|
isLoading: false,
|
|
|
|
|
isError: false,
|
|
|
|
|
error: null,
|
|
|
|
|
isFetching: false,
|
|
|
|
|
} as unknown) as UseQueryResult<
|
|
|
|
|
SuccessResponse<MetricRangePayloadProps, unknown> & {
|
|
|
|
|
warning?: Warning;
|
|
|
|
|
},
|
|
|
|
|
Error
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
describe('WidgetHeader', () => {
|
|
|
|
|
const mockOnView = jest.fn();
|
|
|
|
|
const mockSetSearchTerm = jest.fn();
|
|
|
|
|
const tableProcessedDataRef: MutableRefObject<RowData[]> = {
|
|
|
|
|
current: [
|
|
|
|
|
{
|
|
|
|
|
timestamp: 1234567890,
|
|
|
|
|
key: 'key1',
|
|
|
|
|
col1: 'val1',
|
|
|
|
|
col2: 'val2',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
jest.clearAllMocks();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders widget header with title', () => {
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByText(TEST_WIDGET_TITLE_RESOLVED)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns null for empty widget', () => {
|
|
|
|
|
const emptyWidget = {
|
|
|
|
|
...mockWidget,
|
|
|
|
|
id: PANEL_TYPES.EMPTY_WIDGET,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { container } = render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title="Empty Widget"
|
|
|
|
|
widget={emptyWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(container.firstChild).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows search input for table panels', () => {
|
|
|
|
|
const tableWidget = {
|
|
|
|
|
...mockWidget,
|
|
|
|
|
panelTypes: PANEL_TYPES.TABLE,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TABLE_WIDGET_TITLE}
|
|
|
|
|
widget={tableWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const searchIcon = screen.getByTestId(WIDGET_HEADER_SEARCH);
|
|
|
|
|
expect(searchIcon).toBeInTheDocument();
|
|
|
|
|
|
|
|
|
|
fireEvent.click(searchIcon);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByTestId(WIDGET_HEADER_SEARCH_INPUT)).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('handles search input changes and closing', () => {
|
|
|
|
|
const tableWidget = {
|
|
|
|
|
...mockWidget,
|
|
|
|
|
panelTypes: PANEL_TYPES.TABLE,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TABLE_WIDGET_TITLE}
|
|
|
|
|
widget={tableWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const searchIcon = screen.getByTestId(`${WIDGET_HEADER_SEARCH}`);
|
|
|
|
|
fireEvent.click(searchIcon);
|
|
|
|
|
|
|
|
|
|
const searchInput = screen.getByTestId(WIDGET_HEADER_SEARCH_INPUT);
|
|
|
|
|
fireEvent.change(searchInput, { target: { value: 'test search' } });
|
|
|
|
|
expect(mockSetSearchTerm).toHaveBeenCalledWith('test search');
|
|
|
|
|
|
|
|
|
|
const closeButton = screen
|
|
|
|
|
.getByTestId(WIDGET_HEADER_SEARCH_INPUT)
|
|
|
|
|
.parentElement?.querySelector('.search-header-icons');
|
|
|
|
|
if (closeButton) {
|
|
|
|
|
fireEvent.click(closeButton);
|
|
|
|
|
expect(mockSetSearchTerm).toHaveBeenCalledWith('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows error icon when query has error', () => {
|
|
|
|
|
const errorResponse = {
|
|
|
|
|
...mockQueryResponse,
|
|
|
|
|
isError: true as const,
|
|
|
|
|
error: { message: 'Test error' } as Error,
|
|
|
|
|
data: undefined,
|
|
|
|
|
} as UseQueryResult<
|
|
|
|
|
SuccessResponse<MetricRangePayloadProps, unknown> & {
|
|
|
|
|
warning?: Warning;
|
|
|
|
|
},
|
|
|
|
|
Error
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={errorResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// check if CircleX icon is rendered
|
|
|
|
|
const circleXIcon = document.querySelector('.lucide-circle-x');
|
|
|
|
|
expect(circleXIcon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows warning icon when query has warning', () => {
|
|
|
|
|
const warningData = mockQueryResponse.data
|
|
|
|
|
? {
|
|
|
|
|
...mockQueryResponse.data,
|
|
|
|
|
warning: {
|
|
|
|
|
code: 'WARNING_CODE',
|
|
|
|
|
message: 'Test warning',
|
|
|
|
|
url: 'https://example.com',
|
|
|
|
|
warnings: [{ message: 'Test warning' }],
|
|
|
|
|
} as Warning,
|
|
|
|
|
}
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const warningResponse = {
|
|
|
|
|
...mockQueryResponse,
|
|
|
|
|
data: warningData,
|
|
|
|
|
} as UseQueryResult<
|
|
|
|
|
SuccessResponse<MetricRangePayloadProps, unknown> & {
|
|
|
|
|
warning?: Warning;
|
|
|
|
|
},
|
|
|
|
|
Error
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={warningResponse}
|
|
|
|
|
isWarning
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const triangleAlertIcon = document.querySelector('.lucide-triangle-alert');
|
|
|
|
|
expect(triangleAlertIcon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows spinner when fetching response', () => {
|
|
|
|
|
const fetchingResponse = {
|
|
|
|
|
...mockQueryResponse,
|
|
|
|
|
isFetching: true,
|
|
|
|
|
isLoading: true,
|
|
|
|
|
} as UseQueryResult<
|
|
|
|
|
SuccessResponse<MetricRangePayloadProps, unknown> & {
|
|
|
|
|
warning?: Warning;
|
|
|
|
|
},
|
|
|
|
|
Error
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={fetchingResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const antSpin = document.querySelector('.ant-spin');
|
|
|
|
|
expect(antSpin).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders menu options icon', () => {
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
headerMenuList={[MenuItemKeys.View]}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const moreOptionsIcon = screen.getByTestId('widget-header-options');
|
|
|
|
|
expect(moreOptionsIcon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('shows search icon for table panels', () => {
|
|
|
|
|
const tableWidget = {
|
|
|
|
|
...mockWidget,
|
|
|
|
|
panelTypes: PANEL_TYPES.TABLE,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TABLE_WIDGET_TITLE}
|
|
|
|
|
widget={tableWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const searchIcon = screen.getByTestId(WIDGET_HEADER_SEARCH);
|
|
|
|
|
expect(searchIcon).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('does not show search icon for non-table panels', () => {
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const searchIcon = screen.queryByTestId(WIDGET_HEADER_SEARCH);
|
|
|
|
|
expect(searchIcon).not.toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('renders threshold when provided', () => {
|
|
|
|
|
const threshold = <div data-testid="threshold">Threshold Component</div>;
|
|
|
|
|
|
|
|
|
|
render(
|
|
|
|
|
<WidgetHeader
|
|
|
|
|
title={TEST_WIDGET_TITLE}
|
|
|
|
|
widget={mockWidget}
|
|
|
|
|
onView={mockOnView}
|
|
|
|
|
parentHover={false}
|
|
|
|
|
queryResponse={mockQueryResponse}
|
|
|
|
|
isWarning={false}
|
|
|
|
|
isFetchingResponse={false}
|
|
|
|
|
tableProcessedDataRef={tableProcessedDataRef}
|
|
|
|
|
setSearchTerm={mockSetSearchTerm}
|
|
|
|
|
threshold={threshold}
|
|
|
|
|
/>,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(screen.getByTestId('threshold')).toBeInTheDocument();
|
|
|
|
|
});
|
|
|
|
|
});
|