diff --git a/frontend/src/mocks-server/__mockdata__/explorer_views.ts b/frontend/src/mocks-server/__mockdata__/explorer_views.ts
index b51eb2ee9c..4719e77697 100644
--- a/frontend/src/mocks-server/__mockdata__/explorer_views.ts
+++ b/frontend/src/mocks-server/__mockdata__/explorer_views.ts
@@ -78,13 +78,13 @@ export const explorerView = {
extraData: '{"color":"#00ffd0"}',
},
{
- uuid: '58b010b6-8be9-40d1-8d25-f73b5f7314ad',
- name: 'success traces list view',
+ uuid: '8c4bf492-d54d-4ab2-a8d6-9c1563f46e1f',
+ name: 'R-test panel',
category: '',
- createdAt: '2023-08-30T13:00:40.958011925Z',
- createdBy: 'test-email',
- updatedAt: '2024-04-29T13:09:06.175537361Z',
- updatedBy: 'test-email',
+ createdAt: '2024-07-01T13:45:57.924686766Z',
+ createdBy: 'test-user-test',
+ updatedAt: '2024-07-01T13:48:31.032106578Z',
+ updatedBy: 'test-user-test',
sourcePage: 'traces',
tags: [''],
compositeQuery: {
@@ -106,13 +106,13 @@ export const explorerView = {
items: [
{
key: {
- key: 'responseStatusCode',
+ key: 'httpMethod',
dataType: 'string',
type: 'tag',
isColumn: true,
isJSON: false,
},
- value: '200',
+ value: 'GET',
op: '=',
},
],
@@ -128,7 +128,7 @@ export const explorerView = {
order: 'desc',
},
],
- reduceTo: 'sum',
+ reduceTo: 'avg',
timeAggregation: 'rate',
spaceAggregation: 'sum',
ShiftBy: 0,
@@ -137,7 +137,7 @@ export const explorerView = {
panelType: 'list',
queryType: 'builder',
},
- extraData: '{"color":"#bdff9d"}',
+ extraData: '{"color":"#AD7F58"}',
},
],
};
diff --git a/frontend/src/pages/SaveView/__test__/SaveView.test.tsx b/frontend/src/pages/SaveView/__test__/SaveView.test.tsx
new file mode 100644
index 0000000000..fee0d3d6f0
--- /dev/null
+++ b/frontend/src/pages/SaveView/__test__/SaveView.test.tsx
@@ -0,0 +1,172 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+/* eslint-disable sonarjs/no-duplicate-string */
+import ROUTES from 'constants/routes';
+import { explorerView } from 'mocks-server/__mockdata__/explorer_views';
+import { server } from 'mocks-server/server';
+import { rest } from 'msw';
+import { MemoryRouter, Route } from 'react-router-dom';
+import { fireEvent, render, screen, waitFor, within } from 'tests/test-utils';
+
+import SaveView from '..';
+
+const handleExplorerTabChangeTest = jest.fn();
+jest.mock('hooks/useHandleExplorerTabChange', () => ({
+ useHandleExplorerTabChange: () => ({
+ handleExplorerTabChange: handleExplorerTabChangeTest,
+ }),
+}));
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useLocation: jest.fn().mockReturnValue({
+ pathname: `${ROUTES.TRACES_SAVE_VIEWS}`,
+ }),
+}));
+
+describe('SaveView', () => {
+ it('should render the SaveView component', async () => {
+ render();
+ expect(await screen.findByText('Table View')).toBeInTheDocument();
+
+ const savedViews = screen.getAllByRole('row');
+ expect(savedViews).toHaveLength(2);
+
+ // assert row 1
+ expect(
+ within(document.querySelector('.view-tag') as HTMLElement).getByText('T'),
+ ).toBeInTheDocument();
+ expect(screen.getByText('test-user-1')).toBeInTheDocument();
+
+ // assert row 2
+ expect(screen.getByText('R-test panel')).toBeInTheDocument();
+ expect(screen.getByText('test-user-test')).toBeInTheDocument();
+ });
+
+ it('explorer icon should take the user to the related explorer page', async () => {
+ render(
+
+
+
+
+ ,
+ );
+
+ expect(await screen.findByText('Table View')).toBeInTheDocument();
+
+ const explorerIcon = await screen.findAllByTestId('go-to-explorer');
+ expect(explorerIcon[0]).toBeInTheDocument();
+
+ // Simulate click on explorer icon
+ fireEvent.click(explorerIcon[0]);
+
+ await waitFor(() =>
+ expect(handleExplorerTabChangeTest).toHaveBeenCalledWith(
+ expect.anything(),
+ expect.anything(),
+ '/traces-explorer', // Asserts the third argument is '/traces-explorer'
+ ),
+ );
+ });
+
+ it('should render the SaveView component with a search input', async () => {
+ render();
+ const searchInput = screen.getByPlaceholderText('Search for views...');
+ expect(await screen.findByText('Table View')).toBeInTheDocument();
+
+ expect(searchInput).toBeInTheDocument();
+
+ // search for 'R-test panel'
+ searchInput.focus();
+ (searchInput as HTMLInputElement).setSelectionRange(
+ 0,
+ (searchInput as HTMLInputElement).value.length,
+ );
+
+ fireEvent.change(searchInput, { target: { value: 'R-test panel' } });
+ expect(searchInput).toHaveValue('R-test panel');
+ searchInput.blur();
+
+ expect(await screen.findByText('R-test panel')).toBeInTheDocument();
+
+ // Table View should not be present now
+ const savedViews = screen.getAllByRole('row');
+ expect(savedViews).toHaveLength(1);
+ });
+
+ it('should be able to edit name of view', async () => {
+ server.use(
+ rest.put(
+ 'http://localhost/api/v1/explorer/views/test-uuid-1',
+ // eslint-disable-next-line no-return-assign
+ (_req, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.json({
+ ...explorerView,
+ data: [
+ ...explorerView.data,
+ (explorerView.data[0].name = 'New Table View'),
+ ],
+ }),
+ ),
+ ),
+ );
+ render();
+
+ const editButton = await screen.findAllByTestId('edit-view');
+ fireEvent.click(editButton[0]);
+
+ const viewName = await screen.findByTestId('view-name');
+ expect(viewName).toBeInTheDocument();
+ expect(viewName).toHaveValue('Table View');
+
+ const newViewName = 'New Table View';
+ fireEvent.change(viewName, { target: { value: newViewName } });
+ expect(viewName).toHaveValue(newViewName);
+
+ const saveButton = await screen.findByTestId('save-view');
+ fireEvent.click(saveButton);
+
+ await waitFor(() =>
+ expect(screen.getByText(newViewName)).toBeInTheDocument(),
+ );
+ });
+
+ it('should be able to delete a view', async () => {
+ server.use(
+ rest.delete(
+ 'http://localhost/api/v1/explorer/views/test-uuid-1',
+ (_req, res, ctx) => res(ctx.status(200), ctx.json({ status: 'success' })),
+ ),
+ );
+
+ render();
+
+ const deleteButton = await screen.findAllByTestId('delete-view');
+ fireEvent.click(deleteButton[0]);
+
+ expect(await screen.findByText('delete_confirm_message')).toBeInTheDocument();
+
+ const confirmButton = await screen.findByTestId('confirm-delete');
+ fireEvent.click(confirmButton);
+
+ await waitFor(() => expect(screen.queryByText('Table View')).toBeNull());
+ });
+
+ it('should render empty state', async () => {
+ server.use(
+ rest.get('http://localhost/api/v1/explorer/views', (_req, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.json({
+ status: 'success',
+ data: [],
+ }),
+ ),
+ ),
+ );
+ render();
+
+ expect(screen.getByText('No data')).toBeInTheDocument();
+ });
+});
diff --git a/frontend/src/pages/SaveView/index.tsx b/frontend/src/pages/SaveView/index.tsx
index 7088c4a78c..03c76151b7 100644
--- a/frontend/src/pages/SaveView/index.tsx
+++ b/frontend/src/pages/SaveView/index.tsx
@@ -263,13 +263,19 @@ function SaveView(): JSX.Element {
handleEditModelOpen(view, bgColor)}
/>
- handleRedirectQuery(view)} />
+ handleRedirectQuery(view)}
+ data-testid="go-to-explorer"
+ />
handleDeleteModelOpen(view.uuid, view.name)}
/>
@@ -347,6 +353,7 @@ function SaveView(): JSX.Element {
onClick={onDeleteHandler}
className="delete-btn"
disabled={isDeleteLoading}
+ data-testid="confirm-delete"
>
Delete view
,
@@ -371,6 +378,7 @@ function SaveView(): JSX.Element {
icon={}
onClick={onUpdateQueryHandler}
disabled={isViewUpdating}
+ data-testid="save-view"
>
Save changes
,
@@ -385,6 +393,7 @@ function SaveView(): JSX.Element {
setNewViewName(e.target.value)}
/>
diff --git a/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx b/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
index 0622a365c2..a28776f0d0 100644
--- a/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
+++ b/frontend/src/pages/TracesExplorer/__test__/TracesExplorer.test.tsx
@@ -617,9 +617,7 @@ describe('TracesExplorer - ', () => {
const viewListOptions = await screen.findByRole('listbox');
expect(viewListOptions).toBeInTheDocument();
- expect(
- within(viewListOptions).getByText('success traces list view'),
- ).toBeInTheDocument();
+ expect(within(viewListOptions).getByText('R-test panel')).toBeInTheDocument();
expect(within(viewListOptions).getByText('Table View')).toBeInTheDocument();