Skip to content

Commit 10f7f60

Browse files
authored
feat: improve conversions api dataset setup and support variables in flow links (#995)
1 parent 1b547fb commit 10f7f60

55 files changed

Lines changed: 1269 additions & 79 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/builder/__tests__/capi-dataset-actions-reconnect.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ vi.mock("@chatbotx.io/business/errors", () => ({
6868
vi.mock("@chatbotx.io/integration-meta-conversions", () => ({
6969
getDataset: mockGetDataset,
7070
ensureDataset: mockEnsureDataset,
71+
buildDatasetName: (name: string) =>
72+
name.trim() ? `${name.trim()} Event Data` : "Event Data",
7173
}))
7274

7375
vi.mock("next-intl/server", () => ({
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// @vitest-environment jsdom
2+
3+
import { act } from "react"
4+
import { createRoot, type Root } from "react-dom/client"
5+
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"
6+
import { CapiDatasetCard } from "@/features/meta-conversions/components/capi-dataset-card"
7+
8+
vi.mock("next-intl", () => ({
9+
useTranslations: () => (key: string) => key,
10+
}))
11+
12+
vi.mock("next/navigation", () => ({
13+
useRouter: () => ({ refresh: vi.fn() }),
14+
}))
15+
16+
vi.mock("sonner", () => ({
17+
toast: { error: vi.fn(), success: vi.fn() },
18+
}))
19+
20+
// The card calls `useAction` twice, in order: setDataset then provision. Each
21+
// call gets its own `execute` spy so a test can assert which action ran.
22+
const executes: ReturnType<typeof vi.fn>[] = []
23+
vi.mock("next-safe-action/hooks", () => ({
24+
useAction: () => {
25+
const execute = vi.fn()
26+
executes.push(execute)
27+
return { execute, isPending: false }
28+
},
29+
}))
30+
31+
const SAVE_LABEL = "metaConversions.finalize.save"
32+
const CREATE_LABEL = "metaConversions.finalize.createDataset"
33+
34+
describe("CapiDatasetCard", () => {
35+
let container: HTMLDivElement
36+
let root: Root
37+
38+
beforeEach(() => {
39+
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true })
40+
container = document.createElement("div")
41+
document.body.append(container)
42+
root = createRoot(container)
43+
executes.length = 0
44+
})
45+
46+
afterEach(() => {
47+
act(() => {
48+
root.unmount()
49+
})
50+
container.remove()
51+
})
52+
53+
function render() {
54+
const setDataset = vi.fn()
55+
const provision = vi.fn()
56+
act(() => {
57+
root.render(
58+
<CapiDatasetCard
59+
actions={
60+
{ setDataset, provision } as unknown as Parameters<
61+
typeof CapiDatasetCard
62+
>[0]["actions"]
63+
}
64+
integrationId="int-1"
65+
workspaceId="ws-1"
66+
/>,
67+
)
68+
})
69+
}
70+
71+
// Each render pushes a fresh [setDataset, provision] execute pair; the button
72+
// click runs the latest render's pair.
73+
function latestExecutes() {
74+
return {
75+
setDatasetExecute: executes.at(-2),
76+
provisionExecute: executes.at(-1),
77+
}
78+
}
79+
80+
function button(): HTMLButtonElement {
81+
const found = container.querySelector("button")
82+
if (!found) {
83+
throw new Error("button not rendered")
84+
}
85+
return found
86+
}
87+
88+
function typeDatasetId(value: string) {
89+
const input = container.querySelector("input")
90+
if (!input) {
91+
throw new Error("input not rendered")
92+
}
93+
const setter = Object.getOwnPropertyDescriptor(
94+
window.HTMLInputElement.prototype,
95+
"value",
96+
)?.set
97+
act(() => {
98+
setter?.call(input, value)
99+
input.dispatchEvent(new Event("input", { bubbles: true }))
100+
})
101+
}
102+
103+
test("shows Create Dataset and auto-provisions when the field is empty", () => {
104+
render()
105+
106+
expect(button().textContent).toBe(CREATE_LABEL)
107+
108+
act(() => {
109+
button().click()
110+
})
111+
112+
const { setDatasetExecute, provisionExecute } = latestExecutes()
113+
expect(provisionExecute).toHaveBeenCalledWith()
114+
expect(setDatasetExecute).not.toHaveBeenCalled()
115+
})
116+
117+
test("shows Save and validates the pasted id when the field is filled", () => {
118+
render()
119+
120+
typeDatasetId(" 913591454696998 ")
121+
expect(button().textContent).toBe(SAVE_LABEL)
122+
123+
act(() => {
124+
button().click()
125+
})
126+
127+
const { setDatasetExecute, provisionExecute } = latestExecutes()
128+
expect(setDatasetExecute).toHaveBeenCalledWith({
129+
datasetId: "913591454696998",
130+
})
131+
expect(provisionExecute).not.toHaveBeenCalled()
132+
})
133+
134+
test("rejects a non-numeric Dataset ID with an inline error and a disabled Save", () => {
135+
render()
136+
137+
typeDatasetId("adasdad")
138+
139+
// Still the Save action (field is non-empty), but blocked.
140+
expect(button().textContent).toBe(SAVE_LABEL)
141+
expect(button().disabled).toBe(true)
142+
expect(container.textContent).toContain(
143+
"metaConversions.errors.invalidDatasetId",
144+
)
145+
146+
act(() => {
147+
button().click()
148+
})
149+
150+
const { setDatasetExecute, provisionExecute } = latestExecutes()
151+
expect(setDatasetExecute).not.toHaveBeenCalled()
152+
expect(provisionExecute).not.toHaveBeenCalled()
153+
})
154+
155+
test("reverts to Create Dataset when the field is cleared", () => {
156+
render()
157+
158+
typeDatasetId("913591454696998")
159+
expect(button().textContent).toBe(SAVE_LABEL)
160+
161+
typeDatasetId("")
162+
expect(button().textContent).toBe(CREATE_LABEL)
163+
})
164+
})

apps/builder/__tests__/capi-whatsapp-dataset-actions.test.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ vi.mock("@chatbotx.io/business/errors", () => ({
6161
vi.mock("@chatbotx.io/integration-meta-conversions", () => ({
6262
getDataset: mockGetDataset,
6363
ensureDataset: mockEnsureDataset,
64+
buildDatasetName: (name: string) =>
65+
name.trim() ? `${name.trim()} Event Data` : "Event Data",
6466
}))
6567

6668
vi.mock("next-intl/server", () => ({
@@ -118,7 +120,7 @@ describe("whatsapp CAPI dataset actions", () => {
118120
})
119121
})
120122

121-
test("provision is superadmin-gated and creates a dataset with resourceType waba", async () => {
123+
test("provision creates a dataset with the resolved creation token", async () => {
122124
await call<ProvisionHandler>(provisionWhatsappCapiDatasetAction)({
123125
bindArgsParsedInputs: ["ws-1", "wa-1"],
124126
})
@@ -135,18 +137,23 @@ describe("whatsapp CAPI dataset actions", () => {
135137
provisionDataset: (input: {
136138
accessToken: string
137139
resourceId: string
140+
resourceName: string
138141
}) => Promise<string>
139142
}
140143
mockEnsureDataset.mockResolvedValue("dataset-waba-1")
144+
// `accessToken` is the per-channel creation token supplied by the adapter
145+
// (WhatsApp's agency system-user token); the action forwards it unchanged.
141146
await provisionCall.provisionDataset({
142-
accessToken: "token-1",
147+
accessToken: "wa-system-token",
143148
resourceId: "waba-1",
149+
resourceName: "Acme WABA",
144150
})
145151

146152
expect(mockEnsureDataset).toHaveBeenCalledWith({
147153
resourceType: "waba",
148154
resourceId: "waba-1",
149-
accessToken: "token-1",
155+
accessToken: "wa-system-token",
156+
datasetName: "Acme WABA Event Data",
150157
})
151158
})
152159

apps/builder/messages/ar.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5088,6 +5088,7 @@
50885088
"saveFailed": "تعذّر الحفظ. يُرجى المحاولة مرة أخرى.",
50895089
"datasetRequired": "احفظ معرف Dataset قبل إضافة رمز وصول.",
50905090
"invalidToken": "تعذّر على هذا الرمز الوصول إلى Dataset.",
5091+
"invalidDatasetId": "أدخل معرّف مجموعة بيانات صالحًا (أرقام فقط).",
50915092
"whatsappNotFound": "لم يتم العثور على اتصال WhatsApp."
50925093
},
50935094
"fields": {
@@ -5123,7 +5124,7 @@
51235124
"oauth": {
51245125
"title": "ربط عبر Facebook",
51255126
"description": "استخدم اتصال صفحتك الحالي، ثم اختر معرف Dataset.",
5126-
"connect": "ربط عبر Facebook",
5127+
"connect": "إنشاء مجموعة بيانات",
51275128
"connectedLabel": "عبر Facebook"
51285129
},
51295130
"custom": {
@@ -5136,14 +5137,15 @@
51365137
"whatsapp": {
51375138
"title": "ربط عبر WhatsApp",
51385139
"description": "استخدم اتصال WhatsApp الخاص بك، ثم اختر معرف Dataset.",
5139-
"connect": "ربط عبر WhatsApp",
5140+
"connect": "إنشاء مجموعة بيانات",
51405141
"connectedLabel": "عبر WhatsApp"
51415142
}
51425143
},
51435144
"finalize": {
51445145
"title": "اختر معرف Dataset",
51455146
"description": "أدخل معرف Dataset موجودًا، أو اتركه فارغًا لإنشاء واحد تلقائيًا.",
51465147
"save": "حفظ",
5148+
"createDataset": "إنشاء مجموعة بيانات",
51475149
"caveat": "قد لا يُسند معرف Dataset غير المرتبط بهذه الصفحة أو حساب Instagram الأحداث بشكل صحيح. اترك هذا الحقل فارغًا لإنشاء Dataset مرتبط تلقائيًا."
51485150
},
51495151
"whatsapp": {

apps/builder/messages/da.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5088,6 +5088,7 @@
50885088
"saveFailed": "Kunne ikke gemme. Prøv venligst igen.",
50895089
"datasetRequired": "Gem et Dataset-ID, før du tilføjer en adgangstoken.",
50905090
"invalidToken": "Denne token kunne ikke tilgå datasættet.",
5091+
"invalidDatasetId": "Indtast et gyldigt Dataset ID (kun tal).",
50915092
"whatsappNotFound": "WhatsApp-integration ikke fundet."
50925093
},
50935094
"fields": {
@@ -5123,7 +5124,7 @@
51235124
"oauth": {
51245125
"title": "Forbind via Facebook",
51255126
"description": "Brug din sides eksisterende forbindelse, og vælg derefter et Dataset-ID.",
5126-
"connect": "Forbind via Facebook",
5127+
"connect": "Opret datasæt",
51275128
"connectedLabel": "via Facebook"
51285129
},
51295130
"custom": {
@@ -5136,14 +5137,15 @@
51365137
"whatsapp": {
51375138
"title": "Forbind via WhatsApp",
51385139
"description": "Brug din WhatsApp-forbindelse, og vælg derefter et Dataset-ID.",
5139-
"connect": "Forbind via WhatsApp",
5140+
"connect": "Opret datasæt",
51405141
"connectedLabel": "via WhatsApp"
51415142
}
51425143
},
51435144
"finalize": {
51445145
"title": "Vælg et Dataset-ID",
51455146
"description": "Indtast et eksisterende Dataset-ID, eller lad det stå tomt for automatisk at oprette et.",
51465147
"save": "Gem",
5148+
"createDataset": "Opret datasæt",
51475149
"caveat": "Et Dataset-ID, der ikke er knyttet til denne side eller Instagram-konto, tilskriver muligvis ikke hændelser korrekt. Lad dette felt stå tomt for automatisk at oprette et tilknyttet datasæt."
51485150
},
51495151
"whatsapp": {

apps/builder/messages/de.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5088,6 +5088,7 @@
50885088
"saveFailed": "Speichern nicht möglich. Bitte versuchen Sie es erneut.",
50895089
"datasetRequired": "Speichern Sie eine Dataset-ID, bevor Sie ein Zugriffstoken hinzufügen.",
50905090
"invalidToken": "Mit diesem Token konnte nicht auf das Dataset zugegriffen werden.",
5091+
"invalidDatasetId": "Gib eine gültige Dataset-ID ein (nur Zahlen).",
50915092
"whatsappNotFound": "WhatsApp-Integration nicht gefunden."
50925093
},
50935094
"fields": {
@@ -5123,7 +5124,7 @@
51235124
"oauth": {
51245125
"title": "Über Facebook verbinden",
51255126
"description": "Nutzen Sie die bestehende Verbindung Ihrer Seite und wählen Sie dann eine Dataset-ID aus.",
5126-
"connect": "Über Facebook verbinden",
5127+
"connect": "Datensatz erstellen",
51275128
"connectedLabel": "über Facebook"
51285129
},
51295130
"custom": {
@@ -5136,14 +5137,15 @@
51365137
"whatsapp": {
51375138
"title": "Über WhatsApp verbinden",
51385139
"description": "Nutzen Sie Ihre WhatsApp-Verbindung und wählen Sie dann eine Dataset-ID aus.",
5139-
"connect": "Über WhatsApp verbinden",
5140+
"connect": "Datensatz erstellen",
51405141
"connectedLabel": "über WhatsApp"
51415142
}
51425143
},
51435144
"finalize": {
51445145
"title": "Dataset-ID auswählen",
51455146
"description": "Geben Sie eine vorhandene Dataset-ID ein oder lassen Sie das Feld leer, um automatisch eine zu erstellen.",
51465147
"save": "Speichern",
5148+
"createDataset": "Datensatz erstellen",
51475149
"caveat": "Eine Dataset-ID, die nicht mit dieser Seite oder diesem Instagram-Konto verknüpft ist, kann Ereignisse möglicherweise nicht korrekt zuordnen. Lassen Sie dieses Feld leer, um automatisch ein verknüpftes Dataset zu erstellen."
51485150
},
51495151
"whatsapp": {

apps/builder/messages/en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2114,6 +2114,7 @@
21142114
"saveFailed": "Could not save. Please try again.",
21152115
"datasetRequired": "Save a Dataset ID before adding an access token.",
21162116
"invalidToken": "This token could not access the dataset.",
2117+
"invalidDatasetId": "Enter a valid Dataset ID (numbers only).",
21172118
"whatsappNotFound": "WhatsApp integration not found."
21182119
},
21192120
"fields": {
@@ -2149,7 +2150,7 @@
21492150
"oauth": {
21502151
"title": "Connect via Facebook",
21512152
"description": "Use your page's existing connection, then choose a Dataset ID.",
2152-
"connect": "Connect via Facebook",
2153+
"connect": "Create Dataset",
21532154
"connectedLabel": "via Facebook"
21542155
},
21552156
"custom": {
@@ -2162,14 +2163,15 @@
21622163
"whatsapp": {
21632164
"title": "Connect via WhatsApp",
21642165
"description": "Use your WhatsApp connection, then choose a Dataset ID.",
2165-
"connect": "Connect via WhatsApp",
2166+
"connect": "Create Dataset",
21662167
"connectedLabel": "via WhatsApp"
21672168
}
21682169
},
21692170
"finalize": {
21702171
"title": "Choose a Dataset ID",
21712172
"description": "Enter an existing Dataset ID, or leave it empty to create one automatically.",
21722173
"save": "Save",
2174+
"createDataset": "Create Dataset",
21732175
"caveat": "A Dataset ID that isn't linked to this Page or Instagram account may not attribute events correctly. Leave this field empty to create a linked dataset automatically."
21742176
},
21752177
"whatsapp": {

apps/builder/messages/es.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5088,6 +5088,7 @@
50885088
"saveFailed": "No se pudo guardar. Inténtalo de nuevo.",
50895089
"datasetRequired": "Guarda un ID de conjunto de datos antes de añadir un token de acceso.",
50905090
"invalidToken": "Este token no pudo acceder al conjunto de datos.",
5091+
"invalidDatasetId": "Introduce un Dataset ID válido (solo números).",
50915092
"whatsappNotFound": "No se encontró la integración de WhatsApp."
50925093
},
50935094
"fields": {
@@ -5123,7 +5124,7 @@
51235124
"oauth": {
51245125
"title": "Conectar mediante Facebook",
51255126
"description": "Usa la conexión existente de tu página y luego elige un ID de conjunto de datos.",
5126-
"connect": "Conectar mediante Facebook",
5127+
"connect": "Crear conjunto de datos",
51275128
"connectedLabel": "vía Facebook"
51285129
},
51295130
"custom": {
@@ -5136,14 +5137,15 @@
51365137
"whatsapp": {
51375138
"title": "Conectar mediante WhatsApp",
51385139
"description": "Usa tu conexión de WhatsApp y luego elige un ID de conjunto de datos.",
5139-
"connect": "Conectar mediante WhatsApp",
5140+
"connect": "Crear conjunto de datos",
51405141
"connectedLabel": "vía WhatsApp"
51415142
}
51425143
},
51435144
"finalize": {
51445145
"title": "Elige un ID de conjunto de datos",
51455146
"description": "Introduce un ID de conjunto de datos existente, o déjalo vacío para crear uno automáticamente.",
51465147
"save": "Guardar",
5148+
"createDataset": "Crear conjunto de datos",
51475149
"caveat": "Un ID de conjunto de datos que no esté vinculado a esta página o cuenta de Instagram puede no atribuir los eventos correctamente. Deja este campo vacío para crear un conjunto de datos vinculado automáticamente."
51485150
},
51495151
"whatsapp": {

0 commit comments

Comments
 (0)