palash-signoz b335d440cf
Feat: import export dashboard (#980)
* feat: added import & export functionality in dashboards
2022-04-25 22:41:46 +05:30

271 lines
6.1 KiB
TypeScript

/* eslint-disable react/no-unstable-nested-components */
import { SaveFilled } from '@ant-design/icons';
import { notification } from 'antd';
import updateDashboardApi from 'api/dashboard/update';
import Spinner from 'components/Spinner';
import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider';
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
import { Layout } from 'react-grid-layout';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import DashboardReducer from 'types/reducer/dashboards';
import { v4 } from 'uuid';
import AddWidget from './AddWidget';
import Graph from './Graph';
import {
Button,
ButtonContainer,
Card,
CardContainer,
ReactGridLayout,
} from './styles';
import { updateDashboard } from './utils';
function GridGraph(): JSX.Element {
const { dashboards, loading } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards,
);
const [saveLayoutState, setSaveLayoutState] = useState<State>({
loading: false,
error: false,
errorMessage: '',
payload: [],
});
const [selectedDashboard] = dashboards;
const { data } = selectedDashboard;
const { widgets } = data;
const [layouts, setLayout] = useState<LayoutProps[]>([]);
const AddWidgetWrapper = useCallback(() => <AddWidget />, []);
const isMounted = useRef(true);
const isDeleted = useRef(false);
const getPreLayouts: () => LayoutProps[] = useCallback(() => {
if (widgets === undefined) {
return [];
}
// when the layout is not present
if (data.layout === undefined) {
return widgets.map((e, index) => {
return {
h: 2,
w: 6,
y: Infinity,
i: (index + 1).toString(),
x: (index % 2) * 6,
Component: (): JSX.Element => (
<Graph
name={`${e.id + index}non-expanded`}
isDeleted={isDeleted}
widget={widgets[index]}
yAxisUnit={e.yAxisUnit}
/>
),
};
});
}
return data.layout
.filter((_, index) => widgets[index])
.map((e, index) => ({
...e,
Component: (): JSX.Element => {
if (widgets[index]) {
return (
<Graph
name={e.i + index}
isDeleted={isDeleted}
widget={widgets[index]}
yAxisUnit={widgets[index].yAxisUnit}
/>
);
}
return <div />;
},
}));
}, [widgets, data.layout]);
useEffect(() => {
if (
loading === false &&
(isMounted.current === true || isDeleted.current === true)
) {
const preLayouts = getPreLayouts();
setLayout(() => {
const getX = (): number => {
if (preLayouts && preLayouts?.length > 0) {
const last = preLayouts[(preLayouts?.length || 0) - 1];
return (last.w + last.x) % 12;
}
return 0;
};
return [
...preLayouts,
{
i: (preLayouts.length + 1).toString(),
x: getX(),
y: Infinity,
w: 6,
h: 2,
Component: AddWidgetWrapper,
maxW: 6,
isDraggable: false,
isResizable: false,
isBounded: true,
},
];
});
}
return (): void => {
isMounted.current = false;
};
}, [widgets, layouts.length, AddWidgetWrapper, loading, getPreLayouts]);
const onDropHandler = useCallback(
async (allLayouts: Layout[], currentLayout: Layout, event: DragEvent) => {
event.preventDefault();
if (event.dataTransfer) {
try {
const graphType = event.dataTransfer.getData('text') as GRAPH_TYPES;
const generateWidgetId = v4();
await updateDashboard({
data,
generateWidgetId,
graphType,
selectedDashboard,
layout: allLayouts
.map((e, index) => ({
...e,
i: index.toString(),
// when a new element drops
w: e.i === '__dropping-elem__' ? 6 : e.w,
h: e.i === '__dropping-elem__' ? 2 : e.h,
}))
// removing add widgets layout config
.filter((e) => e.maxW === undefined),
});
} catch (error) {
notification.error({
message:
error instanceof Error ? error.toString() : 'Something went wrong',
});
}
}
},
[data, selectedDashboard],
);
const onLayoutSaveHandler = async (): Promise<void> => {
setSaveLayoutState((state) => ({
...state,
error: false,
errorMessage: '',
loading: true,
}));
const response = await updateDashboardApi({
data: {
title: data.title,
description: data.description,
name: data.name,
tags: data.tags,
widgets: data.widgets,
layout: saveLayoutState.payload.filter((e) => e.maxW === undefined),
},
uuid: selectedDashboard.uuid,
});
if (response.statusCode === 200) {
setSaveLayoutState((state) => ({
...state,
error: false,
errorMessage: '',
loading: false,
}));
} else {
setSaveLayoutState((state) => ({
...state,
error: true,
errorMessage: response.error || 'Something went wrong',
loading: false,
}));
}
};
const onLayoutChangeHandler = (layout: Layout[]): void => {
setSaveLayoutState({
loading: false,
error: false,
errorMessage: '',
payload: layout,
});
};
if (layouts.length === 0) {
return <Spinner height="40vh" size="large" tip="Loading..." />;
}
return (
<>
<ButtonContainer>
<Button
loading={saveLayoutState.loading}
onClick={onLayoutSaveHandler}
icon={<SaveFilled />}
danger={saveLayoutState.error}
>
Save Layout
</Button>
</ButtonContainer>
<ReactGridLayout
isResizable
isDraggable
cols={12}
rowHeight={100}
autoSize
width={100}
isDroppable
useCSSTransforms
onDrop={onDropHandler}
onLayoutChange={onLayoutChangeHandler}
>
{layouts.map(({ Component, ...rest }, index) => {
const widget = (widgets || [])[index] || {};
const type = widget?.panelTypes || 'TIME_SERIES';
const isQueryType = type === 'VALUE';
return (
<CardContainer key={rest.i} data-grid={rest}>
<Card isQueryType={isQueryType}>
<Component />
</Card>
</CardContainer>
);
})}
</ReactGridLayout>
</>
);
}
interface LayoutProps extends Layout {
Component: () => JSX.Element;
}
interface State {
loading: boolean;
error: boolean;
payload: Layout[];
errorMessage: string;
}
export default memo(GridGraph);