Templates&Grouping tweaks&improvements (#1879)
# What this PR does ## Which issue(s) this PR fixes - Adds option to go back to the Integrations table - Fixed IntegrationCollapsibleTreeView component issue on expand/collapse - Reused/Renamed old CounterBadge to TooltipBadge - Added maintenance until/hearbeat display to integration - Changed `maintenace until` display on Integrations table
This commit is contained in:
parent
b62687295d
commit
fcf8a9bacb
15 changed files with 301 additions and 229 deletions
|
|
@ -1,55 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Icon, Tooltip, IconName, VerticalGroup, HorizontalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Text, { TextType } from 'components/Text/Text';
|
||||
|
||||
import styles from './CounterBadge.module.scss';
|
||||
|
||||
interface CounterBadgeProps {
|
||||
borderType: Partial<TextType>;
|
||||
count: number | string;
|
||||
tooltipTitle: string;
|
||||
tooltipContent: React.ReactNode;
|
||||
|
||||
icon?: string;
|
||||
addPadding?: boolean;
|
||||
|
||||
onHover?: () => void;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const CounterBadge: FC<CounterBadgeProps> = (props) => {
|
||||
const { borderType, count, tooltipTitle, tooltipContent, onHover, addPadding, icon } = props;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="bottom-start"
|
||||
interactive
|
||||
content={
|
||||
<div className={cx('tooltip')}>
|
||||
<VerticalGroup spacing="xs">
|
||||
<Text type="primary">{tooltipTitle}</Text>
|
||||
{tooltipContent && <Text type="secondary">{tooltipContent}</Text>}
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cx('root', 'element', { [`element--${borderType}`]: true }, { 'element--padding': addPadding })}
|
||||
onMouseEnter={onHover}
|
||||
>
|
||||
<HorizontalGroup spacing="xs">
|
||||
{icon && (
|
||||
<Icon className={cx('element__icon', { [`element__icon--${borderType}`]: true })} name={icon as IconName} />
|
||||
)}
|
||||
<Text className={cx('element__text', { [`element__text--${borderType}`]: true })}>{count}</Text>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default CounterBadge;
|
||||
|
|
@ -13,6 +13,7 @@ export interface IntegrationCollapsibleItem {
|
|||
expandedView: React.ReactNode;
|
||||
collapsedView: React.ReactNode;
|
||||
isCollapsible: boolean;
|
||||
isExpanded?: boolean;
|
||||
}
|
||||
|
||||
interface IntegrationCollapsibleTreeViewProps {
|
||||
|
|
@ -53,7 +54,11 @@ const IntegrationCollapsibleTreeView: React.FC<IntegrationCollapsibleTreeViewPro
|
|||
function getStartingExpandedState(): Array<boolean | boolean[]> {
|
||||
const expandedArrayValues = new Array<boolean | boolean[]>(configElements.length);
|
||||
configElements.forEach((elem, index) => {
|
||||
expandedArrayValues[index] = Array.isArray(elem) ? new Array(elem.length).fill(true) : true;
|
||||
if (Array.isArray(elem)) {
|
||||
expandedArrayValues[index] = elem.map((el) => !el.isCollapsible || el.isExpanded);
|
||||
} else {
|
||||
expandedArrayValues[index] = !elem.isCollapsible || elem.isExpanded;
|
||||
}
|
||||
});
|
||||
|
||||
return expandedArrayValues;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import React, { FC, useEffect, useState } from 'react';
|
|||
import { Tooltip, VerticalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import CounterBadge from 'components/CounterBadge/CounterBadge';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import { ScheduleQualityDetails } from 'components/ScheduleQualityDetails/ScheduleQualityDetails';
|
||||
import Tag from 'components/Tag/Tag';
|
||||
import Text from 'components/Text/Text';
|
||||
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
|
||||
import { Schedule, ScheduleScoreQualityResponse, ScheduleScoreQualityResult } from 'models/schedule/schedule.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
|
|
@ -40,10 +40,10 @@ const ScheduleQuality: FC<ScheduleQualityProps> = ({ schedule, lastUpdated }) =>
|
|||
<>
|
||||
<div className={cx('root')}>
|
||||
{relatedEscalationChains?.length > 0 && schedule?.number_of_escalation_chains > 0 && (
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="link"
|
||||
addPadding
|
||||
count={schedule.number_of_escalation_chains}
|
||||
text={schedule.number_of_escalation_chains}
|
||||
tooltipTitle="Used in escalations"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="sm">
|
||||
|
|
@ -60,10 +60,10 @@ const ScheduleQuality: FC<ScheduleQualityProps> = ({ schedule, lastUpdated }) =>
|
|||
)}
|
||||
|
||||
{schedule.warnings?.length > 0 && (
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="warning"
|
||||
addPadding
|
||||
count={schedule.warnings.length}
|
||||
text={schedule.warnings.length}
|
||||
tooltipTitle="Warnings"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="none">
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@
|
|||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
padding: 3px 4px;
|
||||
|
||||
&--link,
|
||||
&--warning,
|
||||
&--success {
|
||||
border-radius: 2px;
|
||||
}
|
||||
border-radius: 2px;
|
||||
|
||||
&--primary {
|
||||
background: var(--tag-background-primary);
|
||||
|
|
@ -24,6 +19,11 @@
|
|||
border: 1px solid var(--tag-border-success);
|
||||
color: var(--tag-text-success);
|
||||
}
|
||||
&--danger {
|
||||
background: var(--tag-background-danger);
|
||||
border: 1px solid var(--tag-border-danger);
|
||||
color: var(--tag-text-danger);
|
||||
}
|
||||
|
||||
&--padding {
|
||||
padding: 3px 10px;
|
||||
72
grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx
Normal file
72
grafana-plugin/src/components/TooltipBadge/TooltipBadge.tsx
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import { Icon, Tooltip, IconName, VerticalGroup, HorizontalGroup } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
|
||||
import Text, { TextType } from 'components/Text/Text';
|
||||
|
||||
import styles from './TooltipBadge.module.scss';
|
||||
|
||||
interface TooltipBadgeProps {
|
||||
className?: string;
|
||||
borderType: Partial<TextType>;
|
||||
text?: number | string;
|
||||
tooltipTitle: string;
|
||||
tooltipContent: React.ReactNode;
|
||||
|
||||
icon?: IconName;
|
||||
customIcon?: React.ReactNode;
|
||||
addPadding?: boolean;
|
||||
|
||||
onHover?: () => void;
|
||||
}
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
const TooltipBadge: FC<TooltipBadgeProps> = (props) => {
|
||||
const { borderType, text, tooltipTitle, tooltipContent, onHover, addPadding, icon, customIcon, className } = props;
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
placement="bottom-start"
|
||||
interactive
|
||||
content={
|
||||
<div className={cx('tooltip')}>
|
||||
<VerticalGroup spacing="xs">
|
||||
<Text type="primary">{tooltipTitle}</Text>
|
||||
{tooltipContent && <Text type="secondary">{tooltipContent}</Text>}
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={cx(
|
||||
'root',
|
||||
'element',
|
||||
{ [`element--${borderType}`]: true },
|
||||
{ 'element--padding': addPadding },
|
||||
className
|
||||
)}
|
||||
onMouseEnter={onHover}
|
||||
>
|
||||
<HorizontalGroup spacing="xs">
|
||||
{renderIcon()}
|
||||
{text && <Text className={cx('element__text', { [`element__text--${borderType}`]: true })}>{text}</Text>}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
function renderIcon() {
|
||||
if (customIcon) {
|
||||
return customIcon;
|
||||
}
|
||||
if (!icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Icon className={cx('element__icon', { [`element__icon--${borderType}`]: true })} name={icon as IconName} />;
|
||||
}
|
||||
};
|
||||
|
||||
export default TooltipBadge;
|
||||
|
|
@ -23,7 +23,7 @@ export interface AlertReceiveChannel {
|
|||
instructions: string;
|
||||
demo_alert_enabled: boolean;
|
||||
maintenance_mode?: MaintenanceMode;
|
||||
maintenance_till?: string;
|
||||
maintenance_till?: number;
|
||||
heartbeat: Heartbeat | null;
|
||||
is_available_for_integration_heartbeat: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { Heartbeat } from 'models/heartbeat/heartbeat.types';
|
|||
import { UserDTO as User } from 'models/user';
|
||||
|
||||
export enum MaintenanceMode {
|
||||
Debug,
|
||||
Maintenance,
|
||||
Debug = 0,
|
||||
Maintenance = 1,
|
||||
}
|
||||
|
||||
export interface AlertReceiveChannel {
|
||||
|
|
@ -26,6 +26,7 @@ export interface AlertReceiveChannel {
|
|||
instructions: string;
|
||||
demo_alert_enabled: boolean;
|
||||
maintenance_mode?: MaintenanceMode;
|
||||
maintenance_till?: number;
|
||||
heartbeat: Heartbeat | null;
|
||||
is_available_for_integration_heartbeat: boolean;
|
||||
allow_delete: boolean;
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
}
|
||||
|
||||
.blue {
|
||||
background: var(--timeline-icon-background-resolution-note);
|
||||
background: var(--tag-border-primary);
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
import dayjs from 'dayjs';
|
||||
|
||||
import { MaintenanceMode } from 'models/alert_receive_channel';
|
||||
import { ChannelFilter } from 'models/channel_filter';
|
||||
|
||||
import { MAX_CHARACTERS_COUNT, TEXTAREA_ROWS_COUNT } from './Integration2.config';
|
||||
|
|
@ -36,6 +39,21 @@ const IntegrationHelper = {
|
|||
}
|
||||
return routeIndex ? 'Else' : 'If';
|
||||
},
|
||||
|
||||
getMaintenanceText(maintenanceUntill: number, mode: number = undefined) {
|
||||
const date = dayjs(new Date(maintenanceUntill * 1000));
|
||||
const now = dayjs();
|
||||
const hourDiff = date.diff(now, 'hours');
|
||||
const minDiff = date.diff(now, 'minutes');
|
||||
const totalMinDiff = minDiff - hourDiff * 60;
|
||||
const totalDiffString = `${hourDiff}h ${totalMinDiff}m left`;
|
||||
|
||||
if (mode) {
|
||||
return `${mode === MaintenanceMode.Debug ? 'Debug Maintenance' : 'Maintenance'}: ${totalDiffString}`;
|
||||
}
|
||||
|
||||
return totalDiffString;
|
||||
},
|
||||
};
|
||||
|
||||
export default IntegrationHelper;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,19 @@ $FLEX-GAP: 4px;
|
|||
$MARGIN: 12px;
|
||||
$ITEMS-MARGIN: 24px;
|
||||
|
||||
.integration__heading-container {
|
||||
.integration__heading-container,
|
||||
.integration__subheading-container {
|
||||
margin-bottom: $ITEMS-MARGIN;
|
||||
}
|
||||
|
||||
.integration__heading-container {
|
||||
display: flex;
|
||||
gap: $FLEX-GAP;
|
||||
}
|
||||
|
||||
.integration__heading {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -16,6 +22,7 @@ $ITEMS-MARGIN: 24px;
|
|||
.integration__actions {
|
||||
display: flex;
|
||||
gap: $FLEX-GAP;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.integration__actionsList {
|
||||
|
|
@ -103,3 +110,7 @@ $ITEMS-MARGIN: 24px;
|
|||
.how-to-connect__tag {
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.heartbeat-badge {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useRef } from 'react';
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
HorizontalGroup,
|
||||
VerticalGroup,
|
||||
|
|
@ -10,6 +9,7 @@ import {
|
|||
Tooltip,
|
||||
Modal,
|
||||
CascaderOption,
|
||||
IconButton,
|
||||
} from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { get } from 'lodash-es';
|
||||
|
|
@ -19,7 +19,6 @@ import Emoji from 'react-emoji-render';
|
|||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import { TemplateForEdit, templateForEdit } from 'components/AlertTemplates/AlertTemplatesForm.config';
|
||||
import CounterBadge from 'components/CounterBadge/CounterBadge';
|
||||
import IntegrationCollapsibleTreeView, {
|
||||
IntegrationCollapsibleItem,
|
||||
} from 'components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView';
|
||||
|
|
@ -31,12 +30,14 @@ import PluginLink from 'components/PluginLink/PluginLink';
|
|||
import SourceCode from 'components/SourceCode/SourceCode';
|
||||
import Tag from 'components/Tag/Tag';
|
||||
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 IntegrationTemplate from 'containers/IntegrationTemplate/IntegrationTemplate';
|
||||
import TeamName from 'containers/TeamName/TeamName';
|
||||
import UserDisplayWithAvatar from 'containers/UserDisplay/UserDisplayWithAvatar';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { HeartGreenIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel';
|
||||
import { ChannelFilter } from 'models/channel_filter';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
|
|
@ -54,7 +55,6 @@ import IntegrationHelper from './Integration2.helper';
|
|||
import styles from './Integration2.module.scss';
|
||||
import IntegrationBlock from './IntegrationBlock';
|
||||
import IntegrationTemplateList from './IntegrationTemplatesList';
|
||||
// import { toJS } from 'mobx';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
|
|
@ -127,80 +127,85 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
{() => (
|
||||
<div className={cx('root')}>
|
||||
<div className={cx('integration__heading-container')}>
|
||||
<div className={cx('integration__heading')}>
|
||||
<h1 className={cx('integration__name')}>
|
||||
<Emoji text={alertReceiveChannel.verbal_name} />
|
||||
</h1>
|
||||
<div className={cx('integration__actions')}>
|
||||
<WithPermissionControlTooltip userAction={UserActions.IntegrationsTest}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
onClick={() => this.setState({ isDemoModalOpen: true })}
|
||||
data-testid="send-demo-alert"
|
||||
>
|
||||
Send demo alert
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
<PluginLink query={{ page: 'integrations_2' }}>
|
||||
<IconButton name="arrow-left" size="xxl" />
|
||||
</PluginLink>
|
||||
<h1 className={cx('integration__name')}>
|
||||
<Emoji text={alertReceiveChannel.verbal_name} />
|
||||
</h1>
|
||||
|
||||
<WithContextMenu
|
||||
renderMenuItems={({ closeMenu }) => (
|
||||
<div className={cx('integration__actionsList')} id="integration-menu-options">
|
||||
<div
|
||||
className={cx('integration__actionItem')}
|
||||
onClick={() => this.openIntegrationSettings(id, closeMenu)}
|
||||
>
|
||||
<Text type="primary">Integration Settings</Text>
|
||||
</div>
|
||||
|
||||
<div className={cx('integration__actionItem')} onClick={() => this.openHearbeat(id, closeMenu)}>
|
||||
Hearbeat
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx('integration__actionItem')}
|
||||
onClick={() => this.openStartMaintenance(id, closeMenu)}
|
||||
>
|
||||
<Text type="primary">Start Maintenance</Text>
|
||||
</div>
|
||||
|
||||
<div className="thin-line-break" />
|
||||
|
||||
<WithPermissionControlTooltip userAction={UserActions.IntegrationsWrite}>
|
||||
<div className={cx('integration__actionItem')}>
|
||||
<WithConfirm
|
||||
title="Delete integration?"
|
||||
body={
|
||||
<>
|
||||
Are you sure you want to delete <Emoji text={alertReceiveChannel.verbal_name} />{' '}
|
||||
integration?
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div onClick={() => this.deleteIntegration(id, closeMenu)}>
|
||||
<div
|
||||
onClick={() => {
|
||||
// work-around to prevent 2 modals showing (withContextMenu and ConfirmModal)
|
||||
const contextMenuEl =
|
||||
document.querySelector<HTMLElement>('#integration-menu-options');
|
||||
if (contextMenuEl) {
|
||||
contextMenuEl.style.display = 'none';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text type="danger">Stop Maintenance</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WithConfirm>
|
||||
</div>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
)}
|
||||
<div className={cx('integration__actions')}>
|
||||
<WithPermissionControlTooltip userAction={UserActions.IntegrationsTest}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
onClick={() => this.setState({ isDemoModalOpen: true })}
|
||||
data-testid="send-demo-alert"
|
||||
>
|
||||
{({ openMenu }) => <HamburgerMenu openMenu={openMenu} />}
|
||||
</WithContextMenu>
|
||||
</div>
|
||||
Send demo alert
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
|
||||
<WithContextMenu
|
||||
renderMenuItems={({ closeMenu }) => (
|
||||
<div className={cx('integration__actionsList')} id="integration-menu-options">
|
||||
<div
|
||||
className={cx('integration__actionItem')}
|
||||
onClick={() => this.openIntegrationSettings(id, closeMenu)}
|
||||
>
|
||||
<Text type="primary">Integration Settings</Text>
|
||||
</div>
|
||||
|
||||
<div className={cx('integration__actionItem')} onClick={() => this.openHearbeat(id, closeMenu)}>
|
||||
Hearbeat
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx('integration__actionItem')}
|
||||
onClick={() => this.openStartMaintenance(id, closeMenu)}
|
||||
>
|
||||
<Text type="primary">Start Maintenance</Text>
|
||||
</div>
|
||||
|
||||
<div className="thin-line-break" />
|
||||
|
||||
<WithPermissionControlTooltip userAction={UserActions.IntegrationsWrite}>
|
||||
<div className={cx('integration__actionItem')}>
|
||||
<WithConfirm
|
||||
title="Delete integration?"
|
||||
body={
|
||||
<>
|
||||
Are you sure you want to delete <Emoji text={alertReceiveChannel.verbal_name} />{' '}
|
||||
integration?
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div onClick={() => this.deleteIntegration(id, closeMenu)}>
|
||||
<div
|
||||
onClick={() => {
|
||||
// work-around to prevent 2 modals showing (withContextMenu and ConfirmModal)
|
||||
const contextMenuEl =
|
||||
document.querySelector<HTMLElement>('#integration-menu-options');
|
||||
if (contextMenuEl) {
|
||||
contextMenuEl.style.display = 'none';
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text type="danger">Stop Maintenance</Text>
|
||||
</div>
|
||||
</div>
|
||||
</WithConfirm>
|
||||
</div>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{({ openMenu }) => <HamburgerMenu openMenu={openMenu} />}
|
||||
</WithContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cx('integration__subheading-container')}>
|
||||
{alertReceiveChannel.description_short && (
|
||||
<Text type="secondary" className={cx('integration__description')}>
|
||||
{alertReceiveChannel.description_short}
|
||||
|
|
@ -208,45 +213,39 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
)}
|
||||
<HorizontalGroup>
|
||||
{alertReceiveChannelCounter && (
|
||||
<Tooltip
|
||||
placement="bottom-start"
|
||||
content={
|
||||
alertReceiveChannelCounter?.alerts_count +
|
||||
' alert' +
|
||||
(alertReceiveChannelCounter?.alerts_count === 1 ? '' : 's') +
|
||||
' in ' +
|
||||
alertReceiveChannelCounter?.alert_groups_count +
|
||||
' alert group' +
|
||||
(alertReceiveChannelCounter?.alert_groups_count === 1 ? '' : 's')
|
||||
<TooltipBadge
|
||||
borderType="primary"
|
||||
tooltipTitle={undefined}
|
||||
tooltipContent={this.getAlertReceiveChannelCounterTooltip()}
|
||||
text={
|
||||
alertReceiveChannelCounter?.alerts_count + '/' + alertReceiveChannelCounter?.alert_groups_count
|
||||
}
|
||||
>
|
||||
{/* <span> is needed to be child, otherwise Tooltip won't render */}
|
||||
<span>
|
||||
<PluginLink
|
||||
query={{ page: 'alert-groups', integration: alertReceiveChannel.id }}
|
||||
className={cx('integration__counter')}
|
||||
>
|
||||
<Badge
|
||||
text={
|
||||
alertReceiveChannelCounter?.alerts_count +
|
||||
'/' +
|
||||
alertReceiveChannelCounter?.alert_groups_count
|
||||
}
|
||||
className={cx('integration__countersBadge')}
|
||||
color={'blue'}
|
||||
/>
|
||||
</PluginLink>
|
||||
</span>
|
||||
</Tooltip>
|
||||
/>
|
||||
)}
|
||||
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="success"
|
||||
icon="link"
|
||||
count={channelFilterIds.length}
|
||||
text={channelFilterIds.length}
|
||||
tooltipTitle={`${channelFilterIds.length} Routes`}
|
||||
tooltipContent={undefined}
|
||||
/>
|
||||
|
||||
{alertReceiveChannel.maintenance_till && (
|
||||
<TooltipBadge
|
||||
borderType="primary"
|
||||
icon="pause"
|
||||
text={IntegrationHelper.getMaintenanceText(alertReceiveChannel.maintenance_till)}
|
||||
tooltipTitle={IntegrationHelper.getMaintenanceText(
|
||||
alertReceiveChannel.maintenance_till,
|
||||
alertReceiveChannel.maintenance_mode
|
||||
)}
|
||||
tooltipContent={undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
{this.renderHearbeat(alertReceiveChannel)}
|
||||
|
||||
<HorizontalGroup spacing="xs">
|
||||
<Text type="secondary">Type:</Text>
|
||||
<HorizontalGroup spacing="none">
|
||||
|
|
@ -276,6 +275,7 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
expandedView: <HowToConnectComponent id={id} />,
|
||||
},
|
||||
{
|
||||
isExpanded: false,
|
||||
isCollapsible: true,
|
||||
collapsedView: (
|
||||
<IntegrationBlock
|
||||
|
|
@ -287,10 +287,18 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
Templates
|
||||
</Text>
|
||||
</Tag>
|
||||
|
||||
<HorizontalGroup spacing={'xs'}>
|
||||
<Text type="secondary">Grouping:</Text>
|
||||
<Text type="link">
|
||||
{IntegrationHelper.getFilteredTemplate(templates['grouping_id_template'] || '', false)}
|
||||
{IntegrationHelper.truncateLine(templates['grouping_id_template'] || '')}
|
||||
</Text>
|
||||
</HorizontalGroup>
|
||||
|
||||
<HorizontalGroup spacing={'xs'}>
|
||||
<Text type="secondary">Autoresolve:</Text>
|
||||
<Text type="link">
|
||||
{IntegrationHelper.truncateLine(templates['resolve_condition_template'] || '')}
|
||||
</Text>
|
||||
</HorizontalGroup>
|
||||
|
||||
|
|
@ -384,6 +392,7 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
|
||||
return channelFilterIds.map((channelFilterId: ChannelFilter['id'], routeIndex: number) => ({
|
||||
isCollapsible: true,
|
||||
isExpanded: false,
|
||||
collapsedView: (
|
||||
<CollapsedIntegrationRouteDisplay
|
||||
alertReceiveChannelId={id}
|
||||
|
|
@ -403,6 +412,42 @@ class Integration2 extends React.Component<Integration2Props, Integration2State>
|
|||
}));
|
||||
};
|
||||
|
||||
renderHearbeat = (alertReceiveChannel: AlertReceiveChannel) => {
|
||||
const { heartbeatStore, alertReceiveChannelStore } = this.props.store;
|
||||
|
||||
const heartbeatId = alertReceiveChannelStore.alertReceiveChannelToHeartbeat[alertReceiveChannel.id];
|
||||
const heartbeat = heartbeatStore.items[heartbeatId];
|
||||
|
||||
const heartbeatStatus = Boolean(heartbeat?.status);
|
||||
|
||||
return (
|
||||
<TooltipBadge
|
||||
text={undefined}
|
||||
className={cx('heartbeat-badge')}
|
||||
borderType={alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal ? 'success' : 'danger'}
|
||||
customIcon={heartbeatStatus ? <HeartGreenIcon /> : <HeartRedIcon />}
|
||||
tooltipTitle={`Last heartbeat: ${alertReceiveChannel.heartbeat?.last_heartbeat_time_verbal || 'never'}`}
|
||||
tooltipContent={undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
getAlertReceiveChannelCounterTooltip = () => {
|
||||
const { id } = this.props.match.params;
|
||||
const { alertReceiveChannelStore } = this.props.store;
|
||||
const alertReceiveChannelCounter = alertReceiveChannelStore.counters[id];
|
||||
|
||||
return (
|
||||
alertReceiveChannelCounter?.alerts_count +
|
||||
' alert' +
|
||||
(alertReceiveChannelCounter?.alerts_count === 1 ? '' : 's') +
|
||||
' in ' +
|
||||
alertReceiveChannelCounter?.alert_groups_count +
|
||||
' alert group' +
|
||||
(alertReceiveChannelCounter?.alert_groups_count === 1 ? '' : 's')
|
||||
);
|
||||
};
|
||||
|
||||
handleSlackChannelChange = () => {};
|
||||
|
||||
onUpdateRoutesCallback = ({ routing }: { routing: string }) => {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||
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 IntegrationForm from 'containers/IntegrationForm/IntegrationForm';
|
||||
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
|
||||
|
|
@ -25,10 +26,11 @@ import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/W
|
|||
import { HeartGreenIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannel, MaintenanceMode } from 'models/alert_receive_channel';
|
||||
import { MaintenanceType } from 'models/maintenance/maintenance.types';
|
||||
import IntegrationHelper from 'pages/integration_2/Integration2.helper';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import LocationHelper from 'utils/LocationHelper';
|
||||
import { UserActions, isUserActionAllowed } from 'utils/authorization';
|
||||
import { UserActions } from 'utils/authorization';
|
||||
|
||||
import styles from './Integrations2.module.scss';
|
||||
|
||||
|
|
@ -109,7 +111,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
render() {
|
||||
const { store, query } = this.props;
|
||||
const { alertReceiveChannelId } = this.state;
|
||||
const { grafanaTeamStore, alertReceiveChannelStore, heartbeatStore, maintenanceStore } = store;
|
||||
const { grafanaTeamStore, alertReceiveChannelStore, heartbeatStore } = store;
|
||||
|
||||
const results = alertReceiveChannelStore.getSearchResult();
|
||||
|
||||
|
|
@ -137,7 +139,7 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
width: '10%',
|
||||
title: 'Maintenance',
|
||||
key: 'maintenance',
|
||||
render: (item: AlertReceiveChannel) => this.renderMaintenance(item, maintenanceStore, alertReceiveChannelStore),
|
||||
render: (item: AlertReceiveChannel) => this.renderMaintenance(item),
|
||||
},
|
||||
{
|
||||
width: '5%',
|
||||
|
|
@ -299,52 +301,23 @@ class Integrations extends React.Component<IntegrationsProps, IntegrationsState>
|
|||
);
|
||||
}
|
||||
|
||||
convertTimestampToTimeDifference(timestamp: string) {
|
||||
const date = new Date(Number(timestamp) * 1000);
|
||||
const timezoneOffset = new Date().getTimezoneOffset() * 60;
|
||||
const localTimestamp = date.getTime() + timezoneOffset;
|
||||
const currentTime = Date.now();
|
||||
const difference = localTimestamp - currentTime;
|
||||
|
||||
let timeLeft;
|
||||
if (difference < 3600000) {
|
||||
timeLeft = Math.floor(difference / 60000) + 'm left';
|
||||
} else {
|
||||
timeLeft = Math.floor(difference / 3600000) + 'h left';
|
||||
}
|
||||
|
||||
return timeLeft;
|
||||
}
|
||||
|
||||
renderMaintenance(item: AlertReceiveChannel, maintenanceStore, alertReceiveChannelStore) {
|
||||
renderMaintenance(item: AlertReceiveChannel) {
|
||||
const maintenanceMode = item.maintenance_mode;
|
||||
const maintenanceTill = item.maintenance_till;
|
||||
|
||||
if (maintenanceMode === MaintenanceMode.Debug || maintenanceMode === MaintenanceMode.Maintenance) {
|
||||
return (
|
||||
<Tooltip placement="top" content="Stop maintenance mode">
|
||||
<Badge
|
||||
text={
|
||||
<Button
|
||||
className={cx('maintenance-button')}
|
||||
disabled={!isUserActionAllowed(UserActions.MaintenanceWrite)}
|
||||
fill="text"
|
||||
icon="pause"
|
||||
onClick={() => this.handleStopMaintenance(item, maintenanceStore, alertReceiveChannelStore)}
|
||||
>
|
||||
{this.convertTimestampToTimeDifference(maintenanceTill)}
|
||||
</Button>
|
||||
}
|
||||
color={maintenanceMode === MaintenanceMode.Debug ? 'orange' : 'blue'}
|
||||
tooltip={
|
||||
maintenanceMode === MaintenanceMode.Debug
|
||||
? `Debug Maintenance: ${this.convertTimestampToTimeDifference(maintenanceTill)} left`
|
||||
: `Maintenance: ${this.convertTimestampToTimeDifference(maintenanceTill)} left`
|
||||
}
|
||||
<div className={cx('u-flex')}>
|
||||
<TooltipBadge
|
||||
borderType="primary"
|
||||
icon="pause"
|
||||
text={IntegrationHelper.getMaintenanceText(item.maintenance_till)}
|
||||
tooltipTitle={IntegrationHelper.getMaintenanceText(item.maintenance_till, item.maintenance_mode)}
|
||||
tooltipContent={undefined}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { observer } from 'mobx-react';
|
|||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import Avatar from 'components/Avatar/Avatar';
|
||||
import CounterBadge from 'components/CounterBadge/CounterBadge';
|
||||
import { MatchMediaTooltip } from 'components/MatchMediaTooltip/MatchMediaTooltip';
|
||||
import NewScheduleSelector from 'components/NewScheduleSelector/NewScheduleSelector';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
|
|
@ -16,6 +15,7 @@ import { SchedulesFiltersType } from 'components/SchedulesFilters/SchedulesFilte
|
|||
import Table from 'components/Table/Table';
|
||||
import Text from 'components/Text/Text';
|
||||
import TimelineMarks from 'components/TimelineMarks/TimelineMarks';
|
||||
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
|
||||
import UserTimezoneSelect from 'components/UserTimezoneSelect/UserTimezoneSelect';
|
||||
import WithConfirm from 'components/WithConfirm/WithConfirm';
|
||||
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
|
||||
|
|
@ -306,10 +306,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
return (
|
||||
<HorizontalGroup>
|
||||
{item.number_of_escalation_chains > 0 && (
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="link"
|
||||
icon="link"
|
||||
count={item.number_of_escalation_chains}
|
||||
text={item.number_of_escalation_chains}
|
||||
tooltipTitle="Used in escalations"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="sm">
|
||||
|
|
@ -335,10 +335,10 @@ class SchedulesPage extends React.Component<SchedulesPageProps, SchedulesPageSta
|
|||
)}
|
||||
|
||||
{item.warnings?.length > 0 && (
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="warning"
|
||||
icon="exclamation-triangle"
|
||||
count={item.warnings.length}
|
||||
text={item.warnings.length}
|
||||
tooltipTitle="Warnings"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="none">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import LegacyNavHeading from 'navbar/LegacyNavHeading';
|
|||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import Avatar from 'components/Avatar/Avatar';
|
||||
import CounterBadge from 'components/CounterBadge/CounterBadge';
|
||||
import GTable from 'components/GTable/GTable';
|
||||
import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
|
||||
import {
|
||||
|
|
@ -17,6 +16,7 @@ import {
|
|||
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
|
||||
import UsersFilters from 'components/UsersFilters/UsersFilters';
|
||||
import UserSettings from 'containers/UserSettings/UserSettings';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
|
|
@ -361,10 +361,10 @@ class Users extends React.Component<UsersProps, UsersState> {
|
|||
|
||||
return (
|
||||
<HorizontalGroup>
|
||||
<CounterBadge
|
||||
<TooltipBadge
|
||||
borderType="warning"
|
||||
icon="exclamation-triangle"
|
||||
count={texts.length}
|
||||
text={texts.length}
|
||||
tooltipTitle="Warnings"
|
||||
tooltipContent={
|
||||
<VerticalGroup spacing="none">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
--tag-secondary: #464c54;
|
||||
--tag-background-primary: rgba(56, 113, 220, 0.2);
|
||||
--tag-border-primary: rgba(56, 113, 220, 0.2);
|
||||
--tag-background-danger: rgba(242, 73, 92, 0.15);
|
||||
--tag-text-primary: rgba(110, 159, 255, 1);
|
||||
--tag-border-danger: rgb(151, 11, 27);
|
||||
--tag-text-danger: rgb(247, 144, 156);
|
||||
|
|
@ -30,6 +31,7 @@
|
|||
--tag-background-warning: rgba(245, 183, 61, 0.18);
|
||||
--tag-text-warning: rgb(255, 190, 124);
|
||||
--tag-border-success: rgb(49, 100, 43);
|
||||
--tag-border-link: rgba(56, 113, 220, 0.2);
|
||||
--tag-background-success: rgba(27, 133, 94, 0.15);
|
||||
--tag-text-success: rgb(165, 214, 159);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue