import { type ChangeEventHandler, type FormEventHandler, type MouseEventHandler, useContext, useEffect, useState, type FC, } from 'react'; import { Alert, Button, Divider, Link, styled, Typography, } from '@mui/material'; import produce from 'immer'; import { trim } from 'component/common/util'; import type { AddonSchema, AddonTypeSchema } from 'openapi'; import { IntegrationParameters } from './IntegrationParameters/IntegrationParameters.tsx'; import { IntegrationInstall } from './IntegrationInstall/IntegrationInstall.tsx'; import cloneDeep from 'lodash.clonedeep'; import { useNavigate } from 'react-router-dom'; import useAddonsApi from 'hooks/api/actions/useAddonsApi/useAddonsApi'; import useToast from 'hooks/useToast'; import { formatUnknownError } from 'utils/formatUnknownError'; import useProjects from 'hooks/api/getters/useProjects/useProjects'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import { IntegrationMultiSelector } from './IntegrationMultiSelector/IntegrationMultiSelector.tsx'; import FormTemplate from 'component/common/FormTemplate/FormTemplate'; import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; import PermissionButton from 'component/common/PermissionButton/PermissionButton'; import { CREATE_ADDON, UPDATE_ADDON, } from '../../providers/AccessProvider/permissions.ts'; import { StyledForm, StyledAlerts, StyledTextField, StyledContainer, StyledButtonContainer, StyledButtonSection, StyledConfigurationSection, StyledTitle, StyledRaisedSection, } from './IntegrationForm.styles'; import { GO_BACK } from 'constants/navigate'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { IntegrationDelete } from './IntegrationDelete/IntegrationDelete.tsx'; import { IntegrationStateSwitch } from './IntegrationStateSwitch/IntegrationStateSwitch.tsx'; import { capitalizeFirst } from 'utils/capitalizeFirst'; import { IntegrationHowToSection } from '../IntegrationHowToSection/IntegrationHowToSection.tsx'; import { IntegrationEventsModal } from '../IntegrationEvents/IntegrationEventsModal.tsx'; import AccessContext from 'contexts/AccessContext'; const StyledHeader = styled('div')(({ theme }) => ({ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', marginBottom: theme.fontSizes.mainHeader, })); const StyledHeaderTitle = styled('h1')({ fontWeight: 'normal', }); type IntegrationFormProps = { provider?: AddonTypeSchema; fetch: () => void; editMode: boolean; addon: AddonSchema | Omit; }; export const IntegrationForm: FC = ({ editMode, provider, addon: initialValues, fetch, }) => { const { createAddon, updateAddon } = useAddonsApi(); const { setToastData, setToastApiError } = useToast(); const navigate = useNavigate(); const { projects: availableProjects } = useProjects(); const selectableProjects = availableProjects.map((project) => ({ value: project.id, label: project.name, })); const { environments: availableEnvironments } = useEnvironments(); const selectableEnvironments = availableEnvironments.map((environment) => ({ value: environment.name, label: environment.name, })); const selectableEvents = provider?.events ?.map((event) => ({ value: event, label: event, })) .sort((a, b) => a.label.localeCompare(b.label)); const { uiConfig } = useUiConfig(); const [formValues, setFormValues] = useState(initialValues); const [errors, setErrors] = useState<{ containsErrors: boolean; parameters: Record; events?: string; projects?: string; environments?: string; general?: string; description?: string; }>({ containsErrors: false, parameters: {}, }); const [eventsModalOpen, setEventsModalOpen] = useState(false); const { isAdmin } = useContext(AccessContext); const submitText = editMode ? 'Update' : 'Create'; const url = `${uiConfig.unleashUrl}/api/admin/addons${ editMode ? `/${(formValues as AddonSchema).id}` : `` }`; const formatApiCode = () => { return `curl --location --request ${editMode ? 'PUT' : 'POST'} '${url}' \\ --header 'Authorization: INSERT_API_KEY' \\ --header 'Content-Type: application/json' \\ --data-raw '${JSON.stringify(formValues, undefined, 2)}'`; }; useEffect(() => { if (!provider) { fetch(); } }, [fetch, provider]); // empty array => fetch only first time useEffect(() => { setFormValues({ ...initialValues }); /* eslint-disable-next-line */ }, [initialValues.description, initialValues.provider]); useEffect(() => { if (provider && !formValues.provider) { setFormValues({ ...initialValues, provider: provider.name }); } }, [provider, initialValues, formValues.provider]); const setFieldValue = (field: string): ChangeEventHandler => (event) => { event.preventDefault(); setFormValues({ ...formValues, [field]: event.target.value }); }; const onEnabled: MouseEventHandler = (event) => { event.preventDefault(); setFormValues(({ enabled }) => ({ ...formValues, enabled: !enabled })); }; const setParameterValue = (param: string): ChangeEventHandler => (event) => { event.preventDefault(); const value = trim(event.target.value) === '' ? undefined : event.target.value; setFormValues( produce((draft) => { if (value === undefined) { delete draft.parameters[param]; } else { draft.parameters[param] = value; } }), ); }; const setEventValues = (events: string[]) => { setFormValues( produce((draft) => { draft.events = events; }), ); setErrors((prev) => ({ ...prev, events: undefined, })); }; const setProjects = (projects: string[]) => { setFormValues( produce((draft) => { draft.projects = projects; }), ); setErrors((prev) => ({ ...prev, projects: undefined, })); }; const setEnvironments = (environments: string[]) => { setFormValues( produce((draft) => { draft.environments = environments; }), ); setErrors((prev) => ({ ...prev, environments: undefined, })); }; const onCancel = () => { navigate(GO_BACK); }; const onSubmit: FormEventHandler = async (event) => { event.preventDefault(); if (!provider) { return; } const updatedErrors = cloneDeep(errors); updatedErrors.parameters = {}; updatedErrors.containsErrors = false; // Validations if (formValues.events.length === 0) { updatedErrors.events = 'You must listen to at least one event'; updatedErrors.containsErrors = true; } provider.parameters?.forEach((parameterConfig) => { let value = formValues.parameters[parameterConfig.name]; value = typeof value === 'string' ? trim(value) : value; if (parameterConfig.required && !value) { updatedErrors.parameters[parameterConfig.name] = 'This field is required'; updatedErrors.containsErrors = true; } }); if (updatedErrors.containsErrors) { setErrors(updatedErrors); return; } try { if (editMode) { await updateAddon(formValues as AddonSchema); navigate('/integrations'); setToastData({ type: 'success', text: 'Integration updated', }); } else { await createAddon(formValues as Omit); navigate('/integrations'); setToastData({ type: 'success', text: 'Integration created', }); } } catch (error) { const message = formatUnknownError(error); setToastApiError(message); setErrors({ parameters: {}, general: message, containsErrors: true, }); } }; const { name, displayName, description, documentationUrl = 'https://docs.getunleash.io/reference/integrations', installation, alerts, } = provider ? provider : ({} as Partial); return ( {submitText} } > {submitText}{' '} {displayName || (name ? capitalizeFirst(name) : '')}{' '} integration setEventsModalOpen(true)}> View events } /> ( {alerts?.map(({ type, text }) => ( {text} ))} )} /> ); };