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:
Rares Mardare 2023-05-08 08:42:08 +03:00 committed by GitHub
parent b62687295d
commit fcf8a9bacb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 301 additions and 229 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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">

View file

@ -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;

View 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;

View file

@ -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;
}

View file

@ -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;

View file

@ -106,7 +106,7 @@
}
.blue {
background: var(--timeline-icon-background-resolution-note);
background: var(--tag-border-primary);
}
.timeline-title {

View file

@ -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;

View file

@ -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;
}

View file

@ -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 }) => {

View file

@ -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;
}

View file

@ -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">

View file

@ -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">

View file

@ -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);
}