From bc8a6cb18cfc22e3e4064a4b53ad8f3b9c5938f1 Mon Sep 17 00:00:00 2001 From: Rares Mardare Date: Thu, 1 Feb 2024 13:21:52 +0200 Subject: [PATCH] Integration incoming tab (#3752) # What this PR does ## Which issue(s) this PR fixes ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- grafana-plugin/src/assets/style/vars.css | 2 + ...IntegrationCollapsibleTreeView.module.scss | 51 +++-- .../IntegrationCollapsibleTreeView.tsx | 46 ++++- .../IntegrationInputField.tsx | 2 +- .../components/Policy/EscalationPolicy.tsx | 8 +- .../components/Policy/NotificationPolicy.tsx | 2 +- grafana-plugin/src/components/Text/Text.tsx | 8 +- .../src/components/Timeline/TimelineItem.tsx | 11 +- .../src/containers/AlertRules/parts/index.tsx | 2 +- .../EscalationChainSteps.tsx | 26 ++- ...xpandedIntegrationRouteDisplay.module.scss | 27 ++- .../ExpandedIntegrationRouteDisplay.tsx | 179 ++++++++++++------ .../PersonalNotificationSettings.tsx | 5 +- .../pages/integration/Integration.module.scss | 6 - .../src/pages/integration/Integration.tsx | 21 +- .../src/pages/integrations/Integrations.tsx | 2 +- 16 files changed, 276 insertions(+), 122 deletions(-) diff --git a/grafana-plugin/src/assets/style/vars.css b/grafana-plugin/src/assets/style/vars.css index 17585703..fa267cd3 100644 --- a/grafana-plugin/src/assets/style/vars.css +++ b/grafana-plugin/src/assets/style/vars.css @@ -46,6 +46,7 @@ --background-canvas: #f4f5f5; --background-primary: #fff; --background-secondary: #f4f5f5; + --border-medium-color: rgba(36, 41, 46, 0.3); --border-medium: 1px solid rgba(36, 41, 46, 0.3); --border-strong: 1px solid rgba(36, 41, 46, 0.4); --border-weak: 1px solid rgba(36, 41, 46, 0.12); @@ -94,6 +95,7 @@ --background-canvas: #111217; --background-primary: #181b1f; --background-secondary: #22252b; + --border-medium-color: rgba(204, 204, 220, 0.15); --border-medium: 1px solid rgba(204, 204, 220, 0.15); --border-strong: 1px solid rgba(204, 204, 220, 0.25); --border-weak: 1px solid rgba(204, 204, 220, 0.07); diff --git a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss index 96b652bd..03c4fac7 100644 --- a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss +++ b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.module.scss @@ -1,19 +1,10 @@ .integrationTree__container { margin-left: 32px; position: relative; - - &:before { - content: ''; - position: absolute; - height: calc(100% - 20px); - border: var(--border-weak); - margin-top: 20px; - margin-left: -20px; - } } .integrationTree__element { - margin-left: 6px; + margin-left: 16px; visibility: hidden; overflow-y: hidden; height: 0; @@ -21,26 +12,43 @@ &--visible { visibility: visible; height: auto; + overflow: unset; } } .integrationTree__group { position: relative; - margin-bottom: 12px; + padding-bottom: 12px; + + &:not(:last-child)::before { + content: ''; + position: absolute; + height: 100%; + border: var(--border-weak); + margin-top: 4px; + margin-left: -19px; + top: 20px; + } &--hidden { display: none; } } +.integrationTree__container--timeline-view .integrationTree__group::before { + margin-left: -18px; + border: 0 !important; + width: 4px; + background-color: var(--border-medium-color); +} + .integrationTree__icon { position: absolute; top: 0px; - transform: translateY(50%); left: -30px; color: var(--always-gray); - width: 25px; - height: 32px; + width: 28px; + height: 28px; text-align: center; background-color: var(--primary-background) !important; border: 1px solid var(--primary-background); @@ -50,9 +58,24 @@ display: flex; align-items: center; justify-content: center; + margin-top: 4px; path { // this will overwrite all icons below to be gray fill: var(--always-gray); } } + +.number-icon { + width: 28px; + height: 28px; + border-radius: 50%; + text-align: center; + line-height: 28px; + font-size: 14px; + font-weight: 400; + color: var(--primary-text-color); + background-color: var(--border-medium-color); + flex-shrink: 0; + margin: 0; +} diff --git a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx index 2b9fdcbe..be7c5ae1 100644 --- a/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx +++ b/grafana-plugin/src/components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView.tsx @@ -5,6 +5,8 @@ import cn from 'classnames/bind'; import { isArray, isUndefined } from 'lodash-es'; import { observer } from 'mobx-react'; +import Text from 'components/Text/Text'; + import styles from './IntegrationCollapsibleTreeView.module.scss'; const cx = cn.bind(styles); @@ -13,19 +15,24 @@ export interface IntegrationCollapsibleItem { isHidden?: boolean; customIcon?: IconName; canHoverIcon: boolean; + isTextIcon?: boolean; collapsedView: (toggle?: () => void) => React.ReactNode; // needs toggle param for toggling on click expandedView: () => React.ReactNode; // for consistency, this is also a function isCollapsible: boolean; + iconText?: string; isExpanded?: boolean; + startingElemPosition?: string; onStateChange?(isChecked: boolean): void; } interface IntegrationCollapsibleTreeViewProps { + startingElemPosition?: string; + isRouteView?: boolean; configElements: Array; } const IntegrationCollapsibleTreeView: React.FC = observer((props) => { - const { configElements } = props; + const { configElements, isRouteView } = props; const [expandedList, setExpandedList] = useState(getStartingExpandedState()); @@ -34,7 +41,7 @@ const IntegrationCollapsibleTreeView: React.FC +
{configElements .filter((config) => config) // filter out falsy values .map((item: IntegrationCollapsibleItem | IntegrationCollapsibleItem[], idx) => { @@ -53,6 +60,7 @@ const IntegrationCollapsibleTreeView: React.FC expandOrCollapseAtPos(expandedList[idx] as boolean, idx)} isExpanded={expandedList[idx] as boolean} /> @@ -103,19 +111,21 @@ const IntegrationCollapsibleTreeView: React.FC void; -}> = ({ item, isExpanded, onClick }) => { - const iconOnClickFn = !item.isCollapsible ? undefined : onClick; +}> = ({ item, elementPosition, isExpanded, onClick }) => { + const handleIconClick = !item.isCollapsible ? undefined : onClick; return (
-
- {item.canHoverIcon ? ( - - ) : ( - - )} +
+ {renderIcon()}
{item.expandedView?.()} @@ -126,6 +136,22 @@ const IntegrationCollapsibleTreeItem: React.FC<{
); + function renderIcon() { + if (item.isTextIcon && elementPosition) { + return ( + + {elementPosition} + + ); + } + + if (item.canHoverIcon) { + return ; + } + + return ; + } + function getIconName(): IconName { if (item.customIcon) { return item.customIcon; diff --git a/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx b/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx index 8047ad54..dfe5e85a 100644 --- a/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx +++ b/grafana-plugin/src/components/IntegrationInputField/IntegrationInputField.tsx @@ -20,7 +20,7 @@ interface IntegrationInputFieldProps { const cx = cn.bind(styles); const IntegrationInputField: React.FC = ({ - isMasked = true, + isMasked = false, value, showEye = true, showCopy = true, diff --git a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx index 2eeab636..3b5bd7e2 100644 --- a/grafana-plugin/src/components/Policy/EscalationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/EscalationPolicy.tsx @@ -48,7 +48,8 @@ export interface EscalationPolicyProps extends ElementSortableProps { onDelete: (data: EscalationPolicyType) => void; escalationChoices: any[]; number: number; - backgroundColor: string; + backgroundClassName?: string; + backgroundHexNumber?: string; isSlackInstalled: boolean; teamStore: GrafanaTeamStore; outgoingWebhookStore: OutgoingWebhookStore; @@ -57,7 +58,7 @@ export interface EscalationPolicyProps extends ElementSortableProps { export class EscalationPolicy extends React.Component { render() { - const { data, escalationChoices, number, backgroundColor, isDisabled } = this.props; + const { data, escalationChoices, number, isDisabled, backgroundClassName, backgroundHexNumber } = this.props; const { id, step, is_final } = data; const escalationOption = escalationChoices.find( @@ -70,7 +71,8 @@ export class EscalationPolicy extends React.Component {!isDisabled && ( diff --git a/grafana-plugin/src/components/Policy/NotificationPolicy.tsx b/grafana-plugin/src/components/Policy/NotificationPolicy.tsx index 3d8d909c..b5baf4b3 100644 --- a/grafana-plugin/src/components/Policy/NotificationPolicy.tsx +++ b/grafana-plugin/src/components/Policy/NotificationPolicy.tsx @@ -52,7 +52,7 @@ export class NotificationPolicy extends React.Component +
{!isDisabled && ( diff --git a/grafana-plugin/src/components/Text/Text.tsx b/grafana-plugin/src/components/Text/Text.tsx index 49a052d0..a30894c3 100644 --- a/grafana-plugin/src/components/Text/Text.tsx +++ b/grafana-plugin/src/components/Text/Text.tsx @@ -26,6 +26,7 @@ interface TextProps extends HTMLAttributes { editModalTitle?: string; maxWidth?: string; clickable?: boolean; + customTag?: 'h6' | 'span'; } interface TextInterface extends React.FC { @@ -56,6 +57,7 @@ const Text: TextInterface = (props) => { style, maxWidth, clickable, + customTag, ...rest } = props; @@ -81,8 +83,10 @@ const Text: TextInterface = (props) => { setValue(e.target.value); }, []); + const CustomTag = (customTag || `span`) as unknown as React.ComponentType; + return ( - { )} - + ); }; diff --git a/grafana-plugin/src/components/Timeline/TimelineItem.tsx b/grafana-plugin/src/components/Timeline/TimelineItem.tsx index 077d1943..a972311a 100644 --- a/grafana-plugin/src/components/Timeline/TimelineItem.tsx +++ b/grafana-plugin/src/components/Timeline/TimelineItem.tsx @@ -10,7 +10,8 @@ export interface TimelineItemProps { className?: string; contentClassName?: string; isDisabled?: boolean; - backgroundColor?: string; + backgroundClassName?: string; + backgroundHexNumber?: string; textColor?: string; number?: number; badge?: number; @@ -22,14 +23,18 @@ const TimelineItem: React.FC = ({ contentClassName, children, isDisabled, - backgroundColor = '#3274D9', + backgroundClassName, + backgroundHexNumber, textColor = '#ffffff', number, }) => { return (
  • {!isDisabled && ( -
    +
    {number}
    )} diff --git a/grafana-plugin/src/containers/AlertRules/parts/index.tsx b/grafana-plugin/src/containers/AlertRules/parts/index.tsx index 049219b0..2a4e1654 100644 --- a/grafana-plugin/src/containers/AlertRules/parts/index.tsx +++ b/grafana-plugin/src/containers/AlertRules/parts/index.tsx @@ -37,7 +37,7 @@ export const ChatOpsConnectors = (props: ChatOpsConnectorsProps) => { } return ( - + {isSlackInstalled && } {isTelegramInstalled && } diff --git a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx index 134a91c9..8850b777 100644 --- a/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx +++ b/grafana-plugin/src/containers/EscalationChainSteps/EscalationChainSteps.tsx @@ -1,11 +1,13 @@ import React, { ReactElement, useCallback, useEffect } from 'react'; -import { LoadingPlaceholder, Select } from '@grafana/ui'; +import { css } from '@emotion/css'; +import { GrafanaTheme2 } from '@grafana/data'; +import { LoadingPlaceholder, Select, useStyles2 } from '@grafana/ui'; import cn from 'classnames/bind'; import { get } from 'lodash-es'; import { observer } from 'mobx-react'; -import EscalationPolicy from 'components/Policy/EscalationPolicy'; +import EscalationPolicy, { EscalationPolicyProps } from 'components/Policy/EscalationPolicy'; import SortableList from 'components/SortableList/SortableList'; import Timeline from 'components/Timeline/Timeline'; import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip'; @@ -26,10 +28,19 @@ interface EscalationChainStepsProps { offset?: number; } +const getStyles = (theme: GrafanaTheme2) => { + return { + background: css` + background-color: ${theme.colors.success.main}; + `, + }; +}; + const EscalationChainSteps = observer((props: EscalationChainStepsProps) => { const { id, offset = 0, isDisabled = false, addonBefore } = props; const store = useStore(); + const styles = useStyles2(getStyles); const { escalationPolicyStore } = store; @@ -80,13 +91,19 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => { return null; } + const extraProps: Partial = {}; + if (isDisabled) { + extraProps.backgroundClassName = styles.background; + } else { + extraProps.backgroundHexNumber = STEP_COLORS[index] || COLOR_RED; + } + return ( { scheduleStore={store.scheduleStore} outgoingWebhookStore={store.outgoingWebhookStore} isDisabled={isDisabled} + {...extraProps} /> ); }) @@ -106,7 +124,7 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => { {!isDisabled && ( diff --git a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss index a871fdc2..4e78bbc2 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.module.scss @@ -1,12 +1,15 @@ .input { - &--short { - width: 500px; - } - &--long { - width: 700px; + border: var(--border-weak); + + &--align { + width: 728px; } } +.routing-alert { + width: 765px; +} + .integrations-actionsList { display: flex; flex-direction: column; @@ -31,4 +34,16 @@ &:hover { background: var(--cards-background); } -} \ No newline at end of file +} + +.routing-template-container { + margin-bottom: 8px; +} + +.adjust-element-padding { + padding-top: 6px; +} + +.default-route-view { + min-height: 40px; +} diff --git a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx index 12e83668..864ca1e5 100644 --- a/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx +++ b/grafana-plugin/src/containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay.tsx @@ -4,19 +4,22 @@ import { SelectableValue } from '@grafana/data'; import { Button, HorizontalGroup, - InlineLabel, VerticalGroup, Icon, Tooltip, ConfirmModal, LoadingPlaceholder, Select, + Alert, } 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 IntegrationCollapsibleTreeView, { + IntegrationCollapsibleItem, +} from 'components/IntegrationCollapsibleTreeView/IntegrationCollapsibleTreeView'; import IntegrationBlock from 'components/Integrations/IntegrationBlock'; import MonacoEditor from 'components/MonacoEditor/MonacoEditor'; import { MONACO_READONLY_CONFIG } from 'components/MonacoEditor/MonacoEditor.config'; @@ -117,56 +120,32 @@ const ExpandedIntegrationRouteDisplay: React.FC - - - - - - setState({ routeIdForDeletion: channelFilterId })} - openRouteTemplateEditor={() => handleEditRoutingTemplate(channelFilter, channelFilterId)} - /> - - - } - content={ - + const getTreeViewElements = () => { + const configs: IntegrationCollapsibleItem[] = [ + { + isHidden: false, + isCollapsible: false, + isExpanded: false, + isTextIcon: true, + collapsedView: null, + canHoverIcon: false, + expandedView: () => ( +
    {isDefault ? ( - - All unmatched alerts are directed to this route, grouped using the Grouping Template, sent to - messengers, and trigger the escalation chain - - ) : ( - - - If the Routing Template is True, group alerts with the Grouping Template, send them to messengers, - and trigger the escalation chain. +
    + + All unmatched alerts are directed to this route, grouped using the Grouping Template, sent to + messengers, and trigger the escalation chain +
    + ) : ( + + + Use routing template + + - - Routing Template - -
    +
    handleEditRoutingTemplate(channelFilter, channelFilterId)} /> + +
    + + If the Routing template evaluates to True, the alert will be grouped with the Grouping + template and proceed to the following steps + + ) as any + } + /> +
    )} +
    + ), + }, + IntegrationHelper.hasChatopsInstalled(store) && { + isHidden: false, + isCollapsible: false, + isTextIcon: true, + collapsedView: null, + canHoverIcon: false, + expandedView: () => ( +
    + + + Publish to ChatOps + + + +
    + ), + }, + { + isHidden: false, + isCollapsible: false, + isExpanded: false, + isTextIcon: true, + collapsedView: null, + canHoverIcon: false, + expandedView: () => ( +
    + + + Trigger escalation chain + - {IntegrationHelper.hasChatopsInstalled(store) && ( - - Publish to ChatOps - - - )} - -
    - - Escalation chain -