mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-12 01:21:29 +08:00

* chore: helpers required for dashboards e2e test cases * chore: helpers required for dashboards e2e test cases * chore: helpers required for dashboards e2e test cases
256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
import './WidgetHeader.styles.scss';
|
|
|
|
import {
|
|
AlertOutlined,
|
|
CloudDownloadOutlined,
|
|
CopyOutlined,
|
|
DeleteOutlined,
|
|
EditFilled,
|
|
ExclamationCircleOutlined,
|
|
FullscreenOutlined,
|
|
MoreOutlined,
|
|
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 useCreateAlerts from 'hooks/queryBuilder/useCreateAlerts';
|
|
import useComponentPermission from 'hooks/useComponentPermission';
|
|
import history from 'lib/history';
|
|
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|
import { isEmpty } from 'lodash-es';
|
|
import { unparse } from 'papaparse';
|
|
import { ReactNode, useCallback, useMemo } 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 { errorTooltipPosition, WARNING_MESSAGE } from './config';
|
|
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
|
|
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;
|
|
isFetchingResponse: boolean;
|
|
tableProcessedDataRef: React.MutableRefObject<RowData[]>;
|
|
}
|
|
|
|
function WidgetHeader({
|
|
title,
|
|
widget,
|
|
onView,
|
|
onDelete,
|
|
onClone,
|
|
parentHover,
|
|
queryResponse,
|
|
errorMessage,
|
|
threshold,
|
|
headerMenuList,
|
|
isWarning,
|
|
isFetchingResponse,
|
|
tableProcessedDataRef,
|
|
}: IWidgetHeaderProps): JSX.Element | null {
|
|
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 = useCreateAlerts(widget, 'dashboardView');
|
|
|
|
const onDownloadHandler = useCallback((): void => {
|
|
const csv = unparse(tableProcessedDataRef.current);
|
|
const csvBlob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
const csvUrl = URL.createObjectURL(csvBlob);
|
|
const downloadLink = document.createElement('a');
|
|
downloadLink.href = csvUrl;
|
|
downloadLink.download = `${!isEmpty(title) ? title : 'table-panel'}.csv`;
|
|
downloadLink.click();
|
|
downloadLink.remove();
|
|
}, [tableProcessedDataRef, title]);
|
|
|
|
const keyMethodMapping = useMemo(
|
|
() => ({
|
|
[MenuItemKeys.View]: onView,
|
|
[MenuItemKeys.Edit]: onEditHandler,
|
|
[MenuItemKeys.Delete]: onDelete,
|
|
[MenuItemKeys.Clone]: onClone,
|
|
[MenuItemKeys.CreateAlerts]: onCreateAlertsHandler,
|
|
[MenuItemKeys.Download]: onDownloadHandler,
|
|
}),
|
|
[
|
|
onView,
|
|
onEditHandler,
|
|
onDelete,
|
|
onClone,
|
|
onCreateAlertsHandler,
|
|
onDownloadHandler,
|
|
],
|
|
);
|
|
|
|
const onMenuItemSelectHandler: MenuProps['onClick'] = useCallback(
|
|
({ key }: { key: string }): void => {
|
|
if (isTWidgetOptions(key)) {
|
|
const functionToCall = keyMethodMapping[key];
|
|
|
|
if (functionToCall) {
|
|
functionToCall();
|
|
}
|
|
}
|
|
},
|
|
[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.Download,
|
|
icon: <CloudDownloadOutlined />,
|
|
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.Download],
|
|
isVisible: widget.panelTypes === PANEL_TYPES.TABLE,
|
|
disabled: false,
|
|
},
|
|
{
|
|
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,
|
|
widget.panelTypes,
|
|
],
|
|
);
|
|
|
|
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
|
|
|
|
const menu = useMemo(
|
|
() => ({
|
|
items: updatedMenuList,
|
|
onClick: onMenuItemSelectHandler,
|
|
}),
|
|
[updatedMenuList, onMenuItemSelectHandler],
|
|
);
|
|
|
|
if (widget.id === PANEL_TYPES.EMPTY_WIDGET) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div className="widget-header-container">
|
|
<Typography.Text
|
|
ellipsis
|
|
data-testid={title}
|
|
className="widget-header-title"
|
|
>
|
|
{title}
|
|
</Typography.Text>
|
|
<div className="widget-header-actions">
|
|
<div className="widget-api-actions">{threshold}</div>
|
|
{isFetchingResponse && !queryResponse.isError && (
|
|
<Spinner style={{ paddingRight: '0.25rem' }} />
|
|
)}
|
|
{queryResponse.isError && (
|
|
<Tooltip
|
|
title={errorMessage}
|
|
placement={errorTooltipPosition}
|
|
className="widget-api-actions"
|
|
>
|
|
<ExclamationCircleOutlined />
|
|
</Tooltip>
|
|
)}
|
|
|
|
{isWarning && (
|
|
<Tooltip
|
|
title={WARNING_MESSAGE}
|
|
placement={errorTooltipPosition}
|
|
className="widget-api-actions"
|
|
>
|
|
<WarningOutlined />
|
|
</Tooltip>
|
|
)}
|
|
<Dropdown menu={menu} trigger={['hover']} placement="bottomRight">
|
|
<MoreOutlined
|
|
data-testid="widget-header-options"
|
|
className={`widget-header-more-options ${
|
|
parentHover ? 'widget-header-hover' : ''
|
|
}`}
|
|
/>
|
|
</Dropdown>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
WidgetHeader.defaultProps = {
|
|
onDelete: undefined,
|
|
onClone: undefined,
|
|
threshold: undefined,
|
|
headerMenuList: [MenuItemKeys.View],
|
|
};
|
|
|
|
export default WidgetHeader;
|