Skip to content

Commit 9ff89f8

Browse files
authored
Merge pull request dubinc#2318 from dubinc/fix-partner-customer-events
Refactor `getCustomerEvents` to include optional linkIds parameter
2 parents 71dba4d + 23df1e3 commit 9ff89f8

7 files changed

Lines changed: 62 additions & 186 deletions

File tree

apps/web/app/api/customers/[id]/activity/route.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,9 @@ export const GET = withWorkspace(async ({ workspace, params, session }) => {
3030
}
3131

3232
let [events, link] = await Promise.all([
33-
getCustomerEvents(
34-
{ customerId: customer.id, clickId: customer.clickId },
35-
{
36-
sortOrder: "desc",
37-
interval: "1y",
38-
},
39-
),
33+
getCustomerEvents({
34+
customerId: customer.id,
35+
}),
4036

4137
prisma.link.findUniqueOrThrow({
4238
where: {

apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/activity/route.ts

Lines changed: 9 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { getCustomerEvents } from "@/lib/analytics/get-customer-events";
22
import { DubApiError } from "@/lib/api/errors";
3-
import { decodeLinkIfCaseSensitive } from "@/lib/api/links/case-sensitivity";
43
import { getProgramEnrollmentOrThrow } from "@/lib/api/programs/get-program-enrollment-or-throw";
54
import { withPartnerProfile } from "@/lib/auth/partner";
65
import { customerActivityResponseSchema } from "@/lib/zod/schemas/customer-activity";
@@ -11,87 +10,33 @@ import { NextResponse } from "next/server";
1110
export const GET = withPartnerProfile(async ({ partner, params }) => {
1211
const { customerId, programId } = params;
1312

14-
const { program } = await getProgramEnrollmentOrThrow({
13+
const { program, links } = await getProgramEnrollmentOrThrow({
1514
partnerId: partner.id,
1615
programId: programId,
1716
});
1817

19-
if (program.slug === "framer") {
20-
throw new DubApiError({
21-
code: "forbidden",
22-
message: "Framer program does not support customer activity",
23-
});
24-
}
25-
2618
const customer = await prisma.customer.findUnique({
2719
where: {
2820
id: customerId,
2921
},
30-
include: {
31-
link: {
32-
include: {
33-
programEnrollment: {
34-
include: {
35-
partner: true,
36-
program: true,
37-
},
38-
},
39-
},
40-
},
41-
},
4222
});
4323

44-
if (
45-
!customer ||
46-
![
47-
customer?.link?.programEnrollment?.programId,
48-
customer?.link?.programEnrollment?.program.slug,
49-
].includes(program.id)
50-
) {
24+
if (!customer || customer?.projectId !== program.workspaceId) {
5125
throw new DubApiError({
5226
code: "not_found",
5327
message:
5428
"Customer not found. Make sure you're using the correct customer ID (e.g. `cus_3TagGjzRzmsFJdH8od2BNCsc`).",
5529
});
5630
}
5731

58-
if (!customer.linkId) {
59-
return NextResponse.json(
60-
customerActivityResponseSchema.parse({
61-
customer,
62-
events: [],
63-
ltv: 0,
64-
timeToLead: null,
65-
timeToSale: null,
66-
link: null,
67-
}),
68-
);
69-
}
70-
71-
let [events, link] = await Promise.all([
72-
getCustomerEvents(
73-
{ customerId: customer.id, clickId: customer.clickId },
74-
{
75-
sortOrder: "desc",
76-
interval: "1y",
77-
},
78-
),
79-
80-
prisma.link.findUniqueOrThrow({
81-
where: {
82-
id: customer.linkId!,
83-
},
84-
select: {
85-
id: true,
86-
domain: true,
87-
key: true,
88-
shortLink: true,
89-
folderId: true,
90-
},
91-
}),
92-
]);
32+
const events = await getCustomerEvents({
33+
customerId: customer.id,
34+
linkIds: links.map((link) => link.id),
35+
});
9336

94-
link = decodeLinkIfCaseSensitive(link);
37+
// get the first partner link that this customer interacted with
38+
const firstLinkId = events[events.length - 1].link_id;
39+
const link = links.find((link) => link.id === firstLinkId);
9540

9641
// Find the LTV of the customer
9742
// TODO: Calculate this from all events, not limited

apps/web/app/api/partner-profile/programs/[programId]/customers/[customerId]/route.ts

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,13 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
1616
programId: programId,
1717
});
1818

19-
if (program.slug === "framer") {
20-
throw new DubApiError({
21-
code: "forbidden",
22-
message: "Framer program does not support customer profile",
23-
});
24-
}
25-
2619
const customer = await prisma.customer.findUnique({
2720
where: {
2821
id: customerId,
2922
},
30-
include: {
31-
link: {
32-
include: {
33-
programEnrollment: {
34-
include: {
35-
partner: true,
36-
program: true,
37-
},
38-
},
39-
},
40-
},
41-
},
4223
});
4324

44-
if (
45-
!customer ||
46-
![
47-
customer?.link?.programEnrollment?.programId,
48-
customer?.link?.programEnrollment?.program.slug,
49-
].includes(program.id)
50-
) {
25+
if (!customer || customer?.projectId !== program.workspaceId) {
5126
throw new DubApiError({
5227
code: "not_found",
5328
message:
@@ -63,14 +38,6 @@ export const GET = withPartnerProfile(async ({ partner, params }) => {
6338
transformCustomer({
6439
...customer,
6540
email: customer.email || customer.name || generateRandomName(),
66-
link: customer.link
67-
? {
68-
...customer.link,
69-
programEnrollment: customer.link.programEnrollment
70-
? { ...customer.link.programEnrollment, program: undefined }
71-
: null,
72-
}
73-
: null,
7441
}),
7542
),
7643
);

apps/web/app/partners.dub.co/(dashboard)/programs/[programSlug]/(enrolled)/customers/[customerId]/page.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import { PageContent } from "@/ui/layout/page-content";
22
import { MaxWidthWrapper } from "@dub/ui";
3-
import { redirect } from "next/navigation";
43
import { ProgramCustomerPageClient } from "./page-client";
54

6-
export default function ProgramCustomer({
7-
params,
8-
}: {
9-
params: { programSlug: string; customerId: string };
10-
}) {
11-
if (params.programSlug === "framer") {
12-
redirect("/programs/framer");
13-
}
5+
export default function ProgramCustomer() {
146
return (
157
<PageContent hideReferButton>
168
<MaxWidthWrapper className="flex flex-col gap-6">

apps/web/lib/analytics/get-customer-events.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,23 @@ import {
1010
} from "../zod/schemas/clicks";
1111
import { leadEventResponseSchema } from "../zod/schemas/leads";
1212
import { saleEventResponseSchema } from "../zod/schemas/sales";
13-
import { EventsFilters } from "./types";
14-
import { getStartEndDates } from "./utils/get-start-end-dates";
15-
16-
export const getCustomerEvents = async (
17-
{ customerId, clickId }: { customerId: string; clickId?: string | null },
18-
params: Pick<
19-
EventsFilters,
20-
"sortOrder" | "start" | "end" | "dataAvailableFrom" | "interval"
21-
>,
22-
) => {
23-
let { sortOrder, start, end, dataAvailableFrom, interval } = params;
24-
25-
const { startDate, endDate } = getStartEndDates({
26-
interval,
27-
start,
28-
end,
29-
dataAvailableFrom,
30-
});
3113

14+
export const getCustomerEvents = async ({
15+
customerId,
16+
linkIds,
17+
}: {
18+
customerId: string;
19+
linkIds?: string[];
20+
}) => {
3221
const pipe = tb.buildPipe({
3322
pipe: "v2_customer_events",
3423
parameters: z.any(), // TODO
3524
data: z.any(), // TODO
3625
});
3726

3827
const response = await pipe({
39-
...params,
4028
customerId,
41-
...(clickId ? { clickId } : {}),
42-
order: sortOrder,
43-
start: startDate.toISOString().replace("T", " ").replace("Z", ""),
44-
end: endDate.toISOString().replace("T", " ").replace("Z", ""),
29+
...(linkIds ? { linkIds } : {}),
4530
});
4631

4732
const linksMap = await getLinksMap(response.data.map((d) => d.link_id));

apps/web/lib/zod/schemas/partner-profile.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ export const PartnerProfileCustomerSchema = CustomerEnrichedSchema.pick({
8282
id: true,
8383
createdAt: true,
8484
country: true,
85-
link: true,
8685
}).extend({
8786
email: z
8887
.string()

packages/tinybird/pipes/v2_customer_events.pipe

Lines changed: 40 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ DESCRIPTION >
44

55
TAGS "Dub Endpoints"
66

7-
NODE click_events
7+
NODE lead_events
88
SQL >
99

1010
%
@@ -30,23 +30,28 @@ SQL >
3030
ip,
3131
CONCAT(country, '-', region) as region_processed,
3232
splitByString('?', referer_url)[1] as referer_url_processed,
33-
'click' as event
34-
FROM dub_click_events_id
33+
'lead' as event,
34+
event_id,
35+
event_name,
36+
metadata
37+
FROM dub_lead_events_mv
3538
WHERE
36-
click_id
39+
customer_id
3740
= {{
3841
String(
39-
clickId,
40-
'8bBSF1CbVlXRCMJY',
41-
description="The unique ID for a given click event",
42+
customerId,
43+
'cus_1JRJNSVARH220RCNJ2K5SAX9Q',
44+
description="The unique ID for a given customer",
4245
required=True,
4346
)
4447
}}
48+
{% if defined(linkIds) %} AND link_id IN ({{ Array(linkIds, 'link_id') }}) {% end %}
49+
ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %}
4550
LIMIT {{ Int32(limit, 100) }}
4651

4752

4853

49-
NODE lead_events
54+
NODE click_events
5055
SQL >
5156

5257
%
@@ -72,23 +77,11 @@ SQL >
7277
ip,
7378
CONCAT(country, '-', region) as region_processed,
7479
splitByString('?', referer_url)[1] as referer_url_processed,
75-
'lead' as event,
76-
event_id,
77-
event_name
78-
FROM dub_lead_events_mv
80+
'click' as event
81+
FROM dub_click_events_id
7982
WHERE
80-
customer_id
81-
= {{
82-
String(
83-
customerId,
84-
'cm1a18x7w0001aqhx744lrtxp',
85-
description="The unique ID for a given customer",
86-
required=True,
87-
)
88-
}}
89-
AND timestamp >= {{ DateTime(start, '2025-01-01 00:00:00') }}
90-
AND timestamp < {{ DateTime(end, '2025-06-30 00:00:00') }}
91-
ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %}
83+
click_id IN (SELECT DISTINCT click_id FROM lead_events)
84+
{% if defined(linkIds) %} AND link_id IN ({{ Array(linkIds, 'link_id') }}) {% end %}
9285
LIMIT {{ Int32(limit, 100) }}
9386

9487

@@ -122,6 +115,7 @@ SQL >
122115
'sale' as event,
123116
event_id,
124117
event_name,
118+
metadata,
125119
amount as saleAmount,
126120
invoice_id,
127121
payment_processor
@@ -131,13 +125,12 @@ SQL >
131125
= {{
132126
String(
133127
customerId,
134-
'cm1a18x7w0001aqhx744lrtxp',
128+
'cus_1JRJNSVARH220RCNJ2K5SAX9Q',
135129
description="The unique ID for a given customer",
136130
required=True,
137131
)
138132
}}
139-
AND timestamp >= {{ DateTime(start, '2025-01-01 00:00:00') }}
140-
AND timestamp < {{ DateTime(end, '2025-06-30 00:00:00') }}
133+
{% if defined(linkIds) %} AND link_id IN ({{ Array(linkIds, 'link_id') }}) {% end %}
141134
LIMIT {{ Int32(limit, 100) }}
142135

143136

@@ -146,26 +139,25 @@ NODE endpoint
146139
SQL >
147140

148141
%
149-
SELECT *
150-
FROM
151-
(
152-
{% if defined(clickId) %}
153-
SELECT
154-
*,
155-
NULL AS event_id,
156-
NULL AS event_name,
157-
NULL AS saleAmount,
158-
NULL AS invoice_id,
159-
NULL AS payment_processor
160-
FROM click_events
161-
UNION ALL
162-
{% end %}
163-
SELECT *, NULL AS saleAmount, NULL AS invoice_id, NULL AS payment_processor
164-
FROM lead_events
165-
UNION ALL
166-
SELECT *
167-
FROM sale_events
168-
)
169-
ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %}
142+
SELECT *
143+
FROM
144+
(
145+
SELECT *, NULL AS saleAmount, NULL AS invoice_id, NULL AS payment_processor
146+
FROM lead_events
147+
UNION ALL
148+
SELECT
149+
*,
150+
NULL AS event_id,
151+
NULL AS event_name,
152+
NULL AS metadata,
153+
NULL AS saleAmount,
154+
NULL AS invoice_id,
155+
NULL AS payment_processor
156+
FROM click_events
157+
UNION ALL
158+
SELECT *
159+
FROM sale_events
160+
)
161+
ORDER BY timestamp {% if order == 'asc' %} ASC {% else %} DESC {% end %}
170162

171163

0 commit comments

Comments
 (0)