Snow polishing (#4136)
# What this PR does - Webhook URL should be template editor + move it after HTTP method [Frontend] @brojd - Lack of scrollbar when templates are there in Outgoing webhook details drawer [Frontend] @brojd - On outgoing tab "Open ServiceNow configuration" does nothing [Frontend] @brojd - Remove OK tag next to url in outgoing tab [Frontend] @brojd https://github.com/grafana/oncall-private/issues/2615 <!-- *Note*: if you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
This commit is contained in:
parent
e95125ae5f
commit
9256fbd12c
5 changed files with 86 additions and 34 deletions
|
|
@ -62,12 +62,13 @@ import { ApiSchemas } from 'network/oncall-api/api.types';
|
|||
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';
|
||||
import { PageProps, SelectOption, WithDrawerConfig, WithStoreProps } from 'state/types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import { LocationHelper } from 'utils/LocationHelper';
|
||||
import { UserActions } from 'utils/authorization/authorization';
|
||||
import { PLUGIN_ROOT } from 'utils/consts';
|
||||
import { withDrawer } from 'utils/hoc';
|
||||
import { useDrawer } from 'utils/hooks';
|
||||
import { getItem, setItem } from 'utils/localStorage';
|
||||
import { sanitize } from 'utils/sanitize';
|
||||
|
|
@ -77,7 +78,11 @@ import { OutgoingTab } from './OutgoingTab/OutgoingTab';
|
|||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface IntegrationProps extends WithStoreProps, PageProps, RouteComponentProps<{ id: string }> {}
|
||||
interface IntegrationProps
|
||||
extends WithDrawerConfig<IntegrationDrawerKey>,
|
||||
WithStoreProps,
|
||||
PageProps,
|
||||
RouteComponentProps<{ id: string }> {}
|
||||
|
||||
interface IntegrationState extends PageBaseState {
|
||||
isLoading: boolean;
|
||||
|
|
@ -138,6 +143,7 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
|
|||
match: {
|
||||
params: { id },
|
||||
},
|
||||
drawerConfig,
|
||||
} = this.props;
|
||||
|
||||
const { alertReceiveChannelStore } = store;
|
||||
|
|
@ -227,6 +233,7 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
|
|||
alertReceiveChannel={alertReceiveChannel}
|
||||
changeIsTemplateSettingsOpen={() => this.setState({ isTemplateSettingsOpen: true })}
|
||||
isLegacyIntegration={isLegacyIntegration}
|
||||
drawerConfig={drawerConfig}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -266,7 +273,10 @@ class _IntegrationPage extends React.Component<IntegrationProps, IntegrationStat
|
|||
<Tabs
|
||||
tabs={[
|
||||
{ label: 'Incoming', content: incomingPart },
|
||||
{ label: 'Outgoing', content: <OutgoingTab /> },
|
||||
{
|
||||
label: 'Outgoing',
|
||||
content: <OutgoingTab openSnowConfigurationDrawer={() => drawerConfig.openDrawer('servicenow')} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -807,6 +817,7 @@ interface IntegrationActionsProps {
|
|||
isLegacyIntegration: boolean;
|
||||
alertReceiveChannel: ApiSchemas['AlertReceiveChannel'];
|
||||
changeIsTemplateSettingsOpen: () => void;
|
||||
drawerConfig: ReturnType<typeof useDrawer<IntegrationDrawerKey>>;
|
||||
}
|
||||
|
||||
type IntegrationDrawerKey = 'servicenow' | 'completeConfig';
|
||||
|
|
@ -815,6 +826,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
alertReceiveChannel,
|
||||
isLegacyIntegration,
|
||||
changeIsTemplateSettingsOpen,
|
||||
drawerConfig,
|
||||
}) => {
|
||||
const store = useStore();
|
||||
const { alertReceiveChannelStore } = store;
|
||||
|
|
@ -842,7 +854,7 @@ const IntegrationActions: React.FC<IntegrationActionsProps> = ({
|
|||
alert_receive_channel_id: ApiSchemas['AlertReceiveChannel']['id'];
|
||||
}>(undefined);
|
||||
|
||||
const { closeDrawer, openDrawer, getIsDrawerOpened } = useDrawer<IntegrationDrawerKey>();
|
||||
const { closeDrawer, openDrawer, getIsDrawerOpened } = drawerConfig;
|
||||
|
||||
const { id } = alertReceiveChannel;
|
||||
|
||||
|
|
@ -1274,4 +1286,4 @@ const IntegrationHeader: React.FC<IntegrationHeaderProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
export const IntegrationPage = withRouter(withMobXProviderContext(_IntegrationPage));
|
||||
export const IntegrationPage = withRouter(withMobXProviderContext(withDrawer<IntegrationDrawerKey>(_IntegrationPage)));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useStyles2, Input, IconButton, Drawer, Badge, HorizontalGroup } from '@grafana/ui';
|
||||
import { useStyles2, Input, IconButton, Drawer, HorizontalGroup } from '@grafana/ui';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
import { Button } from 'components/Button/Button';
|
||||
|
|
@ -19,7 +19,7 @@ import { OutgoingTabDrawerKey } from './OutgoingTab.types';
|
|||
import { OutgoingWebhookDetailsDrawerTabs } from './OutgoingWebhookDetailsDrawerTabs';
|
||||
import { OutgoingWebhooksTable } from './OutgoingWebhooksTable';
|
||||
|
||||
export const OutgoingTab = () => {
|
||||
export const OutgoingTab = ({ openSnowConfigurationDrawer }: { openSnowConfigurationDrawer: () => void }) => {
|
||||
const { openDrawer, closeDrawer, getIsDrawerOpened } = useDrawer<OutgoingTabDrawerKey>();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ export const OutgoingTab = () => {
|
|||
{
|
||||
customIcon: 'plug',
|
||||
startingElemPosition: '50%',
|
||||
expandedView: () => <Connection />,
|
||||
expandedView: () => <Connection openSnowConfigurationDrawer={openSnowConfigurationDrawer} />,
|
||||
},
|
||||
{
|
||||
customIcon: 'plus',
|
||||
|
|
@ -79,9 +79,8 @@ export const OutgoingTab = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const Connection = observer(() => {
|
||||
const Connection = observer(({ openSnowConfigurationDrawer }: { openSnowConfigurationDrawer: () => void }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const integration = useCurrentIntegration();
|
||||
// TODO: remove casting once backend narrows down the types
|
||||
const url = integration?.additional_settings?.instance_url as string;
|
||||
|
|
@ -94,7 +93,6 @@ const Connection = observer(() => {
|
|||
heading={
|
||||
<div className={styles.horizontalGroup}>
|
||||
<IntegrationTag>ServiceNow connection</IntegrationTag>
|
||||
<Badge text="OK" color="green" />
|
||||
<Input
|
||||
value={url}
|
||||
disabled
|
||||
|
|
@ -118,6 +116,7 @@ const Connection = observer(() => {
|
|||
name="cog"
|
||||
aria-label="Open ServiceNow configuration"
|
||||
className={styles.openConfigurationBtn}
|
||||
onClick={openSnowConfigurationDrawer}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
Field,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
Select,
|
||||
Switch,
|
||||
|
|
@ -17,7 +16,7 @@ import cn from 'classnames';
|
|||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { MonacoEditor } from 'components/MonacoEditor/MonacoEditor';
|
||||
import { MONACO_READONLY_CONFIG } from 'components/MonacoEditor/MonacoEditor.config';
|
||||
import { MONACO_EDITABLE_CONFIG, MONACO_READONLY_CONFIG } from 'components/MonacoEditor/MonacoEditor.config';
|
||||
import { WebhooksTemplateEditor } from 'containers/WebhooksTemplateEditor/WebhooksTemplateEditor';
|
||||
import { HTTP_METHOD_OPTIONS, WEBHOOK_TRIGGGER_TYPE_OPTIONS } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
|
||||
|
|
@ -37,7 +36,7 @@ interface OutgoingWebhookFormFieldsProps {
|
|||
|
||||
export const OutgoingWebhookFormFields: FC<OutgoingWebhookFormFieldsProps> = ({ webhookId }) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { control, watch, formState, register } = useFormContext<OutgoingTabFormValues>();
|
||||
const { control, watch, formState } = useFormContext<OutgoingTabFormValues>();
|
||||
const [templateToEdit, setTemplateToEdit] = useState<TemplateToEdit>();
|
||||
|
||||
const [showTriggerTemplate] = watch(['triggerTemplateToogle']);
|
||||
|
|
@ -83,26 +82,6 @@ export const OutgoingWebhookFormFields: FC<OutgoingWebhookFormFieldsProps> = ({
|
|||
</Field>
|
||||
)}
|
||||
/>
|
||||
<Field
|
||||
key="url"
|
||||
invalid={Boolean(formState.errors.url)}
|
||||
error={formState.errors.url?.message}
|
||||
label={
|
||||
<Label>
|
||||
<span>Webhook URL</span>
|
||||
<Tooltip content="Some description" placement="right">
|
||||
<Icon name="info-circle" className={styles.infoIcon} />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
}
|
||||
className={styles.selectField}
|
||||
>
|
||||
<Input
|
||||
{...register('url', {
|
||||
required: 'URL is required',
|
||||
})}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
control={control}
|
||||
name="http_method"
|
||||
|
|
@ -134,6 +113,52 @@ export const OutgoingWebhookFormFields: FC<OutgoingWebhookFormFieldsProps> = ({
|
|||
</Field>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="url"
|
||||
render={({ field }) => (
|
||||
<VerticalGroup>
|
||||
<HorizontalGroup width="100%" justify="space-between">
|
||||
<Label>
|
||||
<span>Webhook URL</span>
|
||||
<Tooltip content="Some description" placement="right">
|
||||
<Icon name="info-circle" className={styles.infoIcon} />
|
||||
</Tooltip>
|
||||
</Label>
|
||||
<Button
|
||||
icon="edit"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setTemplateToEdit({
|
||||
value: field.value,
|
||||
displayName: 'webhook url',
|
||||
name: field.name,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</HorizontalGroup>
|
||||
<MonacoEditor
|
||||
{...field}
|
||||
data={{}} // TODO:update
|
||||
showLineNumbers={false}
|
||||
height={30}
|
||||
monacoOptions={MONACO_EDITABLE_CONFIG}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
{templateToEdit?.['name'] === field.name && (
|
||||
<WebhooksTemplateEditor
|
||||
id={webhookId}
|
||||
handleSubmit={(value) => {
|
||||
field.onChange(value);
|
||||
setTemplateToEdit(undefined);
|
||||
}}
|
||||
onHide={() => setTemplateToEdit(undefined)}
|
||||
template={templateToEdit}
|
||||
/>
|
||||
)}
|
||||
</VerticalGroup>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="data"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import { AppPluginMeta, KeyValue } from '@grafana/data';
|
||||
|
||||
import { RootStore } from 'state/rootStore';
|
||||
import { useDrawer } from 'utils/hooks';
|
||||
|
||||
export interface WithStoreProps {
|
||||
store: RootStore;
|
||||
}
|
||||
|
||||
export interface WithDrawerConfig<T extends string> {
|
||||
drawerConfig: ReturnType<typeof useDrawer<T>>;
|
||||
}
|
||||
|
||||
export interface PageProps<T extends KeyValue = KeyValue> {
|
||||
meta: AppPluginMeta<T>;
|
||||
query: KeyValue;
|
||||
|
|
|
|||
11
grafana-plugin/src/utils/hoc.tsx
Normal file
11
grafana-plugin/src/utils/hoc.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useDrawer } from './hooks';
|
||||
|
||||
export const withDrawer = <T extends string>(Component: React.ComponentType<any>) => {
|
||||
const ComponentWithDrawer = (props: any) => {
|
||||
const drawerConfig = useDrawer<T>();
|
||||
return <Component {...props} drawerConfig={drawerConfig} />;
|
||||
};
|
||||
return ComponentWithDrawer;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue