2023-11-23 14:10:34 +05:30
|
|
|
import './GridCardLayout.styles.scss';
|
|
|
|
|
|
2024-01-18 15:01:10 +05:30
|
|
|
import { PlusOutlined } from '@ant-design/icons';
|
2024-04-24 15:48:48 +05:30
|
|
|
import { Flex, Tooltip } from 'antd';
|
2024-04-24 18:56:19 +05:30
|
|
|
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
2024-04-02 16:40:41 +05:30
|
|
|
import { QueryParams } from 'constants/query';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { PANEL_TYPES } from 'constants/queryBuilder';
|
2023-12-08 11:53:56 +05:30
|
|
|
import { themeColors } from 'constants/theme';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
|
|
|
|
|
import useComponentPermission from 'hooks/useComponentPermission';
|
|
|
|
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
|
|
|
import { useNotifications } from 'hooks/useNotifications';
|
2024-04-02 16:40:41 +05:30
|
|
|
import useUrlQuery from 'hooks/useUrlQuery';
|
|
|
|
|
import history from 'lib/history';
|
2024-01-18 15:01:10 +05:30
|
|
|
import isEqual from 'lodash-es/isEqual';
|
2023-11-23 14:10:34 +05:30
|
|
|
import { FullscreenIcon } from 'lucide-react';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { useDashboard } from 'providers/Dashboard/Dashboard';
|
2024-04-02 16:40:41 +05:30
|
|
|
import { useCallback, useEffect, useState } from 'react';
|
2023-11-23 14:10:34 +05:30
|
|
|
import { FullScreen, useFullScreenHandle } from 'react-full-screen';
|
2024-01-18 15:01:10 +05:30
|
|
|
import { Layout } from 'react-grid-layout';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { useTranslation } from 'react-i18next';
|
2024-04-02 16:40:41 +05:30
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
|
import { useLocation } from 'react-router-dom';
|
|
|
|
|
import { UpdateTimeInterval } from 'store/actions';
|
2023-10-08 23:21:17 +05:30
|
|
|
import { AppState } from 'store/reducers';
|
|
|
|
|
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
|
|
|
|
import AppReducer from 'types/reducer/app';
|
2023-11-03 17:27:09 +05:30
|
|
|
import { ROLES, USER_ROLES } from 'types/roles';
|
|
|
|
|
import { ComponentTypes } from 'utils/permission';
|
2023-10-08 23:21:17 +05:30
|
|
|
|
2023-11-03 17:27:09 +05:30
|
|
|
import { EditMenuAction, ViewMenuAction } from './config';
|
2023-10-08 23:21:17 +05:30
|
|
|
import GridCard from './GridCard';
|
|
|
|
|
import {
|
|
|
|
|
Button,
|
|
|
|
|
ButtonContainer,
|
|
|
|
|
Card,
|
|
|
|
|
CardContainer,
|
|
|
|
|
ReactGridLayout,
|
|
|
|
|
} from './styles';
|
|
|
|
|
import { GraphLayoutProps } from './types';
|
2024-01-18 15:01:10 +05:30
|
|
|
import { removeUndefinedValuesFromLayout } from './utils';
|
2023-10-08 23:21:17 +05:30
|
|
|
|
2023-11-15 15:33:45 +05:30
|
|
|
function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element {
|
2023-10-11 23:27:45 +05:30
|
|
|
const {
|
|
|
|
|
selectedDashboard,
|
|
|
|
|
layouts,
|
|
|
|
|
setLayouts,
|
|
|
|
|
setSelectedDashboard,
|
2023-11-03 17:27:09 +05:30
|
|
|
isDashboardLocked,
|
2023-10-11 23:27:45 +05:30
|
|
|
} = useDashboard();
|
2023-11-15 15:33:45 +05:30
|
|
|
const { data } = selectedDashboard || {};
|
2023-11-23 14:10:34 +05:30
|
|
|
const handle = useFullScreenHandle();
|
2024-04-02 16:40:41 +05:30
|
|
|
const { pathname } = useLocation();
|
|
|
|
|
const dispatch = useDispatch();
|
2023-11-15 15:33:45 +05:30
|
|
|
|
|
|
|
|
const { widgets, variables } = data || {};
|
|
|
|
|
|
2023-10-08 23:21:17 +05:30
|
|
|
const { t } = useTranslation(['dashboard']);
|
|
|
|
|
|
2023-11-03 17:27:09 +05:30
|
|
|
const { featureResponse, role, user } = useSelector<AppState, AppReducer>(
|
2023-10-08 23:21:17 +05:30
|
|
|
(state) => state.app,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const isDarkMode = useIsDarkMode();
|
|
|
|
|
|
2024-01-30 16:47:58 +05:30
|
|
|
const [dashboardLayout, setDashboardLayout] = useState<Layout[]>([]);
|
2024-01-18 15:01:10 +05:30
|
|
|
|
2023-10-08 23:21:17 +05:30
|
|
|
const updateDashboardMutation = useUpdateDashboard();
|
|
|
|
|
|
|
|
|
|
const { notifications } = useNotifications();
|
2024-04-02 16:40:41 +05:30
|
|
|
const urlQuery = useUrlQuery();
|
2023-10-08 23:21:17 +05:30
|
|
|
|
2023-11-03 17:27:09 +05:30
|
|
|
let permissions: ComponentTypes[] = ['save_layout', 'add_panel'];
|
|
|
|
|
|
|
|
|
|
if (isDashboardLocked) {
|
|
|
|
|
permissions = ['edit_locked_dashboard', 'add_panel_locked_dashboard'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const userRole: ROLES | null =
|
|
|
|
|
selectedDashboard?.created_by === user?.email
|
|
|
|
|
? (USER_ROLES.AUTHOR as ROLES)
|
|
|
|
|
: role;
|
|
|
|
|
|
2023-10-08 23:21:17 +05:30
|
|
|
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
|
2023-11-03 17:27:09 +05:30
|
|
|
permissions,
|
|
|
|
|
userRole,
|
2023-10-08 23:21:17 +05:30
|
|
|
);
|
|
|
|
|
|
2024-01-30 16:47:58 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
setDashboardLayout(layouts);
|
|
|
|
|
}, [layouts]);
|
|
|
|
|
|
2023-10-08 23:21:17 +05:30
|
|
|
const onSaveHandler = (): void => {
|
|
|
|
|
if (!selectedDashboard) return;
|
|
|
|
|
|
|
|
|
|
const updatedDashboard: Dashboard = {
|
|
|
|
|
...selectedDashboard,
|
|
|
|
|
data: {
|
|
|
|
|
...selectedDashboard.data,
|
2024-01-18 15:01:10 +05:30
|
|
|
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
|
2023-10-08 23:21:17 +05:30
|
|
|
},
|
|
|
|
|
uuid: selectedDashboard.uuid,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
updateDashboardMutation.mutate(updatedDashboard, {
|
2023-10-11 23:27:45 +05:30
|
|
|
onSuccess: (updatedDashboard) => {
|
|
|
|
|
if (updatedDashboard.payload) {
|
|
|
|
|
if (updatedDashboard.payload.data.layout)
|
|
|
|
|
setLayouts(updatedDashboard.payload.data.layout);
|
|
|
|
|
setSelectedDashboard(updatedDashboard.payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
featureResponse.refetch();
|
2023-10-08 23:21:17 +05:30
|
|
|
},
|
|
|
|
|
onError: () => {
|
|
|
|
|
notifications.error({
|
|
|
|
|
message: SOMETHING_WENT_WRONG,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-11-03 17:27:09 +05:30
|
|
|
const widgetActions = !isDashboardLocked
|
|
|
|
|
? [...ViewMenuAction, ...EditMenuAction]
|
|
|
|
|
: [...ViewMenuAction];
|
|
|
|
|
|
2024-01-18 15:01:10 +05:30
|
|
|
const handleLayoutChange = (layout: Layout[]): void => {
|
|
|
|
|
const filterLayout = removeUndefinedValuesFromLayout(layout);
|
|
|
|
|
const filterDashboardLayout = removeUndefinedValuesFromLayout(
|
|
|
|
|
dashboardLayout,
|
|
|
|
|
);
|
|
|
|
|
if (!isEqual(filterLayout, filterDashboardLayout)) {
|
|
|
|
|
setDashboardLayout(layout);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-04-02 16:40:41 +05:30
|
|
|
const onDragSelect = useCallback(
|
|
|
|
|
(start: number, end: number) => {
|
|
|
|
|
const startTimestamp = Math.trunc(start);
|
|
|
|
|
const endTimestamp = Math.trunc(end);
|
|
|
|
|
|
|
|
|
|
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
|
|
|
|
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
|
|
|
|
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
|
|
|
|
history.replace(generatedUrl);
|
|
|
|
|
|
|
|
|
|
if (startTimestamp !== endTimestamp) {
|
|
|
|
|
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[dispatch, pathname, urlQuery],
|
|
|
|
|
);
|
|
|
|
|
|
2024-01-18 15:01:10 +05:30
|
|
|
useEffect(() => {
|
|
|
|
|
if (
|
|
|
|
|
dashboardLayout &&
|
|
|
|
|
Array.isArray(dashboardLayout) &&
|
|
|
|
|
dashboardLayout.length > 0 &&
|
|
|
|
|
!isEqual(layouts, dashboardLayout) &&
|
|
|
|
|
!isDashboardLocked &&
|
|
|
|
|
saveLayoutPermission &&
|
|
|
|
|
!updateDashboardMutation.isLoading
|
|
|
|
|
) {
|
|
|
|
|
onSaveHandler();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [dashboardLayout]);
|
|
|
|
|
|
2023-10-08 23:21:17 +05:30
|
|
|
return (
|
|
|
|
|
<>
|
2024-04-24 15:48:48 +05:30
|
|
|
<Flex justify="flex-end" gap={8} align="center">
|
|
|
|
|
<FacingIssueBtn
|
|
|
|
|
attributes={{
|
|
|
|
|
uuid: selectedDashboard?.uuid,
|
|
|
|
|
title: data?.title,
|
|
|
|
|
screen: 'Dashboard Details',
|
|
|
|
|
}}
|
|
|
|
|
eventName="Dashboard: Facing Issues in dashboard"
|
|
|
|
|
buttonText="Facing Issues in dashboard"
|
|
|
|
|
message={`Hi Team,
|
|
|
|
|
|
|
|
|
|
I am facing issues configuring dashboard in SigNoz. Here are my dashboard details
|
|
|
|
|
|
|
|
|
|
Name: ${data?.title || ''}
|
|
|
|
|
Dashboard Id: ${selectedDashboard?.uuid || ''}
|
|
|
|
|
|
|
|
|
|
Thanks`}
|
|
|
|
|
/>
|
|
|
|
|
<ButtonContainer>
|
|
|
|
|
<Tooltip title="Open in Full Screen">
|
|
|
|
|
<Button
|
|
|
|
|
className="periscope-btn"
|
|
|
|
|
loading={updateDashboardMutation.isLoading}
|
|
|
|
|
onClick={handle.enter}
|
|
|
|
|
icon={<FullscreenIcon size={16} />}
|
|
|
|
|
disabled={updateDashboardMutation.isLoading}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
|
|
|
|
|
{!isDashboardLocked && addPanelPermission && (
|
|
|
|
|
<Button
|
|
|
|
|
className="periscope-btn"
|
|
|
|
|
onClick={onAddPanelHandler}
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
data-testid="add-panel"
|
|
|
|
|
>
|
|
|
|
|
{t('dashboard:add_panel')}
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</ButtonContainer>
|
|
|
|
|
</Flex>
|
2023-10-08 23:21:17 +05:30
|
|
|
|
2023-11-23 14:10:34 +05:30
|
|
|
<FullScreen handle={handle} className="fullscreen-grid-container">
|
|
|
|
|
<ReactGridLayout
|
|
|
|
|
cols={12}
|
|
|
|
|
rowHeight={100}
|
|
|
|
|
autoSize
|
|
|
|
|
width={100}
|
|
|
|
|
useCSSTransforms
|
|
|
|
|
isDraggable={!isDashboardLocked && addPanelPermission}
|
|
|
|
|
isDroppable={!isDashboardLocked && addPanelPermission}
|
|
|
|
|
isResizable={!isDashboardLocked && addPanelPermission}
|
|
|
|
|
allowOverlap={false}
|
2024-01-18 15:01:10 +05:30
|
|
|
onLayoutChange={handleLayoutChange}
|
2023-11-23 14:10:34 +05:30
|
|
|
draggableHandle=".drag-handle"
|
2024-01-18 15:01:10 +05:30
|
|
|
layout={dashboardLayout}
|
2023-12-08 11:53:56 +05:30
|
|
|
style={{ backgroundColor: isDarkMode ? '' : themeColors.snowWhite }}
|
2023-11-23 14:10:34 +05:30
|
|
|
>
|
2024-01-18 15:01:10 +05:30
|
|
|
{dashboardLayout.map((layout) => {
|
2023-11-23 14:10:34 +05:30
|
|
|
const { i: id } = layout;
|
|
|
|
|
const currentWidget = (widgets || [])?.find((e) => e.id === id);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<CardContainer
|
|
|
|
|
className={isDashboardLocked ? '' : 'enable-resize'}
|
|
|
|
|
isDarkMode={isDarkMode}
|
|
|
|
|
key={id}
|
2023-11-29 18:21:26 +05:30
|
|
|
data-grid={JSON.stringify(currentWidget)}
|
2023-11-03 17:27:09 +05:30
|
|
|
>
|
2023-11-23 14:10:34 +05:30
|
|
|
<Card
|
|
|
|
|
className="grid-item"
|
|
|
|
|
$panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}
|
|
|
|
|
>
|
|
|
|
|
<GridCard
|
|
|
|
|
widget={currentWidget || ({ id, query: {} } as Widgets)}
|
|
|
|
|
headerMenuList={widgetActions}
|
|
|
|
|
variables={variables}
|
2024-03-01 14:51:50 +05:30
|
|
|
version={selectedDashboard?.data?.version}
|
2024-04-02 16:40:41 +05:30
|
|
|
onDragSelect={onDragSelect}
|
2023-11-23 14:10:34 +05:30
|
|
|
/>
|
|
|
|
|
</Card>
|
|
|
|
|
</CardContainer>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</ReactGridLayout>
|
|
|
|
|
</FullScreen>
|
2023-10-08 23:21:17 +05:30
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default GraphLayout;
|