test-webhook-dialog.tsx•11.3 kB
import { DialogClose } from '@radix-ui/react-dialog';
import { useMutation } from '@tanstack/react-query';
import { t } from 'i18next';
import { useState } from 'react';
import { ControllerRenderProps, useForm } from 'react-hook-form';
import { z } from 'zod';
import { JsonEditor } from '@/components/custom/json-editor';
import { SearchableSelect } from '@/components/custom/searchable-select';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Form, FormField, FormItem, FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { flagsHooks } from '@/hooks/flags-hooks';
import { api } from '@/lib/api';
import { HttpMethod } from '@activepieces/pieces-common';
import { FlowAction, ApFlagId, apId, FlowTrigger } from '@activepieces/shared';
import { useBuilderStateContext } from '../../builder-hooks';
import { DictionaryProperty } from '../../piece-properties/dictionary-property';
import { testStepHooks } from '../test-step-hooks';
enum BodyType {
JSON = 'json',
TEXT = 'text',
FORM_DATA = 'form-data',
}
const BodyFormInput = ({
bodyType,
field,
}: {
bodyType: BodyType;
field: ControllerRenderProps<any>;
}) => {
switch (bodyType) {
case BodyType.JSON:
return <JsonEditor field={field} readonly={false}></JsonEditor>;
case BodyType.TEXT:
return <Input {...field} />;
case BodyType.FORM_DATA:
return (
<DictionaryProperty
values={field.value}
onChange={field.onChange}
disabled={false}
useMentionTextInput={false}
></DictionaryProperty>
);
}
};
const WebhookRequest = z.object({
bodyType: z.nativeEnum(BodyType),
body: z.union([z.object({}), z.string()]),
headers: z.record(z.string(), z.string()),
queryParams: z.record(z.string(), z.string()),
method: z.nativeEnum(HttpMethod),
});
type TestWaitForNextWebhookDialogProps = {
currentStep: FlowAction;
open: boolean;
onOpenChange: (open: boolean) => void;
testingMode: 'returnResponseAndWaitForNextWebhook';
};
type TestTriggerWebhookDialogProps = {
currentStep: FlowTrigger;
open: boolean;
onOpenChange: (open: boolean) => void;
testingMode: 'trigger';
};
type TestWebhookDialogProps =
| TestWaitForNextWebhookDialogProps
| TestTriggerWebhookDialogProps;
const TestTriggerWebhookDialog = ({
open,
onOpenChange,
}: TestTriggerWebhookDialogProps) => {
const { data: webhookPrefixUrl } = flagsHooks.useFlag<string>(
ApFlagId.WEBHOOK_URL_PREFIX,
);
const flowId = useBuilderStateContext((state) => state.flow.id);
const [isLoading, setIsLoading] = useState(false);
const { mutate: sendRequest } = useMutation<
unknown,
Error,
z.infer<typeof WebhookRequest>
>({
mutationFn: async (data: z.infer<typeof WebhookRequest>) => {
setIsLoading(true);
await api.any(`${webhookPrefixUrl}/${flowId}/test`, {
method: data.method,
data: data.body,
headers: data.headers,
params: data.queryParams,
});
},
});
return (
<Dialog
open={open}
onOpenChange={(open) => {
onOpenChange(open);
}}
>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('Send Sample Data to Webhook')}</DialogTitle>
</DialogHeader>
<TestWebhookFunctionalityForm
showMethodDropdown={true}
onSubmit={sendRequest}
isLoading={isLoading}
/>
</DialogContent>
</Dialog>
);
};
const TestWaitForNextWebhookDialog = ({
currentStep,
onOpenChange,
open,
}: TestWaitForNextWebhookDialogProps) => {
const { mutate: onSubmit, isPending: isLoading } =
testStepHooks.useTestAction({
currentStep,
setErrorMessage: undefined,
setConsoleLogs: undefined,
onSuccess: () => {
onOpenChange(false);
},
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('Send Sample Data to Webhook')}</DialogTitle>
</DialogHeader>
<TestWebhookFunctionalityForm
showMethodDropdown={false}
onSubmit={(data) => {
onSubmit({
preExistingSampleData: {
runId: apId(),
success: true,
output: {
body: data.body,
headers: data.headers,
queryParams: data.queryParams,
},
standardError: '',
standardOutput: '',
input: {},
},
});
}}
isLoading={isLoading}
/>
</DialogContent>
</Dialog>
);
};
type TestingWebhookFunctionalityFormProps = {
onSubmit: (data: z.infer<typeof WebhookRequest>) => void;
isLoading: boolean;
showMethodDropdown: boolean;
};
const TestWebhookFunctionalityForm = (
req: TestingWebhookFunctionalityFormProps,
) => {
const { showMethodDropdown, onSubmit, isLoading } = req;
const form = useForm<z.infer<typeof WebhookRequest>>({
defaultValues: {
bodyType: BodyType.JSON,
body: {},
headers: {},
queryParams: {},
method: HttpMethod.GET,
},
});
return (
<Form {...form}>
<form className="space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
{showMethodDropdown && (
<FormField
control={form.control}
name="method"
render={({ field }) => {
return (
<FormItem>
<FormLabel>{t('Method')}</FormLabel>
<SearchableSelect
options={Object.values(HttpMethod).map((method) => ({
value: method,
label: method,
}))}
onChange={(val) => {
field.onChange(val);
}}
value={field.value}
disabled={false}
placeholder={t('Select an option')}
/>
</FormItem>
);
}}
/>
)}
<Tabs defaultValue="queryParams">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="queryParams">{t('Query Params')}</TabsTrigger>
<TabsTrigger value="headers">{t('Headers')}</TabsTrigger>
<TabsTrigger value="body">{t('Body')}</TabsTrigger>
</TabsList>
<TabsContent value="queryParams">
<FormField
control={form.control}
name="queryParams"
render={({ field }) => {
return (
<FormItem>
<DictionaryProperty
values={field.value}
onChange={field.onChange}
disabled={false}
useMentionTextInput={false}
></DictionaryProperty>
</FormItem>
);
}}
></FormField>
</TabsContent>
<TabsContent value="headers">
<FormField
control={form.control}
name="headers"
render={({ field }) => {
return (
<FormItem>
<DictionaryProperty
values={field.value}
onChange={field.onChange}
disabled={false}
useMentionTextInput={false}
></DictionaryProperty>
</FormItem>
);
}}
></FormField>
</TabsContent>
<TabsContent value="body">
<>
<FormField
name="bodyType"
render={({ field }) => {
return (
<FormItem>
<FormLabel>{t('Type')}</FormLabel>
<SearchableSelect
options={[
{
value: BodyType.JSON,
label: t('JSON'),
},
{
value: BodyType.TEXT,
label: t('Text'),
},
{
value: BodyType.FORM_DATA,
label: t('Form Data'),
},
]}
onChange={(val) => {
field.onChange(val);
switch (val) {
case BodyType.JSON:
case BodyType.FORM_DATA:
form.setValue('body', {});
break;
case BodyType.TEXT:
form.setValue('body', '');
break;
}
}}
value={field.value}
disabled={false}
placeholder={t('Select an option')}
showDeselect={true}
></SearchableSelect>
</FormItem>
);
}}
></FormField>
<FormField
control={form.control}
name="body"
render={({ field }) => {
return (
<FormItem className="mt-4">
<FormLabel>{t('Body')}</FormLabel>
<BodyFormInput
bodyType={form.getValues('bodyType')}
field={field}
></BodyFormInput>
</FormItem>
);
}}
></FormField>
</>
</TabsContent>
</Tabs>
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="outline">
{t('Cancel')}
</Button>
</DialogClose>
<Button type="submit" loading={isLoading}>
{t('Send')}
</Button>
</DialogFooter>
</form>
</Form>
);
};
TestWebhookFunctionalityForm.displayName = 'TestWebhookFunctionalityDialog';
const TestWebhookDialog = (props: TestWebhookDialogProps) => {
const { testingMode, currentStep, open, onOpenChange } = props;
if (testingMode === 'returnResponseAndWaitForNextWebhook') {
return (
<TestWaitForNextWebhookDialog
currentStep={currentStep}
open={open}
onOpenChange={onOpenChange}
testingMode={testingMode}
/>
);
}
if (testingMode === 'trigger') {
return (
<TestTriggerWebhookDialog
currentStep={currentStep}
open={open}
onOpenChange={onOpenChange}
testingMode={testingMode}
/>
);
}
};
TestWebhookDialog.displayName = 'TestWebhookDialog';
export default TestWebhookDialog;