signoz/frontend/src/container/GridGraphLayout/Graph/WidgetGraphComponent.tsx
Rajat Dabade b339f0509b
Improved graph panel full view (#3039)
* feat: done with prd full view

* refactor: updated some variable and naming convection

* feat: when click on label only select associated graph

* feat: made the table scrollable

* feat: update the table column length

* feat: save notification after saving state

* refactor: removed unwanted code

* refactor: renamed some file

* fix: linter issue

* fix: position of save button

* refactor: seperated widgetGraphComponent from gridGraphComponent

* feat: fetching the localstorage data while initial loading of graph

* fix: dependency of graphVisibilityHandler for other component

* refactor: updated the notification msg on save

* fix: linter error

* refactor: remove the update logic of graph from graph component

* refactor: created utils and move some utility code

* refactor: place the checkbox component in fullview

* refactor: updated the utils function added enun localstorage

* refactor: added enum for table columns data

* refactor: name changes to graphVisibilityStates

* refactor: shifted the type to types.ts

* refactor: sepearated the type from graph componnet

* refactor: seperated graphOptions from graph component

* refactor: updated imports

* refactor: shifted the logic to utils

* refactor: remove unused file and check for full view

* refactor: using PanelType instead of GraphType

* refactor: changed the variable name

* refactor: provided checks of useEffect

* test: added unit test case for utility function

* refactor: one on one maping of props and value

* refactor: panelTypeAndGraphManagerVisibility as a props

* refactor: remove the enforing of type in useChartMutable

* refactor: updated the test case

* refactor: moved types to types.ts files

* refactor: separated types from components

* refactor: one to one mapping and cancel feature

* refactor: remove unwanted useEffect and used eventEmitter

* fix: only open chart visibility will change issue

* refactor: removed unwanted useEffect

* refactor: resolve the hang issue for full view

* refactor: legend to checkbox connection, separated code

* refactor: updated styled component GraphContainer

* chore: removed unwanted consoles

* refactor: ux changes

* fix: eslint and updated test case

* refactor: review comments

* chore: fix types

* refactor: made utils for getIsGraphLegendToggleAvailable

* refactor: removed the ref mutation from graphPanelSwitch

* refactor: resolve the issue of chart state not getting reflect outside fullview

* refactor: common utility for toggle graphs visibility in chart

* refactor: shifted ref to perticular component level

* test: removed extra space

* chore: close on save and NaN infinity check

* refactor: added yAxisUnit to GraphManager table header

* refactor: create a function for appending yAxisUnit to table header

* fix: decimal upto 2 decimal points

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
Co-authored-by: Pranay Prateek <pranay@signoz.io>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2023-08-02 20:41:09 +05:30

337 lines
7.8 KiB
TypeScript

import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { Events } from 'constants/events';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { useChartMutable } from 'hooks/useChartMutable';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isEmpty, isEqual } from 'lodash-es';
import {
Dispatch,
memo,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { DeleteWidget } from 'store/actions/dashboard/deleteWidget';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import AppReducer from 'types/reducer/app';
import DashboardReducer from 'types/reducer/dashboards';
import { eventEmitter } from 'utils/getEventEmitter';
import { v4 } from 'uuid';
import { UpdateDashboard } from '../utils';
import WidgetHeader from '../WidgetHeader';
import FullView from './FullView';
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './FullView/contants';
import { FullViewContainer, Modal } from './styles';
import { DispatchProps, WidgetGraphComponentProps } from './types';
import {
getGraphVisibilityStateOnDataChange,
toggleGraphsVisibilityInChart,
} from './utils';
function WidgetGraphComponent({
enableModel,
enableWidgetHeader,
data,
widget,
queryResponse,
errorMessage,
name,
yAxisUnit,
layout = [],
deleteWidget,
setLayout,
onDragSelect,
onClickHandler,
allowClone = true,
allowDelete = true,
allowEdit = true,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [modal, setModal] = useState<boolean>(false);
const [hovered, setHovered] = useState(false);
const { notifications } = useNotifications();
const { t } = useTranslation(['common']);
const { graphVisibilityStates: localstoredVisibilityStates } = useMemo(
() =>
getGraphVisibilityStateOnDataChange({
data,
isExpandedName: true,
name,
}),
[data, name],
);
const [graphsVisibilityStates, setGraphsVisilityStates] = useState<boolean[]>(
localstoredVisibilityStates,
);
const { dashboards } = useSelector<AppState, DashboardReducer>(
(state) => state.dashboards,
);
const [selectedDashboard] = dashboards;
const canModifyChart = useChartMutable({
panelType: widget.panelTypes,
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
});
const lineChartRef = useRef<ToggleGraphProps>();
// Updating the visibility state of the graph on data change according to global time range
useEffect(() => {
if (canModifyChart) {
const newGraphVisibilityState = getGraphVisibilityStateOnDataChange({
data,
isExpandedName: true,
name,
});
setGraphsVisilityStates(newGraphVisibilityState.graphVisibilityStates);
}
}, [canModifyChart, data, name]);
useEffect(() => {
const eventListener = eventEmitter.on(
Events.UPDATE_GRAPH_VISIBILITY_STATE,
(data) => {
if (data.name === `${name}expanded` && canModifyChart) {
setGraphsVisilityStates([...data.graphVisibilityStates]);
}
},
);
return (): void => {
eventListener.off(Events.UPDATE_GRAPH_VISIBILITY_STATE);
};
}, [canModifyChart, name]);
useEffect(() => {
if (canModifyChart && lineChartRef.current) {
toggleGraphsVisibilityInChart({
graphsVisibilityStates,
lineChartRef,
});
}
}, [graphsVisibilityStates, canModifyChart]);
const { featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const onToggleModal = useCallback(
(func: Dispatch<SetStateAction<boolean>>) => {
func((value) => !value);
},
[],
);
const onDeleteHandler = useCallback(() => {
const isEmptyWidget = widget?.id === 'empty' || isEmpty(widget);
const widgetId = isEmptyWidget ? layout[0].i : widget?.id;
featureResponse
.refetch()
.then(() => {
deleteWidget({ widgetId, setLayout });
onToggleModal(setDeleteModal);
})
.catch(() => {
notifications.error({
message: t('common:something_went_wrong'),
});
});
}, [
widget,
layout,
featureResponse,
deleteWidget,
setLayout,
onToggleModal,
notifications,
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 handleOnView = (): void => {
onToggleModal(setModal);
};
const handleOnDelete = (): void => {
onToggleModal(setDeleteModal);
};
const onDeleteModelHandler = (): void => {
onToggleModal(setDeleteModal);
};
const onToggleModelHandler = (): void => {
onToggleModal(setModal);
};
const getModals = (): JSX.Element => (
<>
<Modal
destroyOnClose
onCancel={onDeleteModelHandler}
open={deleteModal}
title="Delete"
height="10vh"
onOk={onDeleteHandler}
centered
>
<Typography>Are you sure you want to delete this widget</Typography>
</Modal>
<Modal
title="View"
footer={[]}
centered
open={modal}
onCancel={onToggleModelHandler}
width="85%"
destroyOnClose
>
<FullViewContainer>
<FullView
name={`${name}expanded`}
widget={widget}
yAxisUnit={yAxisUnit}
graphsVisibilityStates={graphsVisibilityStates}
onToggleModelHandler={onToggleModelHandler}
/>
</FullViewContainer>
</Modal>
</>
);
return (
<span
onMouseOver={(): void => {
setHovered(true);
}}
onFocus={(): void => {
setHovered(true);
}}
onMouseOut={(): void => {
setHovered(false);
}}
onBlur={(): void => {
setHovered(false);
}}
>
{enableModel && getModals()}
{!isEmpty(widget) && data && (
<>
{enableWidgetHeader && (
<div className="drag-handle">
<WidgetHeader
parentHover={hovered}
title={widget?.title}
widget={widget}
onView={handleOnView}
onDelete={handleOnDelete}
onClone={onCloneHandler}
queryResponse={queryResponse}
errorMessage={errorMessage}
allowClone={allowClone}
allowDelete={allowDelete}
allowEdit={allowEdit}
/>
</div>
)}
<GridPanelSwitch
panelType={widget.panelTypes}
data={data}
isStacked={widget.isStacked}
opacity={widget.opacity}
title={' '}
name={name}
yAxisUnit={yAxisUnit}
onClickHandler={onClickHandler}
onDragSelect={onDragSelect}
panelData={[]}
query={widget.query}
ref={lineChartRef}
/>
</>
)}
</span>
);
}
WidgetGraphComponent.defaultProps = {
yAxisUnit: undefined,
layout: undefined,
setLayout: undefined,
onDragSelect: undefined,
onClickHandler: undefined,
allowDelete: true,
allowClone: true,
allowEdit: true,
};
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
deleteWidget: bindActionCreators(DeleteWidget, dispatch),
});
export default connect(
null,
mapDispatchToProps,
)(
memo(
WidgetGraphComponent,
(prevProps, nextProps) =>
isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name,
),
);