Rajat Dabade 12819113c1
[Feat]: Threshold in dashboard for Value Component (#3949)
* feat: done with the basic design

* refactor: started working with functionality

* refactor: done with saving the thresholds

* refactor: done with coloring and conflicting threshold in value chart

* refactor: done with the backgound color and text

* refactor: done with unit in value graphs

* refactor: done with precedence and drag and drop

* refactor: removed the unwanted console

* chore: updated snapshot and test

* refactor: support for dark mode

* refactor: done with the review changes

* refactor: removed the extra created file

* refactor: tsc fixes

* refactor: updated border color

* refactor: updated required mark

* refactor: added missing props

* refactor: tsc fixes

* refactor: addressed review comments
2023-11-15 17:14:09 +05:30

243 lines
6.4 KiB
TypeScript

import {
AlertOutlined,
CopyOutlined,
DeleteOutlined,
DownOutlined,
EditFilled,
ExclamationCircleOutlined,
FullscreenOutlined,
WarningOutlined,
} from '@ant-design/icons';
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import AppReducer from 'types/reducer/app';
import { popupContainer } from 'utils/selectPopupContainer';
import {
errorTooltipPosition,
overlayStyles,
spinnerStyles,
tooltipStyles,
WARNING_MESSAGE,
} from './config';
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
import {
ArrowContainer,
HeaderContainer,
HeaderContentContainer,
ThesholdContainer,
WidgetHeaderContainer,
} from './styles';
import { MenuItem } from './types';
import { generateMenuList, isTWidgetOptions } from './utils';
interface IWidgetHeaderProps {
title: ReactNode;
widget: Widgets;
onView: VoidFunction;
onDelete?: VoidFunction;
onClone?: VoidFunction;
parentHover: boolean;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
>;
errorMessage: string | undefined;
threshold?: ReactNode;
headerMenuList?: MenuItemKeys[];
isWarning: boolean;
}
function WidgetHeader({
title,
widget,
onView,
onDelete,
onClone,
parentHover,
queryResponse,
errorMessage,
threshold,
headerMenuList,
isWarning,
}: IWidgetHeaderProps): JSX.Element | null {
const [localHover, setLocalHover] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
const onEditHandler = useCallback((): void => {
const widgetId = widget.id;
history.push(
`${window.location.pathname}/new?widgetId=${widgetId}&graphType=${
widget.panelTypes
}&${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(widget.query),
)}`,
);
}, [widget.id, widget.panelTypes, widget.query]);
const onCreateAlertsHandler = useCallback(() => {
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(widget.query),
)}`,
);
}, [widget]);
const keyMethodMapping = useMemo(
() => ({
[MenuItemKeys.View]: onView,
[MenuItemKeys.Edit]: onEditHandler,
[MenuItemKeys.Delete]: onDelete,
[MenuItemKeys.Clone]: onClone,
[MenuItemKeys.CreateAlerts]: onCreateAlertsHandler,
}),
[onDelete, onEditHandler, onView, onClone, onCreateAlertsHandler],
);
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
({ key }: { key: string }): void => {
if (isTWidgetOptions(key)) {
const functionToCall = keyMethodMapping[key];
if (functionToCall) {
functionToCall();
setIsOpen(false);
}
}
},
[keyMethodMapping],
);
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const [deleteWidget, editWidget] = useComponentPermission(
['delete_widget', 'edit_widget'],
role,
);
const actions = useMemo(
(): MenuItem[] => [
{
key: MenuItemKeys.View,
icon: <FullscreenOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
isVisible: headerMenuList?.includes(MenuItemKeys.View) || false,
disabled: queryResponse.isFetching,
},
{
key: MenuItemKeys.Edit,
icon: <EditFilled />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Edit],
isVisible: headerMenuList?.includes(MenuItemKeys.Edit) || false,
disabled: !editWidget,
},
{
key: MenuItemKeys.Clone,
icon: <CopyOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Clone],
isVisible: headerMenuList?.includes(MenuItemKeys.Clone) || false,
disabled: !editWidget,
},
{
key: MenuItemKeys.Delete,
icon: <DeleteOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Delete],
isVisible: headerMenuList?.includes(MenuItemKeys.Delete) || false,
disabled: !deleteWidget,
danger: true,
},
{
key: MenuItemKeys.CreateAlerts,
icon: <AlertOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.CreateAlerts],
isVisible: headerMenuList?.includes(MenuItemKeys.CreateAlerts) || false,
disabled: false,
},
],
[headerMenuList, queryResponse.isFetching, editWidget, deleteWidget],
);
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
const onClickHandler = (): void => {
setIsOpen(!isOpen);
};
const menu = useMemo(
() => ({
items: updatedMenuList,
onClick: onMenuItemSelectHandler,
}),
[updatedMenuList, onMenuItemSelectHandler],
);
if (widget.id === PANEL_TYPES.EMPTY_WIDGET) {
return null;
}
return (
<WidgetHeaderContainer>
<Dropdown
getPopupContainer={popupContainer}
destroyPopupOnHide
open={isOpen}
onOpenChange={setIsOpen}
menu={menu}
trigger={['click']}
overlayStyle={overlayStyles}
>
<HeaderContainer
onMouseOver={(): void => setLocalHover(true)}
onMouseOut={(): void => setLocalHover(false)}
hover={localHover}
onClick={onClickHandler}
>
<HeaderContentContainer>
<Typography.Text style={{ maxWidth: '80%' }} ellipsis>
{title}
</Typography.Text>
<ArrowContainer hover={parentHover}>
<DownOutlined />
</ArrowContainer>
</HeaderContentContainer>
</HeaderContainer>
</Dropdown>
<ThesholdContainer>{threshold}</ThesholdContainer>
{queryResponse.isFetching && !queryResponse.isError && (
<Spinner height="5vh" style={spinnerStyles} />
)}
{queryResponse.isError && (
<Tooltip title={errorMessage} placement={errorTooltipPosition}>
<ExclamationCircleOutlined style={tooltipStyles} />
</Tooltip>
)}
{isWarning && (
<Tooltip title={WARNING_MESSAGE} placement={errorTooltipPosition}>
<WarningOutlined style={tooltipStyles} />
</Tooltip>
)}
</WidgetHeaderContainer>
);
}
WidgetHeader.defaultProps = {
onDelete: undefined,
onClone: undefined,
threshold: undefined,
headerMenuList: [MenuItemKeys.View],
};
export default WidgetHeader;