-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Update Slack webhooks to support latest events #2811
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughRefactors Slack message templates (renames and adds bounty support), strengthens commission/partner templates, extends Slack settings with new events, and updates the Singular webhook route to use Axiom, change GET/HEAD signatures, and add token/event/workspace validation and dispatching. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Source as Event Source
participant Formatter as formatEventForSlack
participant Templates as Template Selector
participant Slack as Slack Webhook
Source->>Formatter: Emit event (e.g., bounty.created / commission.created)
Formatter->>Templates: Determine template (isBountyEvent? bountyTemplates : mapping)
Templates-->>Formatter: Render Slack blocks (truncate, amounts, view links)
Formatter->>Slack: POST message payload (blocks + view links)
Slack-->>Source: Delivery response
sequenceDiagram
autonumber
actor HTTP as Incoming Request
participant Axiom as withAxiom wrapper
participant Route as GET handler (AxiomRequest)
participant Auth as Token & env validation
participant Workspace as Workspace lookup
participant Dispatcher as Event dispatcher (tracking functions)
HTTP->>Axiom: GET /singular/webhook?event=...
Axiom->>Route: Invoke handler with AxiomRequest
Route->>Auth: Validate token & event name
Auth-->>Route: OK / Reject
Route->>Workspace: Resolve workspace by token
Workspace-->>Route: workspace
Route->>Dispatcher: Map event -> tracking function (lead/sale) or return OK for unsupported
Dispatcher-->>Route: result
Route-->>HTTP: Response (200 OK / error)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/lib/integrations/slack/transform.ts (1)
15-21
: Escape user-supplied text to avoid Slack mrkdwn breakage and accidental mentionsSeveral fields (names, emails, countries, referer, descriptions, IDs) are inserted directly into mrkdwn. Unescaped
<
,>
,&
, or|
can break formatting;<!here>
/<!channel>
could also ping channels. Add a small escape helper and use it across templates.Add once near the top of this file (outside the shown ranges):
const escapeMrkdwn = (s?: string | null) => (s ?? "Unknown") .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">");Also applies to: 54-60, 106-118, 155-160, 205-212, 247-252, 318-326
🧹 Nitpick comments (7)
apps/web/lib/integrations/slack/ui/settings.tsx (1)
21-24
: Add type-safety and confirm backend trigger availabilityLooks good functionally. To catch typos at compile time and ensure end-to-end wiring, type
supportedEvents
asWebhookTrigger[]
(ifConfigureWebhook
allows) and confirm these four events are present inWEBHOOK_TRIGGERS
and processed server-side.apps/web/lib/integrations/slack/transform.ts (6)
54-60
: Harden link.clicked template: URL-encode params and escape fields
- Encode
domain
/key
in the URL.- Escape
country
andreferer
with the helper.- const linkToClicks = `${APP_DOMAIN}/events?event=clicks&domain=${link.domain}&key=${link.key}`; + const linkToClicks = `${APP_DOMAIN}/events?event=clicks&domain=${encodeURIComponent(link.domain)}&key=${encodeURIComponent(link.key)}`; @@ - text: `*Country*\n${click.country}`, + text: `*Country*\n${escapeMrkdwn(click.country)}`, @@ - text: `*Referrer*\n${click.referer}`, + text: `*Referrer*\n${escapeMrkdwn(click.referer)}`,Also applies to: 70-77, 83-91, 97-101
110-118
: Section block may not support both text and fields together; also escape/encodeSlack Section blocks commonly use either
text
orfields
. To be safe, move the customer/email fields into the next section and escape text. Also URL-encode query params.- const linkToLeads = `${APP_DOMAIN}/events?event=leads&domain=${link.domain}&key=${link.key}`; + const linkToLeads = `${APP_DOMAIN}/events?event=leads&domain=${encodeURIComponent(link.domain)}&key=${encodeURIComponent(link.key)}`; @@ - { - type: "section", - text: { - type: "mrkdwn", - text: "*New lead created* :tada:", - }, - fields: [ - { - type: "mrkdwn", - text: `*Customer*\n${customer.name}`, - }, - { - type: "mrkdwn", - text: `*Email*\n<mailto:${customer.email}|${customer.email}>`, - }, - ], - }, + { + type: "section", + text: { type: "mrkdwn", text: "*New lead created* :tada:" }, + }, @@ - { - type: "mrkdwn", - text: `*Country*\n${click.country}`, - }, + { type: "mrkdwn", text: `*Customer*\n${escapeMrkdwn(customer.name)}` }, + { type: "mrkdwn", text: `*Email*\n<mailto:${customer.email}|${escapeMrkdwn(customer.email)}>` }, + { type: "mrkdwn", text: `*Country*\n${escapeMrkdwn(click.country)}` }, { type: "mrkdwn", text: `*Short link*\n<${link.shortLink}|${link.shortLink}>`, },Also applies to: 118-127, 130-141, 146-149
155-160
: Use currencyFormatter for sales and escape text
- Prefer
currencyFormatter
for locale-aware currency with proper symbols.- Encode query params and escape customer/country text.
- const amountInDollars = (sale.amount / 100).toFixed(2); - const linkToSales = `${APP_DOMAIN}/events?event=sales&domain=${link.domain}&key=${link.key}`; + const formattedAmount = currencyFormatter(sale.amount / 100, { currency: sale.currency.toUpperCase() }); + const linkToSales = `${APP_DOMAIN}/events?event=sales&domain=${encodeURIComponent(link.domain)}&key=${encodeURIComponent(link.key)}`; @@ - text: `*Customer*\n${customer.name}`, + text: `*Customer*\n${escapeMrkdwn(customer.name)}`, @@ - text: `*Email*\n<mailto:${customer.email}|${customer.email}>`, + text: `*Email*\n<mailto:${customer.email}|${escapeMrkdwn(customer.email)}>`, @@ - text: `*Country*\n${click.country}`, + text: `*Country*\n${escapeMrkdwn(click.country)}`, @@ - text: `*Amount*\n${amountInDollars} ${sale.currency.toUpperCase()}`, + text: `*Amount*\n${formattedAmount}`,Also applies to: 169-176, 183-190, 196-199
226-235
: Escape partner fieldsProtect mrkdwn formatting for name/email/country.
- text: `*Name*\n${name}`, + text: `*Name*\n${escapeMrkdwn(name)}`, @@ - text: `*Email*\n<mailto:${email}|${email}>`, + text: `*Email*\n<mailto:${email}|${escapeMrkdwn(email)}>`, @@ - text: `*Country*\n${country}`, + text: `*Country*\n${escapeMrkdwn(country)}`,
267-274
: Escape commission fields; keep currency logic as-isEscape partner/customer text. Currency handling looks solid.
- text: `*Partner*\n${partner.name}`, + text: `*Partner*\n${escapeMrkdwn(partner.name)}`, @@ - text: `*Email*\n<mailto:${partner.email}|${partner.email}>`, + text: `*Email*\n<mailto:${partner.email}|${escapeMrkdwn(partner.email)}>`, @@ - text: `*Customer*\n${customer.name}`, + text: `*Customer*\n${escapeMrkdwn(customer.name)}`, @@ - text: `*Customer Email*\n<mailto:${customer.email}|${customer.email}>`, + text: `*Customer Email*\n<mailto:${customer.email}|${escapeMrkdwn(customer.email)}>`,Also applies to: 276-287, 288-305, 306-312
395-405
: Type the template map for stronger compile-time guaranteesCurrent
Record<WebhookTrigger, any>
loses arg/result types. Consider a discriminated union for args or a sharedTemplateArgs
type to ensure each handler receives the correct shape.Example (outside this hunk):
type BaseArgs<T> = { data: T; event?: WebhookTrigger }; type TemplateFn<T> = (args: BaseArgs<T>) => { blocks: any[] }; const slackTemplates: { "link.created": TemplateFn<LinkWebhookEvent["data"]>; "link.updated": TemplateFn<LinkWebhookEvent["data"]>; "link.deleted": TemplateFn<LinkWebhookEvent["data"]>; "link.clicked": TemplateFn<ClickEventWebhookPayload>; "lead.created": TemplateFn<LeadEventWebhookPayload>; "sale.created": TemplateFn<SaleEventWebhookPayload>; "partner.enrolled": TemplateFn<PartnerEventWebhookPayload>; "commission.created": TemplateFn<CommissionEventWebhookPayload>; "bounty.created": TemplateFn<BountyEventWebhookPayload>; "bounty.updated": TemplateFn<BountyEventWebhookPayload>; } = { /* ... */ };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/web/lib/integrations/slack/transform.ts
(8 hunks)apps/web/lib/integrations/slack/ui/settings.tsx
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/integrations/slack/transform.ts (3)
apps/web/lib/webhook/types.ts (5)
ClickEventWebhookPayload
(15-15)LeadEventWebhookPayload
(17-17)SaleEventWebhookPayload
(19-19)BountyEventWebhookPayload
(27-27)WebhookTrigger
(13-13)packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter
(5-16)packages/utils/src/constants/main.ts (1)
APP_DOMAIN
(13-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Vade Review
- GitHub Check: build
🔇 Additional comments (1)
apps/web/lib/integrations/slack/transform.ts (1)
423-426
: LGTM: event-aware dispatch preserves existing behaviorPassing
event
only for templates that need it keeps signatures minimal and avoids churn.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/app/(ee)/api/singular/webhook/route.ts (1)
62-69
: Add event_name validation schema for better error handling.While the code checks for the presence of
event_name
, it would be more consistent to add it to the schema validation along with the auth parameters.Consider adding event_name to the validation schema:
const authSchema = z.object({ dub_token: z .string() .min(1, "dub_token is required") .describe("Global token to identify Singular events."), dub_workspace_id: z .string() .min(1, "dub_workspace_id is required") .describe( "The Singular advertiser's workspace ID on Dub (see https://d.to/id).", ) .transform((v) => normalizeWorkspaceId(v)), + event_name: z + .string() + .min(1, "event_name is required") + .describe("The Singular event name to process."), });Then update the parsing logic:
- const { dub_token: token, dub_workspace_id: workspaceId } = + const { dub_token: token, dub_workspace_id: workspaceId, event_name: eventName } = authSchema.parse(queryParams); if (token !== singularWebhookToken) { throw new DubApiError({ code: "unauthorized", message: "Invalid Singular webhook token. Skipping event processing.", }); } - const { event_name: eventName } = queryParams; - - if (!eventName) { - throw new DubApiError({ - code: "bad_request", - message: "event_name is required.", - }); - }apps/web/lib/integrations/slack/transform.ts (1)
205-240
: Consider escaping user-controlled text in partner template.Similar to the bounty template, the partner name should be escaped to prevent Slack markdown issues.
{ type: "mrkdwn", - text: `*Name*\n${name}`, + text: `*Name*\n${escapeMrkdwn(name)}`, },
♻️ Duplicate comments (2)
apps/web/lib/integrations/slack/transform.ts (2)
318-392
: Escape user-controlled text and improve date formatting for bounty templates.The bounty template doesn't escape user-controlled text (name, description) which could break Slack's markdown formatting if they contain special characters. Also, date formatting should be consistent and explicit.
Add an escape function and apply proper formatting:
+// Add this helper function at the top of the file +const escapeMrkdwn = (text: string): string => { + return text + .replace(/&/g, "&") + .replace(/</g, "<") + .replace(/>/g, ">"); +}; const bountyTemplates = ({ data, event, }: { data: BountyEventWebhookPayload; event: WebhookTrigger; }) => { const { id, name, description, rewardAmount, type, startsAt, endsAt } = data; const eventMessages = { "bounty.created": "*New bounty created* :money_with_wings:", "bounty.updated": "*Bounty updated* :memo:", }; const formattedReward = currencyFormatter(rewardAmount / 100); const linkToBounty = `${APP_DOMAIN}/bounties/${id}`; + const escapedName = escapeMrkdwn(truncate(name, 140) || "Untitled Bounty"); + const truncatedDescription = description ? truncate(description, 2800) : null; + const escapedDescription = truncatedDescription ? escapeMrkdwn(truncatedDescription) : null; return { blocks: [ { type: "section", text: { type: "mrkdwn", text: eventMessages[event], }, }, { type: "section", fields: [ { type: "mrkdwn", - text: `*Bounty Name*\n${truncate(name, 140) || "Untitled Bounty"}`, + text: `*Bounty Name*\n${escapedName}`, }, { type: "mrkdwn", text: `*Reward Amount*\n${formattedReward}`, }, ], }, { type: "section", fields: [ { type: "mrkdwn", text: `*Type*\n${type.charAt(0).toUpperCase() + type.slice(1)}`, }, { type: "mrkdwn", - text: `*Duration*\n${new Date(startsAt).toLocaleDateString()}${endsAt ? ` - ${new Date(endsAt).toLocaleDateString()}` : " (No end date)"}`, + text: `*Duration*\n${new Date(startsAt).toLocaleDateString("en-US", { dateStyle: "medium" })}${endsAt ? ` - ${new Date(endsAt).toLocaleDateString("en-US", { dateStyle: "medium" })}` : " (No end date)"}`, }, ], }, - ...(description + ...(escapedDescription ? [ { type: "section", text: { type: "mrkdwn", - text: `*Description*\n${truncate(description, 140)}`, + text: `*Description*\n${escapedDescription}`, }, }, ] : []), { type: "context", elements: [ { type: "mrkdwn", text: `<${linkToBounty}|View bounty on Dub>`, }, ], }, ], }; };
362-362
: Add null check for type field to prevent runtime errors.The code assumes
type
is always defined, but should handle the case where it might be null or undefined.- text: `*Type*\n${type.charAt(0).toUpperCase() + type.slice(1)}`, + text: `*Type*\n${type ? type.charAt(0).toUpperCase() + type.slice(1) : "N/A"}`,
🧹 Nitpick comments (2)
apps/web/app/(ee)/api/singular/webhook/route.ts (1)
71-77
: Consider returning 200 OK instead of logging errors for unsupported events.The current approach logs an error to console but returns a 200 OK response. For webhook handlers, it's generally better to silently acknowledge unsupported events without logging errors, as they're expected behavior.
if (!supportedEvents.includes(eventName)) { - console.error( - `Event ${eventName} is not supported by Singular <> Dub integration.`, - ); + // Silently ignore unsupported events - this is expected behavior return NextResponse.json("OK"); }apps/web/lib/integrations/slack/transform.ts (1)
349-349
: Consider using a larger truncation limit for better UX.The current truncation limit of 140 characters for bounty names and descriptions is quite restrictive. Slack can handle much longer text in message blocks.
- text: `*Bounty Name*\n${truncate(name, 140) || "Untitled Bounty"}`, + text: `*Bounty Name*\n${truncate(name, 500) || "Untitled Bounty"}`,- text: `*Description*\n${truncate(description, 140)}`, + text: `*Description*\n${truncate(description, 2800)}`,Also applies to: 376-376
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
apps/web/app/(ee)/api/singular/webhook/route.ts
(3 hunks)apps/web/lib/integrations/slack/transform.ts
(8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-17T06:41:45.620Z
Learnt from: devkiran
PR: dubinc/dub#2637
File: apps/web/app/(ee)/api/singular/webhook/route.ts:0-0
Timestamp: 2025-07-17T06:41:45.620Z
Learning: In the Singular integration (apps/web/app/(ee)/api/singular/webhook/route.ts), the event names in the singularToDubEvent object have intentionally different casing: "Copy GAID" and "copy IDFA". This casing difference is valid and should not be changed, as these are the correct event names expected from Singular.
Applied to files:
apps/web/app/(ee)/api/singular/webhook/route.ts
🧬 Code graph analysis (2)
apps/web/lib/integrations/slack/transform.ts (3)
apps/web/lib/webhook/types.ts (2)
BountyEventWebhookPayload
(27-27)WebhookTrigger
(13-13)packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter
(5-16)packages/utils/src/constants/main.ts (1)
APP_DOMAIN
(13-18)
apps/web/app/(ee)/api/singular/webhook/route.ts (1)
apps/web/app/(ee)/api/events/export/route.ts (1)
GET
(45-136)
🔇 Additional comments (2)
apps/web/app/(ee)/api/singular/webhook/route.ts (1)
7-7
: LGTM! Proper integration of Next.js Axiom for observability.The addition of Axiom for request logging and observability is correctly implemented with the
withAxiom
wrapper.Also applies to: 40-40, 118-118
apps/web/lib/integrations/slack/transform.ts (1)
247-251
: LGTM! Proper currency handling in commission template.The commission template correctly uses the currency from the data payload and passes it to the currency formatter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/web/lib/integrations/slack/transform.ts (2)
155-204
: Use currencyFormatter with currency for sales amounts.toFixed(2) breaks for zero-decimal currencies (e.g., JPY) and locale formatting. Use the shared formatter.
- const amountInDollars = (sale.amount / 100).toFixed(2); + const formattedAmount = currencyFormatter(sale.amount / 100, { + currency: sale.currency?.toUpperCase?.() || "USD", + }); @@ - { - type: "mrkdwn", - text: `*Amount*\n${amountInDollars} ${sale.currency.toUpperCase()}`, - }, + { + type: "mrkdwn", + text: `*Amount*\n${formattedAmount}`, + },
257-326
: Currency normalization and text escaping in commissions.
- Normalize currency to uppercase to avoid Intl errors.
- Escape partner/customer names.
- const { id, amount, earnings, currency, partner, customer } = data; + const { id, amount, earnings, currency, partner, customer } = data; + const cur = (currency || "USD").toUpperCase(); @@ - const formattedAmount = currencyFormatter(amount / 100, { currency }); - const formattedEarnings = currencyFormatter(earnings / 100, { currency }); + const formattedAmount = currencyFormatter(amount / 100, { currency: cur }); + const formattedEarnings = currencyFormatter(earnings / 100, { currency: cur }); @@ - text: `*Partner*\n${partner.name}`, + text: `*Partner*\n${escapeMrkdwn(partner.name)}`, @@ - ...(customer + ...(customer ? [ { type: "section", fields: [ { type: "mrkdwn", - text: `*Customer*\n${customer.name}`, + text: `*Customer*\n${escapeMrkdwn(customer.name)}`, },
♻️ Duplicate comments (2)
apps/web/lib/integrations/slack/transform.ts (2)
1-1
: Escape Slack text and standardize date formatting (repeat of prior ask).User-controlled strings (names, descriptions, types) should be mrkdwn-escaped; dates should use a consistent locale. Add an escape helper and use it in templates; prefer toLocaleDateString("en-US", { dateStyle: "medium" }).
Add once near the top (outside the diff range):
// Minimal Slack mrkdwn escape for &, <, > const escapeMrkdwn = (s: string) => (s ?? "").replace(/[&<>]/g, (ch) => ch === "&" ? "&" : ch === "<" ? "<" : ">", );
328-402
: Bounty template: guard nulls, escape text, and standardize dates.type, startsAt, endsAt, and description may be null/undefined; current code can throw (type.charAt) or output “Invalid Date”.
const bountyTemplates = ({ data, event, }: { data: BountyEventWebhookPayload; event: WebhookTrigger; }) => { const { id, name, description, rewardAmount, type, startsAt, endsAt } = data; const eventMessages = { "bounty.created": "*New bounty created* :money_with_wings:", "bounty.updated": "*Bounty updated* :memo:", }; - const formattedReward = currencyFormatter(rewardAmount / 100); + const formattedReward = currencyFormatter(rewardAmount / 100); const linkToBounty = `${APP_DOMAIN}/program/bounties/${id}`; + const safeName = escapeMrkdwn(truncate(name || "Untitled Bounty", 140)); + const safeType = type + ? `${type.slice(0, 1).toUpperCase()}${type.slice(1)}` + : "N/A"; + const formattedStart = startsAt + ? new Date(startsAt).toLocaleDateString("en-US", { dateStyle: "medium" }) + : "N/A"; + const formattedEnd = endsAt + ? new Date(endsAt).toLocaleDateString("en-US", { dateStyle: "medium" }) + : undefined; + const safeDescription = + description && description.length > 0 + ? escapeMrkdwn(truncate(description, 2800)) + : undefined; @@ { type: "mrkdwn", - text: `*Bounty Name*\n${truncate(name, 140) || "Untitled Bounty"}`, + text: `*Bounty Name*\n${safeName}`, }, @@ { type: "mrkdwn", - text: `*Type*\n${type.charAt(0).toUpperCase() + type.slice(1)}`, + text: `*Type*\n${safeType}`, }, { type: "mrkdwn", - text: `*Duration*\n${new Date(startsAt).toLocaleDateString()}${endsAt ? ` - ${new Date(endsAt).toLocaleDateString()}` : " (No end date)"}`, + text: `*Duration*\n${ + formattedEnd ? `${formattedStart} - ${formattedEnd}` : `${formattedStart} (No end date)` + }`, }, ], }, - ...(description + ...(safeDescription ? [ { type: "section", text: { type: "mrkdwn", - text: `*Description*\n${truncate(description, 140)}`, + text: `*Description*\n${safeDescription}`, }, }, ] : []),If bounties support non-USD currency, pass it to currencyFormatter; otherwise defaults to USD.
#!/bin/bash # Does the bounty payload include a currency field? rg -nP --type=ts -C2 'Bounty(Schema|EventWebhookPayload).*currency' apps/web
🧹 Nitpick comments (2)
apps/web/lib/integrations/slack/transform.ts (2)
205-250
: Partner fields: avoid “undefined” output and escape names.Use safe fallbacks for optional properties and escape text.
const { name, email, country, partnerId } = data; const linkToPartner = `${APP_DOMAIN}/program/partners?partnerId=${partnerId}`; + const safeName = escapeMrkdwn(name || "Unknown"); + const safeEmail = email ? `<mailto:${email}|${email}>` : "—"; + const safeCountry = country || "—"; @@ { type: "mrkdwn", - text: `*Name*\n${name}`, + text: `*Name*\n${safeName}`, }, { type: "mrkdwn", - text: `*Email*\n<mailto:${email}|${email}>`, + text: `*Email*\n${safeEmail}`, }, { type: "mrkdwn", - text: `*Country*\n${country}`, + text: `*Country*\n${safeCountry}`, },
54-104
: Null-safe country and referer fieldsDefault to
"—"
when these values are nullish, and use thereferer
property (single r) as defined in the schema:{ type: "mrkdwn", - text: `*Country*\n${click.country}`, + text: `*Country*\n${click.country ?? "—"}`, }, { type: "mrkdwn", - text: `*Referrer*\n${click.referer}`, + text: `*Referrer*\n${click.referer ?? "—"}`, },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
apps/web/lib/integrations/slack/transform.ts
(8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/lib/integrations/slack/transform.ts (3)
apps/web/lib/webhook/types.ts (7)
ClickEventWebhookPayload
(15-15)LeadEventWebhookPayload
(17-17)SaleEventWebhookPayload
(19-19)PartnerEventWebhookPayload
(21-21)CommissionEventWebhookPayload
(23-25)BountyEventWebhookPayload
(27-27)WebhookTrigger
(13-13)packages/utils/src/constants/main.ts (1)
APP_DOMAIN
(13-18)packages/utils/src/functions/currency-formatter.ts (1)
currencyFormatter
(5-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Vade Review
- GitHub Check: build
🔇 Additional comments (4)
apps/web/lib/integrations/slack/transform.ts (4)
404-415
: Template mapping looks good.New events are wired to the corresponding templates.
417-436
: Dispatcher logic is clear.Passing event only where needed (link/bounty) keeps payloads lean.
106-153
: No changes needed: Section block supports both text and fields
Confirmed via Slack Block Kit docs that a section block may include both atext
object and afields
array simultaneously.
15-52
: Remove unnecessary conditional fordata.url
The ZodlinkEventSchema
definesurl
as a non-nullable string (even on deletes) and the samplelink-deleted.json
always includes aurl
field.data.url
won’t beundefined
, so the suggested conditional spread isn’t needed.Likely an incorrect or invalid review comment.
Summary by CodeRabbit
New Features
Improvements