Pass additional_settings from the integration form to backend (#4047)

# What this PR does

- Pass additional_settings from the integration form to backend

---------

Co-authored-by: Dominik <dominik.broj@grafana.com>
This commit is contained in:
Rares Mardare 2024-03-13 14:54:38 +02:00 committed by GitHub
parent 9b7dce64cd
commit 017afaa1b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 124 additions and 88 deletions

View file

@ -7,5 +7,9 @@ export function prepareForEdit(item: ApiSchemas['AlertReceiveChannel']): Partial
team: item.team,
labels: item.labels,
integration: item.integration,
additional_settings: {
...item.additional_settings,
},
};
}

View file

@ -35,39 +35,26 @@ import { AppFeature } from 'state/features';
import { useStore } from 'state/useStore';
import { UserActions } from 'utils/authorization/authorization';
import { PLUGIN_ROOT, URL_REGEX, generateAssignToTeamInputDescription } from 'utils/consts';
import { OmitReadonlyMembers } from 'utils/types';
import { prepareForEdit } from './IntegrationForm.helpers';
import { getIntegrationFormStyles } from './IntegrationForm.styles';
enum FormFieldKeys {
Name = 'verbal_name',
Description = 'description_short',
Team = 'team',
AlertManager = 'alert_manager',
ContactPoint = 'contact_point',
IsExisting = 'is_existing',
Alerting = 'alerting',
Integration = 'integration',
ServiceNowUrl = 'servicenow_url',
AuthUsername = 'auth_username',
AuthPassword = 'auth_password',
DefaultWebhooks = 'default_webhooks',
}
interface FormFields {
[FormFieldKeys.Name]: string;
[FormFieldKeys.Description]: string;
[FormFieldKeys.Team]: string;
[FormFieldKeys.IsExisting]: boolean;
[FormFieldKeys.AlertManager]: string;
[FormFieldKeys.ContactPoint]: string;
[FormFieldKeys.Alerting]: string;
[FormFieldKeys.ServiceNowUrl]: string;
[FormFieldKeys.AuthUsername]: string;
[FormFieldKeys.AuthPassword]: string;
[FormFieldKeys.Integration]: string;
[FormFieldKeys.DefaultWebhooks]: boolean;
verbal_name?: string;
description_short?: string;
team?: string;
is_existing?: boolean;
alert_manager?: string;
contact_point?: string;
integration: ApiSchemas['AlertReceiveChannel']['integration'];
additional_settings: {
instance_url: string;
username: string;
password: string;
default_webhooks: boolean;
};
}
interface IntegrationFormProps {
@ -107,8 +94,17 @@ export const IntegrationForm = observer(
const isNew = id === 'new';
const { userStore, grafanaTeamStore, alertReceiveChannelStore } = store;
const data = isNew
? { integration: selectedIntegration?.value, team: userStore.currentUser?.current_team, labels: [] }
const data: Partial<ApiSchemas['AlertReceiveChannel']> = isNew
? {
integration: selectedIntegration?.value as ApiSchemas['AlertReceiveChannel']['integration'],
team: userStore.currentUser?.current_team,
labels: [],
additional_settings: {
instance_url: undefined,
password: undefined,
username: undefined,
},
}
: prepareForEdit(alertReceiveChannelStore.items[id]);
const { integration } = data;
@ -117,8 +113,10 @@ export const IntegrationForm = observer(
defaultValues: isNew
? {
// these are the default values for creating an integration
[FormFieldKeys.Integration]: integration,
[FormFieldKeys.DefaultWebhooks]: true,
integration,
additional_settings: {
default_webhooks: true,
},
}
: {
// existing values from existing integration (edit-mode)
@ -126,6 +124,7 @@ export const IntegrationForm = observer(
},
mode: 'onChange',
});
const {
control,
handleSubmit,
@ -175,15 +174,15 @@ export const IntegrationForm = observer(
<FormProvider {...formMethods}>
<form onSubmit={handleSubmit(onFormSubmit)} className={styles.form}>
<Controller
name={FormFieldKeys.Name}
name={'verbal_name'}
control={control}
rules={{ required: 'Name is required' }}
render={({ field }) => (
<Field
key={'Name'}
label={'Integration Name'}
invalid={!!errors[FormFieldKeys.Name]}
error={errors[FormFieldKeys.Name]?.message}
invalid={!!errors.verbal_name}
error={errors.verbal_name?.message}
>
<Input {...field} placeholder={'Integration Name'} />
</Field>
@ -191,15 +190,15 @@ export const IntegrationForm = observer(
/>
<Controller
name={FormFieldKeys.Description}
name={'description_short'}
control={control}
rules={{ required: 'Description is required' }}
render={({ field }) => (
<Field
key={'Description'}
label={'Integration Description'}
invalid={!!errors[FormFieldKeys.Description]}
error={errors[FormFieldKeys.Description]?.message}
invalid={!!errors.description_short}
error={errors.description_short?.message}
>
<TextArea {...field} className={styles.textarea} placeholder={'Integration Description'} />
</Field>
@ -207,7 +206,7 @@ export const IntegrationForm = observer(
/>
<Controller
name={FormFieldKeys.Team}
name={'team'}
control={control}
render={({ field }) => (
<Field
@ -220,8 +219,8 @@ export const IntegrationForm = observer(
</Tooltip>
</Label>
}
invalid={!!errors[FormFieldKeys.Team]}
error={errors[FormFieldKeys.Team]?.message}
invalid={!!errors.team}
error={errors.team?.message}
>
<GSelect<GrafanaTeam>
placeholder="Assign to team"
@ -288,15 +287,15 @@ export const IntegrationForm = observer(
</div>
<Controller
name={FormFieldKeys.ServiceNowUrl}
name={'additional_settings.instance_url'}
control={control}
rules={{ required: 'Instance URL is required', validate: validateURL }}
render={({ field }) => (
<Field
key={'InstanceURL'}
label={'Instance URL'}
invalid={!!errors[FormFieldKeys.ServiceNowUrl]}
error={errors[FormFieldKeys.ServiceNowUrl]?.message}
invalid={!!errors.additional_settings?.instance_url}
error={errors.additional_settings?.instance_url?.message}
>
<Input {...field} />
</Field>
@ -304,15 +303,15 @@ export const IntegrationForm = observer(
/>
<Controller
name={FormFieldKeys.AuthUsername}
name={'additional_settings.username'}
control={control}
rules={{ required: 'Username is required' }}
render={({ field }) => (
<Field
key={'AuthUsername'}
label={'Username'}
invalid={!!errors[FormFieldKeys.AuthUsername]}
error={errors[FormFieldKeys.AuthUsername]?.message}
invalid={!!errors.additional_settings?.username}
error={errors.additional_settings?.username?.message}
>
<Input {...field} />
</Field>
@ -320,15 +319,15 @@ export const IntegrationForm = observer(
/>
<Controller
name={FormFieldKeys.AuthPassword}
name={'additional_settings.password'}
control={control}
rules={{ required: 'Password is required' }}
render={({ field }) => (
<Field
key={'AuthPassword'}
label={'Password'}
invalid={!!errors[FormFieldKeys.AuthPassword]}
error={errors[FormFieldKeys.AuthPassword]?.message as string}
invalid={!!errors.additional_settings?.password}
error={errors.additional_settings?.password?.message}
>
<Input {...field} type="password" />
</Field>
@ -340,7 +339,7 @@ export const IntegrationForm = observer(
</Button>
<Controller
name={FormFieldKeys.DefaultWebhooks}
name={'additional_settings.default_webhooks'}
control={control}
render={({ field }) => (
<div className={styles.webhookSwitch}>
@ -382,9 +381,18 @@ export const IntegrationForm = observer(
async function onWebhookTestClick(): Promise<void> {}
async function onFormSubmit(formData): Promise<void> {
async function onFormSubmit(formData: FormFields): Promise<void> {
const labels = labelsRef.current?.getValue();
const data = { ...formData, labels };
const data: OmitReadonlyMembers<ApiSchemas['AlertReceiveChannel']> = {
labels,
...formData,
};
if (formData.integration !== 'servicenow') {
delete data.additional_settings;
}
const isCreate = id === 'new';
try {
@ -416,9 +424,9 @@ export const IntegrationForm = observer(
pushHistory(response.id);
}
await (data.is_existing
await (formData.is_existing
? AlertReceiveChannelHelper.connectContactPoint
: AlertReceiveChannelHelper.createContactPoint)(response.id, data.alert_manager, data.contact_point);
: AlertReceiveChannelHelper.createContactPoint)(response.id, formData.alert_manager, formData.contact_point);
pushHistory(response.id);
}
@ -473,6 +481,7 @@ const GrafanaContactPoint = observer(
getValues,
setValue,
formState: { errors },
register,
} = useFormContext<FormFields>();
useEffect(() => {
@ -486,7 +495,7 @@ const GrafanaContactPoint = observer(
});
})();
setValue(FormFieldKeys.IsExisting, true);
setValue('is_existing', true);
}, []);
const styles = useStyles2(getIntegrationFormStyles);
@ -503,11 +512,12 @@ const GrafanaContactPoint = observer(
<div className={styles.extraFieldsRadio}>
<Controller
name={FormFieldKeys.IsExisting}
name={'is_existing'}
control={control}
render={({ field }) => (
<RadioButtonGroup
{...field}
{...register('is_existing')}
options={radioOptions}
value={isExistingContactPoint ? 'existing' : 'new'}
onChange={(radioValue) => {
@ -518,9 +528,9 @@ const GrafanaContactPoint = observer(
selectedContactPointOption: null,
});
setValue(FormFieldKeys.IsExisting, radioValue === 'existing');
setValue(FormFieldKeys.AlertManager, undefined);
setValue(FormFieldKeys.ContactPoint, undefined);
setValue('is_existing', radioValue === 'existing');
setValue('alert_manager', undefined);
setValue('contact_point', undefined);
}}
/>
)}
@ -529,15 +539,11 @@ const GrafanaContactPoint = observer(
<div className={styles.selectorsContainer}>
<Controller
name={FormFieldKeys.AlertManager}
name={'alert_manager'}
control={control}
rules={{ required: 'Alert Manager is required' }}
render={({ field }) => (
<Field
key={'AlertManager'}
invalid={!!errors[FormFieldKeys.AlertManager]}
error={errors[FormFieldKeys.AlertManager]?.message}
>
<Field key={'AlertManager'} invalid={!!errors.alert_manager} error={errors.alert_manager?.message}>
<Select
{...field}
options={dataSources}
@ -550,15 +556,11 @@ const GrafanaContactPoint = observer(
/>
<Controller
name={FormFieldKeys.ContactPoint}
name={'contact_point'}
control={control}
rules={{ required: 'Contact Point is required', validate: contactPointValidator }}
render={({ field }) => (
<Field
key={FormFieldKeys.ContactPoint}
invalid={!!errors[FormFieldKeys.ContactPoint]}
error={errors[FormFieldKeys.ContactPoint]?.message}
>
<Field key={'contact_point'} invalid={!!errors.contact_point} error={errors.contact_point?.message}>
{isExistingContactPoint ? (
<Select
{...field}
@ -574,7 +576,7 @@ const GrafanaContactPoint = observer(
onChange={({ currentTarget }) => {
const { value } = currentTarget;
setState({ selectedContactPointOption: value });
setValue(FormFieldKeys.ContactPoint, value, { shouldValidate: true });
setValue('contact_point', value, { shouldValidate: true });
}}
/>
)}
@ -587,8 +589,8 @@ const GrafanaContactPoint = observer(
);
function contactPointValidator(contactPointInputValue: string) {
const alertManager = getValues(FormFieldKeys.AlertManager);
const isExisting = getValues(FormFieldKeys.IsExisting);
const alertManager = getValues('alert_manager');
const isExisting = getValues('is_existing');
const matchingAlertManager = allContactPoints.find((cp) => cp.uid === alertManager);
const hasContactPointInput = alertManager && contactPointInputValue;
@ -617,16 +619,16 @@ const GrafanaContactPoint = observer(
if (isExistingContactPoint) {
newState.selectedContactPointOption = null;
setValue(FormFieldKeys.ContactPoint, undefined);
setValue('contact_point', undefined);
}
setState(newState);
setValue(FormFieldKeys.AlertManager, option.value, { shouldValidate: true });
setValue('alert_manager', option.value, { shouldValidate: true });
}
function onContactPointChange(option: SelectableValue<string>) {
setState({ selectedContactPointOption: option.value });
setValue(FormFieldKeys.ContactPoint, option.value, { shouldValidate: true });
setValue('contact_point', option.value, { shouldValidate: true });
}
}
);

View file

@ -44,10 +44,16 @@ export class AlertReceiveChannelStore {
}
@WithGlobalNotification({ failure: 'There was an issue creating Integration. Please try again.' })
async create({ data, skipErrorHandling }: { data: ApiSchemas['AlertReceiveChannel']; skipErrorHandling?: boolean }) {
async create({
data,
skipErrorHandling,
}: {
data: OmitReadonlyMembers<ApiSchemas['AlertReceiveChannel']>;
skipErrorHandling?: boolean;
}) {
const result = await onCallApi({ skipErrorHandling }).POST('/alert_receive_channels/', {
params: {},
body: data,
body: data as ApiSchemas['AlertReceiveChannel'],
});
await this.rootStore.organizationStore.loadCurrentOrganization();
return result.data;
@ -60,12 +66,12 @@ export class AlertReceiveChannelStore {
skipErrorHandling,
}: {
id: ApiSchemas['AlertReceiveChannelUpdate']['id'];
data: ApiSchemas['AlertReceiveChannelUpdate'];
data: OmitReadonlyMembers<ApiSchemas['AlertReceiveChannelUpdate']>;
skipErrorHandling?: boolean;
}) {
const result = await onCallApi({ skipErrorHandling }).PUT('/alert_receive_channels/{id}/', {
params: { path: { id } },
body: data,
body: data as ApiSchemas['AlertReceiveChannelUpdate'],
});
await this.rootStore.organizationStore.loadCurrentOrganization();
return result.data;

View file

@ -1446,8 +1446,16 @@ export interface components {
/** Format: date-time */
readonly alertmanager_v2_migrated_at: string | null;
additional_settings?: {
[key: string]: unknown;
} | null;
instance_url: string;
username: string;
password: string;
state_mapping?: {
firing: string;
acknowledged: string;
resolved: string;
silenced: string;
};
};
};
AlertReceiveChannelConnectContactPoint: {
datasource_uid: string;
@ -1557,8 +1565,16 @@ export interface components {
/** Format: date-time */
readonly alertmanager_v2_migrated_at: string | null;
additional_settings?: {
[key: string]: unknown;
} | null;
instance_url: string;
username: string;
password: string;
state_mapping?: {
firing: string;
acknowledged: string;
resolved: string;
silenced: string;
};
};
};
/** @enum {integer} */
CloudConnectionStatusEnum: 0 | 1 | 2 | 3;
@ -1867,8 +1883,16 @@ export interface components {
/** Format: date-time */
readonly alertmanager_v2_migrated_at?: string | null;
additional_settings?: {
[key: string]: unknown;
} | null;
instance_url: 'string';
username: 'string';
password: 'string';
state_mapping?: {
firing: null;
acknowledged: null;
resolved: null;
silenced: null;
};
};
};
PatchedUser: {
readonly pk?: string;