Refactored Integration Form to use react-hook-form + ServiceNow changes (#3979)
# What this PR does - Migrates old Integration form to use `react-hook-form` instead - Adds new ServiceNow fields (no backend yet) ## 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)
This commit is contained in:
parent
84b6b7cd29
commit
20973705e9
15 changed files with 982 additions and 509 deletions
|
|
@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Remove explicit uWSGI and Django request size limits by @vadimkerr ([#3878](https://github.com/grafana/oncall/pull/3878))
|
||||
- Migrate webhooks integration_filter to use a m2m field instead ([#3946](https://github.com/grafana/oncall/pull/3946))
|
||||
- Updated Faro package version ([#3970](https://github.com/grafana/oncall/pull/3970))
|
||||
- Integration form migration to react-hook-form ([#3979](https://github.com/grafana/oncall/pull/3979))
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@
|
|||
"react-dom": "18.2.0",
|
||||
"react-draggable": "^4.4.5",
|
||||
"react-emoji-render": "^1.2.4",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"react-modal": "^3.15.1",
|
||||
"react-responsive": "^8.1.0",
|
||||
"react-router-dom": "5.3.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
import React from 'react';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { Collapse } from 'components/Collapse/Collapse';
|
||||
import { Text } from 'components/Text/Text';
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
|
||||
export const HowTheIntegrationWorks: React.FC<{
|
||||
selectedOption: ApiSchemas['AlertReceiveChannelIntegrationOptions'];
|
||||
}> = ({ selectedOption }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
if (!selectedOption) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
headerWithBackground
|
||||
className={styles.collapse}
|
||||
isOpen={false}
|
||||
label={<Text type="link">How the integration works</Text>}
|
||||
contentClassName={styles.collapsableContent}
|
||||
>
|
||||
<Text type="secondary">
|
||||
The integration will generate the following:
|
||||
<ul className={styles.integrationInfoList}>
|
||||
<li className={styles.integrationInfoItem}>Unique URL endpoint for receiving alerts </li>
|
||||
<li className={styles.integrationInfoItem}>
|
||||
Templates to interpret alerts, tailored for {selectedOption.display_name}{' '}
|
||||
</li>
|
||||
<li className={styles.integrationInfoItem}>{selectedOption.display_name} contact point </li>
|
||||
<li className={styles.integrationInfoItem}>{selectedOption.display_name} notification</li>
|
||||
</ul>
|
||||
What you'll need to do next:
|
||||
<ul className={styles.integrationInfoList}>
|
||||
<li className={styles.integrationInfoItem}>
|
||||
Finish connecting Monitoring system using Unique URL that will be provided on the next step{' '}
|
||||
</li>
|
||||
<li className={styles.integrationInfoItem}>
|
||||
Set up routes that are based on alert content, such as severity, region, and service{' '}
|
||||
</li>
|
||||
<li className={styles.integrationInfoItem}>Connect escalation chains to the routes</li>
|
||||
<li className={styles.integrationInfoItem}>
|
||||
Review templates and personalize according to your requirements
|
||||
</li>
|
||||
</ul>
|
||||
</Text>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
collapse: css({
|
||||
width: '100%',
|
||||
marginBottom: '24px',
|
||||
' svg': {
|
||||
color: theme.colors.primary.text,
|
||||
},
|
||||
}),
|
||||
integrationInfoList: css({
|
||||
listStylePosition: 'inside',
|
||||
margin: '16px 0',
|
||||
}),
|
||||
integrationInfoItem: css({
|
||||
marginLeft: '16px',
|
||||
}),
|
||||
collapsableContent: css({
|
||||
width: '100%',
|
||||
backgroundColor: theme.colors.background.secondary,
|
||||
fontSize: 'small',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
|
||||
export function prepareForEdit(item: ApiSchemas['AlertReceiveChannel']) {
|
||||
export function prepareForEdit(item: ApiSchemas['AlertReceiveChannel']): Partial<ApiSchemas['AlertReceiveChannel']> {
|
||||
return {
|
||||
verbal_name: item.verbal_name,
|
||||
description_short: item.description_short,
|
||||
team: item.team,
|
||||
labels: item.labels,
|
||||
integration: item.integration,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,80 +1,7 @@
|
|||
.content {
|
||||
margin: 4px 4px 50px 4px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
overflow: auto;
|
||||
scroll-snap-type: y mandatory;
|
||||
.form {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cards_centered {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 48%;
|
||||
height: 88px;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: normal;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card_featured {
|
||||
width: 100%;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 10px 0 10px 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-integration {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.collapse svg {
|
||||
color: var(--primary-text-link) !important;
|
||||
}
|
||||
|
||||
.collapsable-content {
|
||||
width: 100%;
|
||||
background-color: var(--background-secondary);
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.integration-info-list {
|
||||
list-style-position: inside;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.integration-info-item {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.extra-fields {
|
||||
padding: 12px;
|
||||
margin-bottom: 24px;
|
||||
|
|
@ -97,6 +24,40 @@
|
|||
margin-bottom: -15px;
|
||||
}
|
||||
|
||||
.labels {
|
||||
margin-bottom: 20px;
|
||||
.textarea:hover {
|
||||
// TODO: change this to fetch from emotion instead
|
||||
}
|
||||
|
||||
.collapse {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.collapse svg {
|
||||
color: var(--primary-text-link) !important;
|
||||
}
|
||||
|
||||
.integration-info-list {
|
||||
list-style-position: inside;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.integration-info-item {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.servicenow-heading {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.webhook-test {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.webhook-switch {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export const getIntegrationFormStyles = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
form: css`
|
||||
width: 100%;
|
||||
`,
|
||||
|
||||
extraFields: css`
|
||||
padding: 12px;
|
||||
margin-bottom: 24px;
|
||||
border: var(--border-weak);
|
||||
border-radius: var(--border-radius);
|
||||
`,
|
||||
|
||||
extraFieldsRadio: css`
|
||||
margin-bottom: 12px;
|
||||
`,
|
||||
|
||||
extraFieldsIcon: css`
|
||||
margin-top: -4px;
|
||||
`,
|
||||
|
||||
selectorsContainer: css`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: -15px;
|
||||
`,
|
||||
|
||||
collapse: css`
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
svg {
|
||||
color: ${theme.colors.primary.text} !important;
|
||||
}
|
||||
`,
|
||||
|
||||
serviceNowHeading: css`
|
||||
margin-bottom: 16px;
|
||||
`,
|
||||
|
||||
webhookTest: css`
|
||||
margin-bottom: 16px;
|
||||
`,
|
||||
|
||||
webhookSwitch: css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
`,
|
||||
|
||||
labels: css`
|
||||
margin-bottom: 20px;
|
||||
`,
|
||||
|
||||
// TODO: figure out grafana bug on border
|
||||
textarea: css``,
|
||||
};
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,74 @@
|
|||
.content {
|
||||
margin: 4px 4px 50px 4px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24px;
|
||||
overflow: auto;
|
||||
scroll-snap-type: y mandatory;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cards_centered {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 48%;
|
||||
height: 88px;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: normal;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card_featured {
|
||||
width: 100%;
|
||||
height: 106px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 10px 0 10px 0;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-integration {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.extra-fields {
|
||||
padding: 12px;
|
||||
margin-bottom: 24px;
|
||||
border: var(--border-weak);
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&__radio {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-top: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.selectors-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: -15px;
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
import React, { useState, ChangeEvent } from 'react';
|
||||
|
||||
import { Drawer, VerticalGroup, HorizontalGroup, Input, Tag, EmptySearchResult } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import { Block } from 'components/GBlock/Block';
|
||||
import { IntegrationLogo } from 'components/IntegrationLogo/IntegrationLogo';
|
||||
import { Text } from 'components/Text/Text';
|
||||
import { ApiSchemas } from 'network/oncall-api/api.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import { IntegrationForm } from './IntegrationForm';
|
||||
import styles from './IntegrationFormContainer.module.scss';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface IntegrationFormContainerProps {
|
||||
id: ApiSchemas['AlertReceiveChannel']['id'] | 'new';
|
||||
isTableView?: boolean;
|
||||
onHide: () => void;
|
||||
onSubmit: () => Promise<void>;
|
||||
navigateToAlertGroupLabels: (id: ApiSchemas['AlertReceiveChannel']['id']) => void;
|
||||
}
|
||||
|
||||
export const IntegrationFormContainer = observer((props: IntegrationFormContainerProps) => {
|
||||
const store = useStore();
|
||||
|
||||
const { id, onHide, onSubmit, isTableView = true, navigateToAlertGroupLabels } = props;
|
||||
const { alertReceiveChannelStore } = store;
|
||||
|
||||
const [filterValue, setFilterValue] = useState('');
|
||||
const [showNewIntegrationForm, setShowNewIntegrationForm] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState<ApiSchemas['AlertReceiveChannelIntegrationOptions']>(undefined);
|
||||
const [showIntegrarionsListDrawer, setShowIntegrarionsListDrawer] = useState(id === 'new');
|
||||
|
||||
const { alertReceiveChannelOptions } = alertReceiveChannelStore;
|
||||
|
||||
const options = alertReceiveChannelOptions
|
||||
? alertReceiveChannelOptions.filter((option: ApiSchemas['AlertReceiveChannelIntegrationOptions']) => {
|
||||
if (option.value === 'grafana_alerting' && !window.grafanaBootData.settings.unifiedAlertingEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// don't allow creating direct paging integrations
|
||||
if (option.value === 'direct_paging') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
option.display_name.toLowerCase().includes(filterValue.toLowerCase()) &&
|
||||
!option.value.toLowerCase().startsWith('legacy_')
|
||||
);
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{showIntegrarionsListDrawer && (
|
||||
<Drawer scrollableContent title="New Integration" onClose={onHide} closeOnMaskClick={false} width="640px">
|
||||
<div className={cx('content')}>
|
||||
<VerticalGroup>
|
||||
<Text type="secondary">
|
||||
Integration receives alerts on an unique API URL, interprets them using set of templates tailored for
|
||||
monitoring system and starts escalations.
|
||||
</Text>
|
||||
|
||||
<div className={cx('search-integration')}>
|
||||
<Input
|
||||
autoFocus
|
||||
value={filterValue}
|
||||
placeholder="Search integrations ..."
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => setFilterValue(e.currentTarget.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<IntegrationBlocks options={options} onBlockClick={onBlockClick} />
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
</Drawer>
|
||||
)}
|
||||
{(showNewIntegrationForm || !showIntegrarionsListDrawer) && (
|
||||
<Drawer scrollableContent title={getTitle()} onClose={onHide} closeOnMaskClick={false} width="640px">
|
||||
<div className={cx('content')}>
|
||||
<VerticalGroup>
|
||||
<IntegrationForm
|
||||
id={id}
|
||||
onBackClick={onBackClick}
|
||||
navigateToAlertGroupLabels={navigateToAlertGroupLabels}
|
||||
selectedIntegration={selectedOption}
|
||||
onSubmit={onSubmit}
|
||||
onHide={onHide}
|
||||
/>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
</Drawer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function onBackClick(): void {
|
||||
setShowNewIntegrationForm(false);
|
||||
setShowIntegrarionsListDrawer(true);
|
||||
}
|
||||
|
||||
function onBlockClick(option: ApiSchemas['AlertReceiveChannelIntegrationOptions']): void {
|
||||
setSelectedOption(option);
|
||||
setShowNewIntegrationForm(true);
|
||||
setShowIntegrarionsListDrawer(false);
|
||||
}
|
||||
|
||||
function getTitle(): string {
|
||||
if (!isTableView) {
|
||||
return 'Integration Settings';
|
||||
}
|
||||
|
||||
return id === 'new' ? `New ${selectedOption?.display_name} integration` : `Edit integration`;
|
||||
}
|
||||
});
|
||||
|
||||
const IntegrationBlocks: React.FC<{
|
||||
options: Array<ApiSchemas['AlertReceiveChannelIntegrationOptions']>;
|
||||
onBlockClick: (option: ApiSchemas['AlertReceiveChannelIntegrationOptions']) => void;
|
||||
}> = ({ options, onBlockClick }) => {
|
||||
return (
|
||||
<div className={cx('cards')} data-testid="create-integration-modal">
|
||||
{options.length ? (
|
||||
options.map((alertReceiveChannelChoice) => {
|
||||
return (
|
||||
<Block
|
||||
bordered
|
||||
hover
|
||||
shadowed
|
||||
onClick={() => onBlockClick(alertReceiveChannelChoice)}
|
||||
key={alertReceiveChannelChoice.value}
|
||||
className={cx('card', { card_featured: alertReceiveChannelChoice.featured })}
|
||||
>
|
||||
<div className={cx('card-bg')}>
|
||||
<IntegrationLogo integration={alertReceiveChannelChoice} scale={0.2} />
|
||||
</div>
|
||||
<div className={cx('title')}>
|
||||
<VerticalGroup spacing={alertReceiveChannelChoice.featured ? 'xs' : 'none'}>
|
||||
<HorizontalGroup>
|
||||
<Text strong data-testid="integration-display-name">
|
||||
{alertReceiveChannelChoice.display_name}
|
||||
</Text>
|
||||
{alertReceiveChannelChoice.featured && alertReceiveChannelChoice.featured_tag_name && (
|
||||
<Tag name={alertReceiveChannelChoice.featured_tag_name} colorIndex={5} />
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
<Text type="secondary" size="small">
|
||||
{alertReceiveChannelChoice.short_description}
|
||||
</Text>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
</Block>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EmptySearchResult>Could not find anything matching your query</EmptySearchResult>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -218,10 +218,12 @@ export const PluginConfigPage: FC<OnCallPluginConfigPageProps> = ({
|
|||
);
|
||||
content =
|
||||
licenseType === GRAFANA_LICENSE_OSS ? (
|
||||
<HorizontalGroup>
|
||||
{pluginLink}
|
||||
<RemoveConfigButton />
|
||||
</HorizontalGroup>
|
||||
<div>
|
||||
<HorizontalGroup>
|
||||
{pluginLink}
|
||||
<RemoveConfigButton />
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
) : (
|
||||
<VerticalGroup>
|
||||
<Label>This is a cloud managed configuration.</Label>
|
||||
|
|
|
|||
|
|
@ -121,5 +121,5 @@ export const IntegrationHelper = {
|
|||
},
|
||||
};
|
||||
|
||||
export const getIsBidirectionalIntegration = ({ integration }: ApiSchemas['AlertReceiveChannel']) =>
|
||||
export const getIsBidirectionalIntegration = ({ integration }: Partial<ApiSchemas['AlertReceiveChannel']>) =>
|
||||
integration === ('servicenow' as ApiSchemas['AlertReceiveChannel']['integration']); // TODO: add service now in backend schema as valid value and remove casting
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ import { CollapsedIntegrationRouteDisplay } from 'containers/IntegrationContaine
|
|||
import { ExpandedIntegrationRouteDisplay } from 'containers/IntegrationContainers/ExpandedIntegrationRouteDisplay/ExpandedIntegrationRouteDisplay';
|
||||
import { IntegrationHeartbeatForm } from 'containers/IntegrationContainers/IntegrationHeartbeatForm/IntegrationHeartbeatForm';
|
||||
import { IntegrationTemplateList } from 'containers/IntegrationContainers/IntegrationTemplatesList';
|
||||
import { IntegrationForm } from 'containers/IntegrationForm/IntegrationForm';
|
||||
import { IntegrationFormContainer } from 'containers/IntegrationForm/IntegrationFormContainer';
|
||||
import { IntegrationLabelsForm } from 'containers/IntegrationLabelsForm/IntegrationLabelsForm';
|
||||
import { IntegrationTemplate } from 'containers/IntegrationTemplate/IntegrationTemplate';
|
||||
import { MaintenanceForm } from 'containers/MaintenanceForm/MaintenanceForm';
|
||||
|
|
@ -863,7 +863,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
)}
|
||||
|
||||
{isIntegrationSettingsOpen && (
|
||||
<IntegrationForm
|
||||
<IntegrationFormContainer
|
||||
isTableView={false}
|
||||
onHide={() => setIsIntegrationSettingsOpen(false)}
|
||||
onSubmit={async () => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { Text } from 'components/Text/Text';
|
|||
import { TextEllipsisTooltip } from 'components/TextEllipsisTooltip/TextEllipsisTooltip';
|
||||
import { TooltipBadge } from 'components/TooltipBadge/TooltipBadge';
|
||||
import { WithContextMenu } from 'components/WithContextMenu/WithContextMenu';
|
||||
import { IntegrationForm } from 'containers/IntegrationForm/IntegrationForm';
|
||||
import { IntegrationFormContainer } from 'containers/IntegrationForm/IntegrationFormContainer';
|
||||
import { IntegrationLabelsForm } from 'containers/IntegrationLabelsForm/IntegrationLabelsForm';
|
||||
import { RemoteFilters } from 'containers/RemoteFilters/RemoteFilters';
|
||||
import { TeamName } from 'containers/TeamName/TeamName';
|
||||
|
|
@ -289,7 +289,7 @@ class _IntegrationsPage extends React.Component<IntegrationsProps, IntegrationsS
|
|||
</div>
|
||||
|
||||
{alertReceiveChannelId && (
|
||||
<IntegrationForm
|
||||
<IntegrationFormContainer
|
||||
onHide={() => {
|
||||
this.setState({ alertReceiveChannelId: undefined });
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -76,3 +76,5 @@ export const TEXT_ELLIPSIS_CLASS = 'overflow-child';
|
|||
|
||||
export const INCIDENT_HORIZONTAL_SCROLLING_STORAGE = 'isIncidentalTableHorizontalScrolling';
|
||||
export const IRM_TAB = 'IRM';
|
||||
|
||||
export const URL_REGEX = /^((https?|ftp|smtp):\/\/)?(www.)?[a-z0-9]+\.[a-z]+(\/[a-zA-Z0-9#]+\/?)*$/;
|
||||
|
|
|
|||
|
|
@ -10791,6 +10791,11 @@ react-hook-form@7.5.3:
|
|||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.5.3.tgz#9a624fa14ec153b154891c5ebddae02ec5c2e40f"
|
||||
integrity sha512-5T0mfJ4kCPKljd7t3Rgp7lML4Y2+kaZIeMdN6Zo/J7gBQ+WkrDBHOftdOtz4X+7/eqHGak5yL5evNpYdA9abVA==
|
||||
|
||||
react-hook-form@^7.50.1:
|
||||
version "7.50.1"
|
||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.50.1.tgz#f6aeb17a863327e5a0252de8b35b4fc8990377ed"
|
||||
integrity sha512-3PCY82oE0WgeOgUtIr3nYNNtNvqtJ7BZjsbxh6TnYNbXButaD5WpjOmTjdxZfheuHKR68qfeFnEDVYoSSFPMTQ==
|
||||
|
||||
react-i18next@^12.0.0:
|
||||
version "12.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-12.0.0.tgz#634015a2c035779c5736ae4c2e5c34c1659753b1"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue