From f765214ee81ed7d4ef11d5fb899d6da41b6252cb Mon Sep 17 00:00:00 2001 From: nischit Date: Tue, 31 Dec 2024 15:14:00 +0545 Subject: [PATCH] add optional config to webhooks in engine + backwards compatibility --- .../src/@3rdweb-sdk/react/hooks/useEngine.ts | 5 +- .../components/add-webhook-button.tsx | 30 ++- .../webhooks/components/engine-webhooks.tsx | 16 +- .../webhooks/components/webhooks-table.tsx | 187 +++++++++++------- 4 files changed, 166 insertions(+), 72 deletions(-) diff --git a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts index 4bbc9ba004d..1db660155c9 100644 --- a/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts +++ b/apps/dashboard/src/@3rdweb-sdk/react/hooks/useEngine.ts @@ -118,7 +118,8 @@ type EngineFeature = | "CONTRACT_SUBSCRIPTIONS" | "IP_ALLOWLIST" | "HETEROGENEOUS_WALLET_TYPES" - | "SMART_BACKEND_WALLETS"; + | "SMART_BACKEND_WALLETS" + | "WEBHOOK_CONFIG"; interface EngineSystemHealth { status: string; @@ -835,6 +836,7 @@ export interface EngineWebhook { active: boolean; createdAt: string; id: number; + config?: string; } export function useEngineWebhooks(params: { @@ -1246,6 +1248,7 @@ export type CreateWebhookInput = { url: string; name: string; eventType: string; + config?: string; }; export function useEngineCreateWebhook(params: { diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx index 4f84ea589e4..b436cb6765a 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/add-webhook-button.tsx @@ -20,12 +20,14 @@ import { useTrack } from "hooks/analytics/useTrack"; import { useTxNotifications } from "hooks/useTxNotifications"; import { CirclePlusIcon } from "lucide-react"; import { useForm } from "react-hook-form"; +import { toast } from "sonner"; import { Button, FormLabel } from "tw-components"; import { beautifyString } from "./webhooks-table"; interface AddWebhookButtonProps { instanceUrl: string; authToken: string; + supportedWebhookConfig: boolean; } const WEBHOOK_EVENT_TYPES = [ @@ -41,6 +43,7 @@ const WEBHOOK_EVENT_TYPES = [ export const AddWebhookButton: React.FC = ({ instanceUrl, authToken, + supportedWebhookConfig = false, }) => { const { isOpen, onOpen, onClose } = useDisclosure(); const { mutate: createWebhook } = useEngineCreateWebhook({ @@ -74,7 +77,22 @@ export const AddWebhookButton: React.FC = ({ className="!bg-background rounded-lg border border-border" as="form" onSubmit={form.handleSubmit((data) => { - createWebhook(data, { + let finalData: CreateWebhookInput = data; + + if (supportedWebhookConfig) { + const { config, ..._data } = data; + finalData = _data; + if (config) { + try { + finalData.config = JSON.parse(config); + } catch (_) { + toast.error("Config must be a valid json string"); + return; + } + } + } + + createWebhook(finalData, { onSuccess: () => { onSuccess(); onClose(); @@ -128,6 +146,16 @@ export const AddWebhookButton: React.FC = ({ {...form.register("url", { required: true })} /> + {supportedWebhookConfig && ( + + Config + + + )} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx index 26bc2c9176a..a83271e054e 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/engine-webhooks.tsx @@ -1,6 +1,9 @@ "use client"; -import { useEngineWebhooks } from "@3rdweb-sdk/react/hooks/useEngine"; +import { + useEngineSystemHealth, + useEngineWebhooks, +} from "@3rdweb-sdk/react/hooks/useEngine"; import { Heading, Link, Text } from "tw-components"; import { AddWebhookButton } from "./add-webhook-button"; import { WebhooksTable } from "./webhooks-table"; @@ -18,6 +21,10 @@ export const EngineWebhooks: React.FC = ({ authToken, instanceUrl, }); + const healthQuery = useEngineSystemHealth(instanceUrl); + + const supportedWebhookConfig = + healthQuery.data?.features?.includes("WEBHOOK_CONFIG") || false; return (
@@ -42,8 +49,13 @@ export const EngineWebhooks: React.FC = ({ isPending={webhooks.isPending} isFetched={webhooks.isFetched} authToken={authToken} + supportedWebhookConfig={supportedWebhookConfig} + /> + -
); }; diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx index c8c4fbb43aa..731a6d22490 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/webhooks/components/webhooks-table.tsx @@ -27,7 +27,7 @@ import { TWTable } from "components/shared/TWTable"; import { format, formatDistanceToNowStrict } from "date-fns"; import { useTrack } from "hooks/analytics/useTrack"; import { MailQuestion, TrashIcon } from "lucide-react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { toast } from "sonner"; import { Card, FormLabel, Text } from "tw-components"; import { shortenString } from "utils/usedapp-external"; @@ -39,87 +39,29 @@ export function beautifyString(str: string): string { .join(" "); } +function jsonStringify(data: unknown | undefined): string { + if (!data) return ""; + return JSON.stringify(data, null, 2); +} + interface WebhooksTableProps { instanceUrl: string; webhooks: EngineWebhook[]; isPending: boolean; isFetched: boolean; authToken: string; + supportedWebhookConfig: boolean; } const columnHelper = createColumnHelper(); -const columns = [ - columnHelper.accessor("name", { - header: "Name", - cell: (cell) => { - return {cell.getValue()}; - }, - }), - columnHelper.accessor("eventType", { - header: "Event Type", - cell: (cell) => { - return {beautifyString(cell.getValue())}; - }, - }), - columnHelper.accessor("secret", { - header: "Secret", - cell: (cell) => { - return ( - - ); - }, - }), - columnHelper.accessor("url", { - header: "URL", - cell: (cell) => { - const url = cell.getValue(); - return ( - - {url} - - ); - }, - }), - columnHelper.accessor("createdAt", { - header: "Created At", - cell: (cell) => { - const value = cell.getValue(); - if (!value) { - return; - } - - const date = new Date(value); - return ( - - {format(date, "PP pp z")} - - } - shouldWrapChildren - > - {formatDistanceToNowStrict(date, { addSuffix: true })} - - ); - }, - }), -]; - export const WebhooksTable: React.FC = ({ instanceUrl, webhooks, isPending, isFetched, authToken, + supportedWebhookConfig = false, }) => { const [selectedWebhook, setSelectedWebhook] = useState(); const deleteDisclosure = useDisclosure(); @@ -127,12 +69,95 @@ export const WebhooksTable: React.FC = ({ const activeWebhooks = webhooks.filter((webhook) => webhook.active); + const columns = useMemo(() => { + const _columns = [ + columnHelper.accessor("name", { + header: "Name", + cell: (cell) => { + return {cell.getValue()}; + }, + }), + columnHelper.accessor("eventType", { + header: "Event Type", + cell: (cell) => { + return {beautifyString(cell.getValue())}; + }, + }), + columnHelper.accessor("secret", { + header: "Secret", + cell: (cell) => { + return ( + + ); + }, + }), + columnHelper.accessor("url", { + header: "URL", + cell: (cell) => { + const url = cell.getValue(); + return ( + + {url} + + ); + }, + }), + columnHelper.accessor("createdAt", { + header: "Created At", + cell: (cell) => { + const value = cell.getValue(); + if (!value) { + return; + } + + const date = new Date(value); + return ( + + {format(date, "PP pp z")} + + } + shouldWrapChildren + > + + {formatDistanceToNowStrict(date, { addSuffix: true })} + + + ); + }, + }), + ]; + + if (supportedWebhookConfig) { + _columns.push( + columnHelper.accessor("config", { + header: "Config", + cell: (cell) => { + const config = cell.getValue(); + return {jsonStringify(config)}; + }, + }), + ); + } + + return _columns; + }, [supportedWebhookConfig]); + return ( <> = ({ disclosure={deleteDisclosure} instanceUrl={instanceUrl} authToken={authToken} + supportedWebhookConfig={supportedWebhookConfig} /> )} {selectedWebhook && testDisclosure.isOpen && ( @@ -170,6 +196,7 @@ export const WebhooksTable: React.FC = ({ disclosure={testDisclosure} instanceUrl={instanceUrl} authToken={authToken} + supportedWebhookConfig={supportedWebhookConfig} /> )} @@ -181,12 +208,14 @@ interface DeleteWebhookModalProps { disclosure: UseDisclosureReturn; instanceUrl: string; authToken: string; + supportedWebhookConfig: boolean; } function DeleteWebhookModal({ webhook, disclosure, instanceUrl, authToken, + supportedWebhookConfig = false, }: DeleteWebhookModalProps) { const deleteWebhook = useEngineDeleteWebhook({ authToken, @@ -242,6 +271,14 @@ function DeleteWebhookModal({ URL {webhook.url} + {supportedWebhookConfig && ( + + Config + + {jsonStringify(webhook.config)} + + + )} Created at @@ -269,12 +306,14 @@ interface TestWebhookModalProps { disclosure: UseDisclosureReturn; instanceUrl: string; authToken: string; + supportedWebhookConfig: boolean; } function TestWebhookModal({ webhook, disclosure, instanceUrl, authToken, + supportedWebhookConfig = false, }: TestWebhookModalProps) { const { mutate: testWebhook, isPending } = useEngineTestWebhook({ instanceUrl, @@ -306,8 +345,20 @@ function TestWebhookModal({
URL - {webhook.url} + + {webhook.url} + + {supportedWebhookConfig && ( + + Config + + + {jsonStringify(webhook.config)} + + + + )}