diff --git a/frontend/public/locales/en-GB/explorer.json b/frontend/public/locales/en-GB/explorer.json
new file mode 100644
index 0000000000..b4ffa6148c
--- /dev/null
+++ b/frontend/public/locales/en-GB/explorer.json
@@ -0,0 +1,3 @@
+{
+ "name_of_the_view": "Name of the view"
+}
\ No newline at end of file
diff --git a/frontend/public/locales/en/explorer.json b/frontend/public/locales/en/explorer.json
new file mode 100644
index 0000000000..b4ffa6148c
--- /dev/null
+++ b/frontend/public/locales/en/explorer.json
@@ -0,0 +1,3 @@
+{
+ "name_of_the_view": "Name of the view"
+}
\ No newline at end of file
diff --git a/frontend/src/components/ExplorerCard/ExplorerCard.tsx b/frontend/src/components/ExplorerCard/ExplorerCard.tsx
index a7dda9ce95..4615eedd5a 100644
--- a/frontend/src/components/ExplorerCard/ExplorerCard.tsx
+++ b/frontend/src/components/ExplorerCard/ExplorerCard.tsx
@@ -1,6 +1,5 @@
import {
DeleteOutlined,
- DownOutlined,
MoreOutlined,
SaveOutlined,
ShareAltOutlined,
@@ -13,13 +12,13 @@ import {
MenuProps,
Popover,
Row,
+ Select,
Space,
Typography,
} from 'antd';
import axios from 'axios';
import TextToolTip from 'components/TextToolTip';
import { SOMETHING_WENT_WRONG } from 'constants/api';
-import { querySearchParams } from 'constants/queryBuilderQueryNames';
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
@@ -28,10 +27,14 @@ import { useUpdateView } from 'hooks/saveViews/useUpdateView';
import useErrorNotification from 'hooks/useErrorNotification';
import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
-import { useCallback, useEffect, useMemo, useState } from 'react';
+import { useEffect, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
-import { ExploreHeaderToolTip, SaveButtonText } from './constants';
+import {
+ ExploreHeaderToolTip,
+ querySearchParams,
+ SaveButtonText,
+} from './constants';
import MenuItemGenerator from './MenuItemGenerator';
import SaveViewWithName from './SaveViewWithName';
import {
@@ -63,6 +66,7 @@ function ExplorerCard({
currentQuery,
panelType,
redirectWithQueryBuilderData,
+ updateAllQueriesOperators,
} = useQueryBuilder();
const {
@@ -75,11 +79,7 @@ function ExplorerCard({
useErrorNotification(error);
- const handlePopOverClose = (): void => {
- setIsOpen(false);
- };
-
- const handleOpenChange = (newOpen: boolean): void => {
+ const handleOpenChange = (newOpen = false): void => {
setIsOpen(newOpen);
};
@@ -104,7 +104,7 @@ function ExplorerCard({
});
};
- const onDeleteHandler = useCallback(() => {
+ const onDeleteHandler = (): void =>
deleteViewHandler({
deleteViewAsync,
notifications,
@@ -113,15 +113,9 @@ function ExplorerCard({
refetchAllView,
viewId: viewKey,
viewKey,
+ updateAllQueriesOperators,
+ sourcePage: sourcepage,
});
- }, [
- deleteViewAsync,
- notifications,
- panelType,
- redirectWithQueryBuilderData,
- refetchAllView,
- viewKey,
- ]);
const onUpdateQueryHandler = (): void => {
updateViewAsync(
@@ -165,38 +159,16 @@ function ExplorerCard({
panelType,
]);
- const menu = useMemo(
- (): MenuProps => ({
- items: viewsData?.data?.data?.map((view) => ({
- key: view.uuid,
- label: (
-
- ),
- })),
- }),
- [refetchAllView, viewKey, viewsData?.data?.data],
- );
-
- const moreOptionMenu = useMemo(
- (): MenuProps => ({
- items: [
- {
- key: 'delete',
- label: Delete,
- onClick: onDeleteHandler,
- icon: ,
- },
- ],
- }),
- [onDeleteHandler],
- );
+ const moreOptionMenu: MenuProps = {
+ items: [
+ {
+ key: 'delete',
+ label: Delete,
+ onClick: onDeleteHandler,
+ icon: ,
+ },
+ ],
+ };
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
const saveButtonIcon = isQueryUpdated ? null : ;
@@ -215,20 +187,32 @@ function ExplorerCard({
/>
-
+
{viewsData?.data.data && viewsData?.data.data.length && (
- {/* Saved Views */}
- }
- trigger={['click']}
- overlayStyle={DropDownOverlay}
+ showSearch
+ placeholder="Select a view"
+ dropdownStyle={DropDownOverlay}
+ dropdownMatchSelectWidth={false}
+ value={null}
>
- Select View
-
+ {viewsData?.data.data.map((view) => (
+
+
+
+ ))}
+
)}
{isQueryUpdated && (
@@ -246,7 +230,7 @@ function ExplorerCard({
content={
}
diff --git a/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx b/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
index 2b101385e9..77a143adf9 100644
--- a/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
+++ b/frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
@@ -17,9 +17,15 @@ function MenuItemGenerator({
uuid,
viewData,
refetchAllView,
+ sourcePage,
}: MenuItemLabelGeneratorProps): JSX.Element {
- const { panelType, redirectWithQueryBuilderData } = useQueryBuilder();
+ const {
+ panelType,
+ redirectWithQueryBuilderData,
+ updateAllQueriesOperators,
+ } = useQueryBuilder();
const { handleExplorerTabChange } = useHandleExplorerTabChange();
+
const { notifications } = useNotifications();
const { mutateAsync: deleteViewAsync } = useDeleteView(uuid);
@@ -34,6 +40,8 @@ function MenuItemGenerator({
refetchAllView,
viewId: uuid,
viewKey,
+ updateAllQueriesOperators,
+ sourcePage,
});
};
diff --git a/frontend/src/components/ExplorerCard/SaveViewWithName.tsx b/frontend/src/components/ExplorerCard/SaveViewWithName.tsx
index 3f77a769c8..7e83e54003 100644
--- a/frontend/src/components/ExplorerCard/SaveViewWithName.tsx
+++ b/frontend/src/components/ExplorerCard/SaveViewWithName.tsx
@@ -1,13 +1,13 @@
-import { Card, Input, Typography } from 'antd';
+import { Card, Form, Input, Typography } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useSaveView } from 'hooks/saveViews/useSaveView';
import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
-import { ChangeEvent, useCallback, useState } from 'react';
+import { useTranslation } from 'react-i18next';
import { SaveButton } from './styles';
-import { SaveViewWithNameProps } from './types';
+import { SaveViewFormProps, SaveViewWithNameProps } from './types';
import { saveViewHandler } from './utils';
function SaveViewWithName({
@@ -15,7 +15,8 @@ function SaveViewWithName({
handlePopOverClose,
refetchAllView,
}: SaveViewWithNameProps): JSX.Element {
- const [name, setName] = useState('');
+ const [form] = Form.useForm();
+ const { t } = useTranslation(['explorer']);
const {
currentQuery,
panelType,
@@ -25,19 +26,12 @@ function SaveViewWithName({
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
const { isLoading, mutateAsync: saveViewAsync } = useSaveView({
- viewName: name,
+ viewName: form.getFieldValue('viewName'),
compositeQuery,
sourcePage,
extraData: '',
});
- const onChangeHandler = useCallback(
- (e: ChangeEvent): void => {
- setName(e.target.value);
- },
- [],
- );
-
const onSaveHandler = (): void => {
saveViewHandler({
compositeQuery,
@@ -49,18 +43,32 @@ function SaveViewWithName({
refetchAllView,
saveViewAsync,
sourcePage,
- viewName: name,
- setName,
+ viewName: form.getFieldValue('viewName'),
+ form,
});
};
return (
- Name of the View
-
-
- Save
-
+ {t('name_of_the_view')}
+
+
+
+
+ Save
+
+
);
}
diff --git a/frontend/src/components/ExplorerCard/constants.ts b/frontend/src/components/ExplorerCard/constants.ts
index 8caffb366c..c6e1fd6a9e 100644
--- a/frontend/src/components/ExplorerCard/constants.ts
+++ b/frontend/src/components/ExplorerCard/constants.ts
@@ -8,3 +8,13 @@ export const SaveButtonText = {
SAVE_AS_NEW_VIEW: 'Save as new view',
SAVE_VIEW: 'Save view',
};
+
+export type QuerySearchParamNames = 'viewName' | 'viewKey';
+
+export const querySearchParams: Record<
+ QuerySearchParamNames,
+ QuerySearchParamNames
+> = {
+ viewName: 'viewName',
+ viewKey: 'viewKey',
+};
diff --git a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx
index c7289756d7..7efc8a65cd 100644
--- a/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx
+++ b/frontend/src/components/ExplorerCard/test/ExplorerCard.test.tsx
@@ -62,15 +62,12 @@ describe('ExplorerCard', () => {
const screen = render(
Mock Children,
);
- const selectButton = screen.getByText('Select View');
+ const selectPlaceholder = screen.getByText('Select a view');
- fireEvent.click(selectButton);
-
- const spanElement = screen.getByRole('img', {
- name: 'down',
+ fireEvent.mouseDown(selectPlaceholder);
+ const viewNameText = await screen.getAllByText('View 1');
+ viewNameText.forEach((element) => {
+ expect(element).toBeInTheDocument();
});
- fireEvent.click(spanElement);
- const viewNameText = await screen.findByText('View 2');
- expect(viewNameText).toBeInTheDocument();
});
});
diff --git a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx
index de4f9c06a7..c869024f19 100644
--- a/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx
+++ b/frontend/src/components/ExplorerCard/test/MenuItemGenerator.test.tsx
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import ROUTES from 'constants/routes';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
+import { DataSource } from 'types/common/queryBuilder';
import { viewMockData } from '../__mock__/viewData';
import MenuItemGenerator from '../MenuItemGenerator';
@@ -12,6 +13,12 @@ jest.mock('react-router-dom', () => ({
}),
}));
+jest.mock('antd/es/form/Form', () => ({
+ useForm: jest.fn().mockReturnValue({
+ onFinish: jest.fn(),
+ }),
+}));
+
describe('MenuItemGenerator', () => {
it('should render MenuItemGenerator component', () => {
const screen = render(
@@ -23,6 +30,7 @@ describe('MenuItemGenerator', () => {
uuid={viewMockData[0].uuid}
refetchAllView={jest.fn()}
viewData={viewMockData}
+ sourcePage={DataSource.TRACES}
/>
,
);
@@ -40,6 +48,7 @@ describe('MenuItemGenerator', () => {
uuid={viewMockData[0].uuid}
refetchAllView={jest.fn()}
viewData={viewMockData}
+ sourcePage={DataSource.TRACES}
/>
,
);
diff --git a/frontend/src/components/ExplorerCard/types.ts b/frontend/src/components/ExplorerCard/types.ts
index bda7f702b9..9f4eed3f32 100644
--- a/frontend/src/components/ExplorerCard/types.ts
+++ b/frontend/src/components/ExplorerCard/types.ts
@@ -1,7 +1,7 @@
+import { FormInstance } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import { AxiosResponse } from 'axios';
import { PANEL_TYPES } from 'constants/queryBuilder';
-import { SetStateAction } from 'react';
import { UseMutateAsyncFunction } from 'react-query';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -38,6 +38,10 @@ export interface SaveViewWithNameProps {
refetchAllView: VoidFunction;
}
+export interface SaveViewFormProps {
+ viewName: string;
+}
+
export interface MenuItemLabelGeneratorProps {
viewName: string;
viewKey: string;
@@ -45,6 +49,7 @@ export interface MenuItemLabelGeneratorProps {
uuid: string;
viewData: ViewProps[];
refetchAllView: VoidFunction;
+ sourcePage: ExplorerCardProps['sourcepage'];
}
export interface SaveViewHandlerProps {
@@ -63,7 +68,7 @@ export interface SaveViewHandlerProps {
>;
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
- setName: (value: SetStateAction) => void;
+ form: FormInstance;
}
export interface DeleteViewHandlerProps {
@@ -74,4 +79,6 @@ export interface DeleteViewHandlerProps {
panelType: PANEL_TYPES | null;
viewKey: string;
viewId: string;
+ updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'];
+ sourcePage: ExplorerCardProps['sourcepage'];
}
diff --git a/frontend/src/components/ExplorerCard/utils.ts b/frontend/src/components/ExplorerCard/utils.ts
index e846eb4c6a..012ecd07ee 100644
--- a/frontend/src/components/ExplorerCard/utils.ts
+++ b/frontend/src/components/ExplorerCard/utils.ts
@@ -1,14 +1,12 @@
import { NotificationInstance } from 'antd/es/notification/interface';
import axios from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api';
-import { initialQueriesMap } from 'constants/queryBuilder';
-import {
- queryParamNamesMap,
- querySearchParams,
-} from 'constants/queryBuilderQueryNames';
+import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
+import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import isEqual from 'lodash-es/isEqual';
+import { querySearchParams } from './constants';
import {
DeleteViewHandlerProps,
GetViewDetailsUsingViewKey,
@@ -108,7 +106,7 @@ export const saveViewHandler = ({
extraData,
redirectWithQueryBuilderData,
panelType,
- setName,
+ form,
}: SaveViewHandlerProps): void => {
saveViewAsync(
{
@@ -134,7 +132,7 @@ export const saveViewHandler = ({
},
onSettled: () => {
handlePopOverClose();
- setName('');
+ form.resetFields();
},
},
);
@@ -148,15 +146,24 @@ export const deleteViewHandler = ({
panelType,
viewKey,
viewId,
+ updateAllQueriesOperators,
+ sourcePage,
}: DeleteViewHandlerProps): void => {
deleteViewAsync(viewKey, {
onSuccess: () => {
if (viewId === viewKey) {
- redirectWithQueryBuilderData(initialQueriesMap.traces, {
- [querySearchParams.viewName]: 'Query Builder',
- [queryParamNamesMap.panelTypes]: panelType,
- [querySearchParams.viewKey]: '',
- });
+ redirectWithQueryBuilderData(
+ updateAllQueriesOperators(
+ initialQueriesMap.traces,
+ panelType || PANEL_TYPES.LIST,
+ sourcePage,
+ ),
+ {
+ [querySearchParams.viewName]: 'Query Builder',
+ [queryParamNamesMap.panelTypes]: panelType,
+ [querySearchParams.viewKey]: '',
+ },
+ );
}
notifications.success({
message: 'View Deleted Successfully',
diff --git a/frontend/src/constants/queryBuilderQueryNames.ts b/frontend/src/constants/queryBuilderQueryNames.ts
index fd7311364b..b3ee34cf89 100644
--- a/frontend/src/constants/queryBuilderQueryNames.ts
+++ b/frontend/src/constants/queryBuilderQueryNames.ts
@@ -6,8 +6,6 @@ type QueryParamNames =
| 'selectedFields'
| 'linesPerRow';
-export type QuerySearchParamNames = 'viewName' | 'viewKey';
-
export const queryParamNamesMap: Record = {
compositeQuery: 'compositeQuery',
panelTypes: 'panelTypes',
@@ -16,11 +14,3 @@ export const queryParamNamesMap: Record = {
selectedFields: 'selectedFields',
linesPerRow: 'linesPerRow',
};
-
-export const querySearchParams: Record<
- QuerySearchParamNames,
- QuerySearchParamNames
-> = {
- viewName: 'viewName',
- viewKey: 'viewKey',
-};
diff --git a/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts
index aa4185e900..2fc936518b 100644
--- a/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts
+++ b/frontend/src/hooks/queryBuilder/useGetSearchQueryParam.ts
@@ -1,4 +1,4 @@
-import { QuerySearchParamNames } from 'constants/queryBuilderQueryNames';
+import { QuerySearchParamNames } from 'components/ExplorerCard/constants';
import useUrlQuery from 'hooks/useUrlQuery';
import { useMemo } from 'react';
diff --git a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts
index f1bec4f502..c79da91b3b 100644
--- a/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts
+++ b/frontend/src/hooks/queryBuilder/useShareBuilderUrl.ts
@@ -8,14 +8,21 @@ import { useQueryBuilder } from './useQueryBuilder';
export type UseShareBuilderUrlParams = { defaultValue: Query };
export const useShareBuilderUrl = (defaultQuery: Query): void => {
- const { redirectWithQueryBuilderData } = useQueryBuilder();
+ const { resetQuery, redirectWithQueryBuilderData } = useQueryBuilder();
const urlQuery = useUrlQuery();
const compositeQuery = useGetCompositeQueryParam();
useEffect(() => {
if (!compositeQuery) {
+ resetQuery(defaultQuery);
redirectWithQueryBuilderData(defaultQuery);
}
- }, [defaultQuery, urlQuery, compositeQuery, redirectWithQueryBuilderData]);
+ }, [
+ defaultQuery,
+ urlQuery,
+ redirectWithQueryBuilderData,
+ compositeQuery,
+ resetQuery,
+ ]);
};
diff --git a/frontend/src/hooks/saveViews/useSaveView.ts b/frontend/src/hooks/saveViews/useSaveView.ts
index d3f4da00ef..0f35624c7a 100644
--- a/frontend/src/hooks/saveViews/useSaveView.ts
+++ b/frontend/src/hooks/saveViews/useSaveView.ts
@@ -16,11 +16,5 @@ export const useSaveView = ({
> =>
useMutation({
mutationKey: [viewName, sourcePage, compositeQuery, extraData],
- mutationFn: () =>
- saveView({
- compositeQuery,
- sourcePage,
- viewName,
- extraData,
- }),
+ mutationFn: saveView,
});
diff --git a/frontend/src/hooks/useHandleExplorerTabChange.ts b/frontend/src/hooks/useHandleExplorerTabChange.ts
index a4b605362c..7a6e1020dd 100644
--- a/frontend/src/hooks/useHandleExplorerTabChange.ts
+++ b/frontend/src/hooks/useHandleExplorerTabChange.ts
@@ -1,8 +1,6 @@
+import { querySearchParams } from 'components/ExplorerCard/constants';
import { initialAutocompleteData, PANEL_TYPES } from 'constants/queryBuilder';
-import {
- queryParamNamesMap,
- querySearchParams,
-} from 'constants/queryBuilderQueryNames';
+import { queryParamNamesMap } from 'constants/queryBuilderQueryNames';
import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants';
import { useCallback } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
@@ -25,8 +23,7 @@ export const useHandleExplorerTabChange = (): {
updateQueriesData,
} = useQueryBuilder();
- const viewName =
- useGetSearchQueryParam(querySearchParams.viewName) || 'Query Builder';
+ const viewName = useGetSearchQueryParam(querySearchParams.viewName) || '';
const viewKey = useGetSearchQueryParam(querySearchParams.viewKey) || '';
diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx
index 31b141ccb6..a95d02b71e 100644
--- a/frontend/src/providers/QueryBuilder.tsx
+++ b/frontend/src/providers/QueryBuilder.tsx
@@ -68,6 +68,7 @@ export const QueryBuilderContext = createContext({
addNewQueryItem: () => {},
redirectWithQueryBuilderData: () => {},
handleRunQuery: () => {},
+ resetQuery: () => {},
updateAllQueriesOperators: () => initialQueriesMap.metrics,
updateQueriesData: () => initialQueriesMap.metrics,
initQueryBuilderData: () => {},
@@ -550,6 +551,14 @@ export function QueryBuilderProvider({
stagedQuery,
]);
+ const resetQuery = (newCurrentQuery?: QueryState): void => {
+ setStagedQuery(null);
+
+ if (newCurrentQuery) {
+ setCurrentQuery(newCurrentQuery);
+ }
+ };
+
useEffect(() => {
if (stagedQuery && location.pathname !== currentPathnameRef.current) {
currentPathnameRef.current = location.pathname;
@@ -599,6 +608,7 @@ export function QueryBuilderProvider({
addNewQueryItem,
redirectWithQueryBuilderData,
handleRunQuery,
+ resetQuery,
updateAllQueriesOperators,
updateQueriesData,
initQueryBuilderData,
diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts
index e35cd32bed..eaed61da34 100644
--- a/frontend/src/types/common/queryBuilder.ts
+++ b/frontend/src/types/common/queryBuilder.ts
@@ -6,6 +6,7 @@ import {
IClickHouseQuery,
IPromQLQuery,
Query,
+ QueryState,
} from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from './dashboard';
@@ -187,6 +188,7 @@ export type QueryBuilderContextType = {
searchParams?: Record,
) => void;
handleRunQuery: () => void;
+ resetQuery: (newCurrentQuery?: QueryState) => void;
handleOnUnitsChange: (units: Format['id']) => void;
updateAllQueriesOperators: (
queryData: Query,