UID has been added to Integrations table, Integration page and routes (#2112)

# What this PR does
UID has been added to Integrations table, Integration page and routes
This commit is contained in:
Yulia Shanyrova 2023-06-06 13:59:21 +02:00 committed by GitHub
parent 0a78b99fd8
commit ed7da1953f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 286 additions and 72 deletions

View file

@ -0,0 +1,19 @@
.hamburger-menu {
cursor: pointer;
color: var(--primary-text-color);
}
.hamburger-menu-withBackground {
display: inline-flex;
flex-direction: column;
align-items: center;
vertical-align: middle;
justify-content: center;
background-color: rgba(204, 204, 220, 0.16);
border: 1px solid transparent;
height: 32px;
width: 30px;
padding: 4px;
cursor: pointer;
color: var(--primary-text-color);
}

View file

@ -0,0 +1,39 @@
import React, { useRef } from 'react';
import { Icon } from '@grafana/ui';
import cn from 'classnames/bind';
import styles from './HamburgerMenu.module.css';
interface HamburgerMenuProps {
openMenu: React.MouseEventHandler<HTMLElement>;
listWidth: number;
listBorder: number;
withBackground?: boolean;
className?: string;
}
const cx = cn.bind(styles);
const HamburgerMenu: React.FC<HamburgerMenuProps> = (props) => {
const ref = useRef<HTMLDivElement>();
const { openMenu, listBorder, listWidth, withBackground, className } = props;
return (
<div
ref={ref}
className={withBackground ? cx('hamburger-menu-withBackground') : cx('hamburger-menu', className)}
onClick={() => {
const boundingRect = ref.current.getBoundingClientRect();
openMenu({
pageX: boundingRect.right - listWidth + listBorder * 2,
pageY: boundingRect.top + boundingRect.height,
} as any);
}}
>
<Icon size="sm" name="ellipsis-v" />
</div>
);
};
export default HamburgerMenu;

View file

@ -6,3 +6,45 @@
width: 700px;
}
}
.integrations-actionsList {
display: flex;
flex-direction: column;
width: 200px;
border-radius: 2px;
}
.integrations-actionItem {
padding: 8px;
display: flex;
align-items: center;
flex-direction: row;
flex-shrink: 0;
white-space: nowrap;
border-left: 2px solid transparent;
cursor: pointer;
min-width: 84px;
display: flex;
gap: 8px;
flex-direction: row;
&:hover {
background: var(--gray-9);
}
}
.hamburgerMenu-small {
display: inline-flex;
flex-direction: column;
align-items: center;
vertical-align: middle;
justify-content: center;
background-color: rgba(204, 204, 220, 0.16);
color: var(--secondary-background);
border: 1px solid transparent;
height: 24px;
width: 22px;
padding: 4px;
cursor: pointer;
color: var(--primary-text-color);
}

View file

@ -14,13 +14,16 @@ import {
} from '@grafana/ui';
import cn from 'classnames/bind';
import { observer } from 'mobx-react';
import CopyToClipboard from 'react-copy-to-clipboard';
import HamburgerMenu from 'components/HamburgerMenu/HamburgerMenu';
import IntegrationBlock from 'components/Integrations/IntegrationBlock';
import IntegrationBlockItem from 'components/Integrations/IntegrationBlockItem';
import MonacoEditor from 'components/MonacoEditor/MonacoEditor';
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
import { ChatOpsConnectors } from 'containers/AlertRules/parts';
import EscalationChainSteps from 'containers/EscalationChainSteps/EscalationChainSteps';
import styles from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss';
@ -33,6 +36,7 @@ import { EscalationChain } from 'models/escalation_chain/escalation_chain.types'
import { MONACO_INPUT_HEIGHT_SMALL, MONACO_OPTIONS } from 'pages/integration_2/Integration2.config';
import IntegrationHelper from 'pages/integration_2/Integration2.helper';
import { useStore } from 'state/useStore';
import { openNotification } from 'utils';
import { UserActions } from 'utils/authorization';
const cx = cn.bind(styles);
@ -341,11 +345,38 @@ export const RouteButtonsDisplay: React.FC<RouteButtonsDisplayProps> = ({
)}
{!channelFilter.is_default && (
<WithPermissionControlTooltip userAction={UserActions.IntegrationsWrite}>
<Tooltip placement="top" content={'Delete'}>
<Button variant={'secondary'} icon={'trash-alt'} size={'sm'} onClick={onDelete} />
</Tooltip>
</WithPermissionControlTooltip>
<WithContextMenu
renderMenuItems={() => (
<div className={cx('integrations-actionsList')}>
<CopyToClipboard text={channelFilter.id} onCopy={() => openNotification('Route ID is copied')}>
<div className={cx('integrations-actionItem')}>
<HorizontalGroup spacing={'xs'}>
<Icon name="copy" />
<Text type="primary">UID: {channelFilter.id}</Text>
</HorizontalGroup>
</div>
</CopyToClipboard>
<div className="thin-line-break" />
<WithPermissionControlTooltip key="delete" userAction={UserActions.IntegrationsWrite}>
<div className={cx('integrations-actionItem')} onClick={onDelete}>
<Text type="danger">
<HorizontalGroup spacing={'xs'}>
<Icon name="trash-alt" />
<span>Delete Route</span>
</HorizontalGroup>
</Text>
</div>
</WithPermissionControlTooltip>
</div>
)}
>
{({ openMenu }) => (
<HamburgerMenu openMenu={openMenu} listBorder={2} listWidth={200} className={cx('hamburgerMenu-small')} />
)}
</WithContextMenu>
)}
</HorizontalGroup>
);

View file

@ -43,7 +43,7 @@ $LARGE-MARGIN: 24px;
&__actionsList {
display: flex;
flex-direction: column;
width: 160px;
width: 200px;
border-radius: 2px;
}
@ -85,22 +85,6 @@ $LARGE-MARGIN: 24px;
}
}
.hamburger-menu {
display: inline-flex;
flex-direction: column;
align-items: center;
vertical-align: middle;
justify-content: center;
background-color: rgba(204, 204, 220, 0.16);
color: var(--secondary-background);
border: 1px solid transparent;
height: 32px;
width: 30px;
padding: 4px;
cursor: pointer;
color: var(--primary-text-color);
}
.loadingPlaceholder {
margin-bottom: 0;
margin-right: 4px;

View file

@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react';
import React, { useState } from 'react';
import {
Button,
@ -23,6 +23,7 @@ import { RouteComponentProps, useHistory, withRouter } from 'react-router-dom';
import { debounce } from 'throttle-debounce';
import { TemplateForEdit, templateForEdit } from 'components/AlertTemplates/AlertTemplatesForm.config';
import HamburgerMenu from 'components/HamburgerMenu/HamburgerMenu';
import IntegrationCollapsibleTreeView, {
IntegrationCollapsibleItem,
} from 'components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView';
@ -83,7 +84,7 @@ interface Integration2State extends PageBaseState {
isAddingRoute: boolean;
}
const ACTIONS_LIST_WIDTH = 160;
const ACTIONS_LIST_WIDTH = 200;
const ACTIONS_LIST_BORDER = 2;
const NEW_ROUTE_DEFAULT = '{{ (payload.severity == "foo" and "bar" in payload.region) or True }}';
@ -586,27 +587,6 @@ const DemoNotification: React.FC = () => {
);
};
const HamburgerMenu: React.FC<{ openMenu: React.MouseEventHandler<HTMLElement> }> = ({ openMenu }) => {
const ref = useRef<HTMLDivElement>();
return (
<div
ref={ref}
className={cx('hamburger-menu')}
onClick={() => {
const boundingRect = ref.current.getBoundingClientRect();
openMenu({
pageX: boundingRect.right - ACTIONS_LIST_WIDTH + ACTIONS_LIST_BORDER * 2,
pageY: boundingRect.top + boundingRect.height,
} as any);
}}
>
<Icon size="sm" name="ellipsis-v" />
</div>
);
};
interface IntegrationSendDemoPayloadModalProps {
isOpen: boolean;
alertReceiveChannel: AlertReceiveChannel;
@ -839,6 +819,19 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({ alertReceiveCha
</WithPermissionControlTooltip>
)}
<CopyToClipboard
text={alertReceiveChannel.id}
onCopy={() => openNotification('Integration ID is copied')}
>
<div className={cx('integration__actionItem')}>
<HorizontalGroup spacing={'xs'}>
<Icon name="copy" />
<Text type="primary">UID: {alertReceiveChannel.id}</Text>
</HorizontalGroup>
</div>
</CopyToClipboard>
<div className="thin-line-break" />
<WithPermissionControlTooltip userAction={UserActions.IntegrationsWrite}>
@ -859,6 +852,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({ alertReceiveCha
confirmText: 'Delete',
});
}}
style={{ width: '100%' }}
>
<Text type="danger">
<HorizontalGroup spacing={'xs'}>
@ -872,7 +866,14 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({ alertReceiveCha
</div>
)}
>
{({ openMenu }) => <HamburgerMenu openMenu={openMenu} />}
{({ openMenu }) => (
<HamburgerMenu
openMenu={openMenu}
listBorder={ACTIONS_LIST_BORDER}
listWidth={ACTIONS_LIST_WIDTH}
withBackground
/>
)}
</WithContextMenu>
</div>
</>

View file

@ -19,3 +19,29 @@
padding: 4px 10px;
width: 40px;
}
.integrations-actionsList {
display: flex;
flex-direction: column;
width: 200px;
border-radius: 2px;
}
.integrations-actionItem {
padding: 8px;
display: flex;
align-items: center;
flex-direction: row;
flex-shrink: 0;
white-space: nowrap;
border-left: 2px solid transparent;
cursor: pointer;
min-width: 84px;
display: flex;
gap: 8px;
flex-direction: row;
&:hover {
background: var(--gray-9);
}
}

View file

@ -1,13 +1,15 @@
import React from 'react';
import { HorizontalGroup, Button, IconButton, VerticalGroup } from '@grafana/ui';
import { HorizontalGroup, Button, VerticalGroup, Icon, ConfirmModal } from '@grafana/ui';
import cn from 'classnames/bind';
import { debounce } from 'lodash-es';
import { observer } from 'mobx-react';
import CopyToClipboard from 'react-copy-to-clipboard';
import Emoji from 'react-emoji-render';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import GTable from 'components/GTable/GTable';
import HamburgerMenu from 'components/HamburgerMenu/HamburgerMenu';
import IntegrationLogo from 'components/IntegrationLogo/IntegrationLogo';
import { Filters } from 'components/IntegrationsFilters/IntegrationsFilters';
import { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
@ -18,7 +20,7 @@ import {
import PluginLink from 'components/PluginLink/PluginLink';
import Text from 'components/Text/Text';
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
import WithConfirm from 'components/WithConfirm/WithConfirm';
import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
import IntegrationForm2 from 'containers/IntegrationForm/IntegrationForm2';
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
import TeamName from 'containers/TeamName/TeamName';
@ -28,6 +30,7 @@ import { AlertReceiveChannel, MaintenanceMode } from 'models/alert_receive_chann
import IntegrationHelper from 'pages/integration_2/Integration2.helper';
import { PageProps, WithStoreProps } from 'state/types';
import { withMobXProviderContext } from 'state/withStore';
import { openNotification } from 'utils';
import LocationHelper from 'utils/LocationHelper';
import { UserActions } from 'utils/authorization';
@ -37,11 +40,23 @@ const cx = cn.bind(styles);
const FILTERS_DEBOUNCE_MS = 500;
const ITEMS_PER_PAGE = 15;
const MAX_LINE_LENGTH = 40;
const ACTIONS_LIST_WIDTH = 200;
const ACTIONS_LIST_BORDER = 2;
interface IntegrationsState extends PageBaseState {
integrationsFilters: Filters;
alertReceiveChannelId?: AlertReceiveChannel['id'] | 'new';
page: number;
confirmationModal: {
isOpen: boolean;
title: any;
dismissText: string;
confirmText: string;
body?: React.ReactNode;
description?: string;
confirmationText?: string;
onConfirm: () => void;
};
}
interface IntegrationsProps extends WithStoreProps, PageProps, RouteComponentProps<{ id: string }> {}
@ -52,6 +67,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
integrationsFilters: { searchTerm: '' },
errorData: initErrorDataState(),
page: 1,
confirmationModal: undefined,
};
async componentDidMount() {
@ -110,7 +126,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
render() {
const { store, query } = this.props;
const { alertReceiveChannelId, page } = this.state;
const { alertReceiveChannelId, page, confirmationModal } = this.state;
const { grafanaTeamStore, alertReceiveChannelStore, heartbeatStore } = store;
const { count, results } = alertReceiveChannelStore.getPaginatedSearchResult();
@ -215,6 +231,24 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
id={alertReceiveChannelId}
/>
)}
{confirmationModal && (
<ConfirmModal
isOpen={confirmationModal.isOpen}
title={confirmationModal.title}
confirmText={confirmationModal.confirmText}
dismissText="Cancel"
body={confirmationModal.body}
description={confirmationModal.description}
confirmationText={confirmationModal.confirmationText}
onConfirm={confirmationModal.onConfirm}
onDismiss={() =>
this.setState({
confirmationModal: undefined,
})
}
/>
)}
</>
);
}
@ -354,29 +388,66 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
renderButtons = (item: AlertReceiveChannel) => {
return (
<HorizontalGroup justify="flex-end">
<WithPermissionControlTooltip key="edit" userAction={UserActions.IntegrationsWrite}>
<IconButton tooltip="Settings" name="cog" onClick={() => this.onIntegrationEditClick(item.id)} />
</WithPermissionControlTooltip>
<WithPermissionControlTooltip key="edit" userAction={UserActions.IntegrationsWrite}>
<WithConfirm
description={
<Text>
<Emoji
className={cx('title')}
text={`Are you sure you want to delete ${item.verbal_name} integration?`}
/>
</Text>
}
>
<IconButton
tooltip="Delete"
name="trash-alt"
onClick={() => this.handleDeleteAlertReceiveChannel(item.id)}
/>
</WithConfirm>
</WithPermissionControlTooltip>
</HorizontalGroup>
<WithContextMenu
renderMenuItems={() => (
<div className={cx('integrations-actionsList')}>
<WithPermissionControlTooltip key="edit" userAction={UserActions.IntegrationsWrite}>
<div className={cx('integrations-actionItem')} onClick={() => this.onIntegrationEditClick(item.id)}>
<Text type="primary">Integration settings</Text>
</div>
</WithPermissionControlTooltip>
<CopyToClipboard text={item.id} onCopy={() => openNotification('Integration ID is copied')}>
<div className={cx('integrations-actionItem')}>
<HorizontalGroup spacing={'xs'}>
<Icon name="copy" />
<Text type="primary">UID: {item.id}</Text>
</HorizontalGroup>
</div>
</CopyToClipboard>
<div className="thin-line-break" />
<WithPermissionControlTooltip key="delete" userAction={UserActions.IntegrationsWrite}>
<>
<div className={cx('integrations-actionItem')}>
<div
onClick={() => {
this.setState({
confirmationModal: {
isOpen: true,
confirmText: 'Delete',
dismissText: 'Cancel',
onConfirm: () => this.handleDeleteAlertReceiveChannel(item.id),
title: 'Delete integration',
body: (
<Text type="primary">
Are you sure you want to delete <Emoji text={item.verbal_name} /> integration?
</Text>
),
},
});
}}
style={{ width: '100%' }}
>
<Text type="danger">
<HorizontalGroup spacing={'xs'}>
<Icon name="trash-alt" />
<span>Delete Integration</span>
</HorizontalGroup>
</Text>
</div>
</div>
</>
</WithPermissionControlTooltip>
</div>
)}
>
{({ openMenu }) => (
<HamburgerMenu openMenu={openMenu} listBorder={ACTIONS_LIST_BORDER} listWidth={ACTIONS_LIST_WIDTH} />
)}
</WithContextMenu>
);
};
@ -390,6 +461,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
const { alertReceiveChannelStore } = store;
alertReceiveChannelStore.deleteAlertReceiveChannel(alertReceiveChannelId).then(this.applyFilters);
this.setState({ confirmationModal: undefined });
};
handleIntegrationsFiltersChange = (integrationsFilters: Filters) => {