diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
index f34a4470a9..44378e602b 100644
--- a/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
+++ b/frontend/src/container/ExplorerOptions/ExplorerOptions.tsx
@@ -501,6 +501,7 @@ function ExplorerOptions({
shape="circle"
onClick={hideToolbar}
icon={}
+ data-testid="hide-toolbar"
/>
@@ -530,6 +531,7 @@ function ExplorerOptions({
icon={}
onClick={onSaveHandler}
disabled={isSaveViewLoading}
+ data-testid="save-view-btn"
>
Save this view
,
diff --git a/frontend/src/container/ExplorerOptions/ExplorerOptionsHideArea.tsx b/frontend/src/container/ExplorerOptions/ExplorerOptionsHideArea.tsx
index a420c25ecc..efdaef1cd1 100644
--- a/frontend/src/container/ExplorerOptions/ExplorerOptionsHideArea.tsx
+++ b/frontend/src/container/ExplorerOptions/ExplorerOptionsHideArea.tsx
@@ -65,6 +65,7 @@ function ExplorerOptionsHideArea({
// style={{ alignSelf: 'center', marginRight: 'calc(10% - 20px)' }}
className="explorer-show-btn"
onClick={handleShowExplorerOption}
+ data-testid="show-explorer-option"
>
diff --git a/frontend/src/mocks-server/__mockdata__/explorer_views.ts b/frontend/src/mocks-server/__mockdata__/explorer_views.ts
index ae88071e55..b51eb2ee9c 100644
--- a/frontend/src/mocks-server/__mockdata__/explorer_views.ts
+++ b/frontend/src/mocks-server/__mockdata__/explorer_views.ts
@@ -77,5 +77,67 @@ export const explorerView = {
},
extraData: '{"color":"#00ffd0"}',
},
+ {
+ uuid: '58b010b6-8be9-40d1-8d25-f73b5f7314ad',
+ name: 'success traces list view',
+ category: '',
+ createdAt: '2023-08-30T13:00:40.958011925Z',
+ createdBy: 'test-email',
+ updatedAt: '2024-04-29T13:09:06.175537361Z',
+ updatedBy: 'test-email',
+ sourcePage: 'traces',
+ tags: [''],
+ compositeQuery: {
+ builderQueries: {
+ A: {
+ queryName: 'A',
+ stepInterval: 60,
+ dataSource: 'traces',
+ aggregateOperator: 'noop',
+ aggregateAttribute: {
+ key: '',
+ dataType: '',
+ type: '',
+ isColumn: false,
+ isJSON: false,
+ },
+ filters: {
+ op: 'AND',
+ items: [
+ {
+ key: {
+ key: 'responseStatusCode',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ },
+ value: '200',
+ op: '=',
+ },
+ ],
+ },
+ expression: 'A',
+ disabled: false,
+ limit: 0,
+ offset: 0,
+ pageSize: 0,
+ orderBy: [
+ {
+ columnName: 'timestamp',
+ order: 'desc',
+ },
+ ],
+ reduceTo: 'sum',
+ timeAggregation: 'rate',
+ spaceAggregation: 'sum',
+ ShiftBy: 0,
+ },
+ },
+ panelType: 'list',
+ queryType: 'builder',
+ },
+ extraData: '{"color":"#bdff9d"}',
+ },
],
};
diff --git a/frontend/src/mocks-server/__mockdata__/query_range.ts b/frontend/src/mocks-server/__mockdata__/query_range.ts
index 69ff7bfb66..ef55283a96 100644
--- a/frontend/src/mocks-server/__mockdata__/query_range.ts
+++ b/frontend/src/mocks-server/__mockdata__/query_range.ts
@@ -1,3 +1,4 @@
+/* eslint-disable sonarjs/no-duplicate-string */
import { PANEL_TYPES } from 'constants/queryBuilder';
import { QueryRangePayload } from 'types/api/metrics/getQueryRange';
import { EQueryType } from 'types/common/dashboard';
@@ -77,3 +78,245 @@ export const queryRangeSuccessResponse: QueryRangePayload = {
start: 0,
step: 0,
};
+
+export const queryRangeForTimeSeries = {
+ status: 'success',
+ data: {
+ resultType: '',
+ result: [
+ {
+ queryName: 'A',
+ series: [
+ {
+ labels: {},
+ labelsArray: null,
+ values: [
+ {
+ timestamp: 1721378340000,
+ value: '3074',
+ },
+ {
+ timestamp: 1721378100000,
+ value: '2983',
+ },
+ {
+ timestamp: 1721378040000,
+ value: '2978',
+ },
+ {
+ timestamp: 1721378160000,
+ value: '2940',
+ },
+ {
+ timestamp: 1721377980000,
+ value: '2904',
+ },
+ {
+ timestamp: 1721378280000,
+ value: '2874',
+ },
+ {
+ timestamp: 1721378220000,
+ value: '2667',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+};
+
+export const queryRangeForListView = {
+ status: 'success',
+ data: {
+ resultType: '',
+ result: [
+ {
+ queryName: 'A',
+ list: [
+ {
+ timestamp: '2024-07-19T08:39:59.949129915Z',
+ data: {
+ dbName: '',
+ durationNano: 790949390,
+ httpMethod: '',
+ name: 'authenticate_check_db',
+ responseStatusCode: '',
+ serviceName: 'demo-app',
+ spanID: '5704353737b6778e',
+ statusCode: 0,
+ traceID: 'a364a8e15af3e9a8c866e0528db8b637',
+ },
+ },
+ {
+ timestamp: '2024-07-19T08:39:59.506524482Z',
+ data: {
+ dbName: '',
+ durationNano: 1375203118,
+ httpMethod: '',
+ name: 'check cart in cache',
+ responseStatusCode: '',
+ serviceName: 'demo-app',
+ spanID: '2134bb1165c928aa',
+ statusCode: 0,
+ traceID: '7b565bc351bac2a12c004d92d3a809b1',
+ },
+ },
+ {
+ timestamp: '2024-07-19T08:39:58.735245Z',
+ data: {
+ dbName: '',
+ durationNano: 55306000,
+ httpMethod: 'GET',
+ name: 'HTTP GET',
+ responseStatusCode: '200',
+ serviceName: 'frontend',
+ spanID: '772c4d29dd9076ac',
+ statusCode: 0,
+ traceID: '0000000000000000344ded1387b08a7e',
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
+
+export const queryRangeForTableView = {
+ status: 'success',
+ data: {
+ resultType: '',
+ result: [
+ {
+ queryName: 'A',
+ series: [
+ {
+ labels: {},
+ labelsArray: null,
+ values: [
+ {
+ timestamp: 1721583834000,
+ value: '87798',
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+};
+
+export const queryRangeForTraceView = {
+ status: 'success',
+ data: {
+ resultType: '',
+ result: [
+ {
+ queryName: 'A',
+ list: [
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 7245231266,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '5765b60ba7cc4ddafe8bdaa9c1b4b246',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 7218609120,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '1593c896d96cc6b2478bb95dcc01e3f5',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 7217156051,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: 'dcd145ed13937795c5e2ee8618ec7e32',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 7054152134,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: 'd9ceed0a6b23ed4b3bff664e2b303382',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 7052324178,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: 'f76f1acc10a9149121c2bf715d1f92c5',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 6998186102,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '1e3acf6649147117836cfdde66e2bde5',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 6898849195,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '035b210595493adcef4c7f297a427bb0',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 6829435795,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '4ae4d4d082fc6d7a20d90ae0b1d0fff1',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 6790765891,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: '7975c032b430ac63479e5d578c1f0edd',
+ },
+ },
+ {
+ timestamp: '0001-01-01T00:00:00Z',
+ data: {
+ span_count: 8,
+ 'subQuery.durationNano': 6786616927,
+ 'subQuery.name': 'home',
+ 'subQuery.serviceName': 'demo-app',
+ traceID: 'ce9d3e5d66dbdd41d46d519b615cce52',
+ },
+ },
+ ],
+ },
+ ],
+ },
+};
diff --git a/frontend/src/mocks-server/handlers.ts b/frontend/src/mocks-server/handlers.ts
index 87287a0d1a..22978717a5 100644
--- a/frontend/src/mocks-server/handlers.ts
+++ b/frontend/src/mocks-server/handlers.ts
@@ -210,6 +210,16 @@ export const handlers = [
res(ctx.status(200), ctx.json(explorerView)),
),
+ rest.post('http://localhost/api/v1/explorer/views', (req, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.json({
+ status: 'success',
+ data: '7731ece1-3fa3-4ed4-8b1c-58b4c28723b2',
+ }),
+ ),
+ ),
+
rest.post('http://localhost/api/v1/event', (req, res, ctx) =>
res(
ctx.status(200),
diff --git a/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx b/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
index 1dcaaaa4cf..0622a365c2 100644
--- a/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
+++ b/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
@@ -1,27 +1,56 @@
/* eslint-disable sonarjs/no-duplicate-string */
-/* eslint-disable no-restricted-syntax */
-/* eslint-disable no-await-in-loop */
import userEvent from '@testing-library/user-event';
import {
initialQueriesMap,
initialQueryBuilderFormValues,
+ PANEL_TYPES,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import * as compositeQueryHook from 'hooks/queryBuilder/useGetCompositeQueryParam';
+import {
+ queryRangeForListView,
+ queryRangeForTableView,
+ queryRangeForTimeSeries,
+ queryRangeForTraceView,
+} from 'mocks-server/__mockdata__/query_range';
+import { server } from 'mocks-server/server';
+import { rest } from 'msw';
import { QueryBuilderContext } from 'providers/QueryBuilder';
-import { fireEvent, render, screen, waitFor, within } from 'tests/test-utils';
-import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
-import { Query } from 'types/api/queryBuilder/queryBuilderData';
+import {
+ act,
+ fireEvent,
+ render,
+ screen,
+ waitFor,
+ within,
+} from 'tests/test-utils';
import TracesExplorer from '..';
import { Filter } from '../Filter/Filter';
import { AllTraceFilterKeyValue } from '../Filter/filterUtils';
+import {
+ checkForSectionContent,
+ checkIfSectionIsNotOpen,
+ checkIfSectionIsOpen,
+ compositeQuery,
+ defaultClosedSections,
+ defaultOpenSections,
+ optionMenuReturn,
+ qbProviderValue,
+ redirectWithQueryBuilderData,
+} from './testUtils';
+
+const historyPush = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.TRACES_EXPLORER}/`,
}),
+ useHistory: (): any => ({
+ ...jest.requireActual('react-router-dom').useHistory(),
+ push: historyPush,
+ }),
}));
jest.mock('uplot', () => {
@@ -40,6 +69,25 @@ jest.mock('uplot', () => {
};
});
+jest.mock(
+ 'components/Uplot/Uplot',
+ () =>
+ function MockUplot(): JSX.Element {
+ return MockUplot
;
+ },
+);
+
+const successNotification = jest.fn();
+jest.mock('hooks/useNotifications', () => ({
+ __esModule: true,
+ useNotifications: jest.fn(() => ({
+ notifications: {
+ success: successNotification,
+ error: jest.fn(),
+ },
+ })),
+}));
+
jest.mock(
'container/TopNav/DateTimeSelectionV2/index.tsx',
() =>
@@ -48,84 +96,12 @@ jest.mock(
},
);
-function checkIfSectionIsOpen(
- getByTestId: (testId: string) => HTMLElement,
- panelName: string,
-): void {
- const section = getByTestId(`collapse-${panelName}`);
- expect(section.querySelector('.ant-collapse-item-active')).not.toBeNull();
-}
-
-function checkIfSectionIsNotOpen(
- getByTestId: (testId: string) => HTMLElement,
- panelName: string,
-): void {
- const section = getByTestId(`collapse-${panelName}`);
- expect(section.querySelector('.ant-collapse-item-active')).toBeNull();
-}
-
-const defaultOpenSections = ['hasError', 'durationNano', 'serviceName'];
-
-const defaultClosedSections = Object.keys(AllTraceFilterKeyValue).filter(
- (section) =>
- ![...defaultOpenSections, 'durationNanoMin', 'durationNanoMax'].includes(
- section,
- ),
-);
-
-async function checkForSectionContent(values: string[]): Promise {
- for (const val of values) {
- const sectionContent = await screen.findByText(val);
- await waitFor(() => expect(sectionContent).toBeInTheDocument());
- }
-}
-
-const redirectWithQueryBuilderData = jest.fn();
-
-const compositeQuery: Query = {
- ...initialQueriesMap.traces,
- builder: {
- ...initialQueriesMap.traces.builder,
- queryData: [
- {
- ...initialQueryBuilderFormValues,
- filters: {
- items: [
- {
- id: '95564eb1',
- key: {
- key: 'name',
- dataType: DataTypes.String,
- type: 'tag',
- isColumn: true,
- isJSON: false,
- id: 'name--string--tag--true',
- },
- op: 'in',
- value: ['HTTP GET /customer'],
- },
- {
- id: '3337951c',
- key: {
- key: 'serviceName',
- dataType: DataTypes.String,
- type: 'tag',
- isColumn: true,
- isJSON: false,
- id: 'serviceName--string--tag--true',
- },
- op: 'in',
- value: ['demo-app'],
- },
- ],
- op: 'AND',
- },
- },
- ],
- },
-};
+jest.mock('container/OptionsMenu/useOptionsMenu', () => ({
+ __esModule: true,
+ default: (): any => optionMenuReturn,
+}));
-describe('TracesExplorer - ', () => {
+describe('TracesExplorer - Filters', () => {
// Initial filter panel rendering
// Test the initial state like which filters section are opened, default state of duration slider, etc.
it('should render the Trace filter', async () => {
@@ -457,3 +433,255 @@ describe('TracesExplorer - ', () => {
).toBeInTheDocument();
});
});
+
+const handleExplorerTabChangeTest = jest.fn();
+jest.mock('hooks/useHandleExplorerTabChange', () => ({
+ useHandleExplorerTabChange: jest.fn(() => ({
+ handleExplorerTabChange: handleExplorerTabChangeTest,
+ })),
+}));
+
+describe('TracesExplorer - ', () => {
+ it('should render the traces explorer page', async () => {
+ server.use(
+ rest.post('http://localhost/api/v3/query_range', (req, res, ctx) =>
+ res(ctx.status(200), ctx.json(queryRangeForTimeSeries)),
+ ),
+ );
+ const { findByText, getByText } = render();
+
+ // assert mocked date time selection
+ expect(await findByText('MockDateTimeSelection')).toBeInTheDocument();
+
+ // assert stage&Btn
+ expect(getByText('Stage & Run Query')).toBeInTheDocument();
+
+ // assert QB - will not write tests for QB as that would be covererd in QB tests separately
+ expect(
+ getByText(
+ 'Search Filter : select options from suggested values, for IN/NOT IN operators - press "Enter" after selecting options',
+ ),
+ ).toBeInTheDocument();
+ expect(getByText('AGGREGATION INTERVAL')).toBeInTheDocument();
+ expect(getByText('Metrics name')).toBeInTheDocument();
+ expect(getByText('WHERE')).toBeInTheDocument();
+ expect(getByText('Legend Format')).toBeInTheDocument();
+
+ // assert timeseries chart mock
+ expect(await screen.findByText('MockUplot')).toBeInTheDocument();
+ });
+
+ it('check tab navigation', async () => {
+ const { getByText } = render();
+
+ // switch to list view
+ const listViewBtn = getByText('List View');
+ expect(listViewBtn).toBeInTheDocument();
+ fireEvent.click(listViewBtn);
+
+ expect(handleExplorerTabChangeTest).toBeCalledWith(PANEL_TYPES.LIST);
+
+ // switch to traces view
+ const tracesBtn = getByText('Traces');
+ expect(tracesBtn).toBeInTheDocument();
+ fireEvent.click(tracesBtn);
+
+ expect(handleExplorerTabChangeTest).toBeCalledWith(PANEL_TYPES.TRACE);
+
+ // switch to Table view
+ const TableBtn = getByText('Table View');
+ expect(TableBtn).toBeInTheDocument();
+ fireEvent.click(TableBtn);
+
+ expect(handleExplorerTabChangeTest).toBeCalledWith(PANEL_TYPES.TABLE);
+ });
+
+ it('trace explorer - list view', async () => {
+ server.use(
+ rest.post('http://localhost/api/v3/query_range', (req, res, ctx) =>
+ res(ctx.status(200), ctx.json(queryRangeForListView)),
+ ),
+ );
+
+ const { getByText } = render(
+
+
+ ,
+ );
+
+ expect(await screen.findByText('Timestamp')).toBeInTheDocument();
+ expect(getByText('options_menu.options')).toBeInTheDocument();
+
+ // test if pagination is there
+ expect(getByText('Previous')).toBeInTheDocument();
+ expect(getByText('Next')).toBeInTheDocument();
+
+ // column interaction is covered in E2E tests as its a complex interaction
+ });
+
+ it('trace explorer - table view', async () => {
+ server.use(
+ rest.post('http://localhost/api/v3/query_range', (req, res, ctx) =>
+ res(ctx.status(200), ctx.json(queryRangeForTableView)),
+ ),
+ );
+ render(
+
+
+ ,
+ );
+
+ expect(await screen.findByText('count')).toBeInTheDocument();
+ expect(screen.getByText('87798.00')).toBeInTheDocument();
+ });
+
+ it('trace explorer - trace view', async () => {
+ server.use(
+ rest.post('http://localhost/api/v3/query_range', (req, res, ctx) =>
+ res(ctx.status(200), ctx.json(queryRangeForTraceView)),
+ ),
+ );
+ const { getByText, getAllByText } = render(
+
+
+ ,
+ );
+
+ expect(await screen.findByText('Root Service Name')).toBeInTheDocument();
+
+ // assert table headers
+ expect(getByText('Root Operation Name')).toBeInTheDocument();
+ expect(getByText('Root Duration (in ms)')).toBeInTheDocument();
+ expect(getByText('TraceID')).toBeInTheDocument();
+ expect(getByText('No of Spans')).toBeInTheDocument();
+
+ // assert row values
+ ['demo-app', 'home', '8'].forEach((val) =>
+ expect(getAllByText(val)[0]).toBeInTheDocument(),
+ );
+ expect(getByText('7245.23ms')).toBeInTheDocument();
+
+ // assert traceId and redirection to trace details
+ const traceId = getByText('5765b60ba7cc4ddafe8bdaa9c1b4b246');
+ fireEvent.click(traceId);
+
+ // assert redirection - should go to /trace/:traceId
+ expect(window.location.href).toEqual(
+ 'http://localhost/trace/5765b60ba7cc4ddafe8bdaa9c1b4b246',
+ );
+ });
+
+ it('test for explorer options', async () => {
+ const { getByText, getByTestId } = render();
+
+ // assert explorer options - action btns
+ [
+ 'Save this view',
+ 'Create an Alert',
+ 'Add to Dashboard',
+ 'Select a view',
+ ].forEach((val) => expect(getByText(val)).toBeInTheDocument());
+
+ const hideExplorerOption = getByTestId('hide-toolbar');
+ expect(hideExplorerOption).toBeInTheDocument();
+ fireEvent.click(hideExplorerOption);
+
+ // explorer options should hide and show btn should be present
+ expect(await screen.findByTestId('show-explorer-option')).toBeInTheDocument();
+ expect(screen.queryByTestId('hide-toolbar')).toBeNull();
+
+ // show explorer options
+ const showExplorerOption = screen.getByTestId('show-explorer-option');
+ expect(showExplorerOption).toBeInTheDocument();
+ fireEvent.click(showExplorerOption);
+
+ // explorer options should show and hide btn should be present
+ expect(await screen.findByTestId('hide-toolbar')).toBeInTheDocument();
+ });
+
+ it('select a view options - assert and save this view', async () => {
+ const { container } = render();
+
+ await act(async () => {
+ fireEvent.mouseDown(
+ container.querySelector(
+ '.view-options .ant-select-selection-search-input',
+ ) as HTMLElement,
+ );
+ });
+
+ const viewListOptions = await screen.findByRole('listbox');
+ expect(viewListOptions).toBeInTheDocument();
+
+ expect(
+ within(viewListOptions).getByText('success traces list view'),
+ ).toBeInTheDocument();
+
+ expect(within(viewListOptions).getByText('Table View')).toBeInTheDocument();
+
+ // save this view
+ fireEvent.click(screen.getByText('Save this view'));
+
+ const saveViewModalInput = await screen.findByPlaceholderText(
+ 'e.g. External http method view',
+ );
+ expect(saveViewModalInput).toBeInTheDocument();
+
+ const saveViewModal = document.querySelector(
+ '.ant-modal-content',
+ ) as HTMLElement;
+ expect(saveViewModal).toBeInTheDocument();
+
+ await act(async () =>
+ fireEvent.change(saveViewModalInput, { target: { value: 'test view' } }),
+ );
+
+ expect(saveViewModalInput).toHaveValue('test view');
+ await act(async () => {
+ fireEvent.click(within(saveViewModal).getByTestId('save-view-btn'));
+ });
+
+ expect(successNotification).toHaveBeenCalledWith({
+ message: 'View Saved Successfully',
+ });
+ });
+
+ it('create a dashboard btn assert', async () => {
+ const { getByText } = render();
+
+ const createDashboardBtn = getByText('Add to Dashboard');
+ expect(createDashboardBtn).toBeInTheDocument();
+ fireEvent.click(createDashboardBtn);
+
+ expect(await screen.findByText('Export Panel')).toBeInTheDocument();
+ const createDashboardModal = document.querySelector(
+ '.ant-modal-content',
+ ) as HTMLElement;
+ expect(createDashboardModal).toBeInTheDocument();
+
+ // assert modal content
+ expect(
+ within(createDashboardModal).getByText('Select Dashboard'),
+ ).toBeInTheDocument();
+
+ expect(
+ within(createDashboardModal).getByText('New Dashboard'),
+ ).toBeInTheDocument();
+ });
+
+ it('create an alert btn assert', async () => {
+ const { getByText } = render();
+
+ const createAlertBtn = getByText('Create an Alert');
+ expect(createAlertBtn).toBeInTheDocument();
+ fireEvent.click(createAlertBtn);
+
+ expect(historyPush).toHaveBeenCalledWith(
+ expect.stringContaining(`${ROUTES.ALERTS_NEW}`),
+ );
+ });
+});
diff --git a/frontend/src/pages/TracesExplorer/__test__/testUtils.ts b/frontend/src/pages/TracesExplorer/__test__/testUtils.ts
new file mode 100644
index 0000000000..80d96c9cf3
--- /dev/null
+++ b/frontend/src/pages/TracesExplorer/__test__/testUtils.ts
@@ -0,0 +1,261 @@
+/* eslint-disable sonarjs/no-duplicate-string */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-await-in-loop */
+import {
+ initialQueriesMap,
+ initialQueryBuilderFormValues,
+ PANEL_TYPES,
+} from 'constants/queryBuilder';
+import { noop } from 'lodash-es';
+import { screen, waitFor } from 'tests/test-utils';
+import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
+import { Query } from 'types/api/queryBuilder/queryBuilderData';
+
+import { AllTraceFilterKeyValue } from '../Filter/filterUtils';
+
+export const optionMenuReturn = {
+ options: {
+ selectColumns: [
+ {
+ key: 'serviceName',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'serviceName--string--tag--true',
+ },
+ {
+ key: 'name',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'name--string--tag--true',
+ },
+ {
+ key: 'durationNano',
+ dataType: 'float64',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'durationNano--float64--tag--true',
+ },
+ {
+ key: 'httpMethod',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'httpMethod--string--tag--true',
+ },
+ {
+ key: 'responseStatusCode',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'responseStatusCode--string--tag--true',
+ },
+ {
+ key: 'statusCode',
+ dataType: 'float64',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'statusCode--float64--tag--true',
+ },
+ {
+ key: 'dbName',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'dbName--string--tag--true',
+ },
+ ],
+ maxLines: 2,
+ format: 'list',
+ },
+ handleOptionsChange: jest.fn(),
+ config: {
+ addColumn: {
+ isFetching: false,
+ value: [
+ {
+ key: 'serviceName',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'serviceName--string--tag--true',
+ },
+ {
+ key: 'name',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'name--string--tag--true',
+ },
+ {
+ key: 'durationNano',
+ dataType: 'float64',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'durationNano--float64--tag--true',
+ },
+ {
+ key: 'httpMethod',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'httpMethod--string--tag--true',
+ },
+ {
+ key: 'responseStatusCode',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'responseStatusCode--string--tag--true',
+ },
+ {
+ key: 'statusCode',
+ dataType: 'float64',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'statusCode--float64--tag--true',
+ },
+ {
+ key: 'dbName',
+ dataType: 'string',
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'dbName--string--tag--true',
+ },
+ ],
+ options: [],
+ },
+ format: {
+ value: 'list',
+ },
+ maxLines: {
+ value: 2,
+ },
+ },
+};
+
+export const compositeQuery: Query = {
+ ...initialQueriesMap.traces,
+ builder: {
+ ...initialQueriesMap.traces.builder,
+ queryData: [
+ {
+ ...initialQueryBuilderFormValues,
+ filters: {
+ items: [
+ {
+ id: '95564eb1',
+ key: {
+ key: 'name',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'name--string--tag--true',
+ },
+ op: 'in',
+ value: ['HTTP GET /customer'],
+ },
+ {
+ id: '3337951c',
+ key: {
+ key: 'serviceName',
+ dataType: DataTypes.String,
+ type: 'tag',
+ isColumn: true,
+ isJSON: false,
+ id: 'serviceName--string--tag--true',
+ },
+ op: 'in',
+ value: ['demo-app'],
+ },
+ ],
+ op: 'AND',
+ },
+ },
+ ],
+ },
+};
+
+export const redirectWithQueryBuilderData = jest.fn();
+
+export const qbProviderValue = {
+ currentQuery: {
+ ...initialQueriesMap.traces,
+ builder: {
+ ...initialQueriesMap.traces.builder,
+ queryData: [initialQueryBuilderFormValues],
+ },
+ },
+ redirectWithQueryBuilderData,
+ panelType: PANEL_TYPES.LIST,
+ setSupersetQuery: jest.fn(),
+ supersetQuery: initialQueriesMap.traces,
+ stagedQuery: initialQueriesMap.traces,
+ initialDataSource: null,
+ isEnabledQuery: false,
+ handleSetQueryData: noop,
+ handleSetFormulaData: noop,
+ handleSetQueryItemData: noop,
+ handleSetConfig: noop,
+ removeQueryBuilderEntityByIndex: noop,
+ removeQueryTypeItemByIndex: noop,
+ addNewBuilderQuery: noop,
+ cloneQuery: noop,
+ addNewFormula: noop,
+ addNewQueryItem: noop,
+ handleRunQuery: noop,
+ resetQuery: noop,
+ updateAllQueriesOperators: (): Query => initialQueriesMap.traces,
+ updateQueriesData: (): Query => initialQueriesMap.traces,
+ initQueryBuilderData: noop,
+ handleOnUnitsChange: noop,
+ isStagedQueryUpdated: (): boolean => false,
+} as any;
+
+export function checkIfSectionIsOpen(
+ getByTestId: (testId: string) => HTMLElement,
+ panelName: string,
+): void {
+ const section = getByTestId(`collapse-${panelName}`);
+ expect(section.querySelector('.ant-collapse-item-active')).not.toBeNull();
+}
+
+export function checkIfSectionIsNotOpen(
+ getByTestId: (testId: string) => HTMLElement,
+ panelName: string,
+): void {
+ const section = getByTestId(`collapse-${panelName}`);
+ expect(section.querySelector('.ant-collapse-item-active')).toBeNull();
+}
+
+export const defaultOpenSections = ['hasError', 'durationNano', 'serviceName'];
+
+export const defaultClosedSections = Object.keys(AllTraceFilterKeyValue).filter(
+ (section) =>
+ ![...defaultOpenSections, 'durationNanoMin', 'durationNanoMax'].includes(
+ section,
+ ),
+);
+
+export async function checkForSectionContent(values: string[]): Promise {
+ for (const val of values) {
+ const sectionContent = await screen.findByText(val);
+ await waitFor(() => expect(sectionContent).toBeInTheDocument());
+ }
+}