Feature/cal 605 add webhook test check during webhook (#1035)
* starting point * lint fix * add mock placeholder * simplified a bit * add some placeholder ui * err handling * multiple fixes * post rebase fixes * removed extra webhook enabled button * finishing touches * added translations * removed debug remnants * requested changes Co-authored-by: KATT <alexander@n1s.se>
This commit is contained in:
parent
9842aaaf6a
commit
baba307a9f
4 changed files with 134 additions and 4 deletions
|
@ -1,9 +1,16 @@
|
|||
import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
PencilAltIcon,
|
||||
SwitchHorizontalIcon,
|
||||
TrashIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from "@heroicons/react/outline";
|
||||
import { ClipboardIcon } from "@heroicons/react/solid";
|
||||
import { WebhookTriggerEvents } from "@prisma/client";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import Image from "next/image";
|
||||
import { useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import React, { useState } from "react";
|
||||
import { Controller, useForm, useWatch } from "react-hook-form";
|
||||
import { useMutation } from "react-query";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
@ -113,6 +120,60 @@ function WebhookListItem(props: { webhook: TWebhook; onEditWebhook: () => void }
|
|||
);
|
||||
}
|
||||
|
||||
function WebhookTestDisclosure() {
|
||||
const subscriberUrl: string = useWatch({ name: "subscriberUrl" });
|
||||
const { t } = useLocale();
|
||||
const [open, setOpen] = useState(false);
|
||||
const mutation = trpc.useMutation("viewer.webhook.testTrigger", {
|
||||
onError(err) {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Collapsible open={open} onOpenChange={() => setOpen(!open)}>
|
||||
<CollapsibleTrigger type="button" className={"cursor-pointer flex w-full text-sm"}>
|
||||
{t("webhook_test")}{" "}
|
||||
{open ? (
|
||||
<ChevronUpIcon className="w-5 h-5 text-gray-700" />
|
||||
) : (
|
||||
<ChevronDownIcon className="w-5 h-5 text-gray-700" />
|
||||
)}
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<InputGroupBox className="px-0 space-y-0 border-0">
|
||||
<div className="flex justify-between p-2 bg-gray-50">
|
||||
<h3 className="self-center text-gray-700">{t("webhook_response")}</h3>
|
||||
<Button
|
||||
StartIcon={SwitchHorizontalIcon}
|
||||
type="button"
|
||||
color="minimal"
|
||||
disabled={mutation.isLoading}
|
||||
onClick={() => mutation.mutate({ url: subscriberUrl, type: "PING" })}>
|
||||
{t("ping_test")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="p-2 text-gray-500 border-8 border-gray-50">
|
||||
{!mutation.data && <em>{t("no_data_yet")}</em>}
|
||||
{mutation.status === "success" && (
|
||||
<>
|
||||
<div
|
||||
className={classNames(
|
||||
"px-2 py-1 w-max text-xs ml-auto",
|
||||
mutation.data.status === 200 ? "text-green-500 bg-green-50" : "text-red-500 bg-red-50"
|
||||
)}>
|
||||
{mutation.data.status === 200 ? t("success") : t("failed")}
|
||||
</div>
|
||||
<pre className="overflow-x-auto">{JSON.stringify(mutation.data, null, 4)}</pre>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</InputGroupBox>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function WebhookDialogForm(props: {
|
||||
//
|
||||
defaultValues?: TWebhook;
|
||||
|
@ -133,6 +194,7 @@ function WebhookDialogForm(props: {
|
|||
const form = useForm({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
return (
|
||||
<Form
|
||||
data-testid="WebhookDialogForm"
|
||||
|
@ -209,6 +271,7 @@ function WebhookDialogForm(props: {
|
|||
))}
|
||||
</InputGroupBox>
|
||||
</fieldset>
|
||||
<WebhookTestDisclosure />
|
||||
<DialogFooter>
|
||||
<Button type="button" color="secondary" onClick={props.handleClose} tabIndex={-1}>
|
||||
{t("cancel")}
|
||||
|
|
|
@ -45,11 +45,15 @@
|
|||
"webhook_status": "Webhook Status",
|
||||
"webhook_enabled": "Webhook Enabled",
|
||||
"webhook_disabled": "Webhook Disabled",
|
||||
"webhook_response": "Webhook response",
|
||||
"webhook_test": "Webhook test",
|
||||
"manage_your_webhook": "Manage your webhook",
|
||||
"webhook_created_successfully": "Webhook created successfully!",
|
||||
"webhook_updated_successfully": "Webhook updated successfully!",
|
||||
"webhook_removed_successfully": "Webhook removed successfully!",
|
||||
"dismiss": "Dismiss",
|
||||
"no_data_yet": "No data yet",
|
||||
"ping_test": "Ping test",
|
||||
"add_to_homescreen": "Add this app to your home screen for faster access and improved experience.",
|
||||
"upcoming": "Upcoming",
|
||||
"past": "Past",
|
||||
|
@ -169,6 +173,7 @@
|
|||
"whoops": "Whoops",
|
||||
"login": "Login",
|
||||
"success": "Success",
|
||||
"failed": "Failed",
|
||||
"password_has_been_reset_login": "Your password has been reset. You can now login with your newly created password.",
|
||||
"unexpected_error_try_again": "An unexpected error occurred. Try again.",
|
||||
"back_to_bookings": "Back to bookings",
|
||||
|
|
|
@ -13,6 +13,7 @@ import { TRPCError } from "@trpc/server";
|
|||
|
||||
import { createProtectedRouter, createRouter } from "../createRouter";
|
||||
import { resizeBase64Image } from "../lib/resizeBase64Image";
|
||||
import { webhookRouter } from "./viewer/webhook";
|
||||
|
||||
const checkUsername =
|
||||
process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? checkPremiumUsername : checkRegularUsername;
|
||||
|
@ -383,4 +384,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
},
|
||||
});
|
||||
|
||||
export const viewerRouter = createRouter().merge(publicViewerRouter).merge(loggedInViewerRouter);
|
||||
export const viewerRouter = createRouter()
|
||||
.merge(publicViewerRouter)
|
||||
.merge(loggedInViewerRouter)
|
||||
.merge("webhook.", webhookRouter);
|
||||
|
|
58
server/routers/viewer/webhook.tsx
Normal file
58
server/routers/viewer/webhook.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { getErrorFromUnknown } from "@lib/errors";
|
||||
|
||||
import { createProtectedRouter } from "@server/createRouter";
|
||||
|
||||
export const webhookRouter = createProtectedRouter().mutation("testTrigger", {
|
||||
input: z.object({
|
||||
url: z.string().url(),
|
||||
type: z.string(),
|
||||
}),
|
||||
async resolve({ input }) {
|
||||
const { url, type } = input;
|
||||
|
||||
const responseBodyMocks: Record<"PING", unknown> = {
|
||||
PING: {
|
||||
triggerEvent: "PING",
|
||||
createdAt: new Date().toISOString(),
|
||||
payload: {
|
||||
type: "Test",
|
||||
title: "Test trigger event",
|
||||
description: "",
|
||||
startTime: new Date().toISOString(),
|
||||
endTime: new Date().toISOString(),
|
||||
organizer: {
|
||||
name: "Cal",
|
||||
email: "",
|
||||
timeZone: "Europe/London",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const body = responseBodyMocks[type as "PING"];
|
||||
if (!body) {
|
||||
throw new Error(`Unknown type '${type}'`);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
// [...]
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const text = await res.text();
|
||||
return {
|
||||
status: res.status,
|
||||
message: text,
|
||||
};
|
||||
} catch (_err) {
|
||||
const err = getErrorFromUnknown(_err);
|
||||
return {
|
||||
status: 500,
|
||||
message: err.message,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
Loading…
Reference in a new issue