From 6374fbabd2d508343e1a331f683fa81ef2b9d019 Mon Sep 17 00:00:00 2001 From: Chris Anderson Date: Wed, 25 Jun 2025 12:17:44 -0500 Subject: [PATCH] Improved Webhook Handling (#676) --- apps/platform/src/core/models/SQLModel.ts | 4 +++- .../src/providers/MessageTriggerService.ts | 4 +--- .../providers/webhook/LocalWebhookProvider.ts | 2 +- .../providers/webhook/LoggerWebhookProvider.ts | 2 +- apps/platform/src/providers/webhook/Webhook.ts | 2 +- .../src/providers/webhook/WebhookJob.ts | 2 +- apps/platform/src/render/TemplateService.ts | 4 +++- apps/ui/src/ui/Heading.css | 8 ++++++++ apps/ui/src/ui/Heading.tsx | 2 +- apps/ui/src/ui/Preview.css | 18 ++++++++++++++++++ apps/ui/src/ui/Preview.tsx | 13 +++++++++++-- apps/ui/src/variables.css | 2 +- apps/ui/src/views/campaign/CampaignPreview.tsx | 6 ++++-- .../how-to/journeys/examples/weeklySummary.md | 2 +- 14 files changed, 55 insertions(+), 16 deletions(-) diff --git a/apps/platform/src/core/models/SQLModel.ts b/apps/platform/src/core/models/SQLModel.ts index 7e3f5da9..609a67e2 100644 --- a/apps/platform/src/core/models/SQLModel.ts +++ b/apps/platform/src/core/models/SQLModel.ts @@ -29,7 +29,9 @@ export class SQLModel extends RawModel { // Take JSON attributes and stringify before insertion for (const attribute of this.jsonAttributes) { - obj[attribute] = JSON.stringify(obj[attribute]) + obj[attribute] = obj[attribute] == null + ? undefined + : JSON.stringify(obj[attribute]) } return this.formatJson(obj) diff --git a/apps/platform/src/providers/MessageTriggerService.ts b/apps/platform/src/providers/MessageTriggerService.ts index 6e20f490..916ff3dd 100644 --- a/apps/platform/src/providers/MessageTriggerService.ts +++ b/apps/platform/src/providers/MessageTriggerService.ts @@ -221,9 +221,7 @@ export const notifyJourney = async (reference_id: string, response?: any) => { // Save response into user step if (response) { await JourneyUserStep.update(q => q.where('id', referenceId), { - data: { - response, - }, + data: response, }) } diff --git a/apps/platform/src/providers/webhook/LocalWebhookProvider.ts b/apps/platform/src/providers/webhook/LocalWebhookProvider.ts index 982569d2..50054aab 100644 --- a/apps/platform/src/providers/webhook/LocalWebhookProvider.ts +++ b/apps/platform/src/providers/webhook/LocalWebhookProvider.ts @@ -39,7 +39,7 @@ export default class LocalWebhookProvider extends WebhookProvider { if (response.ok) { return { - message: options, + request: options, success: true, response: responseBody, } diff --git a/apps/platform/src/providers/webhook/LoggerWebhookProvider.ts b/apps/platform/src/providers/webhook/LoggerWebhookProvider.ts index 1a39f211..910e883e 100644 --- a/apps/platform/src/providers/webhook/LoggerWebhookProvider.ts +++ b/apps/platform/src/providers/webhook/LoggerWebhookProvider.ts @@ -25,7 +25,7 @@ export default class LoggerWebhookProvider extends WebhookProvider { logger.info(options, 'provider:webhook:logger') return { - message: options, + request: options, success: true, response: '', } diff --git a/apps/platform/src/providers/webhook/Webhook.ts b/apps/platform/src/providers/webhook/Webhook.ts index a9e1b2ab..7e766ff7 100644 --- a/apps/platform/src/providers/webhook/Webhook.ts +++ b/apps/platform/src/providers/webhook/Webhook.ts @@ -6,7 +6,7 @@ export interface Webhook { } export interface WebhookResponse { - message: Webhook + request: Webhook success: boolean response: Record | string } diff --git a/apps/platform/src/providers/webhook/WebhookJob.ts b/apps/platform/src/providers/webhook/WebhookJob.ts index f8704a06..fa219b3d 100644 --- a/apps/platform/src/providers/webhook/WebhookJob.ts +++ b/apps/platform/src/providers/webhook/WebhookJob.ts @@ -37,7 +37,7 @@ export default class WebhookJob extends Job { try { const result = await channel.send(template, data) - await finalizeSend(data, result) + await finalizeSend(data, result.response) } catch (error: any) { await failSend(data, error) } finally { diff --git a/apps/platform/src/render/TemplateService.ts b/apps/platform/src/render/TemplateService.ts index ac79337d..06bdeee0 100644 --- a/apps/platform/src/render/TemplateService.ts +++ b/apps/platform/src/render/TemplateService.ts @@ -124,7 +124,7 @@ export const sendProof = async (template: TemplateType, variables: Variables, re logger.info(response, 'template:proof:push:result') } else if (template.type === 'webhook') { const channel = await loadWebhookChannel(campaign.provider_id, project.id) - await channel?.send(template, variables) + response = await channel?.send(template, variables) } else { throw new RequestError('Sending template proofs is only supported for email and text message types as this time.') } @@ -141,6 +141,8 @@ export const sendProof = async (template: TemplateType, variables: Variables, re }, }, }).queue() + + return response } // Determine what template to send to the user based on the following: diff --git a/apps/ui/src/ui/Heading.css b/apps/ui/src/ui/Heading.css index 1a9058e1..478fc1a0 100644 --- a/apps/ui/src/ui/Heading.css +++ b/apps/ui/src/ui/Heading.css @@ -48,6 +48,14 @@ padding: 5px 0; } +.heading.heading-h5 { + margin: 5px 0; +} + +.heading.heading-h4 h4 { + padding: 5px 0; +} + .heading label, .heading input { margin: 0; } \ No newline at end of file diff --git a/apps/ui/src/ui/Heading.tsx b/apps/ui/src/ui/Heading.tsx index 654c5953..1e0907e4 100644 --- a/apps/ui/src/ui/Heading.tsx +++ b/apps/ui/src/ui/Heading.tsx @@ -2,7 +2,7 @@ import './Heading.css' interface HeadingProps { title: React.ReactNode - size?: 'h2' | 'h3' | 'h4' + size?: 'h2' | 'h3' | 'h4' | 'h5' actions?: React.ReactNode children?: React.ReactNode } diff --git a/apps/ui/src/ui/Preview.css b/apps/ui/src/ui/Preview.css index 3e72e96f..0bcb478f 100644 --- a/apps/ui/src/ui/Preview.css +++ b/apps/ui/src/ui/Preview.css @@ -157,6 +157,17 @@ width: 100%; } +.webhook-frame .heading { + background: var(--color-grey); + border-radius: var(--border-radius); + padding: 5px 10px; + margin-bottom: 10px; +} + +.webhook-frame .webhook-block { + margin-bottom: 20px; +} + .preview.small .email-frame iframe { width: 200%; height: 200%; @@ -173,4 +184,11 @@ border-radius: 0; transform: scale(0.8); transform-origin: top left; +} + +.preview.small .webhook-frame { + padding: 5px 10px; + transform: scale(0.8); + transform-origin: top left; + width: 125%; } \ No newline at end of file diff --git a/apps/ui/src/ui/Preview.tsx b/apps/ui/src/ui/Preview.tsx index 6b9c30e9..4a61aee2 100644 --- a/apps/ui/src/ui/Preview.tsx +++ b/apps/ui/src/ui/Preview.tsx @@ -6,13 +6,15 @@ import { ReactNode, useContext } from 'react' import { ProjectContext } from '../contexts' import JsonPreview from './JsonPreview' import clsx from 'clsx' +import Heading from './Heading' interface PreviewProps { template: Pick + response?: any size?: 'small' | 'large' } -export default function Preview({ template, size = 'large' }: PreviewProps) { +export default function Preview({ template, response, size = 'large' }: PreviewProps) { const [project] = useContext(ProjectContext) const { data, type } = template @@ -59,7 +61,14 @@ export default function Preview({ template, size = 'large' }: PreviewProps) { } else if (type === 'webhook') { preview = (
- +
+ + +
+ {response &&
+ + +
}
) } diff --git a/apps/ui/src/variables.css b/apps/ui/src/variables.css index 9eaa30b5..3e888072 100644 --- a/apps/ui/src/variables.css +++ b/apps/ui/src/variables.css @@ -5,7 +5,7 @@ --color-primary: var(--color-black); --color-primary-soft: #6b707b; - --color-grey: #eae8e8; + --color-grey: #e8e8ea; --color-grey-soft: #F5F5F7; --color-grey-hard: #cecfd2; diff --git a/apps/ui/src/views/campaign/CampaignPreview.tsx b/apps/ui/src/views/campaign/CampaignPreview.tsx index 470911d9..4459bcff 100644 --- a/apps/ui/src/views/campaign/CampaignPreview.tsx +++ b/apps/ui/src/views/campaign/CampaignPreview.tsx @@ -52,6 +52,7 @@ export default function CampaignPreview() { const [isUserLookupOpen, setIsUserLookupOpen] = useState(false) const [isSendProofOpen, setIsSendProofOpen] = useState(false) const template = campaignState[0].templates.find(template => template.locale === currentLocale?.key) + const [proofResponse, setProofResponse] = useState(undefined) if (!template) { return (<> @@ -82,10 +83,11 @@ export default function CampaignPreview() { const handleSendProof = async (recipient: string) => { try { - await api.templates.proof(project.id, template.id, { + const response = await api.templates.proof(project.id, template.id, { variables: JSON.parse(value ?? '{}'), recipient, }) + setProofResponse(response) } catch (error: any) { if (error.response.data.error) { toast.error(error.response.data.error) @@ -136,7 +138,7 @@ export default function CampaignPreview() { variant="secondary" onClick={() => setIsSendProofOpen(true)}>{t('send_proof')} } /> - + diff --git a/docs/docs/how-to/journeys/examples/weeklySummary.md b/docs/docs/how-to/journeys/examples/weeklySummary.md index 3ae71e97..883e1bd4 100644 --- a/docs/docs/how-to/journeys/examples/weeklySummary.md +++ b/docs/docs/how-to/journeys/examples/weeklySummary.md @@ -3,7 +3,7 @@ title: Weekly Summary sidebar_position: 3 --- -# Weely Summary +# Weekly Summary ## Scenario You have an e-commerce site and you want to sent weekly stats to all vendors that sell products through your platform, letting them know how many people viewed and purchased their products in the past 7 days.