mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-15 04:45:57 +08:00
feat: introducing collapsable rows for dashboards (#4806)
* feat: dashboard panel grouping initial setup * feat: added panel map to the dashboard response and subsequent types for the same * feat: added panel map to the dashboard response and subsequent types for the same * feat: added settings modal * feat: handle panel collapse and open changes * feat: handle creating panel map when dashboard layout changes * feat: handle creating panel map when dashboard layout changes * feat: refactor code * feat: handle multiple collapsable rows * fix: type issues * feat: handle row collapse button and scroll * feat: handle y axis movement for rows * feat: handle delete row * feat: handle settings name change * feat: disable collapse/uncollapse when dashboard loading to avoid async states * feat: decrease the height of the grouping row * fix: row height management * fix: handle empty row case * feat: remove resize handle from the row * feat: handle re-arrangement of panels * feat: increase height of default new widget * feat: added safety checks
This commit is contained in:
parent
7d81bc3417
commit
191d9b0648
@ -16,6 +16,7 @@
|
|||||||
"new_dashboard_title": "Sample Title",
|
"new_dashboard_title": "Sample Title",
|
||||||
"layout_saved_successfully": "Layout saved successfully",
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
"add_panel": "Add Panel",
|
"add_panel": "Add Panel",
|
||||||
|
"add_row": "Add Row",
|
||||||
"save_layout": "Save Layout",
|
"save_layout": "Save Layout",
|
||||||
"variable_updated_successfully": "Variable updated successfully",
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
"error_while_updating_variable": "Error while updating variable",
|
"error_while_updating_variable": "Error while updating variable",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"new_dashboard_title": "Sample Title",
|
"new_dashboard_title": "Sample Title",
|
||||||
"layout_saved_successfully": "Layout saved successfully",
|
"layout_saved_successfully": "Layout saved successfully",
|
||||||
"add_panel": "Add Panel",
|
"add_panel": "Add Panel",
|
||||||
|
"add_row": "Add Row",
|
||||||
"save_layout": "Save Layout",
|
"save_layout": "Save Layout",
|
||||||
"full_view": "Full Screen View",
|
"full_view": "Full Screen View",
|
||||||
"variable_updated_successfully": "Variable updated successfully",
|
"variable_updated_successfully": "Variable updated successfully",
|
||||||
|
@ -289,6 +289,11 @@ export enum PANEL_TYPES {
|
|||||||
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
EMPTY_WIDGET = 'EMPTY_WIDGET',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
export enum PANEL_GROUP_TYPES {
|
||||||
|
ROW = 'row',
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
export enum ATTRIBUTE_TYPES {
|
export enum ATTRIBUTE_TYPES {
|
||||||
SUM = 'Sum',
|
SUM = 'Sum',
|
||||||
|
@ -135,7 +135,7 @@ function WidgetGraphComponent({
|
|||||||
i: uuid,
|
i: uuid,
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 3,
|
h: 6,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import './GridCardLayout.styles.scss';
|
import './GridCardLayout.styles.scss';
|
||||||
|
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
import { Flex, Tooltip } from 'antd';
|
import { Flex, Form, Input, Modal, Tooltip, Typography } from 'antd';
|
||||||
|
import { useForm } from 'antd/es/form/Form';
|
||||||
|
import cx from 'classnames';
|
||||||
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { QueryParams } from 'constants/query';
|
import { QueryParams } from 'constants/query';
|
||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { themeColors } from 'constants/theme';
|
import { themeColors } from 'constants/theme';
|
||||||
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
@ -13,12 +15,21 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
|||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
import useUrlQuery from 'hooks/useUrlQuery';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
|
import { defaultTo } from 'lodash-es';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import { FullscreenIcon } from 'lucide-react';
|
import {
|
||||||
|
FullscreenIcon,
|
||||||
|
GripVertical,
|
||||||
|
MoveDown,
|
||||||
|
MoveUp,
|
||||||
|
Settings,
|
||||||
|
Trash2,
|
||||||
|
} from 'lucide-react';
|
||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
|
import { sortLayout } from 'providers/Dashboard/util';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
||||||
import { Layout } from 'react-grid-layout';
|
import { ItemCallback, Layout } from 'react-grid-layout';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@ -28,6 +39,7 @@ import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
|||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { ROLES, USER_ROLES } from 'types/roles';
|
import { ROLES, USER_ROLES } from 'types/roles';
|
||||||
import { ComponentTypes } from 'utils/permission';
|
import { ComponentTypes } from 'utils/permission';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
import { EditMenuAction, ViewMenuAction } from './config';
|
import { EditMenuAction, ViewMenuAction } from './config';
|
||||||
import GridCard from './GridCard';
|
import GridCard from './GridCard';
|
||||||
@ -46,6 +58,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
layouts,
|
layouts,
|
||||||
setLayouts,
|
setLayouts,
|
||||||
|
panelMap,
|
||||||
|
setPanelMap,
|
||||||
setSelectedDashboard,
|
setSelectedDashboard,
|
||||||
isDashboardLocked,
|
isDashboardLocked,
|
||||||
} = useDashboard();
|
} = useDashboard();
|
||||||
@ -66,6 +80,26 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
|
|
||||||
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
||||||
|
|
||||||
|
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const [currentSelectRowId, setCurrentSelectRowId] = useState<string | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentPanelMap, setCurrentPanelMap] = useState<
|
||||||
|
Record<string, { widgets: Layout[]; collapsed: boolean }>
|
||||||
|
>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentPanelMap(panelMap);
|
||||||
|
}, [panelMap]);
|
||||||
|
|
||||||
|
const [form] = useForm<{
|
||||||
|
title: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
const updateDashboardMutation = useUpdateDashboard();
|
const updateDashboardMutation = useUpdateDashboard();
|
||||||
|
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
@ -88,7 +122,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDashboardLayout(layouts);
|
setDashboardLayout(sortLayout(layouts));
|
||||||
}, [layouts]);
|
}, [layouts]);
|
||||||
|
|
||||||
const onSaveHandler = (): void => {
|
const onSaveHandler = (): void => {
|
||||||
@ -98,6 +132,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
...selectedDashboard,
|
...selectedDashboard,
|
||||||
data: {
|
data: {
|
||||||
...selectedDashboard.data,
|
...selectedDashboard.data,
|
||||||
|
panelMap: { ...currentPanelMap },
|
||||||
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
||||||
},
|
},
|
||||||
uuid: selectedDashboard.uuid,
|
uuid: selectedDashboard.uuid,
|
||||||
@ -107,8 +142,9 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
onSuccess: (updatedDashboard) => {
|
onSuccess: (updatedDashboard) => {
|
||||||
if (updatedDashboard.payload) {
|
if (updatedDashboard.payload) {
|
||||||
if (updatedDashboard.payload.data.layout)
|
if (updatedDashboard.payload.data.layout)
|
||||||
setLayouts(updatedDashboard.payload.data.layout);
|
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
||||||
setSelectedDashboard(updatedDashboard.payload);
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
featureResponse.refetch();
|
featureResponse.refetch();
|
||||||
@ -131,7 +167,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
dashboardLayout,
|
dashboardLayout,
|
||||||
);
|
);
|
||||||
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
||||||
setDashboardLayout(layout);
|
const updatedLayout = sortLayout(layout);
|
||||||
|
setDashboardLayout(updatedLayout);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,6 +205,283 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [dashboardLayout]);
|
}, [dashboardLayout]);
|
||||||
|
|
||||||
|
function handleAddRow(): void {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
const id = uuid();
|
||||||
|
|
||||||
|
const newRowWidgetMap: { widgets: Layout[]; collapsed: boolean } = {
|
||||||
|
widgets: [],
|
||||||
|
collapsed: false,
|
||||||
|
};
|
||||||
|
const currentRowIdx = 0;
|
||||||
|
for (let j = currentRowIdx; j < dashboardLayout.length; j++) {
|
||||||
|
if (!currentPanelMap[dashboardLayout[j].i]) {
|
||||||
|
newRowWidgetMap.widgets.push(dashboardLayout[j]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedDashboard: Dashboard = {
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
layout: [
|
||||||
|
{
|
||||||
|
i: id,
|
||||||
|
w: 12,
|
||||||
|
minW: 12,
|
||||||
|
minH: 1,
|
||||||
|
maxH: 1,
|
||||||
|
x: 0,
|
||||||
|
h: 1,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
...dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
||||||
|
],
|
||||||
|
panelMap: { ...currentPanelMap, [id]: newRowWidgetMap },
|
||||||
|
widgets: [
|
||||||
|
...(selectedDashboard.data.widgets || []),
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
title: 'Sample Row',
|
||||||
|
description: '',
|
||||||
|
panelTypes: PANEL_GROUP_TYPES.ROW,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
uuid: selectedDashboard.uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDashboardMutation.mutate(updatedDashboard, {
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
onSuccess: (updatedDashboard) => {
|
||||||
|
if (updatedDashboard.payload) {
|
||||||
|
if (updatedDashboard.payload.data.layout)
|
||||||
|
setLayouts(sortLayout(updatedDashboard.payload.data.layout));
|
||||||
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
featureResponse.refetch();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRowSettingsClick = (id: string): void => {
|
||||||
|
setIsSettingsModalOpen(true);
|
||||||
|
setCurrentSelectRowId(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSettingsModalSubmit = (): void => {
|
||||||
|
const newTitle = form.getFieldValue('title');
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
|
if (!currentSelectRowId) return;
|
||||||
|
|
||||||
|
const currentWidget = selectedDashboard?.data?.widgets?.find(
|
||||||
|
(e) => e.id === currentSelectRowId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!currentWidget) return;
|
||||||
|
|
||||||
|
currentWidget.title = newTitle;
|
||||||
|
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
||||||
|
(e) => e.id !== currentSelectRowId,
|
||||||
|
);
|
||||||
|
|
||||||
|
updatedWidgets?.push(currentWidget);
|
||||||
|
|
||||||
|
const updatedSelectedDashboard: Dashboard = {
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
widgets: updatedWidgets,
|
||||||
|
},
|
||||||
|
uuid: selectedDashboard.uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
|
onSuccess: (updatedDashboard) => {
|
||||||
|
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||||
|
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||||
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
}
|
||||||
|
if (setPanelMap)
|
||||||
|
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||||
|
form.setFieldValue('title', '');
|
||||||
|
setIsSettingsModalOpen(false);
|
||||||
|
setCurrentSelectRowId(null);
|
||||||
|
featureResponse.refetch();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
|
const handleRowCollapse = (id: string): void => {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
const rowProperties = { ...currentPanelMap[id] };
|
||||||
|
const updatedPanelMap = { ...currentPanelMap };
|
||||||
|
|
||||||
|
let updatedDashboardLayout = [...dashboardLayout];
|
||||||
|
if (rowProperties.collapsed === true) {
|
||||||
|
rowProperties.collapsed = false;
|
||||||
|
const widgetsInsideTheRow = rowProperties.widgets;
|
||||||
|
let maxY = 0;
|
||||||
|
widgetsInsideTheRow.forEach((w) => {
|
||||||
|
maxY = Math.max(maxY, w.y + w.h);
|
||||||
|
});
|
||||||
|
const currentRowWidget = dashboardLayout.find((w) => w.i === id);
|
||||||
|
if (currentRowWidget && widgetsInsideTheRow.length) {
|
||||||
|
maxY -= currentRowWidget.h + currentRowWidget.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idxCurrentRow = dashboardLayout.findIndex((w) => w.i === id);
|
||||||
|
|
||||||
|
for (let j = idxCurrentRow + 1; j < dashboardLayout.length; j++) {
|
||||||
|
updatedDashboardLayout[j].y += maxY;
|
||||||
|
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
||||||
|
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
||||||
|
updatedDashboardLayout[j].i
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
].widgets.map((w) => ({
|
||||||
|
...w,
|
||||||
|
y: w.y + maxY,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatedDashboardLayout = [...updatedDashboardLayout, ...widgetsInsideTheRow];
|
||||||
|
} else {
|
||||||
|
rowProperties.collapsed = true;
|
||||||
|
const currentIdx = dashboardLayout.findIndex((w) => w.i === id);
|
||||||
|
|
||||||
|
let widgetsInsideTheRow: Layout[] = [];
|
||||||
|
let isPanelMapUpdated = false;
|
||||||
|
for (let j = currentIdx + 1; j < dashboardLayout.length; j++) {
|
||||||
|
if (currentPanelMap[dashboardLayout[j].i]) {
|
||||||
|
rowProperties.widgets = widgetsInsideTheRow;
|
||||||
|
widgetsInsideTheRow = [];
|
||||||
|
isPanelMapUpdated = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
widgetsInsideTheRow.push(dashboardLayout[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isPanelMapUpdated) {
|
||||||
|
rowProperties.widgets = widgetsInsideTheRow;
|
||||||
|
}
|
||||||
|
let maxY = 0;
|
||||||
|
widgetsInsideTheRow.forEach((w) => {
|
||||||
|
maxY = Math.max(maxY, w.y + w.h);
|
||||||
|
});
|
||||||
|
const currentRowWidget = dashboardLayout[currentIdx];
|
||||||
|
if (currentRowWidget && widgetsInsideTheRow.length) {
|
||||||
|
maxY -= currentRowWidget.h + currentRowWidget.y;
|
||||||
|
}
|
||||||
|
for (let j = currentIdx + 1; j < updatedDashboardLayout.length; j++) {
|
||||||
|
updatedDashboardLayout[j].y += maxY;
|
||||||
|
if (updatedPanelMap[updatedDashboardLayout[j].i]) {
|
||||||
|
updatedPanelMap[updatedDashboardLayout[j].i].widgets = updatedPanelMap[
|
||||||
|
updatedDashboardLayout[j].i
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
].widgets.map((w) => ({
|
||||||
|
...w,
|
||||||
|
y: w.y + maxY,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedDashboardLayout = updatedDashboardLayout.filter(
|
||||||
|
(widget) => !rowProperties.widgets.some((w: Layout) => w.i === widget.i),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setCurrentPanelMap((prev) => ({
|
||||||
|
...prev,
|
||||||
|
...updatedPanelMap,
|
||||||
|
[id]: {
|
||||||
|
...rowProperties,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
setDashboardLayout(sortLayout(updatedDashboardLayout));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragStop: ItemCallback = (_, oldItem, newItem): void => {
|
||||||
|
if (currentPanelMap[oldItem.i]) {
|
||||||
|
const differenceY = newItem.y - oldItem.y;
|
||||||
|
const widgetsInsideRow = currentPanelMap[oldItem.i].widgets.map((w) => ({
|
||||||
|
...w,
|
||||||
|
y: w.y + differenceY,
|
||||||
|
}));
|
||||||
|
setCurrentPanelMap((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[oldItem.i]: {
|
||||||
|
...prev[oldItem.i],
|
||||||
|
widgets: widgetsInsideRow,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowDelete = (): void => {
|
||||||
|
if (!selectedDashboard) return;
|
||||||
|
|
||||||
|
if (!currentSelectRowId) return;
|
||||||
|
|
||||||
|
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
|
||||||
|
(e) => e.id !== currentSelectRowId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedLayout =
|
||||||
|
selectedDashboard.data.layout?.filter((e) => e.i !== currentSelectRowId) ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
const updatedPanelMap = { ...currentPanelMap };
|
||||||
|
delete updatedPanelMap[currentSelectRowId];
|
||||||
|
|
||||||
|
const updatedSelectedDashboard: Dashboard = {
|
||||||
|
...selectedDashboard,
|
||||||
|
data: {
|
||||||
|
...selectedDashboard.data,
|
||||||
|
widgets: updatedWidgets,
|
||||||
|
layout: updatedLayout,
|
||||||
|
panelMap: updatedPanelMap,
|
||||||
|
},
|
||||||
|
uuid: selectedDashboard.uuid,
|
||||||
|
};
|
||||||
|
|
||||||
|
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
|
||||||
|
onSuccess: (updatedDashboard) => {
|
||||||
|
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
|
||||||
|
if (setSelectedDashboard && updatedDashboard.payload) {
|
||||||
|
setSelectedDashboard(updatedDashboard.payload);
|
||||||
|
}
|
||||||
|
if (setPanelMap)
|
||||||
|
setPanelMap(updatedDashboard.payload?.data?.panelMap || {});
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setCurrentSelectRowId(null);
|
||||||
|
featureResponse.refetch();
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||||
|
onError: () => {
|
||||||
|
notifications.error({
|
||||||
|
message: SOMETHING_WENT_WRONG,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex justify="flex-end" gap={8} align="center">
|
<Flex justify="flex-end" gap={8} align="center">
|
||||||
@ -209,13 +523,23 @@ Thanks`}
|
|||||||
{t('dashboard:add_panel')}
|
{t('dashboard:add_panel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{!isDashboardLocked && addPanelPermission && (
|
||||||
|
<Button
|
||||||
|
className="periscope-btn"
|
||||||
|
onClick={(): void => handleAddRow()}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
data-testid="add-row"
|
||||||
|
>
|
||||||
|
{t('dashboard:add_row')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<FullScreen handle={handle} className="fullscreen-grid-container">
|
<FullScreen handle={handle} className="fullscreen-grid-container">
|
||||||
<ReactGridLayout
|
<ReactGridLayout
|
||||||
cols={12}
|
cols={12}
|
||||||
rowHeight={100}
|
rowHeight={45}
|
||||||
autoSize
|
autoSize
|
||||||
width={100}
|
width={100}
|
||||||
useCSSTransforms
|
useCSSTransforms
|
||||||
@ -224,6 +548,7 @@ Thanks`}
|
|||||||
isResizable={!isDashboardLocked && addPanelPermission}
|
isResizable={!isDashboardLocked && addPanelPermission}
|
||||||
allowOverlap={false}
|
allowOverlap={false}
|
||||||
onLayoutChange={handleLayoutChange}
|
onLayoutChange={handleLayoutChange}
|
||||||
|
onDragStop={handleDragStop}
|
||||||
draggableHandle=".drag-handle"
|
draggableHandle=".drag-handle"
|
||||||
layout={dashboardLayout}
|
layout={dashboardLayout}
|
||||||
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
||||||
@ -232,6 +557,58 @@ Thanks`}
|
|||||||
const { i: id } = layout;
|
const { i: id } = layout;
|
||||||
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
||||||
|
|
||||||
|
if (currentWidget?.panelTypes === PANEL_GROUP_TYPES.ROW) {
|
||||||
|
const rowWidgetProperties = currentPanelMap[id] || {};
|
||||||
|
return (
|
||||||
|
<CardContainer
|
||||||
|
className="row-card"
|
||||||
|
isDarkMode={isDarkMode}
|
||||||
|
key={id}
|
||||||
|
data-grid={JSON.stringify(currentWidget)}
|
||||||
|
>
|
||||||
|
<div className={cx('row-panel')}>
|
||||||
|
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
|
||||||
|
<Button
|
||||||
|
disabled={updateDashboardMutation.isLoading}
|
||||||
|
icon={
|
||||||
|
rowWidgetProperties.collapsed ? (
|
||||||
|
<MoveDown size={14} />
|
||||||
|
) : (
|
||||||
|
<MoveUp size={14} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
onClick={(): void => handleRowCollapse(id)}
|
||||||
|
/>
|
||||||
|
<Typography.Text>{currentWidget.title}</Typography.Text>
|
||||||
|
<Button
|
||||||
|
icon={<Settings size={14} />}
|
||||||
|
type="text"
|
||||||
|
onClick={(): void => handleRowSettingsClick(id)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{rowWidgetProperties.collapsed && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<GripVertical size={14} />}
|
||||||
|
className="drag-handle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!rowWidgetProperties.collapsed && (
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<Trash2 size={14} />}
|
||||||
|
onClick={(): void => {
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
setCurrentSelectRowId(id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer
|
<CardContainer
|
||||||
className={isDashboardLocked ? '' : 'enable-resize'}
|
className={isDashboardLocked ? '' : 'enable-resize'}
|
||||||
@ -244,7 +621,7 @@ Thanks`}
|
|||||||
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
||||||
>
|
>
|
||||||
<GridCard
|
<GridCard
|
||||||
widget={currentWidget || ({ id, query: {} } as Widgets)}
|
widget={(currentWidget as Widgets) || ({ id, query: {} } as Widgets)}
|
||||||
headerMenuList={widgetActions}
|
headerMenuList={widgetActions}
|
||||||
variables={variables}
|
variables={variables}
|
||||||
version={selectedDashboard?.data?.version}
|
version={selectedDashboard?.data?.version}
|
||||||
@ -255,6 +632,46 @@ Thanks`}
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ReactGridLayout>
|
</ReactGridLayout>
|
||||||
|
<Modal
|
||||||
|
open={isSettingsModalOpen}
|
||||||
|
title="Row Options"
|
||||||
|
destroyOnClose
|
||||||
|
footer={null}
|
||||||
|
onCancel={(): void => {
|
||||||
|
setIsSettingsModalOpen(false);
|
||||||
|
setCurrentSelectRowId(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Form form={form} onFinish={onSettingsModalSubmit} requiredMark>
|
||||||
|
<Form.Item required name={['title']}>
|
||||||
|
<Input
|
||||||
|
placeholder="Enter row name here..."
|
||||||
|
defaultValue={defaultTo(
|
||||||
|
widgets?.find((widget) => widget.id === currentSelectRowId)
|
||||||
|
?.title as string,
|
||||||
|
'Sample Title',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit">
|
||||||
|
Apply Changes
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
open={isDeleteModalOpen}
|
||||||
|
title="Delete Row"
|
||||||
|
destroyOnClose
|
||||||
|
onCancel={(): void => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
setCurrentSelectRowId(null);
|
||||||
|
}}
|
||||||
|
onOk={(): void => handleRowDelete()}
|
||||||
|
>
|
||||||
|
<Typography.Text>Are you sure you want to delete this row</Typography.Text>
|
||||||
|
</Modal>
|
||||||
</FullScreen>
|
</FullScreen>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -16,6 +16,6 @@ export const EMPTY_WIDGET_LAYOUT = {
|
|||||||
i: PANEL_TYPES.EMPTY_WIDGET,
|
i: PANEL_TYPES.EMPTY_WIDGET,
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 3,
|
h: 6,
|
||||||
y: 0,
|
y: 0,
|
||||||
};
|
};
|
||||||
|
@ -29,6 +29,17 @@ interface Props {
|
|||||||
export const CardContainer = styled.div<Props>`
|
export const CardContainer = styled.div<Props>`
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
||||||
|
&.row-card {
|
||||||
|
.row-panel {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: var(--bg-ink-400);
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.enable-resize {
|
&.enable-resize {
|
||||||
:hover {
|
:hover {
|
||||||
.react-resizable-handle {
|
.react-resizable-handle {
|
||||||
|
@ -104,7 +104,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
return defaultTo(
|
return defaultTo(
|
||||||
selectedWidget,
|
selectedWidget,
|
||||||
getDefaultWidgetData(widgetId || '', selectedGraph),
|
getDefaultWidgetData(widgetId || '', selectedGraph),
|
||||||
);
|
) as Widgets;
|
||||||
}, [query, selectedGraph, widgets]);
|
}, [query, selectedGraph, widgets]);
|
||||||
|
|
||||||
const [selectedWidget, setSelectedWidget] = useState(getWidget());
|
const [selectedWidget, setSelectedWidget] = useState(getWidget());
|
||||||
@ -257,7 +257,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
|||||||
i: widgetId || '',
|
i: widgetId || '',
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 3,
|
h: 6,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
...updatedLayout,
|
...updatedLayout,
|
||||||
|
@ -16,7 +16,7 @@ export const addEmptyWidgetInDashboardJSONWithQuery = (
|
|||||||
i: widgetId,
|
i: widgetId,
|
||||||
w: 6,
|
w: 6,
|
||||||
x: 0,
|
x: 0,
|
||||||
h: 3,
|
h: 6,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
...(dashboard?.data?.layout || []),
|
...(dashboard?.data?.layout || []),
|
||||||
|
@ -9,6 +9,7 @@ import history from 'lib/history';
|
|||||||
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
import { generatePath, useLocation, useParams } from 'react-router-dom';
|
||||||
|
import { Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
function DashboardWidget(): JSX.Element | null {
|
function DashboardWidget(): JSX.Element | null {
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
@ -24,7 +25,7 @@ function DashboardWidget(): JSX.Element | null {
|
|||||||
const { data } = selectedDashboard || {};
|
const { data } = selectedDashboard || {};
|
||||||
const { widgets } = data || {};
|
const { widgets } = data || {};
|
||||||
|
|
||||||
const selectedWidget = widgets?.find((e) => e.id === widgetId);
|
const selectedWidget = widgets?.find((e) => e.id === widgetId) as Widgets;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const params = new URLSearchParams(search);
|
const params = new URLSearchParams(search);
|
||||||
|
@ -10,6 +10,7 @@ import { useDashboardVariablesFromLocalStorage } from 'hooks/dashboard/useDashbo
|
|||||||
import useAxiosError from 'hooks/useAxiosError';
|
import useAxiosError from 'hooks/useAxiosError';
|
||||||
import useTabVisibility from 'hooks/useTabFocus';
|
import useTabVisibility from 'hooks/useTabFocus';
|
||||||
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
||||||
|
import { defaultTo } from 'lodash-es';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import isUndefined from 'lodash-es/isUndefined';
|
import isUndefined from 'lodash-es/isUndefined';
|
||||||
import omitBy from 'lodash-es/omitBy';
|
import omitBy from 'lodash-es/omitBy';
|
||||||
@ -37,6 +38,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
|||||||
import { v4 as generateUUID } from 'uuid';
|
import { v4 as generateUUID } from 'uuid';
|
||||||
|
|
||||||
import { IDashboardContext } from './types';
|
import { IDashboardContext } from './types';
|
||||||
|
import { sortLayout } from './util';
|
||||||
|
|
||||||
const DashboardContext = createContext<IDashboardContext>({
|
const DashboardContext = createContext<IDashboardContext>({
|
||||||
isDashboardSliderOpen: false,
|
isDashboardSliderOpen: false,
|
||||||
@ -47,6 +49,8 @@ const DashboardContext = createContext<IDashboardContext>({
|
|||||||
selectedDashboard: {} as Dashboard,
|
selectedDashboard: {} as Dashboard,
|
||||||
dashboardId: '',
|
dashboardId: '',
|
||||||
layouts: [],
|
layouts: [],
|
||||||
|
panelMap: {},
|
||||||
|
setPanelMap: () => {},
|
||||||
setLayouts: () => {},
|
setLayouts: () => {},
|
||||||
setSelectedDashboard: () => {},
|
setSelectedDashboard: () => {},
|
||||||
updatedTimeRef: {} as React.MutableRefObject<Dayjs | null>,
|
updatedTimeRef: {} as React.MutableRefObject<Dayjs | null>,
|
||||||
@ -94,6 +98,10 @@ export function DashboardProvider({
|
|||||||
|
|
||||||
const [layouts, setLayouts] = useState<Layout[]>([]);
|
const [layouts, setLayouts] = useState<Layout[]>([]);
|
||||||
|
|
||||||
|
const [panelMap, setPanelMap] = useState<
|
||||||
|
Record<string, { widgets: Layout[]; collapsed: boolean }>
|
||||||
|
>({});
|
||||||
|
|
||||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const dashboardId =
|
const dashboardId =
|
||||||
@ -199,7 +207,9 @@ export function DashboardProvider({
|
|||||||
|
|
||||||
dashboardRef.current = updatedDashboardData;
|
dashboardRef.current = updatedDashboardData;
|
||||||
|
|
||||||
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
setLayouts(sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)));
|
||||||
|
|
||||||
|
setPanelMap(defaultTo(updatedDashboardData?.data?.panelMap, {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -235,7 +245,11 @@ export function DashboardProvider({
|
|||||||
|
|
||||||
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
updatedTimeRef.current = dayjs(updatedDashboardData.updated_at);
|
||||||
|
|
||||||
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
setLayouts(
|
||||||
|
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
|
||||||
|
);
|
||||||
|
|
||||||
|
setPanelMap(defaultTo(updatedDashboardData.data.panelMap, {}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -256,7 +270,11 @@ export function DashboardProvider({
|
|||||||
updatedDashboardData.data.layout,
|
updatedDashboardData.data.layout,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
setLayouts(getUpdatedLayout(updatedDashboardData.data.layout));
|
setLayouts(
|
||||||
|
sortLayout(getUpdatedLayout(updatedDashboardData.data.layout)),
|
||||||
|
);
|
||||||
|
|
||||||
|
setPanelMap(defaultTo(updatedDashboardData.data.panelMap, {}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -323,7 +341,9 @@ export function DashboardProvider({
|
|||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
layouts,
|
layouts,
|
||||||
|
panelMap,
|
||||||
setLayouts,
|
setLayouts,
|
||||||
|
setPanelMap,
|
||||||
setSelectedDashboard,
|
setSelectedDashboard,
|
||||||
updatedTimeRef,
|
updatedTimeRef,
|
||||||
setToScrollWidgetId,
|
setToScrollWidgetId,
|
||||||
@ -339,6 +359,7 @@ export function DashboardProvider({
|
|||||||
selectedDashboard,
|
selectedDashboard,
|
||||||
dashboardId,
|
dashboardId,
|
||||||
layouts,
|
layouts,
|
||||||
|
panelMap,
|
||||||
toScrollWidgetId,
|
toScrollWidgetId,
|
||||||
updateLocalStorageDashboardVariables,
|
updateLocalStorageDashboardVariables,
|
||||||
currentDashboard,
|
currentDashboard,
|
||||||
|
@ -12,6 +12,8 @@ export interface IDashboardContext {
|
|||||||
selectedDashboard: Dashboard | undefined;
|
selectedDashboard: Dashboard | undefined;
|
||||||
dashboardId: string;
|
dashboardId: string;
|
||||||
layouts: Layout[];
|
layouts: Layout[];
|
||||||
|
panelMap: Record<string, { widgets: Layout[]; collapsed: boolean }>;
|
||||||
|
setPanelMap: React.Dispatch<React.SetStateAction<Record<string, any>>>;
|
||||||
setLayouts: React.Dispatch<React.SetStateAction<Layout[]>>;
|
setLayouts: React.Dispatch<React.SetStateAction<Layout[]>>;
|
||||||
setSelectedDashboard: React.Dispatch<
|
setSelectedDashboard: React.Dispatch<
|
||||||
React.SetStateAction<Dashboard | undefined>
|
React.SetStateAction<Dashboard | undefined>
|
||||||
|
@ -1,22 +1,34 @@
|
|||||||
|
import { Layout } from 'react-grid-layout';
|
||||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||||
|
|
||||||
export const getPreviousWidgets = (
|
export const getPreviousWidgets = (
|
||||||
selectedDashboard: Dashboard,
|
selectedDashboard: Dashboard,
|
||||||
selectedWidgetIndex: number,
|
selectedWidgetIndex: number,
|
||||||
): Widgets[] =>
|
): Widgets[] =>
|
||||||
selectedDashboard.data.widgets?.slice(0, selectedWidgetIndex || 0) || [];
|
(selectedDashboard.data.widgets?.slice(
|
||||||
|
0,
|
||||||
|
selectedWidgetIndex || 0,
|
||||||
|
) as Widgets[]) || [];
|
||||||
|
|
||||||
export const getNextWidgets = (
|
export const getNextWidgets = (
|
||||||
selectedDashboard: Dashboard,
|
selectedDashboard: Dashboard,
|
||||||
selectedWidgetIndex: number,
|
selectedWidgetIndex: number,
|
||||||
): Widgets[] =>
|
): Widgets[] =>
|
||||||
selectedDashboard.data.widgets?.slice(
|
(selectedDashboard.data.widgets?.slice(
|
||||||
(selectedWidgetIndex || 0) + 1, // this is never undefined
|
(selectedWidgetIndex || 0) + 1, // this is never undefined
|
||||||
selectedDashboard.data.widgets?.length,
|
selectedDashboard.data.widgets?.length,
|
||||||
) || [];
|
) as Widgets[]) || [];
|
||||||
|
|
||||||
export const getSelectedWidgetIndex = (
|
export const getSelectedWidgetIndex = (
|
||||||
selectedDashboard: Dashboard,
|
selectedDashboard: Dashboard,
|
||||||
widgetId: string | null,
|
widgetId: string | null,
|
||||||
): number =>
|
): number =>
|
||||||
selectedDashboard.data.widgets?.findIndex((e) => e.id === widgetId) || 0;
|
selectedDashboard.data.widgets?.findIndex((e) => e.id === widgetId) || 0;
|
||||||
|
|
||||||
|
export const sortLayout = (layout: Layout[]): Layout[] =>
|
||||||
|
[...layout].sort((a, b) => {
|
||||||
|
if (a.y === b.y) {
|
||||||
|
return a.x - b.x;
|
||||||
|
}
|
||||||
|
return a.y - b.y;
|
||||||
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||||
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
|
||||||
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
@ -59,13 +59,21 @@ export interface DashboardData {
|
|||||||
description?: string;
|
description?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
name?: string;
|
name?: string;
|
||||||
widgets?: Widgets[];
|
widgets?: Array<WidgetRow | Widgets>;
|
||||||
title: string;
|
title: string;
|
||||||
layout?: Layout[];
|
layout?: Layout[];
|
||||||
|
panelMap?: Record<string, { widgets: Layout[]; collapsed: boolean }>;
|
||||||
variables: Record<string, IDashboardVariable>;
|
variables: Record<string, IDashboardVariable>;
|
||||||
version?: string;
|
version?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WidgetRow {
|
||||||
|
id: string;
|
||||||
|
panelTypes: PANEL_GROUP_TYPES;
|
||||||
|
title: ReactNode;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IBaseWidget {
|
export interface IBaseWidget {
|
||||||
isStacked: boolean;
|
isStacked: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user