feat: ability to clone a panel (#2444)

Co-authored-by: gitstart <gitstart@users.noreply.github.com>
Co-authored-by: Nitesh Singh <nitesh.singh@gitstart.dev>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
Co-authored-by: Prashant Shahi <prashant@signoz.io>
Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
This commit is contained in:
GitStart 2023-06-07 08:20:01 +03:00 committed by GitHub
parent 342e94d093
commit 826cbe0803
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 8 deletions

View File

@ -2,10 +2,12 @@ import { Typography } from 'antd';
import { ChartData } from 'chart.js'; import { ChartData } from 'chart.js';
import Spinner from 'components/Spinner'; import Spinner from 'components/Spinner';
import GridGraphComponent from 'container/GridGraphComponent'; import GridGraphComponent from 'container/GridGraphComponent';
import { UpdateDashboard } from 'container/GridGraphLayout/utils';
import { useNotifications } from 'hooks/useNotifications'; import { useNotifications } from 'hooks/useNotifications';
import usePreviousValue from 'hooks/usePreviousValue'; import usePreviousValue from 'hooks/usePreviousValue';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import getChartData from 'lib/getChartData'; import getChartData from 'lib/getChartData';
import history from 'lib/history';
import isEmpty from 'lodash-es/isEmpty'; import isEmpty from 'lodash-es/isEmpty';
import { import {
Dispatch, Dispatch,
@ -33,6 +35,7 @@ import { Widgets } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards'; import DashboardReducer from 'types/reducer/dashboards';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 } from 'uuid';
import { LayoutProps } from '..'; import { LayoutProps } from '..';
import EmptyWidget from '../EmptyWidget'; import EmptyWidget from '../EmptyWidget';
@ -157,6 +160,46 @@ function GridCardGraph({
t, t,
]); ]);
const onCloneHandler = async (): Promise<void> => {
const uuid = v4();
const layout = [
{
i: uuid,
w: 6,
x: 0,
h: 2,
y: 0,
},
...(selectedDashboard.data.layout || []),
];
if (widget) {
await UpdateDashboard(
{
data: selectedDashboard.data,
generateWidgetId: uuid,
graphType: widget.panelTypes,
selectedDashboard,
layout,
widgetData: widget,
isRedirected: false,
},
notifications,
).then(() => {
notifications.success({
message: 'Panel cloned successfully, redirecting to new copy.',
});
setTimeout(() => {
history.push(
`${history.location.pathname}/new?graphType=${widget.panelTypes}&widgetId=${uuid}`,
);
}, 1500);
});
}
};
const getModals = (): JSX.Element => ( const getModals = (): JSX.Element => (
<> <>
<Modal <Modal
@ -210,6 +253,7 @@ function GridCardGraph({
widget={widget} widget={widget}
onView={handleOnView} onView={handleOnView}
onDelete={handleOnDelete} onDelete={handleOnDelete}
onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
/> />
@ -241,6 +285,7 @@ function GridCardGraph({
widget={widget} widget={widget}
onView={handleOnView} onView={handleOnView}
onDelete={handleOnDelete} onDelete={handleOnDelete}
onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
/> />
@ -286,6 +331,7 @@ function GridCardGraph({
widget={widget} widget={widget}
onView={handleOnView} onView={handleOnView}
onDelete={handleOnDelete} onDelete={handleOnDelete}
onClone={onCloneHandler}
queryResponse={queryResponse} queryResponse={queryResponse}
errorMessage={errorMessage} errorMessage={errorMessage}
/> />

View File

@ -1,4 +1,5 @@
import { import {
CopyOutlined,
DeleteOutlined, DeleteOutlined,
DownOutlined, DownOutlined,
EditFilled, EditFilled,
@ -38,6 +39,7 @@ interface IWidgetHeaderProps {
widget: Widgets; widget: Widgets;
onView: VoidFunction; onView: VoidFunction;
onDelete: VoidFunction; onDelete: VoidFunction;
onClone: VoidFunction;
parentHover: boolean; parentHover: boolean;
queryResponse: UseQueryResult< queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps> | ErrorResponse SuccessResponse<MetricRangePayloadProps> | ErrorResponse
@ -49,6 +51,7 @@ function WidgetHeader({
widget, widget,
onView, onView,
onDelete, onDelete,
onClone,
parentHover, parentHover,
queryResponse, queryResponse,
errorMessage, errorMessage,
@ -81,8 +84,12 @@ function WidgetHeader({
key: 'delete', key: 'delete',
method: onDelete, method: onDelete,
}, },
clone: {
key: 'clone',
method: onClone,
},
}), }),
[onDelete, onEditHandler, onView], [onDelete, onEditHandler, onView, onClone],
); );
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback( const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
@ -116,6 +123,12 @@ function WidgetHeader({
disabled: !editWidget, disabled: !editWidget,
label: 'Edit', label: 'Edit',
}, },
{
key: keyMethodMapping.clone.key,
icon: <CopyOutlined />,
disabled: false,
label: 'Clone',
},
{ {
key: keyMethodMapping.delete.key, key: keyMethodMapping.delete.key,
icon: <DeleteOutlined />, icon: <DeleteOutlined />,
@ -130,6 +143,7 @@ function WidgetHeader({
keyMethodMapping.delete.key, keyMethodMapping.delete.key,
keyMethodMapping.edit.key, keyMethodMapping.edit.key,
keyMethodMapping.view.key, keyMethodMapping.view.key,
keyMethodMapping.clone.key,
queryResponse.isLoading, queryResponse.isLoading,
], ],
); );

View File

@ -8,7 +8,7 @@ import {
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import store from 'store'; import store from 'store';
import { Dashboard } from 'types/api/dashboard/getAll'; import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
export const UpdateDashboard = async ( export const UpdateDashboard = async (
@ -19,9 +19,11 @@ export const UpdateDashboard = async (
layout, layout,
selectedDashboard, selectedDashboard,
isRedirected, isRedirected,
widgetData,
}: UpdateDashboardProps, }: UpdateDashboardProps,
notify: NotificationInstance, notify: NotificationInstance,
): Promise<Dashboard | undefined> => { ): Promise<Dashboard | undefined> => {
const copyTitle = `${widgetData?.title} - Copy`;
const updatedSelectedDashboard: Dashboard = { const updatedSelectedDashboard: Dashboard = {
...selectedDashboard, ...selectedDashboard,
data: { data: {
@ -33,13 +35,13 @@ export const UpdateDashboard = async (
widgets: [ widgets: [
...(data.widgets || []), ...(data.widgets || []),
{ {
description: '', description: widgetData?.description || '',
id: generateWidgetId, id: generateWidgetId,
isStacked: false, isStacked: false,
nullZeroValues: '', nullZeroValues: widgetData?.nullZeroValues || '',
opacity: '', opacity: '',
panelTypes: graphType, panelTypes: graphType,
query: { query: widgetData?.query || {
queryType: EQueryType.QUERY_BUILDER, queryType: EQueryType.QUERY_BUILDER,
promql: [initialQueryPromQLData], promql: [initialQueryPromQLData],
clickhouse_sql: [initialClickHouseData], clickhouse_sql: [initialClickHouseData],
@ -49,13 +51,15 @@ export const UpdateDashboard = async (
}, },
}, },
queryData: { queryData: {
data: { queryData: [] }, data: {
queryData: widgetData?.queryData.data.queryData || [],
},
error: false, error: false,
errorMessage: '', errorMessage: '',
loading: false, loading: false,
}, },
timePreferance: 'GLOBAL_TIME', timePreferance: widgetData?.timePreferance || 'GLOBAL_TIME',
title: '', title: widgetData ? copyTitle : '',
}, },
], ],
layout, layout,
@ -91,4 +95,5 @@ interface UpdateDashboardProps {
layout: Layout[]; layout: Layout[];
selectedDashboard: Dashboard; selectedDashboard: Dashboard;
isRedirected: boolean; isRedirected: boolean;
widgetData?: Widgets;
} }