@antelopejs/dms-marketing 0.3.2 → 0.4.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.
Files changed (287) hide show
  1. package/README.md +18 -15
  2. package/dist/config.d.ts +1 -0
  3. package/dist/config.js +4 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/data-api/funnels.d.ts +2 -0
  6. package/dist/data-api/funnels.js +8 -1
  7. package/dist/data-api/funnels.js.map +1 -1
  8. package/dist/db/models/funnels.model.d.ts +2 -0
  9. package/dist/db/models/funnels.model.js +9 -0
  10. package/dist/db/models/funnels.model.js.map +1 -1
  11. package/dist/db/models/index.d.ts +1 -0
  12. package/dist/db/models/index.js +1 -0
  13. package/dist/db/models/index.js.map +1 -1
  14. package/dist/db/models/marketing_events.model.d.ts +22 -0
  15. package/dist/db/models/marketing_events.model.js +70 -5
  16. package/dist/db/models/marketing_events.model.js.map +1 -1
  17. package/dist/db/models/marketing_preferences.model.d.ts +25 -0
  18. package/dist/db/models/marketing_preferences.model.js +29 -0
  19. package/dist/db/models/marketing_preferences.model.js.map +1 -0
  20. package/dist/db/models/marketing_sessions.model.d.ts +2 -1
  21. package/dist/db/models/marketing_sessions.model.js +11 -1
  22. package/dist/db/models/marketing_sessions.model.js.map +1 -1
  23. package/dist/db/models/page_snapshots.model.d.ts +2 -1
  24. package/dist/db/models/page_snapshots.model.js +7 -7
  25. package/dist/db/models/page_snapshots.model.js.map +1 -1
  26. package/dist/db/models/website_statistics.model.d.ts +4 -1
  27. package/dist/db/models/website_statistics.model.js +15 -1
  28. package/dist/db/models/website_statistics.model.js.map +1 -1
  29. package/dist/db/tables/index.d.ts +1 -0
  30. package/dist/db/tables/index.js +1 -0
  31. package/dist/db/tables/index.js.map +1 -1
  32. package/dist/db/tables/marketing_preferences.table.d.ts +6 -0
  33. package/dist/db/tables/marketing_preferences.table.js +31 -0
  34. package/dist/db/tables/marketing_preferences.table.js.map +1 -0
  35. package/dist/hooks/index.js +8 -0
  36. package/dist/hooks/index.js.map +1 -1
  37. package/dist/index.js +18 -2
  38. package/dist/index.js.map +1 -1
  39. package/dist/pages/acquisition/page.d.ts +9 -0
  40. package/dist/pages/acquisition/page.js +181 -0
  41. package/dist/pages/acquisition/page.js.map +1 -0
  42. package/dist/pages/all.d.ts +5 -1
  43. package/dist/pages/all.js +5 -1
  44. package/dist/pages/all.js.map +1 -1
  45. package/dist/pages/blocks.d.ts +18 -0
  46. package/dist/pages/blocks.js +56 -0
  47. package/dist/pages/blocks.js.map +1 -0
  48. package/dist/pages/funnel/page.d.ts +9 -0
  49. package/dist/pages/funnel/page.js +36 -0
  50. package/dist/pages/funnel/page.js.map +1 -0
  51. package/dist/pages/funnel-builder/page.d.ts +9 -0
  52. package/dist/pages/funnel-builder/page.js +29 -0
  53. package/dist/pages/funnel-builder/page.js.map +1 -0
  54. package/dist/pages/funnels/page.d.ts +1 -1
  55. package/dist/pages/funnels/page.js +200 -3
  56. package/dist/pages/funnels/page.js.map +1 -1
  57. package/dist/pages/index.d.ts +1 -0
  58. package/dist/pages/index.js +1 -0
  59. package/dist/pages/index.js.map +1 -1
  60. package/dist/pages/{campaigns → install}/page.d.ts +3 -3
  61. package/dist/pages/{campaigns → install}/page.js +14 -12
  62. package/dist/pages/install/page.js.map +1 -0
  63. package/dist/pages/module.d.ts +3 -0
  64. package/dist/pages/module.js +30 -3
  65. package/dist/pages/module.js.map +1 -1
  66. package/dist/pages/overview/page.d.ts +1 -1
  67. package/dist/pages/overview/page.js +122 -3
  68. package/dist/pages/overview/page.js.map +1 -1
  69. package/dist/pages/pages/page.d.ts +1 -1
  70. package/dist/pages/pages/page.js +9 -4
  71. package/dist/pages/pages/page.js.map +1 -1
  72. package/dist/pages/quick-actions.d.ts +1 -0
  73. package/dist/pages/quick-actions.js +25 -0
  74. package/dist/pages/quick-actions.js.map +1 -0
  75. package/dist/pages/settings/form.d.ts +2 -1
  76. package/dist/pages/settings/form.js +62 -20
  77. package/dist/pages/settings/form.js.map +1 -1
  78. package/dist/pages/settings/page.d.ts +4 -1
  79. package/dist/pages/settings/page.js +50 -4
  80. package/dist/pages/settings/page.js.map +1 -1
  81. package/dist/pages/websites/page.d.ts +11 -0
  82. package/dist/pages/websites/page.js +62 -0
  83. package/dist/pages/websites/page.js.map +1 -0
  84. package/dist/routes/blocks.d.ts +50 -0
  85. package/dist/routes/blocks.js +269 -0
  86. package/dist/routes/blocks.js.map +1 -0
  87. package/dist/routes/collect.d.ts +1 -2
  88. package/dist/routes/collect.js +19 -12
  89. package/dist/routes/collect.js.map +1 -1
  90. package/dist/routes/context.d.ts +8 -0
  91. package/dist/routes/context.js +54 -0
  92. package/dist/routes/context.js.map +1 -0
  93. package/dist/routes/experiments.js +2 -1
  94. package/dist/routes/experiments.js.map +1 -1
  95. package/dist/routes/funnels.d.ts +21 -3
  96. package/dist/routes/funnels.js +150 -7
  97. package/dist/routes/funnels.js.map +1 -1
  98. package/dist/routes/index.d.ts +2 -0
  99. package/dist/routes/index.js +2 -0
  100. package/dist/routes/index.js.map +1 -1
  101. package/dist/routes/settings.d.ts +6 -1
  102. package/dist/routes/settings.js +65 -4
  103. package/dist/routes/settings.js.map +1 -1
  104. package/dist/routes/snapshot.d.ts +1 -1
  105. package/dist/routes/snapshot.js +24 -25
  106. package/dist/routes/snapshot.js.map +1 -1
  107. package/dist/routes/stats.d.ts +14 -23
  108. package/dist/routes/stats.js +73 -97
  109. package/dist/routes/stats.js.map +1 -1
  110. package/dist/routes/tracker.js +6 -4
  111. package/dist/routes/tracker.js.map +1 -1
  112. package/dist/routes/websites.d.ts +4 -0
  113. package/dist/routes/websites.js +111 -12
  114. package/dist/routes/websites.js.map +1 -1
  115. package/dist/services/acquisition-report.d.ts +62 -0
  116. package/dist/services/acquisition-report.js +181 -0
  117. package/dist/services/acquisition-report.js.map +1 -0
  118. package/dist/services/acquisition.d.ts +9 -3
  119. package/dist/services/acquisition.js +35 -5
  120. package/dist/services/acquisition.js.map +1 -1
  121. package/dist/services/catalog.d.ts +3 -0
  122. package/dist/services/catalog.js +17 -0
  123. package/dist/services/catalog.js.map +1 -0
  124. package/dist/services/collect.d.ts +1 -2
  125. package/dist/services/collect.js +49 -24
  126. package/dist/services/collect.js.map +1 -1
  127. package/dist/services/composed.d.ts +9 -0
  128. package/dist/services/composed.js +62 -0
  129. package/dist/services/composed.js.map +1 -0
  130. package/dist/services/context.d.ts +20 -0
  131. package/dist/services/context.js +76 -0
  132. package/dist/services/context.js.map +1 -0
  133. package/dist/services/dashboard.d.ts +74 -0
  134. package/dist/services/dashboard.js +343 -0
  135. package/dist/services/dashboard.js.map +1 -0
  136. package/dist/services/experiments/results.d.ts +12 -1
  137. package/dist/services/experiments/results.js +19 -2
  138. package/dist/services/experiments/results.js.map +1 -1
  139. package/dist/services/experiments/stats.d.ts +7 -1
  140. package/dist/services/experiments/stats.js +43 -5
  141. package/dist/services/experiments/stats.js.map +1 -1
  142. package/dist/services/funnel-report.d.ts +64 -0
  143. package/dist/services/funnel-report.js +280 -0
  144. package/dist/services/funnel-report.js.map +1 -0
  145. package/dist/services/funnels/compute.d.ts +2 -0
  146. package/dist/services/funnels/compute.js +25 -13
  147. package/dist/services/funnels/compute.js.map +1 -1
  148. package/dist/services/funnels/results.js +4 -1
  149. package/dist/services/funnels/results.js.map +1 -1
  150. package/dist/services/funnels/step-path.d.ts +4 -0
  151. package/dist/services/funnels/step-path.js +47 -0
  152. package/dist/services/funnels/step-path.js.map +1 -0
  153. package/dist/services/funnels-table.d.ts +31 -0
  154. package/dist/services/funnels-table.js +230 -0
  155. package/dist/services/funnels-table.js.map +1 -0
  156. package/dist/services/heatmap.d.ts +15 -3
  157. package/dist/services/heatmap.js +54 -11
  158. package/dist/services/heatmap.js.map +1 -1
  159. package/dist/services/hourly-traffic.d.ts +4 -0
  160. package/dist/services/hourly-traffic.js +35 -0
  161. package/dist/services/hourly-traffic.js.map +1 -0
  162. package/dist/services/origin-guard.d.ts +9 -1
  163. package/dist/services/origin-guard.js +46 -1
  164. package/dist/services/origin-guard.js.map +1 -1
  165. package/dist/services/period.d.ts +26 -1
  166. package/dist/services/period.js +99 -9
  167. package/dist/services/period.js.map +1 -1
  168. package/dist/services/prune.d.ts +6 -0
  169. package/dist/services/prune.js +35 -0
  170. package/dist/services/prune.js.map +1 -1
  171. package/dist/services/request-body.d.ts +1 -2
  172. package/dist/services/request-body.js +10 -36
  173. package/dist/services/request-body.js.map +1 -1
  174. package/dist/services/request-meta.d.ts +0 -1
  175. package/dist/services/request-meta.js +0 -11
  176. package/dist/services/request-meta.js.map +1 -1
  177. package/dist/services/rollups.d.ts +25 -0
  178. package/dist/services/rollups.js +102 -0
  179. package/dist/services/rollups.js.map +1 -0
  180. package/dist/services/sessionize.d.ts +5 -1
  181. package/dist/services/sessionize.js +52 -10
  182. package/dist/services/sessionize.js.map +1 -1
  183. package/dist/services/settings.d.ts +41 -2
  184. package/dist/services/settings.js +219 -6
  185. package/dist/services/settings.js.map +1 -1
  186. package/dist/services/snapshots.d.ts +3 -2
  187. package/dist/services/snapshots.js +110 -9
  188. package/dist/services/snapshots.js.map +1 -1
  189. package/dist/services/tenant-website.js +1 -0
  190. package/dist/services/tenant-website.js.map +1 -1
  191. package/dist/services/url.js +12 -4
  192. package/dist/services/url.js.map +1 -1
  193. package/dist/services/website-hosts.d.ts +15 -0
  194. package/dist/services/website-hosts.js +67 -0
  195. package/dist/services/website-hosts.js.map +1 -0
  196. package/dist/services/website-report.d.ts +40 -0
  197. package/dist/services/website-report.js +84 -0
  198. package/dist/services/website-report.js.map +1 -0
  199. package/dist/types/constants.d.ts +14 -2
  200. package/dist/types/constants.js +17 -6
  201. package/dist/types/constants.js.map +1 -1
  202. package/dist/types/events.d.ts +221 -68
  203. package/dist/types/events.js +60 -11
  204. package/dist/types/events.js.map +1 -1
  205. package/dist/types/snapshots.d.ts +6 -0
  206. package/dist/types/snapshots.js +2 -0
  207. package/dist/types/snapshots.js.map +1 -1
  208. package/dist/types/statistics.d.ts +2 -1
  209. package/dist/types/statistics.js +9 -2
  210. package/dist/types/statistics.js.map +1 -1
  211. package/dist/utils/experiment-type.js +1 -1
  212. package/dist/utils/experiment-type.js.map +1 -1
  213. package/dist/utils/funnel-steps-type.d.ts +21 -3
  214. package/dist/utils/funnel-steps-type.js +7 -2
  215. package/dist/utils/funnel-steps-type.js.map +1 -1
  216. package/frontend-vue/app/components/ChannelsCard.vue +265 -0
  217. package/frontend-vue/app/components/Context.vue +479 -0
  218. package/frontend-vue/app/components/ExperimentInput.vue +3 -1
  219. package/frontend-vue/app/components/ExperimentReport.vue +736 -0
  220. package/frontend-vue/app/components/FirstRun.vue +60 -0
  221. package/frontend-vue/app/components/FunnelBuilder.vue +1595 -0
  222. package/frontend-vue/app/components/FunnelFigure.vue +640 -0
  223. package/frontend-vue/app/components/FunnelReport.vue +532 -0
  224. package/frontend-vue/app/components/FunnelStepsInput.vue +3 -1
  225. package/frontend-vue/app/components/FunnelTemplates.vue +161 -0
  226. package/frontend-vue/app/components/HeatmapCanvas.vue +11 -4
  227. package/frontend-vue/app/components/InstallGuide.vue +753 -0
  228. package/frontend-vue/app/components/PagePreview.vue +889 -416
  229. package/frontend-vue/app/components/PagesExplorer.vue +552 -0
  230. package/frontend-vue/app/components/ScrollDepthOverlay.vue +48 -20
  231. package/frontend-vue/app/components/TopListTabs.vue +317 -0
  232. package/frontend-vue/app/components/TrackerSnippet.vue +50 -16
  233. package/frontend-vue/app/components/VariationsInput.vue +15 -5
  234. package/frontend-vue/app/components/WebsitesGrid.vue +799 -0
  235. package/frontend-vue/app/composables/useContextLink.ts +16 -0
  236. package/frontend-vue/app/composables/useExperiment.ts +1 -2
  237. package/frontend-vue/app/composables/useFunnelSteps.ts +1 -2
  238. package/frontend-vue/app/composables/useLatestRequest.ts +2 -4
  239. package/frontend-vue/app/composables/useMarketingApi.ts +298 -133
  240. package/frontend-vue/app/composables/useMarketingContext.ts +55 -0
  241. package/frontend-vue/app/composables/useMarketingRoutes.ts +80 -27
  242. package/frontend-vue/app/composables/useSnapshotFrame.ts +26 -4
  243. package/frontend-vue/app/constants.ts +22 -0
  244. package/frontend-vue/app/plugins/funnel-steps-data-type.client.ts +2 -1
  245. package/frontend-vue/app/utils/channels.ts +37 -0
  246. package/frontend-vue/app/utils/format.ts +132 -0
  247. package/frontend-vue/app/utils/funnel-draft.ts +264 -0
  248. package/frontend-vue/app/utils/heatmap.ts +82 -0
  249. package/frontend-vue/app/utils/heatmapPalette.ts +10 -0
  250. package/frontend-vue/app/utils/host-conflict.ts +48 -0
  251. package/frontend-vue/app/utils/sample-size.ts +17 -0
  252. package/frontend-vue/app/utils/snapshotViewportUnits.ts +15 -5
  253. package/frontend-vue/app/utils/snippet.ts +53 -0
  254. package/frontend-vue/app/utils/step-path.ts +32 -0
  255. package/frontend-vue/dms.frontend.ts +4 -3
  256. package/frontend-vue/i18n/locales/marketing-en-GB.json +1083 -316
  257. package/frontend-vue/i18n/locales/marketing-fr-FR.json +1087 -320
  258. package/frontend-vue/package.json +3 -0
  259. package/frontend-vue/tests/drafts.test.ts +3 -3
  260. package/frontend-vue/tests/format.test.ts +73 -0
  261. package/frontend-vue/tests/funnel-draft.test.ts +194 -0
  262. package/frontend-vue/tests/heatmap.test.ts +32 -0
  263. package/frontend-vue/tests/i18n.test.ts +87 -4
  264. package/frontend-vue/tests/latest-request.test.ts +6 -3
  265. package/frontend-vue/tests/locale-typography.test.ts +115 -0
  266. package/frontend-vue/tests/registration.test.ts +21 -11
  267. package/frontend-vue/tests/routes.test.ts +67 -7
  268. package/frontend-vue/tests/server-parity.test.ts +77 -0
  269. package/frontend-vue/tests/stubs/frontend-module.ts +18 -3
  270. package/package.json +9 -7
  271. package/static/snapshot.js +46 -16
  272. package/static/tracker.js +139 -48
  273. package/dist/pages/campaigns/page.js.map +0 -1
  274. package/frontend-vue/app/components/CampaignsView.vue +0 -330
  275. package/frontend-vue/app/components/FunnelDetail.vue +0 -1026
  276. package/frontend-vue/app/components/FunnelsView.vue +0 -493
  277. package/frontend-vue/app/components/NoWebsiteState.vue +0 -18
  278. package/frontend-vue/app/components/OverviewView.vue +0 -493
  279. package/frontend-vue/app/components/PagesView.vue +0 -391
  280. package/frontend-vue/app/components/TextKpiCard.vue +0 -28
  281. package/frontend-vue/app/components/TopListTabsCard.vue +0 -162
  282. package/frontend-vue/app/composables/useChildId.ts +0 -13
  283. package/frontend-vue/app/composables/useMarketingPeriod.ts +0 -65
  284. package/frontend-vue/app/composables/useMarketingWebsiteForm.ts +0 -69
  285. package/frontend-vue/app/composables/useMarketingWebsites.ts +0 -80
  286. package/frontend-vue/app/utils/dmsComponent.ts +0 -18
  287. package/frontend-vue/tests/period.test.ts +0 -35
@@ -0,0 +1,16 @@
1
+ import { useDmsRoute } from '#dms/frontend-module'
2
+ import { PERIOD_SCOPE } from '../constants'
3
+ import { withWebsite } from './useMarketingRoutes'
4
+
5
+ /**
6
+ * A link out of the current page, in its context: the website the context
7
+ * bar keeps in the page URL ({@link withWebsite}) and the period selector's
8
+ * keys (the DMS `withPeriodQuery`), what the link names itself winning.
9
+ */
10
+ export function useContextLink(): (link: string) => string {
11
+ const route = useDmsRoute()
12
+ return (link) => {
13
+ const query = route.query as Record<string, unknown>
14
+ return withPeriodQuery(withWebsite(link, query), query, PERIOD_SCOPE)
15
+ }
16
+ }
@@ -31,8 +31,7 @@ function parseJsonValue(raw: unknown): unknown {
31
31
  }
32
32
  try {
33
33
  return raw ? JSON.parse(raw) : null
34
- }
35
- catch {
34
+ } catch {
36
35
  return null
37
36
  }
38
37
  }
@@ -16,8 +16,7 @@ export function parseFunnelStepsValue(raw: unknown): FunnelStepDraft[] {
16
16
  if (typeof raw === 'string') {
17
17
  try {
18
18
  parsed = raw ? JSON.parse(raw) : []
19
- }
20
- catch {
19
+ } catch {
21
20
  parsed = []
22
21
  }
23
22
  }
@@ -67,16 +67,14 @@ export function useLatestRequest<T>(
67
67
  return
68
68
  }
69
69
  data.value = result
70
- }
71
- catch (cause) {
70
+ } catch (cause) {
72
71
  if (request !== latest) {
73
72
  return
74
73
  }
75
74
  failed.value = true
76
75
  error.value = cause
77
76
  data.value = null
78
- }
79
- finally {
77
+ } finally {
80
78
  // A superseded run owns none of the shared state — not even the flag
81
79
  // that says the newest one is still in flight.
82
80
  if (request === latest) {
@@ -6,8 +6,71 @@
6
6
  * convention as dms-api's useApiIntrospection).
7
7
  */
8
8
 
9
+ import type {
10
+ ContextWebsite,
11
+ MarketingContextPayload,
12
+ } from './useMarketingContext'
13
+
9
14
  // --- Shapes mirrored from the backend ---------------------------------------
10
15
 
16
+ export type {
17
+ ContextWebsite,
18
+ MarketingContextPayload,
19
+ } from './useMarketingContext'
20
+
21
+ export interface MarketingRejectedSource {
22
+ hostname: string
23
+ count: number
24
+ lastAt: number
25
+ }
26
+
27
+ /** One card of the Websites page (GET /api/marketing/websites/summary). */
28
+ export interface MarketingWebsiteCard extends ContextWebsite {
29
+ extraDomains: string[]
30
+ snapshotsEnabled: boolean
31
+ snapshotMaskText: boolean
32
+ createdAt: number
33
+ /** Over the last 30 days, like `pages` (distinct paths). */
34
+ sessions: number
35
+ pages: number
36
+ funnels: number
37
+ snapshots: number
38
+ sampleRate: number
39
+ rejected: MarketingRejectedSource[]
40
+ }
41
+
42
+ /** What deleting a website discards (GET /websites/:id/deletion). */
43
+ export interface MarketingWebsiteDeletionImpact {
44
+ /** Every stored day, not a window. */
45
+ sessions: number
46
+ pageviews: number
47
+ /** Oldest stored day; null when nothing was recorded. */
48
+ since: number | null
49
+ funnels: number
50
+ snapshots: number
51
+ }
52
+
53
+ export interface MarketingConnectionStatus {
54
+ website: ContextWebsite
55
+ createdAt: number
56
+ /** Server time of the check: the `since` of a later "check again". */
57
+ checkedAt: number
58
+ /** Newest pageview since `since` (the site's creation by default). */
59
+ visit: MarketingConnectionVisit | null
60
+ lastActivityAt: number | null
61
+ rejected: MarketingRejectedSource[]
62
+ }
63
+
64
+ export interface MarketingFunnelSuggestion {
65
+ value: string
66
+ count: number
67
+ }
68
+
69
+ export interface MarketingFunnelSuggestions {
70
+ pages: MarketingFunnelSuggestion[]
71
+ events: MarketingFunnelSuggestion[]
72
+ }
73
+
11
74
  export interface MarketingWebsite {
12
75
  _id: string
13
76
  name: string
@@ -16,13 +79,38 @@ export interface MarketingWebsite {
16
79
  /** Opt-in: page snapshots store page content. */
17
80
  snapshotsEnabled: boolean
18
81
  snapshotMaskText?: boolean
82
+ extraDomains?: string[]
19
83
  }
20
84
 
21
85
  /** Editable website fields; only the keys present are touched. */
22
86
  export type MarketingWebsitePatch = Partial<
23
- Pick<MarketingWebsite, 'name' | 'domain' | 'snapshotsEnabled' | 'snapshotMaskText'>
87
+ Pick<
88
+ MarketingWebsite,
89
+ | 'name'
90
+ | 'domain'
91
+ | 'extraDomains'
92
+ | 'trackingEnabled'
93
+ | 'snapshotsEnabled'
94
+ | 'snapshotMaskText'
95
+ >
24
96
  >
25
97
 
98
+ export interface MarketingWebsiteInput {
99
+ name: string
100
+ domain: string
101
+ extraDomains?: string[]
102
+ }
103
+
104
+ export interface MarketingConnectionVisit {
105
+ at: number
106
+ /** Path only. */
107
+ url: string
108
+ /** The host the beacons came from, when the backend saw it. */
109
+ host?: string
110
+ browser?: string
111
+ deviceType?: string
112
+ }
113
+
26
114
  export type MarketingSnapshotLayout = 'desktop' | 'tablet' | 'phone'
27
115
 
28
116
  /**
@@ -35,8 +123,8 @@ export interface MarketingPageSnapshot {
35
123
  exact: boolean
36
124
  capturedAt: number
37
125
  origin: string
38
- viewport: { width: number, height: number }
39
- document: { width: number, height: number }
126
+ viewport: { width: number; height: number }
127
+ document: { width: number; height: number }
40
128
  colorScheme?: 'light' | 'dark'
41
129
  bytes: number
42
130
  html: string | null
@@ -51,104 +139,40 @@ export interface MarketingPageSnapshotResponse {
51
139
  export interface MarketingPageEntry {
52
140
  path: string
53
141
  pageviews: number
142
+ clicks: number
143
+ /** Page loads sampled for clicks and scrolls, as counted. */
144
+ sampled: number
145
+ }
146
+
147
+ /**
148
+ * The period a read covers: the DMS period scope's state (`usePeriodScope`),
149
+ * appended to the URL as its blocks do. Null reads the routes' default.
150
+ */
151
+ export type MarketingPeriod = Parameters<typeof appendPeriodToUrl>[1]
152
+
153
+ export type MarketingPagesSort = 'views' | 'clicks'
154
+
155
+ /** Clicks and scrolls (`events`) and captures of a page or a whole site. */
156
+ export interface MarketingHeatmapData {
157
+ events: number
158
+ snapshots: number
54
159
  }
55
160
 
56
161
  export interface MarketingPages {
57
162
  pages: MarketingPageEntry[]
163
+ /** Paths matching the search, listed or not. */
164
+ total: number
58
165
  /** The inventory hit its cap; narrow the search to see the rest. */
59
166
  truncated: boolean
60
- /** Share of visits capturing clicks — what makes a heatmap sparse. */
61
- heatmapSampleRate: number
167
+ /**
168
+ * Measured share of page loads sampled for clicks — what makes a heatmap
169
+ * sparse. Null while the site has too few pageviews to tell.
170
+ */
171
+ sampleRate: number | null
62
172
  /** Global collection switch; off means nothing new is being recorded. */
63
173
  trackerEnabled: boolean
64
174
  }
65
175
 
66
- /** The five-way acquisition grouping the backend derives from referrer
67
- * domain + utm_medium at rollup time. */
68
- export type MarketingChannel = 'direct' | 'organic' | 'social' | 'referral' | 'paid'
69
-
70
- export interface MarketingDayStatistics {
71
- day: number
72
- pageviews: number
73
- sessions: number
74
- newVisitors: number
75
- customEvents: number
76
- /** Counted as transitions: a day un-bouncing sessions opened the day
77
- * before can go negative. Clamp before showing a rate. */
78
- bouncedSessions: number
79
- sessionDurationMs: number
80
- topPages: Record<string, number>
81
- topEntryPages?: Record<string, number>
82
- topExitPages?: Record<string, number>
83
- topReferrers: Record<string, number>
84
- topUtmSources: Record<string, number>
85
- topUtmMediums?: Record<string, number>
86
- topUtmCampaigns?: Record<string, number>
87
- topUtmTerms?: Record<string, number>
88
- topUtmContents?: Record<string, number>
89
- topChannels?: Record<string, number>
90
- topDevices?: Record<string, number>
91
- topBrowsers?: Record<string, number>
92
- topCountries?: Record<string, number>
93
- topEvents?: Record<string, number>
94
- topLanguages?: Record<string, number>
95
- }
96
-
97
- export interface MarketingOverviewTotals {
98
- pageviews: number
99
- sessions: number
100
- newVisitors: number
101
- customEvents: number
102
- bouncedSessions: number
103
- sessionDurationMs: number
104
- }
105
-
106
- export interface MarketingOverview {
107
- totals: MarketingOverviewTotals
108
- days: MarketingDayStatistics[]
109
- topPages: Record<string, number>
110
- topEntryPages: Record<string, number>
111
- topExitPages: Record<string, number>
112
- topReferrers: Record<string, number>
113
- topUtmSources: Record<string, number>
114
- topUtmMediums: Record<string, number>
115
- topUtmCampaigns: Record<string, number>
116
- topUtmTerms: Record<string, number>
117
- topUtmContents: Record<string, number>
118
- topChannels: Record<string, number>
119
- topDevices: Record<string, number>
120
- topBrowsers: Record<string, number>
121
- /** ISO 3166-1 alpha-2 keys; only populated when the deployment configures
122
- * a GeoIP database — `geoipEnabled` says whether the card should show. */
123
- topCountries: Record<string, number>
124
- /** Custom event names by trigger count — which CTA gets clicked. */
125
- topEvents: Record<string, number>
126
- /** BCP 47 tags as the visitors' browsers report them (`fr`, `fr-FR`). */
127
- topLanguages: Record<string, number>
128
- geoipEnabled: boolean
129
- }
130
-
131
- /** One (source, medium, campaign) triple observed on new sessions; a blank
132
- * slot is a UTM parameter the landing URL did not carry. */
133
- export interface MarketingCampaignEntry {
134
- source?: string
135
- medium?: string
136
- campaign?: string
137
- sessions: number
138
- }
139
-
140
- export interface MarketingCampaigns {
141
- /** Exhaustive five-key split, not a top list. */
142
- channels: Partial<Record<MarketingChannel, number>>
143
- campaigns: MarketingCampaignEntry[]
144
- /** The table hit its cap; narrow the search to see the rest. */
145
- truncated: boolean
146
- terms: Record<string, number>
147
- contents: Record<string, number>
148
- /** Referrer domains across all channels (search and social included). */
149
- referrers?: Record<string, number>
150
- }
151
-
152
176
  export interface MarketingFunnelStep {
153
177
  kind: 'url' | 'custom'
154
178
  value: string
@@ -195,7 +219,7 @@ export interface MarketingFunnel {
195
219
  * list's foreign join — the write path wants the bare id again. */
196
220
  export interface MarketingFunnelListItem {
197
221
  _id: string
198
- websiteId: string | { _id: string, name?: string, domain?: string }
222
+ websiteId: string | { _id: string; name?: string; domain?: string }
199
223
  name: string
200
224
  steps: MarketingFunnelStep[] | string
201
225
  conversionWindowHours: number
@@ -218,10 +242,15 @@ export interface MarketingFunnelStepResult {
218
242
  step: MarketingFunnelStep
219
243
  sessions: number
220
244
  conversionRate: number
245
+ /** Events of the window matching the step, in order or not: 0 means the
246
+ * site never sent it. */
247
+ matches: number
221
248
  }
222
249
 
223
250
  export interface MarketingFunnelComputation {
224
251
  totalSessions: number
252
+ /** Events read: 0 means the site sent nothing over the window. */
253
+ events: number
225
254
  steps: MarketingFunnelStepResult[]
226
255
  }
227
256
 
@@ -233,8 +262,13 @@ export interface MarketingVariationResult {
233
262
  exposedSessions: number
234
263
  computation: MarketingFunnelComputation
235
264
  conversionRate: number
265
+ /** Exposed sessions that reached the goal. */
266
+ conversions: number
236
267
  /** Relative to the control's conversion; null on the control itself. */
237
268
  uplift: number | null
269
+ /** Bounds of `uplift` at the test's level; null wherever pValue is. */
270
+ upliftLow: number | null
271
+ upliftHigh: number | null
238
272
  /** Two-sided p-value vs the control; null on the control, when the sample
239
273
  * is too small, or when the window is truncated. */
240
274
  pValue: number | null
@@ -253,8 +287,44 @@ export interface MarketingExperimentSrm {
253
287
  export interface MarketingExperimentResults {
254
288
  /** Key of the control arm — the experiment's first variation. */
255
289
  control: string
290
+ /** Level each challenger is tested at: .05, split past two arms. */
291
+ alpha: number
256
292
  variations: MarketingVariationResult[]
257
293
  srm: MarketingExperimentSrm
294
+ /** A verdict waits for a full week and a conversions floor per arm. */
295
+ decision: {
296
+ days: number
297
+ minDays: number
298
+ minConversions: number
299
+ ready: boolean
300
+ }
301
+ }
302
+
303
+ /** What raw events cover of the period and its comparison. */
304
+ export interface MarketingFunnelCoverage {
305
+ /** First instant covered (epoch ms), null without any raw event. */
306
+ since: number | null
307
+ /** The period starts before it. */
308
+ period: boolean
309
+ /** The comparison starts before it: `previous` is left out. */
310
+ previous: boolean
311
+ }
312
+
313
+ /** A funnel the site's own journeys suggest, for the first-run state. */
314
+ export interface MarketingFunnelTemplate {
315
+ steps: MarketingFunnelStep[]
316
+ /** Sessions of the period that matched step 1. */
317
+ entering: number
318
+ }
319
+
320
+ /** What a started test says, read once by the server for the report, its
321
+ * stop dialog and the list: `early` is significant but short of the decision
322
+ * rule, `withheld` voided by a warning (truncated read, SRM, nothing scored). */
323
+ export interface MarketingExperimentVerdict {
324
+ kind: 'winner' | 'loser' | 'flat' | 'early' | 'withheld'
325
+ /** Index of the arm the verdict names, the control being 0; null when no
326
+ * challenger could be scored. */
327
+ leader: number | null
258
328
  }
259
329
 
260
330
  export interface MarketingFunnelResults {
@@ -265,10 +335,21 @@ export interface MarketingFunnelResults {
265
335
  experiment?: MarketingExperimentResults
266
336
  /** The raw-event window hit its cap; verdicts are withheld. */
267
337
  truncated: boolean
338
+ /** Null on a funnel without a started experiment. */
339
+ verdict?: MarketingExperimentVerdict | null
268
340
  /** Epoch ms bounds the numbers cover: the selected period on a plain
269
341
  * funnel, the experiment's own run once one is started — a running split
270
342
  * ends at "now", a stopped one at its stop. */
271
- window: { since: number, until: number }
343
+ window: { since: number; until: number }
344
+ /** The same funnel over the comparison window; null on a split, without
345
+ * one, or when raw events do not cover it. */
346
+ previous?: MarketingFunnelComputation | null
347
+ coverage?: MarketingFunnelCoverage
348
+ }
349
+
350
+ export interface MarketingFunnelPreview {
351
+ computation: MarketingFunnelComputation
352
+ truncated: boolean
272
353
  }
273
354
 
274
355
  /**
@@ -300,10 +381,9 @@ export interface MarketingHeatmap {
300
381
  }
301
382
 
302
383
  /**
303
- * Scroll-depth distribution of one path. `views` counts only sampled views
304
- * that scrolled at all — a view that never scrolls (every page that fits its
305
- * screen included) emits no depth — so shares read "of the views that
306
- * scrolled", never "of all views".
384
+ * Scroll-depth distribution of one path. Every sampled view reports one
385
+ * depth, scrolled or not, so `views` is the number of sampled views (events
386
+ * stored before the tracker did so only cover views that scrolled).
307
387
  */
308
388
  export interface MarketingScrollDepth {
309
389
  views: number
@@ -314,6 +394,10 @@ export interface MarketingScrollDepth {
314
394
 
315
395
  // --- Facade -----------------------------------------------------------------
316
396
 
397
+ function periodUrl(url: string, period: MarketingPeriod): string {
398
+ return appendPeriodToUrl(url, period) ?? url
399
+ }
400
+
317
401
  /** Far above any real tenant's funnel count — the data-api list answers 10
318
402
  * rows unless told otherwise, and caps at 100 whatever is asked. */
319
403
  const FUNNEL_LIST_LIMIT = 100
@@ -322,12 +406,76 @@ export function useMarketingApi() {
322
406
  const { $authFetch } = useAuthFetch()
323
407
 
324
408
  return {
325
- listWebsites: () => $authFetch<MarketingWebsite[]>('/api/marketing/websites'),
409
+ listWebsites: () =>
410
+ $authFetch<MarketingWebsite[]>('/api/marketing/websites'),
326
411
 
327
- createWebsite: (name: string, domain: string) =>
412
+ getContext: () =>
413
+ $authFetch<MarketingContextPayload>('/api/marketing/context'),
414
+
415
+ createWebsite: (input: MarketingWebsiteInput) =>
328
416
  $authFetch<MarketingWebsite>('/api/marketing/websites', {
329
417
  method: 'POST',
330
- body: { name, domain },
418
+ body: input,
419
+ }),
420
+
421
+ selectContextWebsite: (website: string) =>
422
+ $authFetch<MarketingContextPayload>('/api/marketing/context', {
423
+ method: 'PUT',
424
+ body: { website },
425
+ }),
426
+
427
+ listWebsiteCards: () =>
428
+ $authFetch<MarketingWebsiteCard[]>('/api/marketing/websites/summary'),
429
+
430
+ // `since` (epoch ms, server clock): only a pageview from then on counts.
431
+ getConnection: (website: string, since?: number) =>
432
+ $authFetch<MarketingConnectionStatus>(
433
+ `/api/marketing/websites/${encodeURIComponent(website)}/connection`,
434
+ { query: { since } },
435
+ ),
436
+
437
+ getWebsiteDeletionImpact: (website: string) =>
438
+ $authFetch<MarketingWebsiteDeletionImpact>(
439
+ `/api/marketing/websites/${encodeURIComponent(website)}/deletion`,
440
+ ),
441
+
442
+ allowHost: (website: string, hostname: string) =>
443
+ $authFetch<MarketingWebsite>(
444
+ `/api/marketing/websites/${encodeURIComponent(website)}/allow-host`,
445
+ { method: 'POST', body: { hostname } },
446
+ ),
447
+
448
+ deleteWebsite: (website: string) =>
449
+ $authFetch<{ deleted: string }>(
450
+ `/api/marketing/websites/${encodeURIComponent(website)}`,
451
+ { method: 'DELETE' },
452
+ ),
453
+
454
+ getFunnelTemplates: (website: string, period: MarketingPeriod) =>
455
+ $authFetch<MarketingFunnelTemplate[]>(
456
+ periodUrl('/api/marketing/funnels/templates', period),
457
+ {
458
+ query: { website },
459
+ },
460
+ ),
461
+
462
+ getFunnelSuggestions: (website: string) =>
463
+ $authFetch<MarketingFunnelSuggestions>(
464
+ '/api/marketing/funnels/suggestions',
465
+ {
466
+ query: { website },
467
+ },
468
+ ),
469
+
470
+ previewFunnel: (
471
+ website: string,
472
+ steps: MarketingFunnelStep[],
473
+ conversionWindowHours: number,
474
+ ) =>
475
+ $authFetch<MarketingFunnelPreview>('/api/marketing/funnels/preview', {
476
+ method: 'POST',
477
+ query: { website },
478
+ body: { steps, conversionWindowHours },
331
479
  }),
332
480
 
333
481
  updateWebsite: (id: string, patch: MarketingWebsitePatch) =>
@@ -337,55 +485,66 @@ export function useMarketingApi() {
337
485
  ),
338
486
 
339
487
  // Query strings via ofetch's `query` option — encoding is its job.
340
- listPages: (website: string, period = '7d', search = '') =>
341
- $authFetch<MarketingPages>('/api/marketing/stats/pages', {
342
- query: { website, period, search },
343
- }),
344
-
345
- getOverview: (website: string, period = '7d') =>
346
- $authFetch<MarketingOverview>('/api/marketing/stats/overview', {
347
- query: { website, period },
348
- }),
349
-
350
- getCampaigns: (website: string, period = '7d', search = '') =>
351
- $authFetch<MarketingCampaigns>('/api/marketing/stats/campaigns', {
352
- query: { website, period, search },
353
- }),
488
+ listPages: (
489
+ website: string,
490
+ period: MarketingPeriod,
491
+ search = '',
492
+ sort: MarketingPagesSort = 'views',
493
+ ) =>
494
+ $authFetch<MarketingPages>(
495
+ periodUrl('/api/marketing/stats/pages', period),
496
+ {
497
+ query: { website, search, sort },
498
+ },
499
+ ),
354
500
 
355
- getHeatmap: (website: string, path: string, period = '7d') =>
356
- $authFetch<MarketingHeatmap>('/api/marketing/stats/heatmap', {
357
- query: { website, path, period },
358
- }),
501
+ getHeatmap: (website: string, path: string, period: MarketingPeriod) =>
502
+ $authFetch<MarketingHeatmap>(
503
+ periodUrl('/api/marketing/stats/heatmap', period),
504
+ {
505
+ query: { website, path },
506
+ },
507
+ ),
359
508
 
360
- getScrollDepth: (website: string, path: string, period = '7d') =>
361
- $authFetch<MarketingScrollDepth>('/api/marketing/stats/scroll', {
362
- query: { website, path, period },
363
- }),
509
+ getScrollDepth: (website: string, path: string, period: MarketingPeriod) =>
510
+ $authFetch<MarketingScrollDepth>(
511
+ periodUrl('/api/marketing/stats/scroll', period),
512
+ {
513
+ query: { website, path },
514
+ },
515
+ ),
364
516
 
365
- getSnapshot: (website: string, path: string, layout: MarketingSnapshotLayout) =>
366
- $authFetch<MarketingPageSnapshotResponse>('/api/marketing/stats/snapshot', {
367
- query: { website, path, layout },
368
- }),
517
+ getSnapshot: (
518
+ website: string,
519
+ path: string,
520
+ layout: MarketingSnapshotLayout,
521
+ ) =>
522
+ $authFetch<MarketingPageSnapshotResponse>(
523
+ '/api/marketing/stats/snapshot',
524
+ {
525
+ query: { website, path, layout },
526
+ },
527
+ ),
369
528
 
370
529
  // Whole click and scroll history, every period: without `path` the whole
371
530
  // site's.
372
531
  resetHeatmap: (website: string, path?: string) =>
373
- $authFetch<{ deleted: number }>('/api/marketing/stats/heatmap', {
532
+ $authFetch<MarketingHeatmapData>('/api/marketing/stats/heatmap', {
374
533
  method: 'DELETE',
375
534
  query: { website, path },
376
535
  }),
377
536
 
378
- getFunnelResults: (id: string, period = '7d') =>
379
- $authFetch<MarketingFunnelResults>(
380
- `/api/marketing/funnels/${encodeURIComponent(id)}/results`,
381
- { query: { period } },
382
- ),
537
+ /** What `resetHeatmap` would delete with the same arguments. */
538
+ countHeatmap: (website: string, path?: string) =>
539
+ $authFetch<MarketingHeatmapData>('/api/marketing/stats/heatmap/count', {
540
+ query: { website, path },
541
+ }),
383
542
 
384
543
  // Definitions CRUD over the funnels data-api routes. `is` is the DMS
385
544
  // DataType compare mode — the data-api's own `eq` parses but matches
386
545
  // nothing. The list defaults to 10 rows, hence the explicit limit.
387
546
  listFunnels: (website: string) =>
388
- $authFetch<{ results: MarketingFunnelListItem[], total: number }>(
547
+ $authFetch<{ results: MarketingFunnelListItem[]; total: number }>(
389
548
  '/api/marketing/tables/funnels/list',
390
549
  {
391
550
  query: {
@@ -397,6 +556,12 @@ export function useMarketingApi() {
397
556
  },
398
557
  ),
399
558
 
559
+ // One definition, as the builder edits it: no results computed.
560
+ getFunnel: (id: string) =>
561
+ $authFetch<MarketingFunnelListItem>('/api/marketing/tables/funnels/get', {
562
+ query: { id },
563
+ }),
564
+
400
565
  // The insert answers the generated keys, an array — not the row.
401
566
  createFunnel: (input: MarketingFunnelInput) =>
402
567
  $authFetch<string[]>('/api/marketing/tables/funnels/new', {
@@ -0,0 +1,55 @@
1
+ import {
2
+ computed,
3
+ inject,
4
+ type ComputedRef,
5
+ type InjectionKey,
6
+ type Ref,
7
+ } from 'vue'
8
+
9
+ export type WebsiteState = 'live' | 'waiting' | 'paused'
10
+
11
+ export interface ContextWebsite {
12
+ id: string
13
+ name: string
14
+ domain: string
15
+ state: WebsiteState
16
+ lastActivityAt: number | null
17
+ }
18
+
19
+ export interface MarketingContextPayload {
20
+ websites: ContextWebsite[]
21
+ selectedId: string | null
22
+ }
23
+
24
+ /** What the context container hands the blocks it wraps. */
25
+ export interface MarketingContextState {
26
+ websites: ComputedRef<ContextWebsite[]>
27
+ selected: ComputedRef<ContextWebsite | null>
28
+ /** Bumped by the bar's refresh button. */
29
+ refreshToken: Ref<number>
30
+ select: (id: string) => Promise<void>
31
+ reload: () => Promise<void>
32
+ }
33
+
34
+ export const MARKETING_CONTEXT_KEY: InjectionKey<MarketingContextState> =
35
+ Symbol('dms-marketing-context')
36
+
37
+ /**
38
+ * The context of the page the block sits on. Outside a context container (a
39
+ * block dropped on another page) the website is unknown and the routes fall
40
+ * back to the caller's own selection. The period is the DMS period scope's
41
+ * (`usePeriodScope`), which the bar's selector publishes.
42
+ */
43
+ export function useMarketingContext(): MarketingContextState {
44
+ return inject(MARKETING_CONTEXT_KEY, () => detachedContext(), true)
45
+ }
46
+
47
+ function detachedContext(): MarketingContextState {
48
+ return {
49
+ websites: computed(() => []),
50
+ selected: computed(() => null),
51
+ refreshToken: { value: 0 } as Ref<number>,
52
+ select: async () => {},
53
+ reload: async () => {},
54
+ }
55
+ }