@drip-apex/sdk 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/bundle-report.json +584 -226
- package/dist/define.cjs +123 -0
- package/dist/define.cjs.map +7 -0
- package/dist/define.mjs +102 -0
- package/dist/define.mjs.map +7 -0
- package/dist/drip.cjs +19283 -0
- package/dist/drip.cjs.map +7 -0
- package/dist/drip.debug.js +1 -1
- package/dist/drip.debug.js.map +1 -1
- package/dist/drip.heatmap.js +1 -1
- package/dist/drip.heatmap.js.map +1 -1
- package/dist/drip.js +1 -1
- package/dist/drip.js.map +1 -1
- package/dist/drip.live.js +1 -1
- package/dist/drip.live.js.map +1 -1
- package/dist/drip.mjs +19267 -0
- package/dist/drip.mjs.map +7 -0
- package/dist/drip.replay-recorder.js +2 -0
- package/dist/drip.replay-recorder.js.map +1 -0
- package/dist/editor.js +147 -142
- package/dist/editor.js.map +4 -4
- package/dist/flags.cjs +1788 -0
- package/dist/flags.cjs.map +7 -0
- package/dist/flags.mjs +1765 -0
- package/dist/flags.mjs.map +7 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +2 -2
- package/dist/metafiles/define.cjs.meta.json +28 -0
- package/dist/metafiles/define.mjs.meta.json +32 -0
- package/dist/metafiles/drip.cjs.meta.json +3193 -0
- package/dist/metafiles/drip.debug.meta.json +2859 -574
- package/dist/metafiles/drip.heatmap.meta.json +2732 -460
- package/dist/metafiles/drip.live.meta.json +2732 -460
- package/dist/metafiles/drip.meta.json +2859 -574
- package/dist/metafiles/drip.mjs.meta.json +3235 -0
- package/dist/metafiles/drip.replay-recorder.meta.json +82 -0
- package/dist/metafiles/editor.meta.json +67 -27
- package/dist/metafiles/flags.cjs.meta.json +220 -0
- package/dist/metafiles/flags.mjs.meta.json +225 -0
- package/dist/metafiles/index.meta.json +2717 -491
- package/dist/metafiles/index.mjs.meta.json +14 -4
- package/dist/react/index.js +16636 -13533
- package/dist/react/index.js.map +4 -4
- package/dist/sri.json +20 -15
- package/dist/types/activation-runtime.d.ts +91 -0
- package/dist/types/analytics-runtime.d.ts +166 -0
- package/dist/types/antiflicker.d.ts +9 -3
- package/dist/types/assignment-cookie-sync.d.ts +31 -0
- package/dist/types/assignment-runtime.d.ts +257 -0
- package/dist/types/assignment-store.d.ts +4 -8
- package/dist/types/bucket.d.ts +1 -2
- package/dist/types/compact-store.d.ts +1 -0
- package/dist/types/config-runtime.d.ts +313 -0
- package/dist/types/consent-evaluation.d.ts +28 -0
- package/dist/types/css.d.ts +5 -3
- package/dist/types/debug-loader.d.ts +2 -0
- package/dist/types/decide-assignments.d.ts +31 -0
- package/dist/types/delivery-timing.d.ts +8 -0
- package/dist/types/early-interaction-capture.d.ts +79 -0
- package/dist/types/early-reassert.d.ts +32 -0
- package/dist/types/edge-runtime.d.ts +11 -0
- package/dist/types/editor/api.d.ts +3 -0
- package/dist/types/editor/controller.d.ts +3 -1
- package/dist/types/editor/publication-result.d.ts +11 -0
- package/dist/types/editor/types.d.ts +2 -1
- package/dist/types/editor/view-kit.d.ts +1 -1
- package/dist/types/endpoint.d.ts +8 -0
- package/dist/types/exclusion-groups.d.ts +4 -7
- package/dist/types/flags.d.ts +2 -0
- package/dist/types/goal-eligibility.d.ts +4 -0
- package/dist/types/goals.d.ts +10 -1
- package/dist/types/guards.d.ts +7 -7
- package/dist/types/handover.d.ts +12 -1
- package/dist/types/hash.d.ts +8 -4
- package/dist/types/hydration.d.ts +2 -1
- package/dist/types/index.d.ts +24 -128
- package/dist/types/init-test-helpers.d.ts +48 -0
- package/dist/types/inline-bootstrap.d.ts +26 -0
- package/dist/types/js.d.ts +3 -1
- package/dist/types/money.d.ts +12 -0
- package/dist/types/mutate.d.ts +45 -6
- package/dist/types/performance.d.ts +2 -0
- package/dist/types/persistence-decoders.d.ts +12 -0
- package/dist/types/public-api.d.ts +209 -0
- package/dist/types/qa-assistant-state.d.ts +34 -0
- package/dist/types/react/index.d.ts +28 -3
- package/dist/types/reassert-sentinel.d.ts +6 -0
- package/dist/types/redirect.d.ts +48 -0
- package/dist/types/replay-recorder-entry.d.ts +1 -0
- package/dist/types/replay-signal-state.d.ts +6 -0
- package/dist/types/replay-signals.d.ts +13 -0
- package/dist/types/replay.d.ts +110 -0
- package/dist/types/router.d.ts +12 -2
- package/dist/types/runtime-state.d.ts +166 -0
- package/dist/types/sanitize.d.ts +1 -0
- package/dist/types/shadow.d.ts +3 -0
- package/dist/types/shopify-cart.d.ts +25 -1
- package/dist/types/storefront-vitals.d.ts +24 -0
- package/dist/types/targeting-diagnostics.d.ts +1 -0
- package/dist/types/targeting.d.ts +16 -2
- package/dist/types/track.d.ts +15 -1
- package/dist/types/types.d.ts +115 -9
- package/dist/types/wait.d.ts +14 -1
- package/dist/types/weekday-schedule.d.ts +3 -0
- package/package.json +17 -8
- package/src/activation-runtime.ts +2737 -0
- package/src/analytics-runtime.ts +2175 -0
- package/src/antiflicker.test.ts +216 -44
- package/src/antiflicker.ts +103 -28
- package/src/artifact-concurrency.test.ts +1091 -0
- package/src/assignment-cookie-sync.test.ts +202 -0
- package/src/assignment-cookie-sync.ts +189 -0
- package/src/assignment-runtime.ts +3204 -0
- package/src/assignment-store.test.ts +56 -0
- package/src/assignment-store.ts +7 -38
- package/src/bucket.ts +3 -18
- package/src/commerce-analytics-lifecycle.test.ts +75 -15
- package/src/commerce-config-lifecycle.test.ts +81 -1
- package/src/commerce-price-preserve.test.ts +133 -0
- package/src/commerce-price.test.ts +97 -0
- package/src/commerce-price.ts +74 -20
- package/src/commerce-shipping.test.ts +53 -0
- package/src/commerce-shipping.ts +5 -11
- package/src/compact-store.ts +15 -0
- package/src/config-fetch.test.ts +480 -9
- package/src/config-runtime.ts +2071 -0
- package/src/consent-evaluation-borlabs.test.ts +41 -0
- package/src/consent-evaluation-cookiescript.test.ts +50 -0
- package/src/consent-evaluation.ts +151 -0
- package/src/css.test.ts +30 -0
- package/src/css.ts +120 -6
- package/src/cwv.test.ts +60 -77
- package/src/debug-loader.test.ts +214 -0
- package/src/debug-loader.ts +41 -0
- package/src/decide-assignments.test.ts +196 -0
- package/src/decide-assignments.ts +204 -0
- package/src/delivery-timing.test.ts +104 -0
- package/src/delivery-timing.ts +89 -0
- package/src/early-interaction-capture.test.ts +451 -0
- package/src/early-interaction-capture.ts +230 -0
- package/src/early-reassert.test.ts +207 -0
- package/src/early-reassert.ts +152 -0
- package/src/edge-adoption.test.ts +435 -0
- package/src/edge-runtime.ts +30 -0
- package/src/editor/api.test.ts +109 -0
- package/src/editor/api.ts +34 -3
- package/src/editor/controller.test.ts +297 -14
- package/src/editor/controller.ts +69 -5
- package/src/editor/index.test.ts +2 -0
- package/src/editor/index.ts +1 -0
- package/src/editor/publication-result.ts +58 -0
- package/src/editor/types.ts +10 -1
- package/src/editor/view-kit.ts +2 -1
- package/src/editor/view.test.ts +108 -0
- package/src/editor/view.ts +49 -7
- package/src/endpoint.test.ts +15 -0
- package/src/endpoint.ts +10 -0
- package/src/exclusion-groups.ts +8 -18
- package/src/flags.test.ts +88 -1
- package/src/flags.ts +29 -29
- package/src/global-runtime-reconciler.test.ts +271 -0
- package/src/goal-eligibility.test.ts +29 -0
- package/src/goal-eligibility.ts +17 -0
- package/src/goals.test.ts +144 -1
- package/src/goals.ts +97 -18
- package/src/guards.test.ts +99 -1
- package/src/guards.ts +34 -15
- package/src/handover-cache-order.test.ts +6 -3
- package/src/handover.test.ts +154 -8
- package/src/handover.ts +122 -12
- package/src/hash.ts +29 -54
- package/src/holdout.test.ts +26 -0
- package/src/hydration.test.ts +73 -2
- package/src/hydration.ts +18 -9
- package/src/index.ts +130 -7287
- package/src/init-analytics.test.ts +1694 -0
- package/src/init-anti-flicker.test.ts +1211 -0
- package/src/init-cart-attributes.test.ts +2003 -0
- package/src/init-config-resolution.test.ts +999 -0
- package/src/init-consent.test.ts +971 -0
- package/src/init-empty.test.ts +11 -5406
- package/src/init-integrations.test.ts +846 -0
- package/src/init-page-activation.test.ts +5064 -0
- package/src/init-qa-loading.test.ts +740 -0
- package/src/init-redirects.test.ts +2156 -0
- package/src/init-test-helpers.ts +287 -0
- package/src/inline-bootstrap.test.ts +184 -0
- package/src/inline-bootstrap.ts +73 -0
- package/src/js.test.ts +51 -1
- package/src/js.ts +86 -2
- package/src/money.test.ts +55 -1
- package/src/money.ts +104 -2
- package/src/money.web-parity.contract.test.ts +85 -0
- package/src/mutate.test.ts +474 -10
- package/src/mutate.ts +739 -230
- package/src/page-trigger.test.ts +218 -8
- package/src/performance.ts +10 -0
- package/src/persistence-decoders.ts +18 -0
- package/src/public-api.ts +1828 -0
- package/src/qa-assistant-state.test.ts +249 -3
- package/src/qa-assistant-state.ts +146 -6
- package/src/qa-assistant-view.test.ts +220 -2
- package/src/qa-assistant-view.ts +102 -5
- package/src/react/index.test.tsx +37 -0
- package/src/react/index.tsx +19 -3
- package/src/reassert-sentinel.test.ts +155 -1
- package/src/reassert-sentinel.ts +168 -8
- package/src/redirect.test.ts +541 -0
- package/src/redirect.ts +572 -0
- package/src/replay-recorder-entry.ts +115 -0
- package/src/replay-signal-state.ts +100 -0
- package/src/replay-signals.test.ts +249 -0
- package/src/replay-signals.ts +151 -0
- package/src/replay.test.ts +764 -0
- package/src/replay.ts +514 -0
- package/src/router.test.ts +61 -2
- package/src/router.ts +50 -12
- package/src/runtime-state.ts +507 -0
- package/src/runtime.test.ts +174 -7
- package/src/runtime.ts +88 -14
- package/src/sanitize.test.ts +34 -0
- package/src/sanitize.ts +23 -12
- package/src/shadow.test.ts +187 -0
- package/src/shadow.ts +99 -0
- package/src/shopify-cart.test.ts +733 -23
- package/src/shopify-cart.ts +402 -49
- package/src/storefront-vitals.test.ts +217 -0
- package/src/storefront-vitals.ts +206 -0
- package/src/surface-signals.ts +6 -0
- package/src/targeting-conformance.test.ts +9 -0
- package/src/targeting-diagnostics.ts +38 -8
- package/src/targeting-parity-corpus.json +559 -0
- package/src/targeting-parity-corpus.test.ts +18 -0
- package/src/targeting.detailed.test.ts +105 -0
- package/src/targeting.test.ts +211 -0
- package/src/targeting.ts +88 -207
- package/src/track.test.ts +147 -0
- package/src/track.ts +125 -26
- package/src/types.ts +120 -13
- package/src/wait.test.ts +23 -0
- package/src/wait.ts +129 -13
- package/src/weekday-delivery.test.ts +56 -0
- package/src/weekday-schedule-parity-corpus.json +92 -0
- package/src/weekday-schedule.test.ts +69 -0
- package/src/weekday-schedule.ts +20 -0
|
@@ -5,6 +5,7 @@ import { mountQaAssistantView, QA_ASSISTANT_HOST_ID } from "./qa-assistant-view"
|
|
|
5
5
|
|
|
6
6
|
function state(subline = "Showing Variant B"): QaAssistantState {
|
|
7
7
|
return {
|
|
8
|
+
storeVersion: 0,
|
|
8
9
|
experiments: [{
|
|
9
10
|
id: "exp_products", name: "Product CTA", variationId: "var_b", variationName: "Variant B",
|
|
10
11
|
applied: true, forced: false, subline,
|
|
@@ -19,7 +20,7 @@ function state(subline = "Showing Variant B"): QaAssistantState {
|
|
|
19
20
|
{ id: "var_b", name: "Variant B", isControl: false, isCurrent: true, isReal: true },
|
|
20
21
|
],
|
|
21
22
|
}],
|
|
22
|
-
events: [], transactions: { kind: "unsupported" }, forcedSummary: null,
|
|
23
|
+
events: [], goalScopes: [], pageActivation: [], transactions: { kind: "unsupported" }, forcedSummary: null, forcedUnavailable: [], edgeDeviceAudienceUnsupported: [],
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -31,7 +32,13 @@ function fakeStore(initial: QaAssistantState) {
|
|
|
31
32
|
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
32
33
|
forceVariation: vi.fn(), resetForce: vi.fn(), exitQa: vi.fn(),
|
|
33
34
|
};
|
|
34
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
store,
|
|
37
|
+
emit(next: QaAssistantState) {
|
|
38
|
+
current = { ...next, storeVersion: current.storeVersion + 1 };
|
|
39
|
+
listeners.forEach((listener) => listener(current));
|
|
40
|
+
},
|
|
41
|
+
};
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
afterEach(() => {
|
|
@@ -40,6 +47,217 @@ afterEach(() => {
|
|
|
40
47
|
});
|
|
41
48
|
|
|
42
49
|
describe("mountQaAssistantView", () => {
|
|
50
|
+
it("gives the empty shadow host defensive inline styles", () => {
|
|
51
|
+
const view = mountQaAssistantView(fakeStore(state()).store);
|
|
52
|
+
const host = document.getElementById(QA_ASSISTANT_HOST_ID)!;
|
|
53
|
+
|
|
54
|
+
expect(host.childElementCount).toBe(0);
|
|
55
|
+
expect(host.style.position).toBe("fixed");
|
|
56
|
+
expect(host.style.zIndex).toBe("2147483647");
|
|
57
|
+
expect(host.style.display).toBe("block");
|
|
58
|
+
expect(host.style.top).toBe("0px");
|
|
59
|
+
expect(host.style.left).toBe("0px");
|
|
60
|
+
expect(host.style.width).toBe("0px");
|
|
61
|
+
expect(host.style.height).toBe("0px");
|
|
62
|
+
expect(host.style.overflow).toBe("visible");
|
|
63
|
+
expect(host.style.contain).toBe("none");
|
|
64
|
+
expect(host.getAttribute("style")).toContain("display: block !important");
|
|
65
|
+
view.destroy();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("keeps the host displayed when storefront CSS hides empty divs", () => {
|
|
69
|
+
const storefrontStyle = document.createElement("style");
|
|
70
|
+
storefrontStyle.textContent = "div:empty { display: none; }";
|
|
71
|
+
document.head.appendChild(storefrontStyle);
|
|
72
|
+
const view = mountQaAssistantView(fakeStore(state()).store);
|
|
73
|
+
const host = document.getElementById(QA_ASSISTANT_HOST_ID)!;
|
|
74
|
+
|
|
75
|
+
expect(getComputedStyle(host).display).toBe("block");
|
|
76
|
+
|
|
77
|
+
view.destroy();
|
|
78
|
+
storefrontStyle.remove();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("humanizes a skipped unresolved signal as an audience rule", () => {
|
|
82
|
+
const current = state();
|
|
83
|
+
current.experiments[0]!.conditions.push({
|
|
84
|
+
id: "segment:device:1",
|
|
85
|
+
group: "audience",
|
|
86
|
+
state: "skip",
|
|
87
|
+
text: "Desktop visitors",
|
|
88
|
+
detail: "Skipped — signal_unresolved failed first",
|
|
89
|
+
rawCode: null,
|
|
90
|
+
});
|
|
91
|
+
const source = fakeStore(current);
|
|
92
|
+
const view = mountQaAssistantView(source.store);
|
|
93
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
94
|
+
(shadow.querySelector('[data-action="toggle-exp"]') as HTMLElement).click();
|
|
95
|
+
|
|
96
|
+
expect(shadow.textContent).toContain("Skipped — the audience rule failed first");
|
|
97
|
+
view.destroy();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("re-renders when Page activation is the only diagnostic change", () => {
|
|
101
|
+
vi.useFakeTimers();
|
|
102
|
+
const initial = {
|
|
103
|
+
...state(),
|
|
104
|
+
pageActivation: [{
|
|
105
|
+
pageId: "page-products",
|
|
106
|
+
pageName: "Product pages",
|
|
107
|
+
active: true,
|
|
108
|
+
lastTransitionMode: null,
|
|
109
|
+
isolationFallback: null,
|
|
110
|
+
}],
|
|
111
|
+
};
|
|
112
|
+
const source = fakeStore(initial);
|
|
113
|
+
const view = mountQaAssistantView(source.store);
|
|
114
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
115
|
+
|
|
116
|
+
expect(shadow.querySelector('.scope-row[data-status="active"] .scope-status')?.textContent)
|
|
117
|
+
.toBe("Active");
|
|
118
|
+
source.emit({
|
|
119
|
+
...initial,
|
|
120
|
+
pageActivation: [{
|
|
121
|
+
...initial.pageActivation[0]!,
|
|
122
|
+
active: false,
|
|
123
|
+
lastTransitionMode: "deactivate",
|
|
124
|
+
}],
|
|
125
|
+
});
|
|
126
|
+
vi.advanceTimersByTime(100);
|
|
127
|
+
|
|
128
|
+
expect(shadow.querySelector('.scope-row[data-status="inactive"] .scope-status')?.textContent)
|
|
129
|
+
.toBe("Inactive");
|
|
130
|
+
expect(shadow.textContent).toContain("Last transition · Deactivate");
|
|
131
|
+
view.destroy();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("shows per-Page active state and the last transition mode", () => {
|
|
135
|
+
const source = fakeStore({
|
|
136
|
+
...state(),
|
|
137
|
+
pageActivation: [
|
|
138
|
+
{
|
|
139
|
+
pageId: "page-products",
|
|
140
|
+
pageName: "Product pages",
|
|
141
|
+
active: false,
|
|
142
|
+
lastTransitionMode: "deactivate",
|
|
143
|
+
isolationFallback: null,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
pageId: "page-cart",
|
|
147
|
+
pageName: "Cart",
|
|
148
|
+
active: true,
|
|
149
|
+
lastTransitionMode: "reset",
|
|
150
|
+
isolationFallback: "whole_experiment",
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
const view = mountQaAssistantView(source.store);
|
|
155
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
156
|
+
|
|
157
|
+
expect(shadow.textContent).toContain("Page activation");
|
|
158
|
+
expect(shadow.textContent).toContain("Product pages");
|
|
159
|
+
expect(shadow.textContent).toContain("Last transition · Deactivate");
|
|
160
|
+
expect(shadow.textContent).toContain("whole-experiment fallback");
|
|
161
|
+
view.destroy();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("announces an unavailable forced experiment with its reason", () => {
|
|
165
|
+
const source = fakeStore({
|
|
166
|
+
...state(),
|
|
167
|
+
forcedUnavailable: [{ experimentId: "exp_archived_123456", reason: "archived" }],
|
|
168
|
+
});
|
|
169
|
+
const view = mountQaAssistantView(source.store);
|
|
170
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
171
|
+
|
|
172
|
+
expect(shadow.querySelector('[role="alert"]')?.textContent).toContain(
|
|
173
|
+
"Forced experiment not available (archived): exp_arch…",
|
|
174
|
+
);
|
|
175
|
+
view.destroy();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("names a device audience that falls back from edge to browser evaluation", () => {
|
|
179
|
+
const source = fakeStore({
|
|
180
|
+
...state(),
|
|
181
|
+
edgeDeviceAudienceUnsupported: [{
|
|
182
|
+
experimentId: "exp_redirect",
|
|
183
|
+
audienceName: "Mobile shoppers",
|
|
184
|
+
}],
|
|
185
|
+
});
|
|
186
|
+
const view = mountQaAssistantView(source.store);
|
|
187
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
188
|
+
|
|
189
|
+
expect(shadow.querySelector('[role="alert"]')?.textContent).toContain(
|
|
190
|
+
"Audience “Mobile shoppers” cannot be evaluated at the edge. Apex falls back to evaluating it in the browser.",
|
|
191
|
+
);
|
|
192
|
+
view.destroy();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("keeps resolved force summaries visible alongside unavailable force warnings", () => {
|
|
196
|
+
const source = fakeStore({
|
|
197
|
+
...state(),
|
|
198
|
+
forcedSummary: "Product CTA: Variant B",
|
|
199
|
+
forcedUnavailable: [{ experimentId: "exp_archived_123456", reason: "archived" }],
|
|
200
|
+
});
|
|
201
|
+
const view = mountQaAssistantView(source.store);
|
|
202
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
203
|
+
|
|
204
|
+
expect(shadow.querySelector('[role="alert"]')?.textContent).toContain("exp_arch…");
|
|
205
|
+
expect(shadow.textContent).toContain("Product CTA: Variant B");
|
|
206
|
+
view.destroy();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("does not render an unavailable warning when every force resolves", () => {
|
|
210
|
+
const source = fakeStore({ ...state(), forcedSummary: "Product CTA: Variant B" });
|
|
211
|
+
const view = mountQaAssistantView(source.store);
|
|
212
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
213
|
+
|
|
214
|
+
expect(shadow.querySelector('[role="alert"]')).toBeNull();
|
|
215
|
+
expect(shadow.textContent).toContain("Product CTA: Variant B");
|
|
216
|
+
view.destroy();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("updates the unavailable warning when it is the only state change", () => {
|
|
220
|
+
vi.useFakeTimers();
|
|
221
|
+
const initial = state();
|
|
222
|
+
const source = fakeStore(initial);
|
|
223
|
+
const view = mountQaAssistantView(source.store);
|
|
224
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
225
|
+
|
|
226
|
+
source.emit({
|
|
227
|
+
...initial,
|
|
228
|
+
forcedUnavailable: [{ experimentId: "exp_archived_123456", reason: "archived" }],
|
|
229
|
+
});
|
|
230
|
+
vi.advanceTimersByTime(100);
|
|
231
|
+
expect(shadow.querySelector('[role="alert"]')?.textContent).toContain(
|
|
232
|
+
"Forced experiment not available (archived): exp_arch…",
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
source.emit(initial);
|
|
236
|
+
vi.advanceTimersByTime(100);
|
|
237
|
+
expect(shadow.querySelector('[role="alert"]')).toBeNull();
|
|
238
|
+
view.destroy();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("shows active, inactive, and not-wired goal scopes in Events", () => {
|
|
242
|
+
const source = fakeStore({
|
|
243
|
+
...state(),
|
|
244
|
+
goalScopes: [
|
|
245
|
+
{ id: "goal-active", name: "Add to cart", pageId: "page-products", pageLabel: "product", status: "active" },
|
|
246
|
+
{ id: "goal-inactive", name: "Checkout click", pageId: "page-checkout", pageLabel: "checkout", status: "inactive" },
|
|
247
|
+
{ id: "goal-unwired", name: "Signup", pageId: null, pageLabel: null, status: "not_wired" },
|
|
248
|
+
],
|
|
249
|
+
});
|
|
250
|
+
const view = mountQaAssistantView(source.store);
|
|
251
|
+
const shadow = document.getElementById(QA_ASSISTANT_HOST_ID)!.shadowRoot!;
|
|
252
|
+
(shadow.querySelector('[data-action="tab"][data-tab="ev"]') as HTMLElement).click();
|
|
253
|
+
|
|
254
|
+
expect(shadow.textContent).toContain("Page · product");
|
|
255
|
+
expect(shadow.textContent).toContain("Wired · page active");
|
|
256
|
+
expect(shadow.textContent).toContain("Wired · page inactive");
|
|
257
|
+
expect(shadow.textContent).toContain("Not wired");
|
|
258
|
+
view.destroy();
|
|
259
|
+
});
|
|
260
|
+
|
|
43
261
|
it("coalesces refresh bursts while preserving the panel, expanded rows, and scroll", () => {
|
|
44
262
|
vi.useFakeTimers();
|
|
45
263
|
const source = fakeStore(state());
|
package/src/qa-assistant-view.ts
CHANGED
|
@@ -27,6 +27,20 @@ type ViewState = {
|
|
|
27
27
|
|
|
28
28
|
const MINIMIZED_KEY = "drip_qa_assistant_min";
|
|
29
29
|
|
|
30
|
+
const HOST_STYLES: ReadonlyArray<readonly [string, string]> = [
|
|
31
|
+
["position", "fixed"],
|
|
32
|
+
["z-index", "2147483647"],
|
|
33
|
+
["display", "block"],
|
|
34
|
+
["top", "0px"],
|
|
35
|
+
["left", "0px"],
|
|
36
|
+
["width", "0px"],
|
|
37
|
+
["height", "0px"],
|
|
38
|
+
["overflow", "visible"],
|
|
39
|
+
["contain", "none"],
|
|
40
|
+
["visibility", "visible"],
|
|
41
|
+
["opacity", "1"],
|
|
42
|
+
];
|
|
43
|
+
|
|
30
44
|
/* ── icons (Lucide-style paths from the design's icon set) ─────────────── */
|
|
31
45
|
const P = {
|
|
32
46
|
check: "M5 13l4 4L19 7",
|
|
@@ -72,6 +86,8 @@ const REASON_WORDS: Record<string, string> = {
|
|
|
72
86
|
url_exclude_matched: "page rules",
|
|
73
87
|
page_rule_failed: "page rules",
|
|
74
88
|
segment_mismatch: "the audience rule",
|
|
89
|
+
segments_unexpanded: "the unexpanded audience segments",
|
|
90
|
+
signal_unresolved: "the audience rule",
|
|
75
91
|
segment_and_failed: "the audience rule",
|
|
76
92
|
attribute_mismatch: "the audience rule",
|
|
77
93
|
js_false: "the custom JS trigger",
|
|
@@ -138,6 +154,7 @@ button { font-family: inherit; background: none; border: 0; padding: 0; color: i
|
|
|
138
154
|
border-bottom: 1px solid var(--p-bd); flex-shrink: 0; color: var(--p200); }
|
|
139
155
|
.forced-bar .txt { font-size: 11px; font-weight: 600; flex: 1; min-width: 0; }
|
|
140
156
|
.forced-bar .reset { font-size: 11px; font-weight: 600; color: var(--t1); white-space: nowrap; }
|
|
157
|
+
.forced-bar.warning { background: var(--warn-soft); border-color: var(--warn-bd); color: var(--warn); }
|
|
141
158
|
.tabs { display: flex; padding: 0 8px; border-bottom: 1px solid var(--bd); background: rgba(255,255,255,0.015); flex-shrink: 0; }
|
|
142
159
|
.tab { display: inline-flex; align-items: center; gap: 6px; padding: 10px 9px 9px; font-size: 12px; font-weight: 600;
|
|
143
160
|
color: var(--t3); white-space: nowrap; }
|
|
@@ -199,6 +216,19 @@ button { font-family: inherit; background: none; border: 0; padding: 0; color: i
|
|
|
199
216
|
.live .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--pos); box-shadow: 0 0 6px var(--pos); }
|
|
200
217
|
.live .t { font-size: 11px; color: var(--t3); }
|
|
201
218
|
.live .clear { margin-left: auto; font-size: 11px; color: var(--t4); font-weight: 600; }
|
|
219
|
+
.scope-list { padding: 0 14px 8px; border-bottom: 1px solid var(--bd); }
|
|
220
|
+
.scope-row { display: flex; align-items: center; gap: 10px; padding: 7px 9px; border: 1px solid var(--bd); background: var(--inset); }
|
|
221
|
+
.scope-row + .scope-row { border-top: 0; }
|
|
222
|
+
.scope-row:first-child { border-radius: 8px 8px 0 0; }
|
|
223
|
+
.scope-row:last-child { border-radius: 0 0 8px 8px; }
|
|
224
|
+
.scope-row:only-child { border-radius: 8px; }
|
|
225
|
+
.scope-copy { display: grid; flex: 1; min-width: 0; }
|
|
226
|
+
.scope-name { overflow: hidden; font-size: 11.5px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
|
|
227
|
+
.scope-page { overflow: hidden; margin-top: 1px; color: var(--t3); font-size: 10.5px; text-overflow: ellipsis; white-space: nowrap; }
|
|
228
|
+
.scope-status { flex-shrink: 0; color: var(--t3); font-size: 10.5px; font-weight: 600; }
|
|
229
|
+
.scope-row[data-status="active"] .scope-status { color: var(--pos); }
|
|
230
|
+
.scope-row[data-status="inactive"] { box-shadow: inset 2px 0 0 var(--warn); }
|
|
231
|
+
.scope-row[data-status="inactive"] .scope-status { color: var(--warn); }
|
|
202
232
|
.ev { padding: 8px 14px; border-bottom: 1px solid var(--bd); }
|
|
203
233
|
.ev:last-child { border-bottom: 0; }
|
|
204
234
|
.ev[data-dup="true"] { background: rgba(245,158,11,0.05); box-shadow: inset 2px 0 0 var(--warn); }
|
|
@@ -368,6 +398,28 @@ function experimentsBody(state: QaAssistantState, view: ViewState): string {
|
|
|
368
398
|
<select data-action="status-filter"><option value="all">All</option><option value="accepted" ${view.statusFilter === "accepted" ? "selected" : ""}>Applied</option><option value="rejected" ${view.statusFilter === "rejected" ? "selected" : ""}>Not applied</option><option value="forced" ${view.statusFilter === "forced" ? "selected" : ""}>Forced</option></select>
|
|
369
399
|
<input data-action="search" aria-label="Search campaigns" placeholder="Search name or ID" value="${esc(view.search)}">
|
|
370
400
|
</div>`;
|
|
401
|
+
const transitionLabels = {
|
|
402
|
+
reset: "Reset",
|
|
403
|
+
deactivate: "Deactivate",
|
|
404
|
+
force_reset: "Force-reset",
|
|
405
|
+
persistent: "Persistent",
|
|
406
|
+
} as const;
|
|
407
|
+
const pageActivation = state.pageActivation.length
|
|
408
|
+
? `<div class="grp"><span class="l">Page activation</span><span class="n">${state.pageActivation.length}</span></div>` +
|
|
409
|
+
`<div class="scope-list">${state.pageActivation.map((page) => {
|
|
410
|
+
const transition = page.lastTransitionMode
|
|
411
|
+
? `Last transition · ${transitionLabels[page.lastTransitionMode]}`
|
|
412
|
+
: "No transition yet";
|
|
413
|
+
const fallback = page.isolationFallback === "whole_experiment"
|
|
414
|
+
? " · whole-experiment fallback"
|
|
415
|
+
: "";
|
|
416
|
+
const pollAttempts = page.triggerPollAttempts ?? 0;
|
|
417
|
+
const trigger = page.triggerDetector
|
|
418
|
+
? ` · ${page.triggerDetector} detector · ${pollAttempts} poll attempt${pollAttempts === 1 ? "" : "s"} · ${page.triggerOutcome ?? "pending"} · ${page.triggerElapsedMs ?? 0} ms`
|
|
419
|
+
: "";
|
|
420
|
+
return `<div class="scope-row" data-status="${page.active ? "active" : "inactive"}"><span class="scope-copy"><span class="scope-name">${esc(page.pageName)}</span><span class="scope-page">${esc(transition + fallback + trigger)}</span></span><span class="scope-status">${page.active ? "Active" : "Inactive"}</span></div>`;
|
|
421
|
+
}).join("")}</div>`
|
|
422
|
+
: "";
|
|
371
423
|
// Summary lines describe the whole campaign set, never the filtered view —
|
|
372
424
|
// a filter that hides rejected rows must not make "all apply" claims.
|
|
373
425
|
const totalRejected = state.experiments.filter((e) => !e.applied && !e.forced).length;
|
|
@@ -377,7 +429,7 @@ function experimentsBody(state: QaAssistantState, view: ViewState): string {
|
|
|
377
429
|
} else if (state.experiments.length > 0 && totalRejected === state.experiments.length) {
|
|
378
430
|
summary = `<div class="sumline" style="color:var(--t2)"><span style="color:var(--warn);display:inline-flex">${svg(P.alert, 12)}</span>Nothing runs on this page — every experiment was rejected.</div>`;
|
|
379
431
|
} else if (state.experiments.length === 0) {
|
|
380
|
-
return filters + `<div class="empty">
|
|
432
|
+
return pageActivation + filters + `<div class="empty">
|
|
381
433
|
<div class="icon">${svg(P.target, 16)}</div>
|
|
382
434
|
<div class="h">No experiments on this shop yet</div>
|
|
383
435
|
<div class="s">Running and draft experiments show up here when the QA link opens a page they could target.</div>
|
|
@@ -391,7 +443,7 @@ function experimentsBody(state: QaAssistantState, view: ViewState): string {
|
|
|
391
443
|
? `<div class="grp"><span class="l">Not applied</span><span class="n">${rejected.length}</span></div>` +
|
|
392
444
|
rejected.map((e) => experimentRow(e, view)).join("")
|
|
393
445
|
: "";
|
|
394
|
-
return filters + summary + appliedHtml + rejectedHtml;
|
|
446
|
+
return pageActivation + filters + summary + appliedHtml + rejectedHtml;
|
|
395
447
|
}
|
|
396
448
|
|
|
397
449
|
function eventRow(ev: QaEventModel): string {
|
|
@@ -417,6 +469,22 @@ function eventRow(ev: QaEventModel): string {
|
|
|
417
469
|
}
|
|
418
470
|
|
|
419
471
|
function eventsBody(state: QaAssistantState): string {
|
|
472
|
+
const goalScopes = state.goalScopes.length
|
|
473
|
+
? `<div class="grp"><span class="l">Goal wiring</span><span class="n">${state.goalScopes.length}</span></div>` +
|
|
474
|
+
`<div class="scope-list">${state.goalScopes.map((goal) => {
|
|
475
|
+
const status = goal.status === "active"
|
|
476
|
+
? goal.assignmentPageActive === false ? "Wired · page active · treatment off-target" : "Wired · page active"
|
|
477
|
+
: goal.status === "inactive"
|
|
478
|
+
? goal.assignmentPageActive === false ? "Wired · page inactive · treatment off-target" : "Wired · page inactive"
|
|
479
|
+
: "Not wired";
|
|
480
|
+
const scope = goal.pageLabel
|
|
481
|
+
? `Page · ${goal.pageLabel}`
|
|
482
|
+
: goal.urlPattern
|
|
483
|
+
? `URL · ${goal.urlPattern}`
|
|
484
|
+
: "Project-wide";
|
|
485
|
+
return `<div class="scope-row" data-status="${goal.status}"><span class="scope-copy"><span class="scope-name">${esc(goal.name)}</span><span class="scope-page">${esc(scope)}</span></span><span class="scope-status">${status}</span></div>`;
|
|
486
|
+
}).join("")}</div>`
|
|
487
|
+
: "";
|
|
420
488
|
const live = `
|
|
421
489
|
<div class="live">
|
|
422
490
|
<span class="dot"></span>
|
|
@@ -425,7 +493,7 @@ function eventsBody(state: QaAssistantState): string {
|
|
|
425
493
|
</div>`;
|
|
426
494
|
if (state.events.length === 0) {
|
|
427
495
|
return (
|
|
428
|
-
live +
|
|
496
|
+
goalScopes + live +
|
|
429
497
|
`<div class="empty">
|
|
430
498
|
<div class="icon">${svg(P.activity, 16)}</div>
|
|
431
499
|
<div class="h">No events yet</div>
|
|
@@ -438,7 +506,7 @@ function eventsBody(state: QaAssistantState): string {
|
|
|
438
506
|
.slice(0, 80)
|
|
439
507
|
.map(eventRow)
|
|
440
508
|
.join("");
|
|
441
|
-
return live + rows;
|
|
509
|
+
return goalScopes + live + rows;
|
|
442
510
|
}
|
|
443
511
|
|
|
444
512
|
function attributionRows(list: Array<{ experimentName: string; variationName: string | null }>): string {
|
|
@@ -549,9 +617,31 @@ function panelHtml(state: QaAssistantState, view: ViewState): string {
|
|
|
549
617
|
}
|
|
550
618
|
|
|
551
619
|
function forcedBarHtml(state: QaAssistantState): string {
|
|
552
|
-
|
|
620
|
+
let unavailableHtml = "";
|
|
621
|
+
if (state.forcedUnavailable.length > 0) {
|
|
622
|
+
const reasonLabels = {
|
|
623
|
+
archived: "archived",
|
|
624
|
+
runtime_disabled: "runtime-disabled",
|
|
625
|
+
not_found: "not found on this shop",
|
|
626
|
+
environment_mismatch: "scoped to another environment",
|
|
627
|
+
not_in_config: "not in served config",
|
|
628
|
+
} as const;
|
|
629
|
+
const warnings = state.forcedUnavailable.map(({ experimentId, reason }) =>
|
|
630
|
+
`Forced experiment not available (${reasonLabels[reason]}): ${shortId(experimentId)}`,
|
|
631
|
+
).join(", ");
|
|
632
|
+
unavailableHtml = `<div class="forced-bar warning" role="alert">${svg(P.alert, 12)}<span class="txt">${esc(warnings)}</span><button class="reset" data-action="reset-force">Reset</button></div>`;
|
|
633
|
+
}
|
|
634
|
+
const summaryHtml = state.forcedSummary
|
|
553
635
|
? `<div class="forced-bar">${svg(P.bolt, 12)}<span class="txt">${esc(state.forcedSummary)}</span><button class="reset" data-action="reset-force">Reset</button></div>`
|
|
554
636
|
: "";
|
|
637
|
+
const edgeAudienceHtml = state.edgeDeviceAudienceUnsupported.map(({ audienceName }) =>
|
|
638
|
+
`<div class="forced-bar warning" role="alert">${svg(P.alert, 12)}<span class="txt">${esc(`Audience “${audienceName}” cannot be evaluated at the edge. Apex falls back to evaluating it in the browser.`)}</span></div>`,
|
|
639
|
+
).join("");
|
|
640
|
+
return unavailableHtml + edgeAudienceHtml + summaryHtml;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function shortId(id: string): string {
|
|
644
|
+
return id.length > 12 ? `${id.slice(0, 8)}…` : id;
|
|
555
645
|
}
|
|
556
646
|
|
|
557
647
|
function pillHtml(state: QaAssistantState): string {
|
|
@@ -581,6 +671,9 @@ export function mountQaAssistantView(store: QaAssistantStore): QaAssistantView {
|
|
|
581
671
|
|
|
582
672
|
const host = document.createElement("div");
|
|
583
673
|
host.id = QA_ASSISTANT_HOST_ID;
|
|
674
|
+
for (const [property, value] of HOST_STYLES) {
|
|
675
|
+
host.style.setProperty(property, value, "important");
|
|
676
|
+
}
|
|
584
677
|
const shadow = host.attachShadow({ mode: "open" });
|
|
585
678
|
const style = document.createElement("style");
|
|
586
679
|
style.textContent = CSS;
|
|
@@ -615,10 +708,14 @@ export function mountQaAssistantView(store: QaAssistantStore): QaAssistantView {
|
|
|
615
708
|
};
|
|
616
709
|
|
|
617
710
|
const signature = (state: QaAssistantState): string => JSON.stringify({
|
|
711
|
+
storeVersion: state.storeVersion,
|
|
618
712
|
experiments: state.experiments,
|
|
619
713
|
forcedSummary: state.forcedSummary,
|
|
714
|
+
forcedUnavailable: state.forcedUnavailable.map(({ experimentId, reason }) => [experimentId, reason]),
|
|
715
|
+
edgeDeviceAudienceUnsupported: state.edgeDeviceAudienceUnsupported.map(({ experimentId, audienceName }) => [experimentId, audienceName]),
|
|
620
716
|
transactions: state.transactions,
|
|
621
717
|
events: view.tab === "ev" ? state.events : undefined,
|
|
718
|
+
goalScopes: view.tab === "ev" ? state.goalScopes : undefined,
|
|
622
719
|
view: [view.tab, view.minimized, [...view.expanded], [...view.codeOpen], view.pageFilter, view.statusFilter, view.search],
|
|
623
720
|
});
|
|
624
721
|
|
package/src/react/index.test.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import type { Assignment, Experiment } from "../types";
|
|
|
6
6
|
import {
|
|
7
7
|
DripProvider,
|
|
8
8
|
useFeatureFlag,
|
|
9
|
+
useGetCartMetafields,
|
|
9
10
|
useTrackGoal,
|
|
10
11
|
useVariation,
|
|
11
12
|
} from "./index";
|
|
@@ -17,6 +18,7 @@ const sdkMock = vi.hoisted(() => ({
|
|
|
17
18
|
trackGoal: vi.fn(),
|
|
18
19
|
track: vi.fn(),
|
|
19
20
|
trackRevenue: vi.fn(),
|
|
21
|
+
getCartMetafields: vi.fn(),
|
|
20
22
|
}));
|
|
21
23
|
|
|
22
24
|
vi.mock("../index", () => sdkMock);
|
|
@@ -29,6 +31,7 @@ function assignment(
|
|
|
29
31
|
return {
|
|
30
32
|
experimentId,
|
|
31
33
|
variationId,
|
|
34
|
+
pageActive: true,
|
|
32
35
|
index: 0,
|
|
33
36
|
mutations: [],
|
|
34
37
|
isControl,
|
|
@@ -43,6 +46,7 @@ function resetSdkMock() {
|
|
|
43
46
|
sdkMock.trackGoal.mockReset();
|
|
44
47
|
sdkMock.track.mockReset();
|
|
45
48
|
sdkMock.trackRevenue.mockReset();
|
|
49
|
+
sdkMock.getCartMetafields.mockReset();
|
|
46
50
|
sdkMock.init.mockReturnValue([]);
|
|
47
51
|
sdkMock.getAssignments.mockReturnValue([]);
|
|
48
52
|
sdkMock.getExperiments.mockReturnValue([]);
|
|
@@ -70,6 +74,18 @@ function GoalButton() {
|
|
|
70
74
|
);
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
function CartMetafieldsButton() {
|
|
78
|
+
const getCartMetafields = useGetCartMetafields();
|
|
79
|
+
return (
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
onClick={() => getCartMetafields({ namespace: "merchant_bi" })}
|
|
83
|
+
>
|
|
84
|
+
Build cart metafields
|
|
85
|
+
</button>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
describe("React bindings", () => {
|
|
74
90
|
beforeEach(() => {
|
|
75
91
|
resetSdkMock();
|
|
@@ -174,4 +190,25 @@ describe("React bindings", () => {
|
|
|
174
190
|
expect(sdkMock.trackGoal).toHaveBeenCalledWith("signup", { plan: "pro" });
|
|
175
191
|
});
|
|
176
192
|
});
|
|
193
|
+
|
|
194
|
+
it("exposes the cart-metafield callback through a public hook", async () => {
|
|
195
|
+
sdkMock.getCartMetafields.mockReturnValue([{
|
|
196
|
+
key: "merchant_bi.apex_assignments",
|
|
197
|
+
type: "json",
|
|
198
|
+
value: "[]",
|
|
199
|
+
}]);
|
|
200
|
+
|
|
201
|
+
render(
|
|
202
|
+
<DripProvider shopId="shop_123">
|
|
203
|
+
<CartMetafieldsButton />
|
|
204
|
+
</DripProvider>,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
await waitFor(() => expect(sdkMock.init).toHaveBeenCalled());
|
|
208
|
+
fireEvent.click(screen.getByRole("button", { name: "Build cart metafields" }));
|
|
209
|
+
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
expect(sdkMock.getCartMetafields).toHaveBeenCalledWith({ namespace: "merchant_bi" });
|
|
212
|
+
});
|
|
213
|
+
});
|
|
177
214
|
});
|
package/src/react/index.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* React bindings for Drip SDK.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
|
-
* import { DripProvider, useExperiment, useVariation, useFeatureFlag, useFeatureFlagValue, useTrackGoal } from "@drip-apex/sdk/react";
|
|
5
|
+
* import { DripProvider, useExperiment, useVariation, useFeatureFlag, useFeatureFlagValue, useTrackGoal, useGetCartMetafields } from "@drip-apex/sdk/react";
|
|
6
6
|
*
|
|
7
7
|
* function App() {
|
|
8
8
|
* return (
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from "react";
|
|
41
41
|
import type { Assignment, Experiment, InitConfig, Variation } from "../types";
|
|
42
42
|
import type { RevenueEventData } from "../index";
|
|
43
|
+
import type { CartMetafieldInput } from "../shopify-cart";
|
|
43
44
|
|
|
44
45
|
// ---------------------------------------------------------------------------
|
|
45
46
|
// Context
|
|
@@ -58,6 +59,8 @@ interface DripContextValue {
|
|
|
58
59
|
track: (type: string, data?: Record<string, unknown>) => void;
|
|
59
60
|
/** Track revenue. */
|
|
60
61
|
trackRevenue: (revenue: number, data?: RevenueEventData) => void;
|
|
62
|
+
/** Build headless Shopify cart metafields from the current attribution stamp. */
|
|
63
|
+
getCartMetafields: (options?: { namespace?: string }) => Promise<CartMetafieldInput[]>;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
const DripContext = createContext<DripContextValue | null>(null);
|
|
@@ -140,9 +143,13 @@ export function DripProvider({
|
|
|
140
143
|
import("../index").then((sdk) => sdk.trackRevenue(revenue, data));
|
|
141
144
|
}, []);
|
|
142
145
|
|
|
146
|
+
const getCartMetafields = useCallback((options?: { namespace?: string }) => {
|
|
147
|
+
return import("../index").then((sdk) => sdk.getCartMetafields(options));
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
143
150
|
const value = useMemo<DripContextValue>(
|
|
144
|
-
() => ({ assignments, experiments, loading, trackGoal, track, trackRevenue }),
|
|
145
|
-
[assignments, experiments, loading, trackGoal, track, trackRevenue],
|
|
151
|
+
() => ({ assignments, experiments, loading, trackGoal, track, trackRevenue, getCartMetafields }),
|
|
152
|
+
[assignments, experiments, loading, trackGoal, track, trackRevenue, getCartMetafields],
|
|
146
153
|
);
|
|
147
154
|
|
|
148
155
|
return <DripContext.Provider value={value}>{children}</DripContext.Provider>;
|
|
@@ -232,6 +239,14 @@ export function useTrackRevenue(): (revenue: number, data?: Record<string, unkno
|
|
|
232
239
|
return trackRevenue;
|
|
233
240
|
}
|
|
234
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Returns a function that builds the current Shopify cart-metafield mirror.
|
|
244
|
+
*/
|
|
245
|
+
export function useGetCartMetafields(): DripContextValue["getCartMetafields"] {
|
|
246
|
+
const { getCartMetafields } = useDrip();
|
|
247
|
+
return getCartMetafields;
|
|
248
|
+
}
|
|
249
|
+
|
|
235
250
|
/**
|
|
236
251
|
* Get all current assignments.
|
|
237
252
|
*/
|
|
@@ -250,3 +265,4 @@ export function useLoading(): boolean {
|
|
|
250
265
|
|
|
251
266
|
// Re-export types for convenience
|
|
252
267
|
export type { Assignment, Experiment, Variation, InitConfig } from "../types";
|
|
268
|
+
export type { CartMetafieldInput } from "../shopify-cart";
|