mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 15:39:06 +08:00
refactor: pop for unsaved changes (#4188)
This commit is contained in:
parent
ec500831ef
commit
9c1ea0cde9
@ -21,5 +21,9 @@
|
|||||||
"error_while_updating_variable": "Error while updating variable",
|
"error_while_updating_variable": "Error while updating variable",
|
||||||
"dashboard_has_been_updated": "Dashboard has been updated",
|
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
||||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
|
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
|
||||||
|
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||||
|
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||||
|
"your_graph_build_with": "Your graph built with",
|
||||||
|
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
|
||||||
}
|
}
|
||||||
|
@ -24,5 +24,9 @@
|
|||||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?",
|
||||||
"locked_dashboard_delete_tooltip_admin_author": "Dashboard is locked. Please unlock the dashboard to enable delete.",
|
"locked_dashboard_delete_tooltip_admin_author": "Dashboard is locked. Please unlock the dashboard to enable delete.",
|
||||||
"locked_dashboard_delete_tooltip_editor": "Dashboard is locked. Please contact admin to delete the dashboard.",
|
"locked_dashboard_delete_tooltip_editor": "Dashboard is locked. Please contact admin to delete the dashboard.",
|
||||||
"delete_dashboard_success": "{{name}} dashboard deleted successfully"
|
"delete_dashboard_success": "{{name}} dashboard deleted successfully",
|
||||||
|
"dashboard_unsave_changes": "There are unsaved changes in the Query builder, please stage and run the query or the changes will be lost. Press OK to discard.",
|
||||||
|
"dashboard_save_changes": "Your graph built with {{queryTag}} query will be saved. Press OK to confirm.",
|
||||||
|
"your_graph_build_with": "Your graph built with",
|
||||||
|
"dashboar_ok_confirm": "query will be saved. Press OK to confirm."
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { QueryParams } from 'constants/query';
|
|||||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DeleteViewHandlerProps,
|
DeleteViewHandlerProps,
|
||||||
@ -35,6 +36,45 @@ export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export const omitIdFromQuery = (query: Query | null): any => ({
|
||||||
|
...query,
|
||||||
|
builder: {
|
||||||
|
...query?.builder,
|
||||||
|
queryData: query?.builder.queryData.map((queryData) => {
|
||||||
|
const { id, ...rest } = queryData.aggregateAttribute;
|
||||||
|
const newAggregateAttribute = rest;
|
||||||
|
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
||||||
|
const { id, ...rest } = groupByAttribute;
|
||||||
|
return rest;
|
||||||
|
});
|
||||||
|
const newItems = queryData.filters.items.map((item) => {
|
||||||
|
const { id, ...newItem } = item;
|
||||||
|
if (item.key) {
|
||||||
|
const { id, ...rest } = item.key;
|
||||||
|
return {
|
||||||
|
...newItem,
|
||||||
|
key: rest,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return newItem;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...queryData,
|
||||||
|
aggregateAttribute: newAggregateAttribute,
|
||||||
|
groupBy: newGroupByAttributes,
|
||||||
|
filters: {
|
||||||
|
...queryData.filters,
|
||||||
|
items: newItems,
|
||||||
|
},
|
||||||
|
limit: queryData.limit ? queryData.limit : 0,
|
||||||
|
offset: queryData.offset ? queryData.offset : 0,
|
||||||
|
pageSize: queryData.pageSize ? queryData.pageSize : 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const isQueryUpdatedInView = ({
|
export const isQueryUpdatedInView = ({
|
||||||
viewKey,
|
viewKey,
|
||||||
data,
|
data,
|
||||||
@ -48,43 +88,7 @@ export const isQueryUpdatedInView = ({
|
|||||||
const { query, panelType } = currentViewDetails;
|
const { query, panelType } = currentViewDetails;
|
||||||
|
|
||||||
// Omitting id from aggregateAttribute and groupBy
|
// Omitting id from aggregateAttribute and groupBy
|
||||||
const updatedCurrentQuery = {
|
const updatedCurrentQuery = omitIdFromQuery(stagedQuery);
|
||||||
...stagedQuery,
|
|
||||||
builder: {
|
|
||||||
...stagedQuery?.builder,
|
|
||||||
queryData: stagedQuery?.builder.queryData.map((queryData) => {
|
|
||||||
const { id, ...rest } = queryData.aggregateAttribute;
|
|
||||||
const newAggregateAttribute = rest;
|
|
||||||
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
|
||||||
const { id, ...rest } = groupByAttribute;
|
|
||||||
return rest;
|
|
||||||
});
|
|
||||||
const newItems = queryData.filters.items.map((item) => {
|
|
||||||
const { id, ...newItem } = item;
|
|
||||||
if (item.key) {
|
|
||||||
const { id, ...rest } = item.key;
|
|
||||||
return {
|
|
||||||
...newItem,
|
|
||||||
key: rest,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return newItem;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
...queryData,
|
|
||||||
aggregateAttribute: newAggregateAttribute,
|
|
||||||
groupBy: newGroupByAttributes,
|
|
||||||
filters: {
|
|
||||||
...queryData.filters,
|
|
||||||
items: newItems,
|
|
||||||
},
|
|
||||||
limit: queryData.limit ? queryData.limit : 0,
|
|
||||||
offset: queryData.offset ? queryData.offset : 0,
|
|
||||||
pageSize: queryData.pageSize ? queryData.pageSize : 0,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
panelType !== currentPanelType ||
|
panelType !== currentPanelType ||
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { LockFilled } from '@ant-design/icons';
|
/* eslint-disable sonarjs/cognitive-complexity */
|
||||||
import { Button, Modal, Tooltip, Typography } from 'antd';
|
import { LockFilled, WarningOutlined } from '@ant-design/icons';
|
||||||
|
import { Button, Modal, Space, Tooltip, Typography } from 'antd';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
getSelectedWidgetIndex,
|
getSelectedWidgetIndex,
|
||||||
} from 'providers/Dashboard/util';
|
} from 'providers/Dashboard/util';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@ -39,6 +41,7 @@ import {
|
|||||||
RightContainerWrapper,
|
RightContainerWrapper,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
import { NewWidgetProps } from './types';
|
import { NewWidgetProps } from './types';
|
||||||
|
import { getIsQueryModified } from './utils';
|
||||||
|
|
||||||
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||||
const {
|
const {
|
||||||
@ -47,7 +50,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
setToScrollWidgetId,
|
setToScrollWidgetId,
|
||||||
} = useDashboard();
|
} = useDashboard();
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
const { t } = useTranslation(['dashboard']);
|
||||||
|
|
||||||
|
const { currentQuery, stagedQuery } = useQueryBuilder();
|
||||||
|
|
||||||
|
const isQueryModified = useMemo(
|
||||||
|
() => getIsQueryModified(currentQuery, stagedQuery),
|
||||||
|
[currentQuery, stagedQuery],
|
||||||
|
);
|
||||||
|
|
||||||
const { featureResponse } = useSelector<AppState, AppReducer>(
|
const { featureResponse } = useSelector<AppState, AppReducer>(
|
||||||
(state) => state.app,
|
(state) => state.app,
|
||||||
@ -92,6 +102,12 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
selectedWidget?.fillSpans || false,
|
selectedWidget?.fillSpans || false,
|
||||||
);
|
);
|
||||||
const [saveModal, setSaveModal] = useState(false);
|
const [saveModal, setSaveModal] = useState(false);
|
||||||
|
const [discardModal, setDiscardModal] = useState(false);
|
||||||
|
|
||||||
|
const closeModal = (): void => {
|
||||||
|
setSaveModal(false);
|
||||||
|
setDiscardModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
const [graphType, setGraphType] = useState(selectedGraph);
|
const [graphType, setGraphType] = useState(selectedGraph);
|
||||||
|
|
||||||
@ -206,6 +222,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const onClickDiscardHandler = useCallback(() => {
|
const onClickDiscardHandler = useCallback(() => {
|
||||||
|
if (isQueryModified) {
|
||||||
|
setDiscardModal(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
|
||||||
|
}, [dashboardId, isQueryModified]);
|
||||||
|
|
||||||
|
const discardChanges = useCallback(() => {
|
||||||
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
|
history.push(generatePath(ROUTES.DASHBOARD, { dashboardId }));
|
||||||
}, [dashboardId]);
|
}, [dashboardId]);
|
||||||
|
|
||||||
@ -321,21 +345,54 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
</RightContainerWrapper>
|
</RightContainerWrapper>
|
||||||
</PanelContainer>
|
</PanelContainer>
|
||||||
<Modal
|
<Modal
|
||||||
title="Save Changes"
|
title={
|
||||||
|
isQueryModified ? (
|
||||||
|
<Space>
|
||||||
|
<WarningOutlined style={{ fontSize: '16px', color: '#fdd600' }} />
|
||||||
|
Unsaved Changes
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
'Save Widget'
|
||||||
|
)
|
||||||
|
}
|
||||||
focusTriggerAfterClose
|
focusTriggerAfterClose
|
||||||
forceRender
|
forceRender
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
closable
|
closable
|
||||||
onCancel={(): void => setSaveModal(false)}
|
onCancel={closeModal}
|
||||||
onOk={onClickSaveHandler}
|
onOk={onClickSaveHandler}
|
||||||
centered
|
centered
|
||||||
open={saveModal}
|
open={saveModal}
|
||||||
width={600}
|
width={600}
|
||||||
>
|
>
|
||||||
<Typography>
|
{!isQueryModified ? (
|
||||||
Your graph built with <QueryTypeTag queryType={currentQuery.queryType} />{' '}
|
<Typography>
|
||||||
query will be saved. Press OK to confirm.
|
{t('your_graph_build_with')}{' '}
|
||||||
</Typography>
|
<QueryTypeTag queryType={currentQuery.queryType} />
|
||||||
|
{t('dashboar_ok_confirm')}
|
||||||
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography>{t('dashboard_unsave_changes')} </Typography>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
title={
|
||||||
|
<Space>
|
||||||
|
<WarningOutlined style={{ fontSize: '16px', color: '#fdd600' }} />
|
||||||
|
Unsaved Changes
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
focusTriggerAfterClose
|
||||||
|
forceRender
|
||||||
|
destroyOnClose
|
||||||
|
closable
|
||||||
|
onCancel={closeModal}
|
||||||
|
onOk={discardChanges}
|
||||||
|
centered
|
||||||
|
open={discardModal}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Typography>{t('dashboard_unsave_changes')}</Typography>
|
||||||
</Modal>
|
</Modal>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
15
frontend/src/container/NewWidget/utils.ts
Normal file
15
frontend/src/container/NewWidget/utils.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { omitIdFromQuery } from 'components/ExplorerCard/utils';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||||
|
|
||||||
|
export const getIsQueryModified = (
|
||||||
|
currentQuery: Query,
|
||||||
|
stagedQuery: Query | null,
|
||||||
|
): boolean => {
|
||||||
|
if (!stagedQuery) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const omitIdFromStageQuery = omitIdFromQuery(stagedQuery);
|
||||||
|
const omitIdFromCurrentQuery = omitIdFromQuery(currentQuery);
|
||||||
|
return !isEqual(omitIdFromStageQuery, omitIdFromCurrentQuery);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user