From 20dcd6b3c6b21dde07ebdafe434f66ac701ddca5 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Tue, 15 Apr 2025 16:41:14 -0700 Subject: [PATCH 01/10] Show events metadata on customers page --- .../tinybird/pipes/v2_customer_events.pipe | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/tinybird/pipes/v2_customer_events.pipe b/packages/tinybird/pipes/v2_customer_events.pipe index 143873515f5..75962b6f602 100644 --- a/packages/tinybird/pipes/v2_customer_events.pipe +++ b/packages/tinybird/pipes/v2_customer_events.pipe @@ -37,12 +37,13 @@ SQL > = {{ String( clickId, - '8bBSF1CbVlXRCMJY', + 'aVneMglsZ6qkydu0', description="The unique ID for a given click event", required=True, ) }} - LIMIT {{ Int32(limit, 100) }} + ORDER BY timestamp DESC + LIMIT 1 @@ -74,19 +75,20 @@ SQL > splitByString('?', referer_url)[1] as referer_url_processed, 'lead' as event, event_id, - event_name - FROM dub_lead_events_mv + event_name, + metadata + FROM dub_lead_events_mv_new WHERE customer_id = {{ String( customerId, - 'cm1a18x7w0001aqhx744lrtxp', + 'cm1hxwtve0001pgdvwkmjjdrv', description="The unique ID for a given customer", required=True, ) }} - AND timestamp >= {{ DateTime(start, '2025-01-01 00:00:00') }} + AND timestamp >= {{ DateTime(start, '2024-01-01 00:00:00') }} AND timestamp < {{ DateTime(end, '2025-06-30 00:00:00') }} ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %} LIMIT {{ Int32(limit, 100) }} @@ -122,10 +124,11 @@ SQL > 'sale' as event, event_id, event_name, + metadata, amount as saleAmount, invoice_id, payment_processor - FROM dub_sale_events_mv + FROM dub_sale_events_mv_new WHERE customer_id = {{ @@ -154,6 +157,7 @@ SQL > *, NULL AS event_id, NULL AS event_name, + NULL AS metadata, NULL AS saleAmount, NULL AS invoice_id, NULL AS payment_processor From a8c747ff755a9445c2b308b2e7dedc82c7ca145e Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Tue, 15 Apr 2025 16:51:23 -0700 Subject: [PATCH 02/10] Update v2_events.pipe --- packages/tinybird/pipes/v2_events.pipe | 74 ++++++++++++++------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/tinybird/pipes/v2_events.pipe b/packages/tinybird/pipes/v2_events.pipe index 00f9808f94c..f5cbd568e57 100644 --- a/packages/tinybird/pipes/v2_events.pipe +++ b/packages/tinybird/pipes/v2_events.pipe @@ -2,8 +2,6 @@ DESCRIPTION > Top countries -TOKEN "v2_events_endpoint_read_6623" READ - TAGS "Dub Endpoints" NODE workspace_links @@ -11,16 +9,23 @@ SQL > % SELECT link_id, domain, key - from dub_links_metadata_latest FINAL + FROM + {% if defined(isMegaFolder) and Boolean(isMegaFolder) == 1 %} dub_links_metadata_latest + {% else %} dub_regular_links_metadata_latest + {% end %} FINAL WHERE deleted == 0 {% if defined(workspaceId) %} AND workspace_id = {{ workspaceId }} {% end %} {% if defined(programId) %} AND program_id = {{ programId }} {% end %} {% if defined(partnerId) %} AND partner_id = {{ partnerId }} {% end %} {% if defined(tenantId) %} AND tenant_id = {{ tenantId }} {% end %} - {% if defined(folderId) %} AND folder_id = {{ folderId }} {% end %} + {% if defined(folderIds) %} AND folder_id IN {{ Array(folderIds, 'String') }} + {% elif defined(folderId) %} AND folder_id = {{ folderId }} + {% end %} {% if defined(domain) %} AND domain IN {{ Array(domain, 'String') }} {% end %} - {% if defined(tagIds) %} AND arrayIntersect(tag_ids, {{ Array(tagIds, 'String') }}) != [] {% end %} + {% if defined(tagIds) %} + AND arrayIntersect(tag_ids, {{ Array(tagIds, 'String') }}) != [] + {% end %} {% if defined(root) %} {% if Boolean(root) == 1 %} AND key = '_root' {% else %} AND key != '_root' {% end %} {% end %} @@ -54,21 +59,11 @@ SQL > {% if defined(os) %} AND os = {{ os }} {% end %} {% if defined(referer) %} AND referer = {{ referer }} {% end %} {% if defined(refererUrl) %} AND splitByString('?', referer_url)[1] = {{ refererUrl }} {% end %} - {% if defined(utm_source) %} - AND url LIKE concat('%utm_source=', {{ String(utm_source) }}, '%') - {% end %} - {% if defined(utm_medium) %} - AND url LIKE concat('%utm_medium=', {{ String(utm_medium) }}, '%') - {% end %} - {% if defined(utm_campaign) %} - AND url LIKE concat('%utm_campaign=', {{ String(utm_campaign) }}, '%') - {% end %} - {% if defined(utm_term) %} - AND url LIKE concat('%utm_term=', {{ String(utm_term) }}, '%') - {% end %} - {% if defined(utm_content) %} - AND url LIKE concat('%utm_content=', {{ String(utm_content) }}, '%') - {% end %} + {% if defined(utm_source) %} AND url LIKE concat('%utm_source=', encodeURLFormComponent({{ String(utm_source) }}), '%') {% end %} + {% if defined(utm_medium) %} AND url LIKE concat('%utm_medium=', encodeURLFormComponent({{ String(utm_medium) }}), '%') {% end %} + {% if defined(utm_campaign) %} AND url LIKE concat('%utm_campaign=', encodeURLFormComponent({{ String(utm_campaign) }}), '%') {% end %} + {% if defined(utm_term) %} AND url LIKE concat('%utm_term=', encodeURLFormComponent({{ String(utm_term) }}), '%') {% end %} + {% if defined(utm_content) %} AND url LIKE concat('%utm_content=', encodeURLFormComponent({{ String(utm_content) }}), '%') {% end %} {% if defined(url) %} AND url = {{ url }} {% end %} ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %} LIMIT {{ Int32(limit, 100) }} @@ -85,10 +80,10 @@ SQL > splitByString('?', referer_url)[1] as referer_url_processed, CONCAT(country, '-', region) as region_processed, 'lead' as event - FROM dub_lead_events_mv + FROM dub_lead_events_mv_new WHERE - timestamp >= {{ DateTime(start, '2024-06-01 00:00:00') }} - AND timestamp < {{ DateTime(end, '2024-06-07 00:00:00') }} + timestamp >= {{ DateTime(start, '2024-01-01 00:00:00') }} + AND timestamp < {{ DateTime(end, '2025-12-31 00:00:00') }} {% if defined(linkId) %} AND link_id = {{ String(linkId) }} {% elif defined(workspaceId) or defined(partnerId) or defined(programId) %} AND link_id IN (SELECT link_id FROM workspace_links) @@ -104,11 +99,22 @@ SQL > {% if defined(os) %} AND os = {{ os }} {% end %} {% if defined(referer) %} AND referer = {{ referer }} {% end %} {% if defined(refererUrl) %} AND splitByString('?', referer_url)[1] = {{ refererUrl }} {% end %} - {% if defined(utm_source) %} AND url LIKE concat('%utm_source=', {{ String(utm_source) }}, '%') {% end %} - {% if defined(utm_medium) %} AND url LIKE concat('%utm_medium=', {{ String(utm_medium) }}, '%') {% end %} - {% if defined(utm_campaign) %} AND url LIKE concat('%utm_campaign=', {{ String(utm_campaign) }}, '%') {% end %} - {% if defined(utm_term) %} AND url LIKE concat('%utm_term=', {{ String(utm_term) }}, '%') {% end %} - {% if defined(utm_content) %} AND url LIKE concat('%utm_content=', {{ String(utm_content) }}, '%') {% end %} + {% if defined(utm_source) %} + AND url LIKE concat('%utm_source=', encodeURLFormComponent({{ String(utm_source) }}), '%') + {% end %} + {% if defined(utm_medium) %} + AND url LIKE concat('%utm_medium=', encodeURLFormComponent({{ String(utm_medium) }}), '%') + {% end %} + {% if defined(utm_campaign) %} + AND url + LIKE concat('%utm_campaign=', encodeURLFormComponent({{ String(utm_campaign) }}), '%') + {% end %} + {% if defined(utm_term) %} + AND url LIKE concat('%utm_term=', encodeURLFormComponent({{ String(utm_term) }}), '%') + {% end %} + {% if defined(utm_content) %} + AND url LIKE concat('%utm_content=', encodeURLFormComponent({{ String(utm_content) }}), '%') + {% end %} {% if defined(url) %} AND url = {{ url }} {% end %} ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %} LIMIT {{ Int32(limit, 100) }} @@ -126,7 +132,7 @@ SQL > CONCAT(country, '-', region) as region_processed, splitByString('?', referer_url)[1] as referer_url_processed, 'sale' as event - FROM dub_sale_events_mv + FROM dub_sale_events_mv_new WHERE timestamp >= {{ DateTime(start, '2024-06-01 00:00:00') }} AND timestamp < {{ DateTime(end, '2024-06-07 00:00:00') }} @@ -145,11 +151,11 @@ SQL > {% if defined(os) %} AND os = {{ os }} {% end %} {% if defined(referer) %} AND referer = {{ referer }} {% end %} {% if defined(refererUrl) %} AND splitByString('?', referer_url)[1] = {{ refererUrl }} {% end %} - {% if defined(utm_source) %} AND url LIKE concat('%utm_source=', {{ String(utm_source) }}, '%') {% end %} - {% if defined(utm_medium) %} AND url LIKE concat('%utm_medium=', {{ String(utm_medium) }}, '%') {% end %} - {% if defined(utm_campaign) %} AND url LIKE concat('%utm_campaign=', {{ String(utm_campaign) }}, '%') {% end %} - {% if defined(utm_term) %} AND url LIKE concat('%utm_term=', {{ String(utm_term) }}, '%') {% end %} - {% if defined(utm_content) %} AND url LIKE concat('%utm_content=', {{ String(utm_content) }}, '%') {% end %} + {% if defined(utm_source) %} AND url LIKE concat('%utm_source=', encodeURLFormComponent({{ String(utm_source) }}), '%') {% end %} + {% if defined(utm_medium) %} AND url LIKE concat('%utm_medium=', encodeURLFormComponent({{ String(utm_medium) }}), '%') {% end %} + {% if defined(utm_campaign) %} AND url LIKE concat('%utm_campaign=', encodeURLFormComponent({{ String(utm_campaign) }}), '%') {% end %} + {% if defined(utm_term) %} AND url LIKE concat('%utm_term=', encodeURLFormComponent({{ String(utm_term) }}), '%') {% end %} + {% if defined(utm_content) %} AND url LIKE concat('%utm_content=', encodeURLFormComponent({{ String(utm_content) }}), '%') {% end %} {% if defined(url) %} AND url = {{ url }} {% end %} ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %} LIMIT {{ Int32(limit, 100) }} From e402bd3ab335ea69737b9966a7936aa56286ed53 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 17:43:55 +0530 Subject: [PATCH 03/10] display metadata --- .../app/api/customers/[id]/activity/route.ts | 2 +- .../customers/[customerId]/route.ts | 1 + apps/web/lib/analytics/get-customer-events.ts | 3 + .../verify-analytics-allowed-hostnames.ts | 2 +- apps/web/lib/zod/schemas/leads.ts | 5 ++ apps/web/lib/zod/schemas/sales.ts | 5 ++ .../ui/customers/customer-activity-list.tsx | 72 ++++++++++++++++++- 7 files changed, 85 insertions(+), 5 deletions(-) diff --git a/apps/web/app/api/customers/[id]/activity/route.ts b/apps/web/app/api/customers/[id]/activity/route.ts index 7e7deef5346..bc7f46301f5 100644 --- a/apps/web/app/api/customers/[id]/activity/route.ts +++ b/apps/web/app/api/customers/[id]/activity/route.ts @@ -7,7 +7,7 @@ import { prisma } from "@dub/prisma"; import { NextResponse } from "next/server"; // GET /api/customers/[id]/activity - get a customer's activity -export const GET = withWorkspace(async ({ workspace, params, session }) => { +export const GET = withWorkspace(async ({ workspace, params }) => { const { id: customerId } = params; const customer = await getCustomerOrThrow({ diff --git a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts index 0dfd98e3a41..c141d63da4e 100644 --- a/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts +++ b/apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts @@ -33,6 +33,7 @@ export const GET = withPartnerProfile(async ({ partner, params }) => { const events = await getCustomerEvents({ customerId: customer.id, linkIds: links.map((link) => link.id), + hideMetadata: true, // don't expose metadata to partners }); if (events.length === 0) { diff --git a/apps/web/lib/analytics/get-customer-events.ts b/apps/web/lib/analytics/get-customer-events.ts index 3ac23ddbb0f..e96ab21aa27 100644 --- a/apps/web/lib/analytics/get-customer-events.ts +++ b/apps/web/lib/analytics/get-customer-events.ts @@ -14,9 +14,11 @@ import { saleEventResponseSchema } from "../zod/schemas/sales"; export const getCustomerEvents = async ({ customerId, linkIds, + hideMetadata = false, }: { customerId: string; linkIds?: string[]; + hideMetadata?: boolean; }) => { const pipe = tb.buildPipe({ pipe: "v2_customer_events", @@ -60,6 +62,7 @@ export const getCustomerEvents = async ({ ? { eventId: evt.event_id, eventName: evt.event_name, + metadata: hideMetadata ? null : evt.metadata, ...(evt.event === "sale" ? { sale: { diff --git a/apps/web/lib/analytics/verify-analytics-allowed-hostnames.ts b/apps/web/lib/analytics/verify-analytics-allowed-hostnames.ts index 094d6559157..ec3ee8bac88 100644 --- a/apps/web/lib/analytics/verify-analytics-allowed-hostnames.ts +++ b/apps/web/lib/analytics/verify-analytics-allowed-hostnames.ts @@ -5,7 +5,7 @@ export const verifyAnalyticsAllowedHostnames = ({ allowedHostnames: string[]; req: Request; }) => { - if (allowedHostnames.length > 0) { + if (allowedHostnames && allowedHostnames.length > 0) { const source = req.headers.get("referer") || req.headers.get("origin"); const sourceUrl = source ? new URL(source) : null; const hostname = sourceUrl?.hostname.replace(/^www\./, ""); diff --git a/apps/web/lib/zod/schemas/leads.ts b/apps/web/lib/zod/schemas/leads.ts index 71fe681731d..bd348009e6e 100644 --- a/apps/web/lib/zod/schemas/leads.ts +++ b/apps/web/lib/zod/schemas/leads.ts @@ -129,6 +129,11 @@ export const leadEventResponseSchema = z timestamp: z.coerce.string(), eventId: z.string(), eventName: z.string(), + metadata: z + .string() + .nullable() + .transform((val) => (val === "" ? null : val)) + .default(null), // nested objects click: clickEventSchema, link: linkEventSchema, diff --git a/apps/web/lib/zod/schemas/sales.ts b/apps/web/lib/zod/schemas/sales.ts index bf1b4bcfd66..25186911337 100644 --- a/apps/web/lib/zod/schemas/sales.ts +++ b/apps/web/lib/zod/schemas/sales.ts @@ -160,6 +160,11 @@ export const saleEventResponseSchema = z payment_processor: z .string() .describe("Deprecated. Use `sale.paymentProcessor` instead."), + metadata: z + .string() + .nullable() + .transform((val) => (val === "" ? null : val)) + .default(null), }) .merge(commonDeprecatedEventFields) .openapi({ ref: "SaleEvent" }); diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index 0434803ab6d..06334823dc9 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -1,5 +1,10 @@ import { CustomerActivityResponse } from "@/lib/types"; -import { DynamicTooltipWrapper, LinkLogo } from "@dub/ui"; +import { + Button, + DynamicTooltipWrapper, + LinkLogo, + useCopyToClipboard, +} from "@dub/ui"; import { CursorRays, MoneyBill2, UserCheck } from "@dub/ui/icons"; import { formatDateTimeSmart, getApexDomain, getPrettyUrl } from "@dub/utils"; import Link from "next/link"; @@ -80,10 +85,71 @@ const activityData = { ); }, }, - lead: { icon: UserCheck, content: (event) => event.eventName || "New lead" }, - sale: { icon: MoneyBill2, content: (event) => event.eventName || "New sale" }, + + lead: { + icon: UserCheck, + content: (event) => { + const metadata = event.metadata ? JSON.parse(event.metadata) : null; + + return ( +
+ {event.eventName || "New lead"} + {metadata && } +
+ ); + }, + }, + + sale: { + icon: MoneyBill2, + content: (event) => { + const metadata = event.metadata ? JSON.parse(event.metadata) : null; + + return ( +
+ {event.eventName || "New sale"} + {metadata && } +
+ ); + }, + }, }; +// Display the event metadata +function MetadataViewer({ metadata }: { metadata: Record }) { + const [copied, copyToClipboard] = useCopyToClipboard(); + + return ( + +
+
+
+                  {JSON.stringify(metadata, null, 1)}
+                
+
+
+ + +
+ ); +} + export function CustomerActivityList({ activity, isLoading, From bd5ec0c6123fe5f23e0a7d65d0da583717234926 Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 18:53:29 +0530 Subject: [PATCH 04/10] fix the button --- apps/web/ui/customers/customer-activity-list.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index 06334823dc9..46e53a84b24 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -124,7 +124,7 @@ function MetadataViewer({ metadata }: { metadata: Record }) { tooltipProps={{ content: (
-
+
                   {JSON.stringify(metadata, null, 1)}
@@ -138,11 +138,12 @@ function MetadataViewer({ metadata }: { metadata: Record }) {
             />
           
), + align: "start", }} > From 3cda066c783666fc841fd519b68408a205606e2b Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 18:56:32 +0530 Subject: [PATCH 05/10] add try catch --- apps/web/ui/customers/customer-activity-list.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index 46e53a84b24..58a2e435ff2 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -89,7 +89,13 @@ const activityData = { lead: { icon: UserCheck, content: (event) => { - const metadata = event.metadata ? JSON.parse(event.metadata) : null; + let metadata = null; + + try { + metadata = event.metadata ? JSON.parse(event.metadata) : null; + } catch (e) { + // + } return (
@@ -103,7 +109,13 @@ const activityData = { sale: { icon: MoneyBill2, content: (event) => { - const metadata = event.metadata ? JSON.parse(event.metadata) : null; + let metadata = null; + + try { + metadata = event.metadata ? JSON.parse(event.metadata) : null; + } catch (e) { + // + } return (
From 094aaf0ed131491dd35b74ac5198d7e2fdbd594d Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 21:56:53 +0530 Subject: [PATCH 06/10] display JSON properties --- .../ui/customers/customer-activity-list.tsx | 78 +++++++++++++------ 1 file changed, 53 insertions(+), 25 deletions(-) diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index 58a2e435ff2..6af21e897f6 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -9,6 +9,7 @@ import { CursorRays, MoneyBill2, UserCheck } from "@dub/ui/icons"; import { formatDateTimeSmart, getApexDomain, getPrettyUrl } from "@dub/utils"; import Link from "next/link"; import { useParams } from "next/navigation"; +import { Fragment } from "react"; const activityData = { click: { @@ -131,35 +132,62 @@ const activityData = { function MetadataViewer({ metadata }: { metadata: Record }) { const [copied, copyToClipboard] = useCopyToClipboard(); + const displayEntries = Object.entries(metadata).map(([key, value]) => { + const displayValue = + typeof value === "object" && value !== null + ? JSON.stringify(value).slice(0, 20) + "..." + : String(value); + + return `${key}: ${displayValue}`; + }); + + const hasMoreItems = displayEntries.length > 3; + const visibleEntries = hasMoreItems + ? displayEntries.slice(0, 3) + : displayEntries; + return ( - -
-
-
-                  {JSON.stringify(metadata, null, 1)}
-                
+
+ {visibleEntries.map((entry, i) => ( + + + {entry} + + + ))} + + +
+
+
+                    {JSON.stringify(metadata, null, 2)}
+                  
+
+
- -
- ), - align: "start", - }} - > - - + + +
); } From 96041196960d1b60b60a4934b28e35be354033fd Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 22:13:17 +0530 Subject: [PATCH 07/10] make nullish --- apps/web/lib/zod/schemas/leads.ts | 2 +- apps/web/lib/zod/schemas/sales.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/zod/schemas/leads.ts b/apps/web/lib/zod/schemas/leads.ts index bd348009e6e..6d555e01dd3 100644 --- a/apps/web/lib/zod/schemas/leads.ts +++ b/apps/web/lib/zod/schemas/leads.ts @@ -131,7 +131,7 @@ export const leadEventResponseSchema = z eventName: z.string(), metadata: z .string() - .nullable() + .nullish() .transform((val) => (val === "" ? null : val)) .default(null), // nested objects diff --git a/apps/web/lib/zod/schemas/sales.ts b/apps/web/lib/zod/schemas/sales.ts index 25186911337..617020db488 100644 --- a/apps/web/lib/zod/schemas/sales.ts +++ b/apps/web/lib/zod/schemas/sales.ts @@ -162,7 +162,7 @@ export const saleEventResponseSchema = z .describe("Deprecated. Use `sale.paymentProcessor` instead."), metadata: z .string() - .nullable() + .nullish() .transform((val) => (val === "" ? null : val)) .default(null), }) From da4ebd32d659ced0b9428cfb4126031cb6c4521b Mon Sep 17 00:00:00 2001 From: Kiran K Date: Fri, 18 Apr 2025 22:22:40 +0530 Subject: [PATCH 08/10] fix openapi --- apps/web/lib/zod/schemas/leads.ts | 3 ++- apps/web/lib/zod/schemas/sales.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/zod/schemas/leads.ts b/apps/web/lib/zod/schemas/leads.ts index 6d555e01dd3..0f38f699b37 100644 --- a/apps/web/lib/zod/schemas/leads.ts +++ b/apps/web/lib/zod/schemas/leads.ts @@ -133,7 +133,8 @@ export const leadEventResponseSchema = z .string() .nullish() .transform((val) => (val === "" ? null : val)) - .default(null), + .default(null) + .openapi({ type: "string" }), // nested objects click: clickEventSchema, link: linkEventSchema, diff --git a/apps/web/lib/zod/schemas/sales.ts b/apps/web/lib/zod/schemas/sales.ts index 617020db488..49e34962d3c 100644 --- a/apps/web/lib/zod/schemas/sales.ts +++ b/apps/web/lib/zod/schemas/sales.ts @@ -164,7 +164,8 @@ export const saleEventResponseSchema = z .string() .nullish() .transform((val) => (val === "" ? null : val)) - .default(null), + .default(null) + .openapi({ type: "string" }), }) .merge(commonDeprecatedEventFields) .openapi({ ref: "SaleEvent" }); From eddb59a24834d6be286f3e95588dd195cf945d0b Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 18 Apr 2025 10:53:10 -0700 Subject: [PATCH 09/10] improve MetadataViewer logic --- .../ui/analytics/events/metadata-viewer.tsx | 80 +++++++++++++++++++ .../ui/customers/customer-activity-list.tsx | 72 +---------------- 2 files changed, 82 insertions(+), 70 deletions(-) create mode 100644 apps/web/ui/analytics/events/metadata-viewer.tsx diff --git a/apps/web/ui/analytics/events/metadata-viewer.tsx b/apps/web/ui/analytics/events/metadata-viewer.tsx new file mode 100644 index 00000000000..49b01170406 --- /dev/null +++ b/apps/web/ui/analytics/events/metadata-viewer.tsx @@ -0,0 +1,80 @@ +import { Button, Tooltip, useCopyToClipboard } from "@dub/ui"; +import { truncate } from "@dub/utils"; +import { Fragment } from "react"; + +// Display the event metadata +export function MetadataViewer({ + metadata, +}: { + metadata: Record; +}) { + const [copied, copyToClipboard] = useCopyToClipboard(); + + const displayEntries = Object.entries(metadata) + .map(([key, value]) => { + if (typeof value === "object" && value !== null) { + // Only show nested properties if the parent object has exactly one property + if (Object.keys(metadata).length === 1) { + const nestedEntries = Object.entries(value).map( + ([nestedKey, nestedValue]) => { + const displayValue = + typeof nestedValue === "object" && nestedValue !== null + ? truncate(JSON.stringify(nestedValue), 20) + : truncate(String(nestedValue), 20); + return `${key}.${nestedKey}: ${displayValue}`; + }, + ); + // else show the parent object properties + return nestedEntries; + } + return [`${key}: ${truncate(JSON.stringify(value), 20)}`]; + } + return [`${key}: ${truncate(String(value), 20)}`]; + }) + .flat(); + + const hasMoreItems = displayEntries.length > 3; + const visibleEntries = hasMoreItems + ? displayEntries.slice(0, 3) + : displayEntries; + + return ( +
+ {visibleEntries.map((entry, i) => ( + + + {entry} + + + ))} + + +
+
+
+                  {JSON.stringify(metadata, null, 2)}
+                
+
+
+
+ } + align="start" + > + + +
+ ); +} diff --git a/apps/web/ui/customers/customer-activity-list.tsx b/apps/web/ui/customers/customer-activity-list.tsx index 6af21e897f6..b0de54c8069 100644 --- a/apps/web/ui/customers/customer-activity-list.tsx +++ b/apps/web/ui/customers/customer-activity-list.tsx @@ -1,15 +1,10 @@ import { CustomerActivityResponse } from "@/lib/types"; -import { - Button, - DynamicTooltipWrapper, - LinkLogo, - useCopyToClipboard, -} from "@dub/ui"; +import { DynamicTooltipWrapper, LinkLogo } from "@dub/ui"; import { CursorRays, MoneyBill2, UserCheck } from "@dub/ui/icons"; import { formatDateTimeSmart, getApexDomain, getPrettyUrl } from "@dub/utils"; import Link from "next/link"; import { useParams } from "next/navigation"; -import { Fragment } from "react"; +import { MetadataViewer } from "../analytics/events/metadata-viewer"; const activityData = { click: { @@ -128,69 +123,6 @@ const activityData = { }, }; -// Display the event metadata -function MetadataViewer({ metadata }: { metadata: Record }) { - const [copied, copyToClipboard] = useCopyToClipboard(); - - const displayEntries = Object.entries(metadata).map(([key, value]) => { - const displayValue = - typeof value === "object" && value !== null - ? JSON.stringify(value).slice(0, 20) + "..." - : String(value); - - return `${key}: ${displayValue}`; - }); - - const hasMoreItems = displayEntries.length > 3; - const visibleEntries = hasMoreItems - ? displayEntries.slice(0, 3) - : displayEntries; - - return ( -
- {visibleEntries.map((entry, i) => ( - - - {entry} - - - ))} - - -
-
-
-                    {JSON.stringify(metadata, null, 2)}
-                  
-
-
-
- ), - align: "start", - }} - > - - -
- ); -} - export function CustomerActivityList({ activity, isLoading, From 227e8d522cd76e9257efbff12a2d3abb6b232ab5 Mon Sep 17 00:00:00 2001 From: Steven Tey Date: Fri, 18 Apr 2025 10:56:46 -0700 Subject: [PATCH 10/10] improve copy button --- .../ui/analytics/events/metadata-viewer.tsx | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/apps/web/ui/analytics/events/metadata-viewer.tsx b/apps/web/ui/analytics/events/metadata-viewer.tsx index 49b01170406..84742913799 100644 --- a/apps/web/ui/analytics/events/metadata-viewer.tsx +++ b/apps/web/ui/analytics/events/metadata-viewer.tsx @@ -1,5 +1,6 @@ import { Button, Tooltip, useCopyToClipboard } from "@dub/ui"; -import { truncate } from "@dub/utils"; +import { cn, truncate } from "@dub/utils"; +import { Check, Copy } from "lucide-react"; import { Fragment } from "react"; // Display the event metadata @@ -59,7 +60,28 @@ export function MetadataViewer({
+ } + className="h-9" + text={copied ? "Copied metadata" : "Copy metadata"} onClick={() => copyToClipboard(JSON.stringify(metadata, null, 2))} />