remove old webhook UI (#2536)
# What this PR does remove old webhook UI ## 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
065f9aea7f
commit
9c13acb9f5
30 changed files with 638 additions and 1261 deletions
|
|
@ -22,7 +22,6 @@ import {
|
|||
} from 'models/escalation_policy/escalation_policy.types';
|
||||
import { GrafanaTeamStore } from 'models/grafana_team/grafana_team';
|
||||
import { OutgoingWebhookStore } from 'models/outgoing_webhook/outgoing_webhook';
|
||||
import { OutgoingWebhook2Store } from 'models/outgoing_webhook_2/outgoing_webhook_2';
|
||||
import { ScheduleStore } from 'models/schedule/schedule';
|
||||
import { WaitDelay } from 'models/wait_delay';
|
||||
import { SelectOption } from 'state/types';
|
||||
|
|
@ -54,7 +53,6 @@ export interface EscalationPolicyProps extends ElementSortableProps {
|
|||
isSlackInstalled: boolean;
|
||||
teamStore: GrafanaTeamStore;
|
||||
outgoingWebhookStore: OutgoingWebhookStore;
|
||||
outgoingWebhook2Store: OutgoingWebhook2Store;
|
||||
scheduleStore: ScheduleStore;
|
||||
}
|
||||
|
||||
|
|
@ -111,8 +109,6 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
return this._renderNotifyUserGroup();
|
||||
case 'schedule':
|
||||
return this._renderNotifySchedule();
|
||||
case 'custom_action':
|
||||
return this._renderTriggerCustomAction();
|
||||
case 'custom_webhook':
|
||||
return this._renderTriggerCustomWebhook();
|
||||
case 'num_alerts_in_window':
|
||||
|
|
@ -381,39 +377,8 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
);
|
||||
}
|
||||
|
||||
private _renderTriggerCustomAction() {
|
||||
const { data, isDisabled, teamStore, outgoingWebhookStore } = this.props;
|
||||
const { custom_button_trigger } = data;
|
||||
|
||||
return (
|
||||
<WithPermissionControlTooltip key="custom-button" disableByPaywall userAction={UserActions.EscalationChainsWrite}>
|
||||
<GSelect
|
||||
showSearch
|
||||
disabled={isDisabled}
|
||||
modelName="outgoingWebhookStore"
|
||||
displayField="name"
|
||||
valueField="id"
|
||||
placeholder="Select Webhook"
|
||||
className={cx('select', 'control')}
|
||||
value={custom_button_trigger}
|
||||
onChange={this._getOnChangeHandler('custom_button_trigger')}
|
||||
getOptionLabel={(item: SelectableValue) => {
|
||||
const team = teamStore.items[outgoingWebhookStore.items[item.value].team];
|
||||
return (
|
||||
<>
|
||||
<Text>{item.label} </Text>
|
||||
<TeamName team={team} size="small" />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
width={'auto'}
|
||||
/>
|
||||
</WithPermissionControlTooltip>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderTriggerCustomWebhook() {
|
||||
const { data, isDisabled, teamStore, outgoingWebhook2Store } = this.props;
|
||||
const { data, isDisabled, teamStore, outgoingWebhookStore } = this.props;
|
||||
const { custom_webhook } = data;
|
||||
|
||||
return (
|
||||
|
|
@ -425,7 +390,7 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
<GSelect
|
||||
showSearch
|
||||
disabled={isDisabled}
|
||||
modelName="outgoingWebhook2Store"
|
||||
modelName="outgoingWebhookStore"
|
||||
displayField="name"
|
||||
valueField="id"
|
||||
placeholder="Select Webhook"
|
||||
|
|
@ -433,7 +398,7 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
value={custom_webhook}
|
||||
onChange={this._getOnChangeHandler('custom_webhook')}
|
||||
getOptionLabel={(item: SelectableValue) => {
|
||||
const team = teamStore.items[outgoingWebhook2Store.items[item.value].team];
|
||||
const team = teamStore.items[outgoingWebhookStore.items[item.value].team];
|
||||
return (
|
||||
<>
|
||||
<Text>{item.label} </Text>
|
||||
|
|
@ -443,7 +408,7 @@ export class EscalationPolicy extends React.Component<EscalationPolicyProps, any
|
|||
}}
|
||||
width={'auto'}
|
||||
filterOptions={(id) => {
|
||||
const webhook = outgoingWebhook2Store.items[id];
|
||||
const webhook = outgoingWebhookStore.items[id];
|
||||
return webhook.trigger_type_name === 'Escalation step';
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import Text from 'components/Text/Text';
|
|||
import TeamName from 'containers/TeamName/TeamName';
|
||||
import { HeartGreenIcon, HeartRedIcon } from 'icons';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { AppFeature } from 'state/features';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './AlertReceiveChannelCard.module.scss';
|
||||
|
|
@ -65,22 +64,20 @@ const AlertReceiveChannelCard = observer((props: AlertReceiveChannelCardProps) =
|
|||
<Text type="primary" size="medium">
|
||||
<Emoji className={cx('title')} text={alertReceiveChannel.verbal_name} />
|
||||
</Text>
|
||||
{store.hasFeature(AppFeature.Webhooks2) && (
|
||||
<CopyToClipboard text={alertReceiveChannel.id}>
|
||||
<IconButton
|
||||
variant="primary"
|
||||
tooltip={
|
||||
<div>
|
||||
ID {alertReceiveChannel.id}
|
||||
<br />
|
||||
(click to copy ID to clipboard)
|
||||
</div>
|
||||
}
|
||||
tooltipPlacement="top"
|
||||
name="info-circle"
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
)}
|
||||
<CopyToClipboard text={alertReceiveChannel.id}>
|
||||
<IconButton
|
||||
variant="primary"
|
||||
tooltip={
|
||||
<div>
|
||||
ID {alertReceiveChannel.id}
|
||||
<br />
|
||||
(click to copy ID to clipboard)
|
||||
</div>
|
||||
}
|
||||
tooltipPlacement="top"
|
||||
name="info-circle"
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
{alertReceiveChannelCounter && (
|
||||
<PluginLink
|
||||
query={{ page: 'alert-groups', integration: alertReceiveChannel.id }}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ const EscalationChainSteps = observer((props: EscalationChainStepsProps) => {
|
|||
teamStore={store.grafanaTeamStore}
|
||||
scheduleStore={store.scheduleStore}
|
||||
outgoingWebhookStore={store.outgoingWebhookStore}
|
||||
outgoingWebhook2Store={store.outgoingWebhook2Store}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
.root {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 16px 0 0 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.tabs__content {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* TODO: figure out why this is not picked */
|
||||
.webhooks__drawerContent .cursor.monaco-mouse-cursor-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
@ -1,312 +0,0 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, ConfirmModal, ConfirmModalProps, Drawer, HorizontalGroup, Tab, TabsBar } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import GForm from 'components/GForm/GForm';
|
||||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
import Text from 'components/Text/Text';
|
||||
import OutgoingWebhook2Status from 'containers/OutgoingWebhook2Status/OutgoingWebhook2Status';
|
||||
import WebhooksTemplateEditor from 'containers/WebhooksTemplateEditor/WebhooksTemplateEditor';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { WebhookFormActionType } from 'pages/outgoing_webhooks_2/OutgoingWebhooks2.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { KeyValuePair } from 'utils';
|
||||
import { UserActions } from 'utils/authorization';
|
||||
import { PLUGIN_ROOT } from 'utils/consts';
|
||||
|
||||
import { form } from './OutgoingWebhook2Form.config';
|
||||
|
||||
import styles from 'containers/OutgoingWebhook2Form/OutgoingWebhook2Form.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface OutgoingWebhook2FormProps {
|
||||
id: OutgoingWebhook2['id'] | 'new';
|
||||
action: WebhookFormActionType;
|
||||
onHide: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const WebhookTabs = {
|
||||
Settings: new KeyValuePair('Settings', 'Settings'),
|
||||
LastRun: new KeyValuePair('LastRun', 'Last Run'),
|
||||
};
|
||||
|
||||
const OutgoingWebhook2Form = observer((props: OutgoingWebhook2FormProps) => {
|
||||
const history = useHistory();
|
||||
const { id, action, onUpdate, onHide, onDelete } = props;
|
||||
const [onFormChangeFn, setOnFormChangeFn] = useState<{ fn: (value: string) => void }>(undefined);
|
||||
const [templateToEdit, setTemplateToEdit] = useState(undefined);
|
||||
const [activeTab, setActiveTab] = useState<string>(
|
||||
action === WebhookFormActionType.EDIT_SETTINGS ? WebhookTabs.Settings.key : WebhookTabs.LastRun.key
|
||||
);
|
||||
|
||||
const { outgoingWebhook2Store } = useStore();
|
||||
const isNew = action === WebhookFormActionType.NEW;
|
||||
const isNewOrCopy = isNew || action === WebhookFormActionType.COPY;
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(data: Partial<OutgoingWebhook2>) => {
|
||||
(isNewOrCopy ? outgoingWebhook2Store.create(data) : outgoingWebhook2Store.update(id, data)).then(() => {
|
||||
onHide();
|
||||
onUpdate();
|
||||
});
|
||||
},
|
||||
[id]
|
||||
);
|
||||
|
||||
const getTemplateEditClickHandler = (formItem: FormItem, values, setFormFieldValue) => {
|
||||
return () => {
|
||||
const formValue = values[formItem.name];
|
||||
setTemplateToEdit({ value: formValue, displayName: undefined, description: undefined, name: formItem.name });
|
||||
setOnFormChangeFn({ fn: (value) => setFormFieldValue(value) });
|
||||
};
|
||||
};
|
||||
|
||||
const enrchField = (
|
||||
formItem: FormItem,
|
||||
disabled: boolean,
|
||||
renderedControl: React.ReactElement,
|
||||
values,
|
||||
setFormFieldValue
|
||||
) => {
|
||||
if (formItem.type === FormItemType.Monaco) {
|
||||
return (
|
||||
<div className={cx('form-row')}>
|
||||
<div className={cx('form-field')}>{renderedControl}</div>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
icon="edit"
|
||||
variant="secondary"
|
||||
onClick={getTemplateEditClickHandler(formItem, values, setFormFieldValue)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return renderedControl;
|
||||
};
|
||||
|
||||
if (
|
||||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) &&
|
||||
!outgoingWebhook2Store.items[id]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let data:
|
||||
| OutgoingWebhook2
|
||||
| {
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
};
|
||||
|
||||
if (isNew) {
|
||||
data = { is_webhook_enabled: true, is_legacy: false };
|
||||
} else if (isNewOrCopy) {
|
||||
data = { ...outgoingWebhook2Store.items[id], is_legacy: false, name: '' };
|
||||
} else {
|
||||
data = outgoingWebhook2Store.items[id];
|
||||
}
|
||||
|
||||
if (
|
||||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) &&
|
||||
!outgoingWebhook2Store.items[id]
|
||||
) {
|
||||
// nothing to show if we open invalid ID for edit/last_run
|
||||
return null;
|
||||
}
|
||||
|
||||
const formElement = <GForm form={form} data={data} onSubmit={handleSubmit} onFieldRender={enrchField} />;
|
||||
|
||||
if (action === WebhookFormActionType.NEW || action === WebhookFormActionType.COPY) {
|
||||
// show just the creation form, not the tabs
|
||||
return (
|
||||
<>
|
||||
<Drawer scrollableContent title={'Create Outgoing Webhook'} onClose={onHide} closeOnMaskClick={false}>
|
||||
<div className="webhooks__drawerContent">{renderWebhookForm()}</div>
|
||||
</Drawer>
|
||||
{templateToEdit && (
|
||||
<WebhooksTemplateEditor
|
||||
id={id}
|
||||
handleSubmit={(value) => {
|
||||
onFormChangeFn?.fn(value);
|
||||
setTemplateToEdit(undefined);
|
||||
}}
|
||||
onHide={() => setTemplateToEdit(undefined)}
|
||||
template={templateToEdit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
// show tabbed drawer (edit/live_run)
|
||||
<>
|
||||
<Drawer scrollableContent title={'Outgoing webhook details'} onClose={onHide} closeOnMaskClick={false}>
|
||||
<div className={cx('webhooks__drawerContent')}>
|
||||
<TabsBar>
|
||||
<Tab
|
||||
key={WebhookTabs.Settings.key}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(WebhookTabs.Settings.key);
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/edit/${id}`);
|
||||
}}
|
||||
active={activeTab === WebhookTabs.Settings.key}
|
||||
label={WebhookTabs.Settings.value}
|
||||
/>
|
||||
|
||||
<Tab
|
||||
key={WebhookTabs.LastRun.key}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(WebhookTabs.LastRun.key);
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/last_run/${id}`);
|
||||
}}
|
||||
active={activeTab === WebhookTabs.LastRun.key}
|
||||
label={WebhookTabs.LastRun.value}
|
||||
/>
|
||||
</TabsBar>
|
||||
|
||||
<WebhookTabsContent
|
||||
id={id}
|
||||
action={action}
|
||||
activeTab={activeTab}
|
||||
data={data}
|
||||
handleSubmit={handleSubmit}
|
||||
onDelete={onDelete}
|
||||
onHide={onHide}
|
||||
onUpdate={onUpdate}
|
||||
formElement={formElement}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
{templateToEdit && (
|
||||
<WebhooksTemplateEditor
|
||||
id={id}
|
||||
handleSubmit={(value) => {
|
||||
onFormChangeFn?.fn(value);
|
||||
setTemplateToEdit(undefined);
|
||||
}}
|
||||
onHide={() => setTemplateToEdit(undefined)}
|
||||
template={templateToEdit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function renderWebhookForm() {
|
||||
return (
|
||||
<>
|
||||
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm">
|
||||
<GForm form={form} data={data} onSubmit={handleSubmit} onFieldRender={enrchField} />
|
||||
<div className={cx('buttons')}>
|
||||
<HorizontalGroup justify={'flex-end'}>
|
||||
<Button variant="secondary" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button form={form.name} type="submit" disabled={data.is_legacy}>
|
||||
{isNewOrCopy ? 'Create' : 'Update'} Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
interface WebhookTabsProps {
|
||||
id: OutgoingWebhook2['id'] | 'new';
|
||||
activeTab: string;
|
||||
action: WebhookFormActionType;
|
||||
data:
|
||||
| OutgoingWebhook2
|
||||
| {
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
};
|
||||
onHide: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
handleSubmit: (data: Partial<OutgoingWebhook2>) => void;
|
||||
formElement: React.ReactElement;
|
||||
}
|
||||
|
||||
const WebhookTabsContent: React.FC<WebhookTabsProps> = ({
|
||||
id,
|
||||
action,
|
||||
activeTab,
|
||||
data,
|
||||
onHide,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
formElement,
|
||||
}) => {
|
||||
const [confirmationModal, setConfirmationModal] = useState<ConfirmModalProps>(undefined);
|
||||
|
||||
return (
|
||||
<div className={cx('tabs__content')}>
|
||||
{confirmationModal && (
|
||||
<ConfirmModal {...(confirmationModal as ConfirmModalProps)} onDismiss={() => setConfirmationModal(undefined)} />
|
||||
)}
|
||||
|
||||
{activeTab === WebhookTabs.Settings.key && (
|
||||
<>
|
||||
<div className={cx('content')} data-testid="test__outgoingWebhook2EditForm">
|
||||
{formElement}
|
||||
<div className={cx('buttons')}>
|
||||
<HorizontalGroup justify={'flex-end'}>
|
||||
<Button variant="secondary" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button
|
||||
form={form.name}
|
||||
variant="destructive"
|
||||
type="button"
|
||||
disabled={data.is_legacy}
|
||||
onClick={() => {
|
||||
setConfirmationModal({
|
||||
isOpen: true,
|
||||
body: 'The action cannot be undone.',
|
||||
confirmText: 'Delete',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: onDelete,
|
||||
title: `Are you sure you want to delete webhook?`,
|
||||
} as ConfirmModalProps);
|
||||
}}
|
||||
>
|
||||
Delete Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button form={form.name} type="submit" disabled={data.is_legacy}>
|
||||
{action === WebhookFormActionType.NEW ? 'Create' : 'Update'} Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
{data.is_legacy ? (
|
||||
<div className={cx('content')}>
|
||||
<Text type="secondary">Legacy migrated webhooks are not editable. Make a copy to make changes.</Text>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeTab === WebhookTabs.LastRun.key && <OutgoingWebhook2Status id={id} onUpdate={onUpdate} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutgoingWebhook2Form;
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
|
||||
export const form: { name: string; fields: FormItem[] } = {
|
||||
name: 'OutgoingWebhook',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: FormItemType.Input,
|
||||
validation: { required: true },
|
||||
},
|
||||
{
|
||||
name: 'team',
|
||||
label: 'Assign to team',
|
||||
description:
|
||||
'Assigning to the teams allows you to filter Outgoing Webhooks and configure their visibility. Go to OnCall -> Settings -> Team and Access Settings for more details',
|
||||
type: FormItemType.GSelect,
|
||||
extra: {
|
||||
modelName: 'grafanaTeamStore',
|
||||
displayField: 'name',
|
||||
valueField: 'id',
|
||||
showSearch: true,
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'webhook',
|
||||
label: 'Webhook URL',
|
||||
type: FormItemType.Input,
|
||||
validation: { required: true },
|
||||
},
|
||||
{
|
||||
name: 'user',
|
||||
type: FormItemType.Input,
|
||||
},
|
||||
{
|
||||
name: 'password',
|
||||
type: FormItemType.Input,
|
||||
},
|
||||
{
|
||||
name: 'authorization_header',
|
||||
type: FormItemType.TextArea,
|
||||
extra: {
|
||||
rows: 5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
getDisabled: (form_data) => Boolean(form_data?.forward_whole_payload),
|
||||
type: FormItemType.TextArea,
|
||||
description: 'Available variables: {{ alert_payload }}, {{ alert_group_id }}',
|
||||
extra: {
|
||||
rows: 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'forward_whole_payload',
|
||||
normalize: (value) => Boolean(value),
|
||||
type: FormItemType.Switch,
|
||||
description: "Forwards whole payload of the alert to the webhook's url as POST data",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -18,7 +18,7 @@ export const WebhookTriggerType = {
|
|||
};
|
||||
|
||||
export const form: { name: string; fields: FormItem[] } = {
|
||||
name: 'OutgoingWebhook2',
|
||||
name: 'OutgoingWebhook',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
|
|
@ -9,3 +9,22 @@
|
|||
.content {
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.tabs__content {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* TODO: figure out why this is not picked */
|
||||
.webhooks__drawerContent .cursor.monaco-mouse-cursor-text {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Button, Drawer, HorizontalGroup } from '@grafana/ui';
|
||||
import { Button, ConfirmModal, ConfirmModalProps, Drawer, HorizontalGroup, Tab, TabsBar } from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import GForm from 'components/GForm/GForm';
|
||||
import { FormItem, FormItemType } from 'components/GForm/GForm.types';
|
||||
import Text from 'components/Text/Text';
|
||||
import OutgoingWebhookStatus from 'containers/OutgoingWebhookStatus/OutgoingWebhookStatus';
|
||||
import WebhooksTemplateEditor from 'containers/WebhooksTemplateEditor/WebhooksTemplateEditor';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { WebhookFormActionType } from 'pages/outgoing_webhooks/OutgoingWebhooks.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { KeyValuePair } from 'utils';
|
||||
import { UserActions } from 'utils/authorization';
|
||||
import { PLUGIN_ROOT } from 'utils/consts';
|
||||
|
||||
import { form } from './OutgoingWebhookForm.config';
|
||||
|
||||
|
|
@ -18,53 +26,287 @@ const cx = cn.bind(styles);
|
|||
|
||||
interface OutgoingWebhookFormProps {
|
||||
id: OutgoingWebhook['id'] | 'new';
|
||||
action: WebhookFormActionType;
|
||||
onHide: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const WebhookTabs = {
|
||||
Settings: new KeyValuePair('Settings', 'Settings'),
|
||||
LastRun: new KeyValuePair('LastRun', 'Last Run'),
|
||||
};
|
||||
|
||||
const OutgoingWebhookForm = observer((props: OutgoingWebhookFormProps) => {
|
||||
const { id, onUpdate, onHide } = props;
|
||||
const history = useHistory();
|
||||
const { id, action, onUpdate, onHide, onDelete } = props;
|
||||
const [onFormChangeFn, setOnFormChangeFn] = useState<{ fn: (value: string) => void }>(undefined);
|
||||
const [templateToEdit, setTemplateToEdit] = useState(undefined);
|
||||
const [activeTab, setActiveTab] = useState<string>(
|
||||
action === WebhookFormActionType.EDIT_SETTINGS ? WebhookTabs.Settings.key : WebhookTabs.LastRun.key
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const { outgoingWebhookStore, userStore } = store;
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const data = id === 'new' ? { team: user.current_team } : outgoingWebhookStore.items[id];
|
||||
const { outgoingWebhookStore } = useStore();
|
||||
const isNew = action === WebhookFormActionType.NEW;
|
||||
const isNewOrCopy = isNew || action === WebhookFormActionType.COPY;
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(data: Partial<OutgoingWebhook>) => {
|
||||
(id === 'new' ? outgoingWebhookStore.create(data) : outgoingWebhookStore.update(id, data)).then(() => {
|
||||
(isNewOrCopy ? outgoingWebhookStore.create(data) : outgoingWebhookStore.update(id, data)).then(() => {
|
||||
onHide();
|
||||
|
||||
onUpdate();
|
||||
});
|
||||
},
|
||||
[id]
|
||||
);
|
||||
|
||||
const getTemplateEditClickHandler = (formItem: FormItem, values, setFormFieldValue) => {
|
||||
return () => {
|
||||
const formValue = values[formItem.name];
|
||||
setTemplateToEdit({ value: formValue, displayName: undefined, description: undefined, name: formItem.name });
|
||||
setOnFormChangeFn({ fn: (value) => setFormFieldValue(value) });
|
||||
};
|
||||
};
|
||||
|
||||
const enrchField = (
|
||||
formItem: FormItem,
|
||||
disabled: boolean,
|
||||
renderedControl: React.ReactElement,
|
||||
values,
|
||||
setFormFieldValue
|
||||
) => {
|
||||
if (formItem.type === FormItemType.Monaco) {
|
||||
return (
|
||||
<div className={cx('form-row')}>
|
||||
<div className={cx('form-field')}>{renderedControl}</div>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
icon="edit"
|
||||
variant="secondary"
|
||||
onClick={getTemplateEditClickHandler(formItem, values, setFormFieldValue)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return renderedControl;
|
||||
};
|
||||
|
||||
if (
|
||||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) &&
|
||||
!outgoingWebhookStore.items[id]
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let data:
|
||||
| OutgoingWebhook
|
||||
| {
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
};
|
||||
|
||||
if (isNew) {
|
||||
data = { is_webhook_enabled: true, is_legacy: false };
|
||||
} else if (isNewOrCopy) {
|
||||
data = { ...outgoingWebhookStore.items[id], is_legacy: false, name: '' };
|
||||
} else {
|
||||
data = outgoingWebhookStore.items[id];
|
||||
}
|
||||
|
||||
if (
|
||||
(action === WebhookFormActionType.EDIT_SETTINGS || action === WebhookFormActionType.VIEW_LAST_RUN) &&
|
||||
!outgoingWebhookStore.items[id]
|
||||
) {
|
||||
// nothing to show if we open invalid ID for edit/last_run
|
||||
return null;
|
||||
}
|
||||
|
||||
const formElement = <GForm form={form} data={data} onSubmit={handleSubmit} onFieldRender={enrchField} />;
|
||||
|
||||
if (action === WebhookFormActionType.NEW || action === WebhookFormActionType.COPY) {
|
||||
// show just the creation form, not the tabs
|
||||
return (
|
||||
<>
|
||||
<Drawer scrollableContent title={'Create Outgoing Webhook'} onClose={onHide} closeOnMaskClick={false}>
|
||||
<div className="webhooks__drawerContent">{renderWebhookForm()}</div>
|
||||
</Drawer>
|
||||
{templateToEdit && (
|
||||
<WebhooksTemplateEditor
|
||||
id={id}
|
||||
handleSubmit={(value) => {
|
||||
onFormChangeFn?.fn(value);
|
||||
setTemplateToEdit(undefined);
|
||||
}}
|
||||
onHide={() => setTemplateToEdit(undefined)}
|
||||
template={templateToEdit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
scrollableContent
|
||||
title={id === 'new' ? 'Create Outgoing Webhook' : 'Edit Outgoing Webhook'}
|
||||
onClose={onHide}
|
||||
closeOnMaskClick={false}
|
||||
>
|
||||
<div className={cx('content')} data-testid="test__outgoingWebhookEditForm">
|
||||
<GForm form={form} data={data} onSubmit={handleSubmit} />
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<Button variant="secondary" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button form={form.name} type="submit">
|
||||
{id === 'new' ? 'Create' : 'Update'} Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</Drawer>
|
||||
// show tabbed drawer (edit/live_run)
|
||||
<>
|
||||
<Drawer scrollableContent title={'Outgoing webhook details'} onClose={onHide} closeOnMaskClick={false}>
|
||||
<div className={cx('webhooks__drawerContent')}>
|
||||
<TabsBar>
|
||||
<Tab
|
||||
key={WebhookTabs.Settings.key}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(WebhookTabs.Settings.key);
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/edit/${id}`);
|
||||
}}
|
||||
active={activeTab === WebhookTabs.Settings.key}
|
||||
label={WebhookTabs.Settings.value}
|
||||
/>
|
||||
|
||||
<Tab
|
||||
key={WebhookTabs.LastRun.key}
|
||||
onChangeTab={() => {
|
||||
setActiveTab(WebhookTabs.LastRun.key);
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/last_run/${id}`);
|
||||
}}
|
||||
active={activeTab === WebhookTabs.LastRun.key}
|
||||
label={WebhookTabs.LastRun.value}
|
||||
/>
|
||||
</TabsBar>
|
||||
|
||||
<WebhookTabsContent
|
||||
id={id}
|
||||
action={action}
|
||||
activeTab={activeTab}
|
||||
data={data}
|
||||
handleSubmit={handleSubmit}
|
||||
onDelete={onDelete}
|
||||
onHide={onHide}
|
||||
onUpdate={onUpdate}
|
||||
formElement={formElement}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
{templateToEdit && (
|
||||
<WebhooksTemplateEditor
|
||||
id={id}
|
||||
handleSubmit={(value) => {
|
||||
onFormChangeFn?.fn(value);
|
||||
setTemplateToEdit(undefined);
|
||||
}}
|
||||
onHide={() => setTemplateToEdit(undefined)}
|
||||
template={templateToEdit}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
function renderWebhookForm() {
|
||||
return (
|
||||
<>
|
||||
<div className={cx('content')}>
|
||||
<GForm form={form} data={data} onSubmit={handleSubmit} onFieldRender={enrchField} />
|
||||
<div className={cx('buttons')}>
|
||||
<HorizontalGroup justify={'flex-end'}>
|
||||
<Button variant="secondary" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button form={form.name} type="submit" disabled={data.is_legacy}>
|
||||
{isNewOrCopy ? 'Create' : 'Update'} Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
interface WebhookTabsProps {
|
||||
id: OutgoingWebhook['id'] | 'new';
|
||||
activeTab: string;
|
||||
action: WebhookFormActionType;
|
||||
data:
|
||||
| OutgoingWebhook
|
||||
| {
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
};
|
||||
onHide: () => void;
|
||||
onUpdate: () => void;
|
||||
onDelete: () => void;
|
||||
handleSubmit: (data: Partial<OutgoingWebhook>) => void;
|
||||
formElement: React.ReactElement;
|
||||
}
|
||||
|
||||
const WebhookTabsContent: React.FC<WebhookTabsProps> = ({
|
||||
id,
|
||||
action,
|
||||
activeTab,
|
||||
data,
|
||||
onHide,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
formElement,
|
||||
}) => {
|
||||
const [confirmationModal, setConfirmationModal] = useState<ConfirmModalProps>(undefined);
|
||||
|
||||
return (
|
||||
<div className={cx('tabs__content')}>
|
||||
{confirmationModal && (
|
||||
<ConfirmModal {...(confirmationModal as ConfirmModalProps)} onDismiss={() => setConfirmationModal(undefined)} />
|
||||
)}
|
||||
|
||||
{activeTab === WebhookTabs.Settings.key && (
|
||||
<>
|
||||
<div className={cx('content')}>
|
||||
{formElement}
|
||||
<div className={cx('buttons')}>
|
||||
<HorizontalGroup justify={'flex-end'}>
|
||||
<Button variant="secondary" onClick={onHide}>
|
||||
Cancel
|
||||
</Button>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button
|
||||
form={form.name}
|
||||
variant="destructive"
|
||||
type="button"
|
||||
disabled={data.is_legacy}
|
||||
onClick={() => {
|
||||
setConfirmationModal({
|
||||
isOpen: true,
|
||||
body: 'The action cannot be undone.',
|
||||
confirmText: 'Delete',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: onDelete,
|
||||
title: `Are you sure you want to delete webhook?`,
|
||||
} as ConfirmModalProps);
|
||||
}}
|
||||
>
|
||||
Delete Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button form={form.name} type="submit" disabled={data.is_legacy}>
|
||||
{action === WebhookFormActionType.NEW ? 'Create' : 'Update'} Webhook
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
{data.is_legacy ? (
|
||||
<div className={cx('content')}>
|
||||
<Text type="secondary">Legacy migrated webhooks are not editable. Make a copy to make changes.</Text>
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeTab === WebhookTabs.LastRun.key && <OutgoingWebhookStatus id={id} onUpdate={onUpdate} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default OutgoingWebhookForm;
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@ import { observer } from 'mobx-react';
|
|||
import Block from 'components/GBlock/Block';
|
||||
import SourceCode from 'components/SourceCode/SourceCode';
|
||||
import Text from 'components/Text/Text';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from 'containers/OutgoingWebhook2Form/OutgoingWebhook2Form.module.css';
|
||||
import styles from 'containers/OutgoingWebhookForm/OutgoingWebhookForm.module.css';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface OutgoingWebhook2StatusProps {
|
||||
id: OutgoingWebhook2['id'];
|
||||
interface OutgoingWebhookStatusProps {
|
||||
id: OutgoingWebhook['id'];
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -47,14 +47,14 @@ function format_response_field(str) {
|
|||
}
|
||||
}
|
||||
|
||||
const OutgoingWebhook2Status = observer((props: OutgoingWebhook2StatusProps) => {
|
||||
const OutgoingWebhookStatus = observer((props: OutgoingWebhookStatusProps) => {
|
||||
const { id } = props;
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const { outgoingWebhook2Store } = store;
|
||||
const { outgoingWebhookStore } = store;
|
||||
|
||||
const data = outgoingWebhook2Store.items[id];
|
||||
const data = outgoingWebhookStore.items[id];
|
||||
|
||||
return (
|
||||
<div className={cx('content')}>
|
||||
|
|
@ -119,4 +119,4 @@ const OutgoingWebhook2Status = observer((props: OutgoingWebhook2StatusProps) =>
|
|||
);
|
||||
});
|
||||
|
||||
export default OutgoingWebhook2Status;
|
||||
export default OutgoingWebhookStatus;
|
||||
|
|
@ -7,7 +7,7 @@ import { observer } from 'mobx-react';
|
|||
import Text from 'components/Text/Text';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { openErrorNotification } from 'utils';
|
||||
import { useDebouncedCallback } from 'utils/hooks';
|
||||
|
|
@ -25,7 +25,7 @@ interface TemplatePreviewProps {
|
|||
payload?: JSON;
|
||||
alertReceiveChannelId: AlertReceiveChannel['id'];
|
||||
alertGroupId?: Alert['pk'];
|
||||
outgoingWebhookId?: OutgoingWebhook2['id'];
|
||||
outgoingWebhookId?: OutgoingWebhook['id'];
|
||||
templatePage: TEMPLATE_PAGE;
|
||||
}
|
||||
interface ConditionalResult {
|
||||
|
|
@ -55,11 +55,11 @@ const TemplatePreview = observer((props: TemplatePreviewProps) => {
|
|||
const [conditionalResult, setConditionalResult] = useState<ConditionalResult>({});
|
||||
|
||||
const store = useStore();
|
||||
const { alertReceiveChannelStore, alertGroupStore, outgoingWebhook2Store } = store;
|
||||
const { alertReceiveChannelStore, alertGroupStore, outgoingWebhookStore } = store;
|
||||
|
||||
const handleTemplateBodyChange = useDebouncedCallback(() => {
|
||||
(templatePage === TEMPLATE_PAGE.Webhooks
|
||||
? outgoingWebhook2Store.renderPreview(outgoingWebhookId, templateName, templateBody, payload)
|
||||
? outgoingWebhookStore.renderPreview(outgoingWebhookId, templateName, templateBody, payload)
|
||||
: alertGroupId
|
||||
? alertGroupStore.renderPreview(alertGroupId, templateName, templateBody)
|
||||
: alertReceiveChannelStore.renderPreview(alertReceiveChannelId, templateName, templateBody, payload)
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ import Text from 'components/Text/Text';
|
|||
import styles from 'containers/IntegrationTemplate/IntegrationTemplate.module.scss';
|
||||
import TemplatePreview, { TEMPLATE_PAGE } from 'containers/TemplatePreview/TemplatePreview';
|
||||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface ResultProps {
|
||||
alertReceiveChannelId?: AlertReceiveChannel['id'];
|
||||
outgoingWebhookId?: OutgoingWebhook2['id'];
|
||||
outgoingWebhookId?: OutgoingWebhook['id'];
|
||||
templateBody: string;
|
||||
template: TemplateForEdit;
|
||||
isAlertGroupExisting?: boolean;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import TooltipBadge from 'components/TooltipBadge/TooltipBadge';
|
|||
import { AlertReceiveChannel } from 'models/alert_receive_channel/alert_receive_channel.types';
|
||||
import { AlertTemplatesDTO } from 'models/alert_templates';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import { OutgoingWebhook2, OutgoingWebhook2Response } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { OutgoingWebhook, OutgoingWebhookResponse } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { useStore } from 'state/useStore';
|
||||
|
||||
import styles from './TemplatesAlertGroupsList.module.css';
|
||||
|
|
@ -29,7 +29,7 @@ interface TemplatesAlertGroupsListProps {
|
|||
templatePage: TEMPLATE_PAGE;
|
||||
templates: AlertTemplatesDTO[];
|
||||
alertReceiveChannelId?: AlertReceiveChannel['id'];
|
||||
outgoingwebhookId?: OutgoingWebhook2['id'];
|
||||
outgoingwebhookId?: OutgoingWebhook['id'];
|
||||
heading?: string;
|
||||
|
||||
onSelectAlertGroup?: (alertGroup: Alert) => void;
|
||||
|
|
@ -52,7 +52,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => {
|
|||
const store = useStore();
|
||||
const [alertGroupsList, setAlertGroupsList] = useState(undefined);
|
||||
const [outgoingWebhookLastResponses, setOutgoingWebhookLastResponses] =
|
||||
useState<OutgoingWebhook2Response[]>(undefined);
|
||||
useState<OutgoingWebhookResponse[]>(undefined);
|
||||
|
||||
const [selectedTitle, setSelectedTitle] = useState<string>(undefined);
|
||||
const [selectedPayload, setSelectedPayload] = useState<string>(undefined);
|
||||
|
|
@ -61,7 +61,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => {
|
|||
useEffect(() => {
|
||||
if (templatePage === TEMPLATE_PAGE.Webhooks) {
|
||||
if (outgoingwebhookId !== 'new') {
|
||||
store.outgoingWebhook2Store.getLastResponses(outgoingwebhookId).then(setOutgoingWebhookLastResponses);
|
||||
store.outgoingWebhookStore.getLastResponses(outgoingwebhookId).then(setOutgoingWebhookLastResponses);
|
||||
}
|
||||
} else if (templatePage === TEMPLATE_PAGE.Integrations) {
|
||||
store.alertGroupStore.getAlertGroupsForIntegration(alertReceiveChannelId).then((result) => {
|
||||
|
|
@ -117,7 +117,7 @@ const TemplatesAlertGroupsList = (props: TemplatesAlertGroupsListProps) => {
|
|||
|
||||
// for Outgoing webhooks
|
||||
|
||||
const handleOutgoingWebhookResponseSelect = (response: OutgoingWebhook2Response) => {
|
||||
const handleOutgoingWebhookResponseSelect = (response: OutgoingWebhookResponse) => {
|
||||
setSelectedTitle(response.timestamp);
|
||||
|
||||
setSelectedPayload(JSON.parse(response.event_data));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import styles from 'containers/IntegrationTemplate/IntegrationTemplate.module.sc
|
|||
import TemplateResult from 'containers/TemplateResult/TemplateResult';
|
||||
import TemplatesAlertGroupsList, { TEMPLATE_PAGE } from 'containers/TemplatesAlertGroupsList/TemplatesAlertGroupsList';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { waitForElement } from 'utils/DOM';
|
||||
import { UserActions } from 'utils/authorization';
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ interface Template {
|
|||
|
||||
interface WebhooksTemplateEditorProps {
|
||||
template: Template;
|
||||
id: OutgoingWebhook2['id'];
|
||||
id: OutgoingWebhook['id'];
|
||||
onHide: () => void;
|
||||
handleSubmit: (template: string) => void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import { AlertReceiveChannel } from './alert_receive_channel/alert_receive_channel.types';
|
||||
|
||||
export interface ActionDTO {
|
||||
id: string;
|
||||
name: string;
|
||||
webhook: string;
|
||||
user: string;
|
||||
password: string;
|
||||
alert_receive_channel: AlertReceiveChannel['id'];
|
||||
data: string;
|
||||
authorization_header: string;
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { omit } from 'lodash-es';
|
||||
import { action, observable } from 'mobx';
|
||||
|
||||
import { ActionDTO } from 'models/action';
|
||||
import { AlertTemplatesDTO } from 'models/alert_templates';
|
||||
import { Alert } from 'models/alertgroup/alertgroup.types';
|
||||
import BaseStore from 'models/base_store';
|
||||
|
|
@ -359,28 +358,6 @@ export class AlertReceiveChannelStore extends BaseStore {
|
|||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async updateCustomButtons(alertReceiveChannelId: AlertReceiveChannel['id']) {
|
||||
const response = await makeRequest(`/custom_buttons/`, {
|
||||
params: {
|
||||
alert_receive_channel: alertReceiveChannelId,
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
this.actions = {
|
||||
...this.actions,
|
||||
[alertReceiveChannelId]: response,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteCustomButton(id: ActionDTO['id']) {
|
||||
await makeRequest(`/custom_buttons/${id}/`, {
|
||||
method: 'DELETE',
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
async getAccessLogs(alertReceiveChannelId: AlertReceiveChannel['id']) {
|
||||
const { integration_log } = await makeRequest(`/alert_receive_channel_access_log/${alertReceiveChannelId}/`, {});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { Channel } from 'models/channel';
|
|||
import { Schedule } from 'models/schedule/schedule.types';
|
||||
import { UserGroup } from 'models/user_group/user_group.types';
|
||||
|
||||
import { ActionDTO } from './action';
|
||||
import { ChannelFilter } from './channel_filter';
|
||||
import { ScheduleDTO } from './schedule';
|
||||
import { UserDTO as User } from './user';
|
||||
|
|
@ -20,7 +19,6 @@ export interface EscalationPolicyType {
|
|||
to_time: string | null;
|
||||
notify_to_schedule: ScheduleDTO['id'] | null;
|
||||
notify_to_channel: Channel['id'] | null;
|
||||
custom_button_trigger: ActionDTO['id'] | null;
|
||||
notify_to_group: UserGroup['id'];
|
||||
notify_schedule: Schedule['id'];
|
||||
}
|
||||
|
|
@ -34,6 +32,5 @@ export function prepareEscalationPolicy(value: EscalationPolicyType): Escalation
|
|||
from_time: null,
|
||||
to_time: null,
|
||||
notify_to_schedule: null,
|
||||
custom_button_trigger: null,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ActionDTO } from 'models/action';
|
||||
import { Channel } from 'models/channel';
|
||||
import { EscalationChain } from 'models/escalation_chain/escalation_chain.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { Schedule } from 'models/schedule/schedule.types';
|
||||
import { User } from 'models/user/user.types';
|
||||
import { UserGroup } from 'models/user_group/user_group.types';
|
||||
|
|
@ -17,8 +17,7 @@ export interface EscalationPolicy {
|
|||
from_time: string | null;
|
||||
to_time: string | null;
|
||||
notify_to_channel: Channel['id'] | null;
|
||||
custom_button_trigger: ActionDTO['id'] | null;
|
||||
custom_webhook: ActionDTO['id'] | null;
|
||||
custom_webhook: OutgoingWebhook['id'] | null;
|
||||
notify_to_group: UserGroup['id'] | null;
|
||||
notify_schedule: Schedule['id'] | null;
|
||||
important: boolean | null;
|
||||
|
|
|
|||
|
|
@ -13,13 +13,10 @@ export class OutgoingWebhookStore extends BaseStore {
|
|||
@observable.shallow
|
||||
searchResult: { [key: string]: Array<OutgoingWebhook['id']> } = {};
|
||||
|
||||
@observable
|
||||
incidentFilters: any;
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore);
|
||||
|
||||
this.path = '/custom_buttons/';
|
||||
this.path = '/webhooks/';
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
@ -46,26 +43,11 @@ export class OutgoingWebhookStore extends BaseStore {
|
|||
|
||||
@action
|
||||
async updateItem(id: OutgoingWebhook['id'], fromOrganization = false) {
|
||||
let outgoingWebhook;
|
||||
|
||||
try {
|
||||
outgoingWebhook = await this.getById(id, true, fromOrganization);
|
||||
} catch (error) {
|
||||
if (error.response.data.error_code === 'wrong_team') {
|
||||
outgoingWebhook = {
|
||||
id,
|
||||
name: '🔒 Private outgoing webhook',
|
||||
private: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (outgoingWebhook) {
|
||||
this.items = {
|
||||
...this.items,
|
||||
[id]: outgoingWebhook,
|
||||
};
|
||||
}
|
||||
const response = await this.getById(id, false, fromOrganization);
|
||||
this.items = {
|
||||
...this.items,
|
||||
[id]: response,
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
@ -95,13 +77,6 @@ export class OutgoingWebhookStore extends BaseStore {
|
|||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async updateOutgoingWebhooksFilters(params: any) {
|
||||
this.incidentFilters = params;
|
||||
|
||||
this.updateItems();
|
||||
}
|
||||
|
||||
getSearchResult(query = '') {
|
||||
if (!this.searchResult[query]) {
|
||||
return undefined;
|
||||
|
|
@ -109,4 +84,17 @@ export class OutgoingWebhookStore extends BaseStore {
|
|||
|
||||
return this.searchResult[query].map((outgoingWebhookId: OutgoingWebhook['id']) => this.items[outgoingWebhookId]);
|
||||
}
|
||||
|
||||
async getLastResponses(id: OutgoingWebhook['id']) {
|
||||
const result = await makeRequest(`${this.path}${id}/responses`, {});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async renderPreview(id: OutgoingWebhook['id'], template_name: string, template_body: string, payload) {
|
||||
return await makeRequest(`${this.path}${id}/preview_template/`, {
|
||||
method: 'POST',
|
||||
data: { template_name, template_body, payload },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,30 @@ import { GrafanaTeam } from 'models/grafana_team/grafana_team.types';
|
|||
export interface OutgoingWebhook {
|
||||
authorization_header: string;
|
||||
data: string;
|
||||
forward_whole_payload: boolean;
|
||||
forward_all: boolean;
|
||||
http_method: string;
|
||||
id: string;
|
||||
name: string;
|
||||
password: string;
|
||||
team: GrafanaTeam['id'];
|
||||
user: null;
|
||||
webhook: string;
|
||||
trigger_type: number;
|
||||
trigger_type_name: string;
|
||||
url: string;
|
||||
username: null;
|
||||
headers: string;
|
||||
trigger_template: string;
|
||||
last_response_log?: OutgoingWebhookResponse;
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
}
|
||||
|
||||
export interface OutgoingWebhookResponse {
|
||||
timestamp: string;
|
||||
url: string;
|
||||
request_trigger: string;
|
||||
request_headers: string;
|
||||
request_data: string;
|
||||
status_code: string;
|
||||
content: string;
|
||||
event_data: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,110 +0,0 @@
|
|||
import { action, observable } from 'mobx';
|
||||
|
||||
import BaseStore from 'models/base_store';
|
||||
import { makeRequest } from 'network';
|
||||
import { RootStore } from 'state';
|
||||
|
||||
import { OutgoingWebhook2 } from './outgoing_webhook_2.types';
|
||||
|
||||
export class OutgoingWebhook2Store extends BaseStore {
|
||||
@observable.shallow
|
||||
items: { [id: string]: OutgoingWebhook2 } = {};
|
||||
|
||||
@observable.shallow
|
||||
searchResult: { [key: string]: Array<OutgoingWebhook2['id']> } = {};
|
||||
|
||||
@observable
|
||||
incidentFilters: any;
|
||||
|
||||
constructor(rootStore: RootStore) {
|
||||
super(rootStore);
|
||||
|
||||
this.path = '/webhooks/';
|
||||
}
|
||||
|
||||
@action
|
||||
async loadItem(id: OutgoingWebhook2['id'], skipErrorHandling = false): Promise<OutgoingWebhook2> {
|
||||
const outgoingWebhook2 = await this.getById(id, skipErrorHandling);
|
||||
|
||||
this.items = {
|
||||
...this.items,
|
||||
[id]: outgoingWebhook2,
|
||||
};
|
||||
|
||||
return outgoingWebhook2;
|
||||
}
|
||||
|
||||
@action
|
||||
async updateById(id: OutgoingWebhook2['id']) {
|
||||
const response = await this.getById(id);
|
||||
|
||||
this.items = {
|
||||
...this.items,
|
||||
[id]: response,
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async updateItem(id: OutgoingWebhook2['id'], fromOrganization = false) {
|
||||
const response = await this.getById(id, false, fromOrganization);
|
||||
this.items = {
|
||||
...this.items,
|
||||
[id]: response,
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async updateItems(query: any = '') {
|
||||
const params = typeof query === 'string' ? { search: query } : query;
|
||||
|
||||
const results = await makeRequest(`${this.path}`, {
|
||||
params,
|
||||
});
|
||||
|
||||
this.items = {
|
||||
...this.items,
|
||||
...results.reduce(
|
||||
(acc: { [key: number]: OutgoingWebhook2 }, item: OutgoingWebhook2) => ({
|
||||
...acc,
|
||||
[item.id]: item,
|
||||
}),
|
||||
{}
|
||||
),
|
||||
};
|
||||
|
||||
const key = typeof query === 'string' ? query : '';
|
||||
|
||||
this.searchResult = {
|
||||
...this.searchResult,
|
||||
[key]: results.map((item: OutgoingWebhook2) => item.id),
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async updateOutgoingWebhooks2Filters(params: any) {
|
||||
this.incidentFilters = params;
|
||||
|
||||
this.updateItems();
|
||||
}
|
||||
|
||||
getSearchResult(query = '') {
|
||||
if (!this.searchResult[query]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.searchResult[query].map((outgoingWebhook2Id: OutgoingWebhook2['id']) => this.items[outgoingWebhook2Id]);
|
||||
}
|
||||
|
||||
async getLastResponses(id: OutgoingWebhook2['id']) {
|
||||
const result = await makeRequest(`${this.path}${id}/responses`, {});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async renderPreview(id: OutgoingWebhook2['id'], template_name: string, template_body: string, payload) {
|
||||
return await makeRequest(`${this.path}${id}/preview_template/`, {
|
||||
method: 'POST',
|
||||
data: { template_name, template_body, payload },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { GrafanaTeam } from 'models/grafana_team/grafana_team.types';
|
||||
|
||||
export interface OutgoingWebhook2 {
|
||||
authorization_header: string;
|
||||
data: string;
|
||||
forward_all: boolean;
|
||||
http_method: string;
|
||||
id: string;
|
||||
name: string;
|
||||
password: string;
|
||||
team: GrafanaTeam['id'];
|
||||
trigger_type: number;
|
||||
trigger_type_name: string;
|
||||
url: string;
|
||||
username: null;
|
||||
headers: string;
|
||||
trigger_template: string;
|
||||
last_response_log?: OutgoingWebhook2Response;
|
||||
is_webhook_enabled: boolean;
|
||||
is_legacy: boolean;
|
||||
}
|
||||
|
||||
export interface OutgoingWebhook2Response {
|
||||
timestamp: string;
|
||||
url: string;
|
||||
request_trigger: string;
|
||||
request_headers: string;
|
||||
request_data: string;
|
||||
status_code: string;
|
||||
content: string;
|
||||
event_data: string;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
@ -1,12 +1,24 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, HorizontalGroup } from '@grafana/ui';
|
||||
import {
|
||||
Button,
|
||||
ConfirmModal,
|
||||
ConfirmModalProps,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
IconButton,
|
||||
VerticalGroup,
|
||||
WithContextMenu,
|
||||
} from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment-timezone';
|
||||
import LegacyNavHeading from 'navbar/LegacyNavHeading';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import GTable from 'components/GTable/GTable';
|
||||
import HamburgerMenu from 'components/HamburgerMenu/HamburgerMenu';
|
||||
import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
|
||||
import {
|
||||
getWrongTeamResponseInfo,
|
||||
|
|
@ -14,37 +26,43 @@ import {
|
|||
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
import WithConfirm from 'components/WithConfirm/WithConfirm';
|
||||
import OutgoingWebhookForm from 'containers/OutgoingWebhookForm/OutgoingWebhookForm';
|
||||
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
|
||||
import TeamName from 'containers/TeamName/TeamName';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { ActionDTO } from 'models/action';
|
||||
import { FiltersValues } from 'models/filters/filters.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import { openErrorNotification, openNotification } from 'utils';
|
||||
import { isUserActionAllowed, UserActions } from 'utils/authorization';
|
||||
import { PLUGIN_ROOT } from 'utils/consts';
|
||||
|
||||
import styles from './OutgoingWebhooks.module.css';
|
||||
import styles from './OutgoingWebhooks.module.scss';
|
||||
import { WebhookFormActionType } from './OutgoingWebhooks.types';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface OutgoingWebhooksProps extends WithStoreProps, PageProps, RouteComponentProps<{ id: string }> {}
|
||||
interface OutgoingWebhooksProps
|
||||
extends WithStoreProps,
|
||||
PageProps,
|
||||
RouteComponentProps<{ id: string; action: string }> {}
|
||||
|
||||
interface OutgoingWebhooksState extends PageBaseState {
|
||||
outgoingWebhookIdToEdit?: OutgoingWebhook['id'] | 'new';
|
||||
outgoingWebhookAction?: WebhookFormActionType;
|
||||
outgoingWebhookId?: OutgoingWebhook['id'];
|
||||
confirmationModal: ConfirmModalProps;
|
||||
}
|
||||
|
||||
@observer
|
||||
class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWebhooksState> {
|
||||
state: OutgoingWebhooksState = {
|
||||
errorData: initErrorDataState(),
|
||||
confirmationModal: undefined,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: OutgoingWebhooksProps) {
|
||||
if (prevProps.match.params.id !== this.props.match.params.id) {
|
||||
if (prevProps.match.params.id !== this.props.match.params.id && !this.state.outgoingWebhookAction) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
}
|
||||
|
|
@ -52,56 +70,71 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
parseQueryParams = async () => {
|
||||
this.setState((_prevState) => ({
|
||||
errorData: initErrorDataState(),
|
||||
outgoingWebhookIdToEdit: undefined,
|
||||
outgoingWebhookId: undefined,
|
||||
})); // reset state on query parse
|
||||
|
||||
const {
|
||||
store,
|
||||
match: {
|
||||
params: { id },
|
||||
params: { id, action },
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (!id) {
|
||||
return;
|
||||
if (action) {
|
||||
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: convertWebhookUrlToAction(action) });
|
||||
}
|
||||
|
||||
let outgoingWebhook: OutgoingWebhook | void = undefined;
|
||||
const isNewWebhook = id === 'new';
|
||||
|
||||
if (!isNewWebhook) {
|
||||
outgoingWebhook = await store.outgoingWebhookStore
|
||||
if (isNewWebhook) {
|
||||
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: WebhookFormActionType.NEW });
|
||||
} else if (id) {
|
||||
await store.outgoingWebhookStore
|
||||
.loadItem(id, true)
|
||||
.catch((error) => this.setState({ errorData: { ...getWrongTeamResponseInfo(error) } }));
|
||||
}
|
||||
|
||||
if (outgoingWebhook || isNewWebhook) {
|
||||
this.setState({ outgoingWebhookIdToEdit: id });
|
||||
.catch((error) =>
|
||||
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) }, outgoingWebhookAction: undefined })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
update = () => {
|
||||
const { store } = this.props;
|
||||
|
||||
return store.outgoingWebhookStore.updateItems();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { store, query } = this.props;
|
||||
const { outgoingWebhookIdToEdit, errorData } = this.state;
|
||||
const {
|
||||
store,
|
||||
history,
|
||||
match: {
|
||||
params: { id },
|
||||
},
|
||||
} = this.props;
|
||||
const { outgoingWebhookId, outgoingWebhookAction, errorData, confirmationModal } = this.state;
|
||||
|
||||
const webhooks = store.outgoingWebhookStore.getSearchResult();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
width: '35%',
|
||||
width: '25%',
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
render: this.renderName,
|
||||
},
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Trigger type',
|
||||
dataIndex: 'trigger_type_name',
|
||||
},
|
||||
{
|
||||
width: '35%',
|
||||
title: 'Url',
|
||||
dataIndex: 'webhook',
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
render: this.renderUrl,
|
||||
},
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Last run',
|
||||
render: this.renderLastRun,
|
||||
},
|
||||
{
|
||||
width: '15%',
|
||||
|
|
@ -109,7 +142,7 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
render: (item: OutgoingWebhook) => this.renderTeam(item, store.grafanaTeamStore.items),
|
||||
},
|
||||
{
|
||||
width: '15%',
|
||||
width: '20%',
|
||||
key: 'action',
|
||||
render: this.renderActionButtons,
|
||||
},
|
||||
|
|
@ -120,19 +153,32 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
errorData={errorData}
|
||||
objectName="outgoing webhook"
|
||||
pageName="outgoing_webhooks"
|
||||
itemNotFoundMessage={`Outgoing webhook with id=${query?.id} is not found. Please select outgoing webhook from the list.`}
|
||||
itemNotFoundMessage={`Outgoing webhook with id=${id} was not found. Please select outgoing webhook from the list.`}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
{confirmationModal && (
|
||||
<ConfirmModal
|
||||
{...(confirmationModal as ConfirmModalProps)}
|
||||
onDismiss={() =>
|
||||
this.setState({
|
||||
confirmationModal: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={cx('root')}>
|
||||
{this.renderOutgoingWebhooksFilters()}
|
||||
<GTable
|
||||
emptyText={webhooks ? 'No outgoing webhooks found' : 'Loading...'}
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Outgoing Webhooks</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
<div className="header__title">
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Outgoing Webhooks</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
</div>
|
||||
<div className="u-pull-right">
|
||||
<PluginLink
|
||||
query={{ page: 'outgoing_webhooks', id: 'new' }}
|
||||
|
|
@ -140,7 +186,7 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button variant="primary" icon="plus">
|
||||
New outgoing webhook
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</PluginLink>
|
||||
|
|
@ -152,11 +198,19 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
{outgoingWebhookIdToEdit && (
|
||||
|
||||
{outgoingWebhookId && outgoingWebhookAction && (
|
||||
<OutgoingWebhookForm
|
||||
id={outgoingWebhookIdToEdit}
|
||||
id={outgoingWebhookId}
|
||||
action={outgoingWebhookAction}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
onDelete={() => {
|
||||
this.onDeleteClick(outgoingWebhookId).then(() => {
|
||||
this.setState({ outgoingWebhookId: undefined, outgoingWebhookAction: undefined });
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -171,7 +225,7 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
<div className={cx('filters')}>
|
||||
<RemoteFilters
|
||||
query={query}
|
||||
page="outgoing_webhooks"
|
||||
page="webhooks"
|
||||
grafanaTeamStore={store.grafanaTeamStore}
|
||||
onChange={this.handleFiltersChange}
|
||||
/>
|
||||
|
|
@ -195,50 +249,198 @@ class OutgoingWebhooks extends React.Component<OutgoingWebhooksProps, OutgoingWe
|
|||
return <TeamName team={teams[record.team]} />;
|
||||
}
|
||||
|
||||
renderActionButtons = (record: ActionDTO) => {
|
||||
renderActionButtons = (record: OutgoingWebhook) => {
|
||||
return (
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<WithPermissionControlTooltip key={'edit_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button onClick={this.getEditClickHandler(record.id)} fill="text">
|
||||
Edit
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
<WithPermissionControlTooltip key={'delete_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<WithConfirm>
|
||||
<Button onClick={this.getDeleteClickHandler(record.id)} fill="text" variant="destructive">
|
||||
Delete
|
||||
</Button>
|
||||
</WithConfirm>
|
||||
</WithPermissionControlTooltip>
|
||||
</HorizontalGroup>
|
||||
<WithContextMenu
|
||||
renderMenuItems={() => (
|
||||
<div className={cx('hamburgerMenu')}>
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onLastRunClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'status_action'} userAction={UserActions.OutgoingWebhooksRead}>
|
||||
<Text type="primary">View Last Run</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onEditClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'edit_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">Edit settings</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx('hamburgerMenu__item')}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Confirm',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.onDisableWebhook(record.id, !record.is_webhook_enabled),
|
||||
title: `Are you sure you want to ${record.is_webhook_enabled ? 'disable' : 'enable'} webhook?`,
|
||||
} as ConfirmModalProps,
|
||||
})
|
||||
}
|
||||
>
|
||||
<WithPermissionControlTooltip key={'disable_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">{record.is_webhook_enabled ? 'Disable' : 'Enable'}</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onCopyClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'copy_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">Make a copy</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<CopyToClipboard text={record.id} onCopy={() => openNotification('Webhook ID has been copied')}>
|
||||
<div className={cx('hamburgerMenu__item')}>
|
||||
<HorizontalGroup type="primary" spacing="xs">
|
||||
<Icon name="clipboard-alt" />
|
||||
<Text type="primary">UID: {record.id}</Text>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
|
||||
<div className={cx('thin-line-break')} />
|
||||
|
||||
<div
|
||||
className={cx('hamburgerMenu__item')}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Confirm',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.onDeleteClick(record.id),
|
||||
body: 'The action cannot be undone.',
|
||||
title: `Are you sure you want to delete webhook?`,
|
||||
} as Partial<ConfirmModalProps> as ConfirmModalProps,
|
||||
})
|
||||
}
|
||||
>
|
||||
<WithPermissionControlTooltip key={'delete_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<IconButton tooltip="Remove" tooltipPlacement="top" variant="destructive" name="trash-alt" />
|
||||
<Text type="danger">Delete Webhook</Text>
|
||||
</HorizontalGroup>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{({ openMenu }) => <HamburgerMenu openMenu={openMenu} listBorder={2} listWidth={225} withBackground />}
|
||||
</WithContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
getDeleteClickHandler = (id: OutgoingWebhook['id']) => {
|
||||
renderName(name: String) {
|
||||
return (
|
||||
<div className="u-break-word">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUrl(url: string) {
|
||||
return (
|
||||
<div className="u-break-word">
|
||||
<span>{url}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLastRun(record: OutgoingWebhook) {
|
||||
const lastRunMoment = moment(record.last_response_log?.timestamp);
|
||||
|
||||
return !record.is_webhook_enabled ? (
|
||||
<Text type="secondary">Disabled</Text>
|
||||
) : (
|
||||
<VerticalGroup spacing="none">
|
||||
<Text type="secondary">{lastRunMoment.isValid() ? lastRunMoment.format('MMM DD, YYYY') : '-'}</Text>
|
||||
<Text type="secondary">{lastRunMoment.isValid() ? lastRunMoment.format('HH:mm') : ''}</Text>
|
||||
<Text type="secondary">
|
||||
{lastRunMoment.isValid()
|
||||
? record.last_response_log?.status_code
|
||||
? 'Status: ' + record.last_response_log?.status_code
|
||||
: 'Check Status'
|
||||
: ''}
|
||||
</Text>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
onDeleteClick = (id: OutgoingWebhook['id']): Promise<void> => {
|
||||
const { store } = this.props;
|
||||
return () => {
|
||||
store.alertReceiveChannelStore.deleteCustomButton(id).then(this.update);
|
||||
};
|
||||
return store.outgoingWebhookStore
|
||||
.delete(id)
|
||||
.then(this.update)
|
||||
.then(() => openNotification('Webhook has been removed'))
|
||||
.catch(() => openNotification('Webook could not been removed'))
|
||||
.finally(() => this.setState({ confirmationModal: undefined }));
|
||||
};
|
||||
|
||||
getEditClickHandler = (id: OutgoingWebhook['id']) => {
|
||||
onEditClick = (id: OutgoingWebhook['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
return () => {
|
||||
this.setState({ outgoingWebhookIdToEdit: id });
|
||||
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: WebhookFormActionType.EDIT_SETTINGS }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/edit/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/${id}`);
|
||||
onCopyClick = (id: OutgoingWebhook['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: WebhookFormActionType.COPY }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/copy/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
onDisableWebhook = (id: OutgoingWebhook['id'], isEnabled: boolean) => {
|
||||
const {
|
||||
store: { outgoingWebhookStore },
|
||||
} = this.props;
|
||||
|
||||
const data = {
|
||||
...{ ...outgoingWebhookStore.items[id], is_webhook_enabled: isEnabled },
|
||||
is_legacy: false,
|
||||
};
|
||||
|
||||
outgoingWebhookStore
|
||||
.update(id, data)
|
||||
.then(() => this.update())
|
||||
.then(() => openNotification(`Webhook has been ${isEnabled ? 'enabled' : 'disabled'}`))
|
||||
.catch(() => openErrorNotification('Webhook could not been updated'))
|
||||
.finally(() => this.setState({ confirmationModal: undefined }));
|
||||
};
|
||||
|
||||
onLastRunClick = (id: OutgoingWebhook['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhookId: id, outgoingWebhookAction: WebhookFormActionType.VIEW_LAST_RUN }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/last_run/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
handleOutgoingWebhookFormHide = () => {
|
||||
const { history } = this.props;
|
||||
this.setState({ outgoingWebhookIdToEdit: undefined });
|
||||
|
||||
this.setState({ outgoingWebhookId: undefined, outgoingWebhookAction: undefined });
|
||||
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
|
||||
};
|
||||
}
|
||||
|
||||
function convertWebhookUrlToAction(urlAction: string) {
|
||||
if (urlAction === 'new') {
|
||||
return WebhookFormActionType.NEW;
|
||||
} else if (urlAction === 'copy') {
|
||||
return WebhookFormActionType.COPY;
|
||||
} else if (urlAction === 'edit') {
|
||||
return WebhookFormActionType.EDIT_SETTINGS;
|
||||
} else {
|
||||
return WebhookFormActionType.VIEW_LAST_RUN;
|
||||
}
|
||||
}
|
||||
|
||||
export { OutgoingWebhooks };
|
||||
|
||||
export default withRouter(withMobXProviderContext(OutgoingWebhooks));
|
||||
|
|
|
|||
|
|
@ -1,450 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
ConfirmModal,
|
||||
ConfirmModalProps,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
IconButton,
|
||||
VerticalGroup,
|
||||
WithContextMenu,
|
||||
} from '@grafana/ui';
|
||||
import cn from 'classnames/bind';
|
||||
import { observer } from 'mobx-react';
|
||||
import moment from 'moment-timezone';
|
||||
import LegacyNavHeading from 'navbar/LegacyNavHeading';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||
|
||||
import GTable from 'components/GTable/GTable';
|
||||
import HamburgerMenu from 'components/HamburgerMenu/HamburgerMenu';
|
||||
import PageErrorHandlingWrapper, { PageBaseState } from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper';
|
||||
import {
|
||||
getWrongTeamResponseInfo,
|
||||
initErrorDataState,
|
||||
} from 'components/PageErrorHandlingWrapper/PageErrorHandlingWrapper.helpers';
|
||||
import PluginLink from 'components/PluginLink/PluginLink';
|
||||
import Text from 'components/Text/Text';
|
||||
import OutgoingWebhook2Form from 'containers/OutgoingWebhook2Form/OutgoingWebhook2Form';
|
||||
import RemoteFilters from 'containers/RemoteFilters/RemoteFilters';
|
||||
import TeamName from 'containers/TeamName/TeamName';
|
||||
import { WithPermissionControlTooltip } from 'containers/WithPermissionControl/WithPermissionControlTooltip';
|
||||
import { FiltersValues } from 'models/filters/filters.types';
|
||||
import { OutgoingWebhook } from 'models/outgoing_webhook/outgoing_webhook.types';
|
||||
import { OutgoingWebhook2 } from 'models/outgoing_webhook_2/outgoing_webhook_2.types';
|
||||
import { AppFeature } from 'state/features';
|
||||
import { PageProps, WithStoreProps } from 'state/types';
|
||||
import { withMobXProviderContext } from 'state/withStore';
|
||||
import { openErrorNotification, openNotification } from 'utils';
|
||||
import { isUserActionAllowed, UserActions } from 'utils/authorization';
|
||||
import { PLUGIN_ROOT } from 'utils/consts';
|
||||
|
||||
import styles from './OutgoingWebhooks2.module.scss';
|
||||
import { WebhookFormActionType } from './OutgoingWebhooks2.types';
|
||||
|
||||
const cx = cn.bind(styles);
|
||||
|
||||
interface OutgoingWebhooks2Props
|
||||
extends WithStoreProps,
|
||||
PageProps,
|
||||
RouteComponentProps<{ id: string; action: string }> {}
|
||||
|
||||
interface OutgoingWebhooks2State extends PageBaseState {
|
||||
outgoingWebhook2Action?: WebhookFormActionType;
|
||||
outgoingWebhook2Id?: OutgoingWebhook2['id'];
|
||||
confirmationModal: ConfirmModalProps;
|
||||
}
|
||||
|
||||
@observer
|
||||
class OutgoingWebhooks2 extends React.Component<OutgoingWebhooks2Props, OutgoingWebhooks2State> {
|
||||
state: OutgoingWebhooks2State = {
|
||||
errorData: initErrorDataState(),
|
||||
confirmationModal: undefined,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps: OutgoingWebhooks2Props) {
|
||||
if (prevProps.match.params.id !== this.props.match.params.id && !this.state.outgoingWebhook2Action) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
}
|
||||
|
||||
parseQueryParams = async () => {
|
||||
this.setState((_prevState) => ({
|
||||
errorData: initErrorDataState(),
|
||||
outgoingWebhook2Id: undefined,
|
||||
})); // reset state on query parse
|
||||
|
||||
const {
|
||||
store,
|
||||
match: {
|
||||
params: { id, action },
|
||||
},
|
||||
} = this.props;
|
||||
|
||||
if (action) {
|
||||
this.setState({ outgoingWebhook2Id: id, outgoingWebhook2Action: convertWebhookUrlToAction(action) });
|
||||
}
|
||||
|
||||
const isNewWebhook = id === 'new';
|
||||
if (isNewWebhook) {
|
||||
this.setState({ outgoingWebhook2Id: id, outgoingWebhook2Action: WebhookFormActionType.NEW });
|
||||
} else if (id) {
|
||||
await store.outgoingWebhook2Store
|
||||
.loadItem(id, true)
|
||||
.catch((error) =>
|
||||
this.setState({ errorData: { ...getWrongTeamResponseInfo(error) }, outgoingWebhook2Action: undefined })
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
update = () => {
|
||||
const { store } = this.props;
|
||||
return store.outgoingWebhook2Store.updateItems();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
store,
|
||||
history,
|
||||
match: {
|
||||
params: { id },
|
||||
},
|
||||
} = this.props;
|
||||
const { outgoingWebhook2Id, outgoingWebhook2Action, errorData, confirmationModal } = this.state;
|
||||
|
||||
const webhooks = store.outgoingWebhook2Store.getSearchResult();
|
||||
|
||||
const columns = [
|
||||
{
|
||||
width: '25%',
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
render: this.renderName,
|
||||
},
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Trigger type',
|
||||
dataIndex: 'trigger_type_name',
|
||||
},
|
||||
{
|
||||
width: '35%',
|
||||
title: 'URL',
|
||||
dataIndex: 'url',
|
||||
render: this.renderUrl,
|
||||
},
|
||||
{
|
||||
width: '10%',
|
||||
title: 'Last run',
|
||||
render: this.renderLastRun,
|
||||
},
|
||||
{
|
||||
width: '15%',
|
||||
title: 'Team',
|
||||
render: (item: OutgoingWebhook) => this.renderTeam(item, store.grafanaTeamStore.items),
|
||||
},
|
||||
{
|
||||
width: '20%',
|
||||
key: 'action',
|
||||
render: this.renderActionButtons,
|
||||
},
|
||||
];
|
||||
|
||||
return store.hasFeature(AppFeature.Webhooks2) ? (
|
||||
<PageErrorHandlingWrapper
|
||||
errorData={errorData}
|
||||
objectName="outgoing webhook 2"
|
||||
pageName="outgoing_webhooks_2"
|
||||
itemNotFoundMessage={`Outgoing webhook with id=${id} was not found. Please select outgoing webhook from the list.`}
|
||||
>
|
||||
{() => (
|
||||
<>
|
||||
{confirmationModal && (
|
||||
<ConfirmModal
|
||||
{...(confirmationModal as ConfirmModalProps)}
|
||||
onDismiss={() =>
|
||||
this.setState({
|
||||
confirmationModal: undefined,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className={cx('root')}>
|
||||
{this.renderOutgoingWebhooksFilters()}
|
||||
<GTable
|
||||
emptyText={webhooks ? 'No outgoing webhooks found' : 'Loading...'}
|
||||
title={() => (
|
||||
<div className={cx('header')}>
|
||||
<div className="header__title">
|
||||
<LegacyNavHeading>
|
||||
<Text.Title level={3}>Outgoing Webhooks</Text.Title>
|
||||
</LegacyNavHeading>
|
||||
</div>
|
||||
<div className="u-pull-right">
|
||||
<PluginLink
|
||||
query={{ page: 'outgoing_webhooks', id: 'new' }}
|
||||
disabled={!isUserActionAllowed(UserActions.OutgoingWebhooksWrite)}
|
||||
>
|
||||
<WithPermissionControlTooltip userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Button variant="primary" icon="plus">
|
||||
Create
|
||||
</Button>
|
||||
</WithPermissionControlTooltip>
|
||||
</PluginLink>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
data={webhooks}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{outgoingWebhook2Id && outgoingWebhook2Action && (
|
||||
<OutgoingWebhook2Form
|
||||
id={outgoingWebhook2Id}
|
||||
action={outgoingWebhook2Action}
|
||||
onUpdate={this.update}
|
||||
onHide={this.handleOutgoingWebhookFormHide}
|
||||
onDelete={() => {
|
||||
this.onDeleteClick(outgoingWebhook2Id).then(() => {
|
||||
this.setState({ outgoingWebhook2Id: undefined, outgoingWebhook2Action: undefined });
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageErrorHandlingWrapper>
|
||||
) : (
|
||||
<Text>Outgoing webhooks 2 functionality is not enabled.</Text>
|
||||
);
|
||||
}
|
||||
|
||||
renderOutgoingWebhooksFilters() {
|
||||
const { query, store } = this.props;
|
||||
return (
|
||||
<div className={cx('filters')}>
|
||||
<RemoteFilters
|
||||
query={query}
|
||||
page="webhooks"
|
||||
grafanaTeamStore={store.grafanaTeamStore}
|
||||
onChange={this.handleFiltersChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleFiltersChange = (filters: FiltersValues, isOnMount) => {
|
||||
const { store } = this.props;
|
||||
|
||||
const { outgoingWebhook2Store } = store;
|
||||
|
||||
outgoingWebhook2Store.updateItems(filters).then(() => {
|
||||
if (isOnMount) {
|
||||
this.parseQueryParams();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderTeam(record: OutgoingWebhook, teams: any) {
|
||||
return <TeamName team={teams[record.team]} />;
|
||||
}
|
||||
|
||||
renderActionButtons = (record: OutgoingWebhook2) => {
|
||||
return (
|
||||
<WithContextMenu
|
||||
renderMenuItems={() => (
|
||||
<div className={cx('hamburgerMenu')}>
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onLastRunClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'status_action'} userAction={UserActions.OutgoingWebhooksRead}>
|
||||
<Text type="primary">View Last Run</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onEditClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'edit_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">Edit settings</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={cx('hamburgerMenu__item')}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Confirm',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.onDisableWebhook(record.id, !record.is_webhook_enabled),
|
||||
title: `Are you sure you want to ${record.is_webhook_enabled ? 'disable' : 'enable'} webhook?`,
|
||||
} as ConfirmModalProps,
|
||||
})
|
||||
}
|
||||
>
|
||||
<WithPermissionControlTooltip key={'disable_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">{record.is_webhook_enabled ? 'Disable' : 'Enable'}</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<div className={cx('hamburgerMenu__item')} onClick={() => this.onCopyClick(record.id)}>
|
||||
<WithPermissionControlTooltip key={'copy_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<Text type="primary">Make a copy</Text>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
|
||||
<CopyToClipboard text={record.id} onCopy={() => openNotification('Webhook ID has been copied')}>
|
||||
<div className={cx('hamburgerMenu__item')}>
|
||||
<HorizontalGroup type="primary" spacing="xs">
|
||||
<Icon name="clipboard-alt" />
|
||||
<Text type="primary">UID: {record.id}</Text>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</CopyToClipboard>
|
||||
|
||||
<div className={cx('thin-line-break')} />
|
||||
|
||||
<div
|
||||
className={cx('hamburgerMenu__item')}
|
||||
onClick={() =>
|
||||
this.setState({
|
||||
confirmationModal: {
|
||||
isOpen: true,
|
||||
confirmText: 'Confirm',
|
||||
dismissText: 'Cancel',
|
||||
onConfirm: () => this.onDeleteClick(record.id),
|
||||
body: 'The action cannot be undone.',
|
||||
title: `Are you sure you want to delete webhook?`,
|
||||
} as Partial<ConfirmModalProps> as ConfirmModalProps,
|
||||
})
|
||||
}
|
||||
>
|
||||
<WithPermissionControlTooltip key={'delete_action'} userAction={UserActions.OutgoingWebhooksWrite}>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<IconButton tooltip="Remove" tooltipPlacement="top" variant="destructive" name="trash-alt" />
|
||||
<Text type="danger">Delete Webhook</Text>
|
||||
</HorizontalGroup>
|
||||
</WithPermissionControlTooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{({ openMenu }) => <HamburgerMenu openMenu={openMenu} listBorder={2} listWidth={225} withBackground />}
|
||||
</WithContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
renderName(name: String) {
|
||||
return (
|
||||
<div className="u-break-word">
|
||||
<span>{name}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderUrl(url: string) {
|
||||
return (
|
||||
<div className="u-break-word">
|
||||
<span>{url}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderLastRun(record: OutgoingWebhook2) {
|
||||
const lastRunMoment = moment(record.last_response_log?.timestamp);
|
||||
|
||||
return !record.is_webhook_enabled ? (
|
||||
<Text type="secondary">Disabled</Text>
|
||||
) : (
|
||||
<VerticalGroup spacing="none">
|
||||
<Text type="secondary">{lastRunMoment.isValid() ? lastRunMoment.format('MMM DD, YYYY') : '-'}</Text>
|
||||
<Text type="secondary">{lastRunMoment.isValid() ? lastRunMoment.format('HH:mm') : ''}</Text>
|
||||
<Text type="secondary">
|
||||
{lastRunMoment.isValid()
|
||||
? record.last_response_log?.status_code
|
||||
? 'Status: ' + record.last_response_log?.status_code
|
||||
: 'Check Status'
|
||||
: ''}
|
||||
</Text>
|
||||
</VerticalGroup>
|
||||
);
|
||||
}
|
||||
|
||||
onDeleteClick = (id: OutgoingWebhook2['id']): Promise<void> => {
|
||||
const { store } = this.props;
|
||||
return store.outgoingWebhook2Store
|
||||
.delete(id)
|
||||
.then(this.update)
|
||||
.then(() => openNotification('Webhook has been removed'))
|
||||
.catch(() => openNotification('Webook could not been removed'))
|
||||
.finally(() => this.setState({ confirmationModal: undefined }));
|
||||
};
|
||||
|
||||
onEditClick = (id: OutgoingWebhook2['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhook2Id: id, outgoingWebhook2Action: WebhookFormActionType.EDIT_SETTINGS }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/edit/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
onCopyClick = (id: OutgoingWebhook2['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhook2Id: id, outgoingWebhook2Action: WebhookFormActionType.COPY }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/copy/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
onDisableWebhook = (id: OutgoingWebhook2['id'], isEnabled: boolean) => {
|
||||
const {
|
||||
store: { outgoingWebhook2Store },
|
||||
} = this.props;
|
||||
|
||||
const data = {
|
||||
...{ ...outgoingWebhook2Store.items[id], is_webhook_enabled: isEnabled },
|
||||
is_legacy: false,
|
||||
};
|
||||
|
||||
outgoingWebhook2Store
|
||||
.update(id, data)
|
||||
.then(() => this.update())
|
||||
.then(() => openNotification(`Webhook has been ${isEnabled ? 'enabled' : 'disabled'}`))
|
||||
.catch(() => openErrorNotification('Webhook could not been updated'))
|
||||
.finally(() => this.setState({ confirmationModal: undefined }));
|
||||
};
|
||||
|
||||
onLastRunClick = (id: OutgoingWebhook2['id']) => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhook2Id: id, outgoingWebhook2Action: WebhookFormActionType.VIEW_LAST_RUN }, () =>
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks/last_run/${id}`)
|
||||
);
|
||||
};
|
||||
|
||||
handleOutgoingWebhookFormHide = () => {
|
||||
const { history } = this.props;
|
||||
|
||||
this.setState({ outgoingWebhook2Id: undefined, outgoingWebhook2Action: undefined });
|
||||
|
||||
history.push(`${PLUGIN_ROOT}/outgoing_webhooks`);
|
||||
};
|
||||
}
|
||||
|
||||
function convertWebhookUrlToAction(urlAction: string) {
|
||||
if (urlAction === 'new') {
|
||||
return WebhookFormActionType.NEW;
|
||||
} else if (urlAction === 'copy') {
|
||||
return WebhookFormActionType.COPY;
|
||||
} else if (urlAction === 'edit') {
|
||||
return WebhookFormActionType.EDIT_SETTINGS;
|
||||
} else {
|
||||
return WebhookFormActionType.VIEW_LAST_RUN;
|
||||
}
|
||||
}
|
||||
|
||||
export { OutgoingWebhooks2 };
|
||||
|
||||
export default withRouter(withMobXProviderContext(OutgoingWebhooks2));
|
||||
|
|
@ -28,7 +28,6 @@ import Integration from 'pages/integration/Integration';
|
|||
import Integrations from 'pages/integrations/Integrations';
|
||||
import Maintenance from 'pages/maintenance/Maintenance';
|
||||
import OutgoingWebhooks from 'pages/outgoing_webhooks/OutgoingWebhooks';
|
||||
import OutgoingWebhooks2 from 'pages/outgoing_webhooks_2/OutgoingWebhooks2';
|
||||
import Schedule from 'pages/schedule/Schedule';
|
||||
import Schedules from 'pages/schedules/Schedules';
|
||||
import SettingsPage from 'pages/settings/SettingsPage';
|
||||
|
|
@ -37,7 +36,6 @@ import CloudPage from 'pages/settings/tabs/Cloud/CloudPage';
|
|||
import LiveSettings from 'pages/settings/tabs/LiveSettings/LiveSettingsPage';
|
||||
import Users from 'pages/users/Users';
|
||||
import { rootStore } from 'state';
|
||||
import { AppFeature } from 'state/features';
|
||||
import { useStore } from 'state/useStore';
|
||||
import { isUserActionAllowed } from 'utils/authorization';
|
||||
|
||||
|
|
@ -154,11 +152,7 @@ export const Root = observer((props: AppRootProps) => {
|
|||
<Schedule query={query} basicDataLoaded={basicDataLoaded} />
|
||||
</Route>
|
||||
<Route path={getRoutesForPage('outgoing_webhooks')} exact>
|
||||
{rootStore.hasFeature(AppFeature.Webhooks2) ? (
|
||||
<OutgoingWebhooks2 query={query} />
|
||||
) : (
|
||||
<OutgoingWebhooks query={query} />
|
||||
)}
|
||||
<OutgoingWebhooks query={query} />
|
||||
</Route>
|
||||
<Route path={getRoutesForPage('maintenance')} exact>
|
||||
<Maintenance />
|
||||
|
|
|
|||
|
|
@ -5,5 +5,4 @@ export enum AppFeature {
|
|||
CloudNotifications = 'grafana_cloud_notifications',
|
||||
CloudConnection = 'grafana_cloud_connection',
|
||||
WebSchedules = 'web_schedules',
|
||||
Webhooks2 = 'webhooks2',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import { GrafanaTeamStore } from 'models/grafana_team/grafana_team';
|
|||
import { HeartbeatStore } from 'models/heartbeat/heartbeat';
|
||||
import { OrganizationStore } from 'models/organization/organization';
|
||||
import { OutgoingWebhookStore } from 'models/outgoing_webhook/outgoing_webhook';
|
||||
import { OutgoingWebhook2Store } from 'models/outgoing_webhook_2/outgoing_webhook_2';
|
||||
import { ResolutionNotesStore } from 'models/resolution_note/resolution_note';
|
||||
import { ScheduleStore } from 'models/schedule/schedule';
|
||||
import { SlackStore } from 'models/slack/slack';
|
||||
|
|
@ -84,15 +83,12 @@ export class RootBaseStore {
|
|||
onCallApiUrl: string;
|
||||
|
||||
// --------------------------
|
||||
|
||||
userStore = new UserStore(this);
|
||||
cloudStore = new CloudStore(this);
|
||||
directPagingStore = new DirectPagingStore(this);
|
||||
grafanaTeamStore = new GrafanaTeamStore(this);
|
||||
alertReceiveChannelStore = new AlertReceiveChannelStore(this);
|
||||
outgoingWebhookStore = new OutgoingWebhookStore(this);
|
||||
|
||||
outgoingWebhook2Store = new OutgoingWebhook2Store(this);
|
||||
alertReceiveChannelFiltersStore = new AlertReceiveChannelFiltersStore(this);
|
||||
escalationChainStore = new EscalationChainStore(this);
|
||||
escalationPolicyStore = new EscalationPolicyStore(this);
|
||||
|
|
@ -108,6 +104,7 @@ export class RootBaseStore {
|
|||
apiTokenStore = new ApiTokenStore(this);
|
||||
globalSettingStore = new GlobalSettingStore(this);
|
||||
filtersStore = new FiltersStore(this);
|
||||
|
||||
// stores
|
||||
|
||||
async updateBasicData() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue