From fdbccdac9979e7de44832202188ed7d146fccf80 Mon Sep 17 00:00:00 2001 From: Dominik Broj Date: Mon, 22 Jan 2024 16:24:54 +0100 Subject: [PATCH] add tabs for snow integration (#3726) # What this PR does add incoming / outgoing tabs when integration is service now ## Which issue(s) this PR fixes https://github.com/grafana/oncall-private/issues/2460 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --- grafana-plugin/src/components/Tabs/Tabs.tsx | 47 +++++++++++++ .../pages/integration/Integration.helper.ts | 2 + .../src/pages/integration/Integration.tsx | 66 ++++++++++++------- 3 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 grafana-plugin/src/components/Tabs/Tabs.tsx diff --git a/grafana-plugin/src/components/Tabs/Tabs.tsx b/grafana-plugin/src/components/Tabs/Tabs.tsx new file mode 100644 index 00000000..3c03d33c --- /dev/null +++ b/grafana-plugin/src/components/Tabs/Tabs.tsx @@ -0,0 +1,47 @@ +import React, { FC, useState } from 'react'; + +import { css } from '@emotion/css'; +import { Tab, TabsBar, TabContent, useStyles2 } from '@grafana/ui'; +import cn from 'classnames'; + +interface TabConfig { + label: string; + content: React.ReactNode; +} + +interface TabsProps { + tabs: TabConfig[]; + defaultActiveLabel?: string; + tabContentClassName?: string; +} + +const Tabs: FC = ({ tabs, defaultActiveLabel, tabContentClassName }) => { + const styles = useStyles2(getStyles); + const [activeTabLabel, setActiveTabLabel] = useState(defaultActiveLabel || tabs[0].label); + + return ( + <> + + {tabs.map(({ label }) => ( + setActiveTabLabel(label)} + active={activeTabLabel === label} + /> + ))} + + + {tabs.find(({ label }) => label === activeTabLabel)?.content} + + + ); +}; + +export const getStyles = () => ({ + content: css({ + marginTop: '16px', + }), +}); + +export default Tabs; diff --git a/grafana-plugin/src/pages/integration/Integration.helper.ts b/grafana-plugin/src/pages/integration/Integration.helper.ts index 0b44f8a6..2f2ec189 100644 --- a/grafana-plugin/src/pages/integration/Integration.helper.ts +++ b/grafana-plugin/src/pages/integration/Integration.helper.ts @@ -125,3 +125,5 @@ const IntegrationHelper = { }; export default IntegrationHelper; + +export const getIsBidirectionalIntegration = ({ integration }: AlertReceiveChannel) => integration === 'servicenow'; diff --git a/grafana-plugin/src/pages/integration/Integration.tsx b/grafana-plugin/src/pages/integration/Integration.tsx index abf37e2b..e0783449 100644 --- a/grafana-plugin/src/pages/integration/Integration.tsx +++ b/grafana-plugin/src/pages/integration/Integration.tsx @@ -34,6 +34,7 @@ import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHan import { initErrorDataState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers'; import PluginLink from 'components/PluginLink/PluginLink'; import RenderConditionally from 'components/RenderConditionally/RenderConditionally'; +import Tabs from 'components/Tabs/Tabs'; import Tag from 'components/Tag/Tag'; import Text from 'components/Text/Text'; import TooltipBadge from 'components/TooltipBadge/TooltipBadge'; @@ -57,7 +58,7 @@ import { } from 'models/alert_receive_channel/alert_receive_channel.types'; import { AlertTemplatesDTO } from 'models/alert_templates/alert_templates'; import { ChannelFilter } from 'models/channel_filter/channel_filter.types'; -import IntegrationHelper from 'pages/integration/Integration.helper'; +import IntegrationHelper, { getIsBidirectionalIntegration } from 'pages/integration/Integration.helper'; import styles from 'pages/integration/Integration.module.scss'; import { AppFeature } from 'state/features'; import { PageProps, SelectOption, WithStoreProps } from 'state/types'; @@ -155,6 +156,36 @@ class Integration extends React.Component { const isLegacyIntegration = integration && (integration?.value as string).toLowerCase().startsWith('legacy_'); const contactPoints = alertReceiveChannelStore.connectedContactPoints?.[alertReceiveChannel.id]; + const incomingPart = ( + <> + + {isEditTemplateModalOpen && ( + { + this.setState({ + isEditTemplateModalOpen: undefined, + }); + if (selectedTemplate?.name !== 'route_template') { + this.setState({ isTemplateSettingsOpen: true }); + } + LocationHelper.update({ template: undefined, routeId: undefined }, 'partial'); + }} + channelFilterId={channelFilterIdForEdit} + onUpdateTemplates={this.onUpdateTemplatesCallback} + onUpdateRoute={this.onUpdateRoutesCallback} + template={selectedTemplate} + templateBody={ + selectedTemplate?.name === 'route_template' + ? this.getRoutingTemplate(channelFilterIdForEdit) + : templates[selectedTemplate?.name] + } + templates={templates} + /> + )} + + ); + return ( {() => ( @@ -223,32 +254,17 @@ class Integration extends React.Component { )} - - - {isEditTemplateModalOpen && ( - { - this.setState({ - isEditTemplateModalOpen: undefined, - }); - if (selectedTemplate?.name !== 'route_template') { - this.setState({ isTemplateSettingsOpen: true }); - } - LocationHelper.update({ template: undefined, routeId: undefined }, 'partial'); - }} - channelFilterId={channelFilterIdForEdit} - onUpdateTemplates={this.onUpdateTemplatesCallback} - onUpdateRoute={this.onUpdateRoutesCallback} - template={selectedTemplate} - templateBody={ - selectedTemplate?.name === 'route_template' - ? this.getRoutingTemplate(channelFilterIdForEdit) - : templates[selectedTemplate?.name] - } - templates={templates} + {getIsBidirectionalIntegration(alertReceiveChannel) ? ( + outgoing tab content }, + ]} /> + ) : ( + <>{incomingPart} )} + {isEditRegexpRouteTemplateModalOpen && (