@antelopejs/dms-marketing 0.2.2
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/LICENSE +190 -0
- package/README.md +143 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/data-api/body.d.ts +1 -0
- package/dist/data-api/body.js +15 -0
- package/dist/data-api/body.js.map +1 -0
- package/dist/data-api/funnels.d.ts +32 -0
- package/dist/data-api/funnels.js +302 -0
- package/dist/data-api/funnels.js.map +1 -0
- package/dist/data-api/hidden-filter.d.ts +1 -0
- package/dist/data-api/hidden-filter.js +11 -0
- package/dist/data-api/hidden-filter.js.map +1 -0
- package/dist/data-api/index.d.ts +4 -0
- package/dist/data-api/index.js +21 -0
- package/dist/data-api/index.js.map +1 -0
- package/dist/data-api/tenant-scope.d.ts +15 -0
- package/dist/data-api/tenant-scope.js +43 -0
- package/dist/data-api/tenant-scope.js.map +1 -0
- package/dist/data-api/websites.d.ts +25 -0
- package/dist/data-api/websites.js +73 -0
- package/dist/data-api/websites.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.js +19 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/models/funnels.model.d.ts +25 -0
- package/dist/db/models/funnels.model.js +39 -0
- package/dist/db/models/funnels.model.js.map +1 -0
- package/dist/db/models/index.d.ts +7 -0
- package/dist/db/models/index.js +24 -0
- package/dist/db/models/index.js.map +1 -0
- package/dist/db/models/marketing_events.model.d.ts +49 -0
- package/dist/db/models/marketing_events.model.js +85 -0
- package/dist/db/models/marketing_events.model.js.map +1 -0
- package/dist/db/models/marketing_sessions.model.d.ts +33 -0
- package/dist/db/models/marketing_sessions.model.js +50 -0
- package/dist/db/models/marketing_sessions.model.js.map +1 -0
- package/dist/db/models/marketing_settings.model.d.ts +23 -0
- package/dist/db/models/marketing_settings.model.js +13 -0
- package/dist/db/models/marketing_settings.model.js.map +1 -0
- package/dist/db/models/page_snapshots.model.d.ts +27 -0
- package/dist/db/models/page_snapshots.model.js +32 -0
- package/dist/db/models/page_snapshots.model.js.map +1 -0
- package/dist/db/models/retention.d.ts +6 -0
- package/dist/db/models/retention.js +26 -0
- package/dist/db/models/retention.js.map +1 -0
- package/dist/db/models/website_statistics.model.d.ts +31 -0
- package/dist/db/models/website_statistics.model.js +63 -0
- package/dist/db/models/website_statistics.model.js.map +1 -0
- package/dist/db/models/websites.model.d.ts +26 -0
- package/dist/db/models/websites.model.js +27 -0
- package/dist/db/models/websites.model.js.map +1 -0
- package/dist/db/tables/funnels.table.d.ts +31 -0
- package/dist/db/tables/funnels.table.js +54 -0
- package/dist/db/tables/funnels.table.js.map +1 -0
- package/dist/db/tables/index.d.ts +7 -0
- package/dist/db/tables/index.js +24 -0
- package/dist/db/tables/index.js.map +1 -0
- package/dist/db/tables/marketing_events.table.d.ts +15 -0
- package/dist/db/tables/marketing_events.table.js +66 -0
- package/dist/db/tables/marketing_events.table.js.map +1 -0
- package/dist/db/tables/marketing_sessions.table.d.ts +26 -0
- package/dist/db/tables/marketing_sessions.table.js +115 -0
- package/dist/db/tables/marketing_sessions.table.js.map +1 -0
- package/dist/db/tables/marketing_settings.table.d.ts +11 -0
- package/dist/db/tables/marketing_settings.table.js +51 -0
- package/dist/db/tables/marketing_settings.table.js.map +1 -0
- package/dist/db/tables/page_snapshots.table.d.ts +17 -0
- package/dist/db/tables/page_snapshots.table.js +72 -0
- package/dist/db/tables/page_snapshots.table.js.map +1 -0
- package/dist/db/tables/website_statistics.table.d.ts +14 -0
- package/dist/db/tables/website_statistics.table.js +60 -0
- package/dist/db/tables/website_statistics.table.js.map +1 -0
- package/dist/db/tables/websites.table.d.ts +13 -0
- package/dist/db/tables/websites.table.js +60 -0
- package/dist/db/tables/websites.table.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +39 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/implementations/dms-marketing/index.d.ts +2 -0
- package/dist/implementations/dms-marketing/index.js +8 -0
- package/dist/implementations/dms-marketing/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/all.d.ts +5 -0
- package/dist/pages/all.js +22 -0
- package/dist/pages/all.js.map +1 -0
- package/dist/pages/campaigns/page.d.ts +9 -0
- package/dist/pages/campaigns/page.js +27 -0
- package/dist/pages/campaigns/page.js.map +1 -0
- package/dist/pages/funnels/page.d.ts +9 -0
- package/dist/pages/funnels/page.js +27 -0
- package/dist/pages/funnels/page.js.map +1 -0
- package/dist/pages/index.d.ts +4 -0
- package/dist/pages/index.js +21 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/module.d.ts +1 -0
- package/dist/pages/module.js +13 -0
- package/dist/pages/module.js.map +1 -0
- package/dist/pages/overview/page.d.ts +9 -0
- package/dist/pages/overview/page.js +27 -0
- package/dist/pages/overview/page.js.map +1 -0
- package/dist/pages/pages/page.d.ts +9 -0
- package/dist/pages/pages/page.js +27 -0
- package/dist/pages/pages/page.js.map +1 -0
- package/dist/pages/settings/form.d.ts +3 -0
- package/dist/pages/settings/form.js +35 -0
- package/dist/pages/settings/form.js.map +1 -0
- package/dist/pages/settings/page.d.ts +9 -0
- package/dist/pages/settings/page.js +27 -0
- package/dist/pages/settings/page.js.map +1 -0
- package/dist/routes/collect.d.ts +7 -0
- package/dist/routes/collect.js +62 -0
- package/dist/routes/collect.js.map +1 -0
- package/dist/routes/experiments.d.ts +6 -0
- package/dist/routes/experiments.js +63 -0
- package/dist/routes/experiments.js.map +1 -0
- package/dist/routes/funnels.d.ts +16 -0
- package/dist/routes/funnels.js +44 -0
- package/dist/routes/funnels.js.map +1 -0
- package/dist/routes/index.d.ts +8 -0
- package/dist/routes/index.js +25 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/settings.d.ts +8 -0
- package/dist/routes/settings.js +50 -0
- package/dist/routes/settings.js.map +1 -0
- package/dist/routes/snapshot.d.ts +10 -0
- package/dist/routes/snapshot.js +147 -0
- package/dist/routes/snapshot.js.map +1 -0
- package/dist/routes/stats.d.ts +44 -0
- package/dist/routes/stats.js +278 -0
- package/dist/routes/stats.js.map +1 -0
- package/dist/routes/tracker.d.ts +6 -0
- package/dist/routes/tracker.js +57 -0
- package/dist/routes/tracker.js.map +1 -0
- package/dist/routes/websites.d.ts +13 -0
- package/dist/routes/websites.js +144 -0
- package/dist/routes/websites.js.map +1 -0
- package/dist/services/acquisition.d.ts +15 -0
- package/dist/services/acquisition.js +153 -0
- package/dist/services/acquisition.js.map +1 -0
- package/dist/services/bounded-cache.d.ts +14 -0
- package/dist/services/bounded-cache.js +45 -0
- package/dist/services/bounded-cache.js.map +1 -0
- package/dist/services/collect.d.ts +11 -0
- package/dist/services/collect.js +193 -0
- package/dist/services/collect.js.map +1 -0
- package/dist/services/experiment-definitions.d.ts +4 -0
- package/dist/services/experiment-definitions.js +36 -0
- package/dist/services/experiment-definitions.js.map +1 -0
- package/dist/services/experiments/assign.d.ts +5 -0
- package/dist/services/experiments/assign.js +52 -0
- package/dist/services/experiments/assign.js.map +1 -0
- package/dist/services/experiments/index.d.ts +3 -0
- package/dist/services/experiments/index.js +20 -0
- package/dist/services/experiments/index.js.map +1 -0
- package/dist/services/experiments/results.d.ts +20 -0
- package/dist/services/experiments/results.js +91 -0
- package/dist/services/experiments/results.js.map +1 -0
- package/dist/services/experiments/stats.d.ts +12 -0
- package/dist/services/experiments/stats.js +62 -0
- package/dist/services/experiments/stats.js.map +1 -0
- package/dist/services/funnels/compute.d.ts +15 -0
- package/dist/services/funnels/compute.js +68 -0
- package/dist/services/funnels/compute.js.map +1 -0
- package/dist/services/funnels/index.d.ts +2 -0
- package/dist/services/funnels/index.js +19 -0
- package/dist/services/funnels/index.js.map +1 -0
- package/dist/services/funnels/results.d.ts +14 -0
- package/dist/services/funnels/results.js +72 -0
- package/dist/services/funnels/results.js.map +1 -0
- package/dist/services/geoip.d.ts +3 -0
- package/dist/services/geoip.js +38 -0
- package/dist/services/geoip.js.map +1 -0
- package/dist/services/heatmap.d.ts +10 -0
- package/dist/services/heatmap.js +180 -0
- package/dist/services/heatmap.js.map +1 -0
- package/dist/services/index.d.ts +26 -0
- package/dist/services/index.js +43 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/ingest-guard.d.ts +11 -0
- package/dist/services/ingest-guard.js +44 -0
- package/dist/services/ingest-guard.js.map +1 -0
- package/dist/services/keyed-queue.d.ts +4 -0
- package/dist/services/keyed-queue.js +19 -0
- package/dist/services/keyed-queue.js.map +1 -0
- package/dist/services/origin-guard.d.ts +5 -0
- package/dist/services/origin-guard.js +43 -0
- package/dist/services/origin-guard.js.map +1 -0
- package/dist/services/period.d.ts +2 -0
- package/dist/services/period.js +19 -0
- package/dist/services/period.js.map +1 -0
- package/dist/services/prune.d.ts +7 -0
- package/dist/services/prune.js +49 -0
- package/dist/services/prune.js.map +1 -0
- package/dist/services/query-param.d.ts +1 -0
- package/dist/services/query-param.js +11 -0
- package/dist/services/query-param.js.map +1 -0
- package/dist/services/request-body.d.ts +2 -0
- package/dist/services/request-body.js +41 -0
- package/dist/services/request-body.js.map +1 -0
- package/dist/services/request-meta.d.ts +3 -0
- package/dist/services/request-meta.js +19 -0
- package/dist/services/request-meta.js.map +1 -0
- package/dist/services/rollup-write.d.ts +4 -0
- package/dist/services/rollup-write.js +84 -0
- package/dist/services/rollup-write.js.map +1 -0
- package/dist/services/sessionize.d.ts +23 -0
- package/dist/services/sessionize.js +78 -0
- package/dist/services/sessionize.js.map +1 -0
- package/dist/services/settings.d.ts +6 -0
- package/dist/services/settings.js +113 -0
- package/dist/services/settings.js.map +1 -0
- package/dist/services/snapshots.d.ts +24 -0
- package/dist/services/snapshots.js +139 -0
- package/dist/services/snapshots.js.map +1 -0
- package/dist/services/tenant-funnel.d.ts +3 -0
- package/dist/services/tenant-funnel.js +17 -0
- package/dist/services/tenant-funnel.js.map +1 -0
- package/dist/services/tenant-website.d.ts +5 -0
- package/dist/services/tenant-website.js +34 -0
- package/dist/services/tenant-website.js.map +1 -0
- package/dist/services/url.d.ts +10 -0
- package/dist/services/url.js +81 -0
- package/dist/services/url.js.map +1 -0
- package/dist/services/user-agent.d.ts +2 -0
- package/dist/services/user-agent.js +17 -0
- package/dist/services/user-agent.js.map +1 -0
- package/dist/services/visitor-id.d.ts +1 -0
- package/dist/services/visitor-id.js +19 -0
- package/dist/services/visitor-id.js.map +1 -0
- package/dist/services/website-cache.d.ts +3 -0
- package/dist/services/website-cache.js +22 -0
- package/dist/services/website-cache.js.map +1 -0
- package/dist/types/constants.d.ts +74 -0
- package/dist/types/constants.js +79 -0
- package/dist/types/constants.js.map +1 -0
- package/dist/types/events.d.ts +114 -0
- package/dist/types/events.js +29 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +22 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/settings.d.ts +67 -0
- package/dist/types/settings.js +58 -0
- package/dist/types/settings.js.map +1 -0
- package/dist/types/snapshots.d.ts +95 -0
- package/dist/types/snapshots.js +41 -0
- package/dist/types/snapshots.js.map +1 -0
- package/dist/types/statistics.d.ts +38 -0
- package/dist/types/statistics.js +33 -0
- package/dist/types/statistics.js.map +1 -0
- package/dist/utils/experiment-type.d.ts +52 -0
- package/dist/utils/experiment-type.js +94 -0
- package/dist/utils/experiment-type.js.map +1 -0
- package/dist/utils/funnel-steps-type.d.ts +42 -0
- package/dist/utils/funnel-steps-type.js +62 -0
- package/dist/utils/funnel-steps-type.js.map +1 -0
- package/frontend-vue/.editorconfig +12 -0
- package/frontend-vue/.prettierrc +7 -0
- package/frontend-vue/app/components/CampaignsView.vue +330 -0
- package/frontend-vue/app/components/ExperimentInput.vue +128 -0
- package/frontend-vue/app/components/FunnelDetail.vue +1026 -0
- package/frontend-vue/app/components/FunnelStepsInput.vue +154 -0
- package/frontend-vue/app/components/FunnelsView.vue +493 -0
- package/frontend-vue/app/components/HeatmapCanvas.vue +116 -0
- package/frontend-vue/app/components/NoWebsiteState.vue +18 -0
- package/frontend-vue/app/components/OverviewView.vue +493 -0
- package/frontend-vue/app/components/PagePreview.vue +915 -0
- package/frontend-vue/app/components/PagesView.vue +378 -0
- package/frontend-vue/app/components/ScrollDepthOverlay.vue +158 -0
- package/frontend-vue/app/components/TextKpiCard.vue +28 -0
- package/frontend-vue/app/components/TopListTabsCard.vue +162 -0
- package/frontend-vue/app/components/TrackerSnippet.vue +31 -0
- package/frontend-vue/app/components/VariationsInput.vue +164 -0
- package/frontend-vue/app/composables/useChildId.ts +13 -0
- package/frontend-vue/app/composables/useExperiment.ts +59 -0
- package/frontend-vue/app/composables/useFunnelSteps.ts +25 -0
- package/frontend-vue/app/composables/useLatestRequest.ts +90 -0
- package/frontend-vue/app/composables/useMarketingApi.ts +420 -0
- package/frontend-vue/app/composables/useMarketingPeriod.ts +66 -0
- package/frontend-vue/app/composables/useMarketingRoutes.ts +51 -0
- package/frontend-vue/app/composables/useMarketingWebsiteForm.ts +69 -0
- package/frontend-vue/app/composables/useMarketingWebsites.ts +80 -0
- package/frontend-vue/app/composables/useSnapshotFrame.ts +183 -0
- package/frontend-vue/app/plugins/funnel-steps-data-type.client.ts +46 -0
- package/frontend-vue/app/utils/dmsComponent.ts +18 -0
- package/frontend-vue/app/utils/heatmapPalette.ts +41 -0
- package/frontend-vue/app/utils/snapshotViewportUnits.ts +63 -0
- package/frontend-vue/dms.frontend.ts +35 -0
- package/frontend-vue/eslint.config.mjs +16 -0
- package/frontend-vue/i18n/locales/marketing-en-GB.json +382 -0
- package/frontend-vue/i18n/locales/marketing-fr-FR.json +382 -0
- package/frontend-vue/package.json +30 -0
- package/frontend-vue/pnpm-lock.yaml +5226 -0
- package/frontend-vue/pnpm-workspace.yaml +2 -0
- package/frontend-vue/tests/drafts.test.ts +54 -0
- package/frontend-vue/tests/i18n.test.ts +86 -0
- package/frontend-vue/tests/period.test.ts +35 -0
- package/frontend-vue/tests/registration.test.ts +63 -0
- package/frontend-vue/tests/routes.test.ts +31 -0
- package/frontend-vue/tests/stubs/frontend-module.ts +56 -0
- package/frontend-vue/vitest.config.ts +20 -0
- package/package.json +91 -0
- package/static/snapshot.js +497 -0
- package/static/tracker.js +503 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
parseExperimentValue,
|
|
4
|
+
parseExperimentVariationsValue,
|
|
5
|
+
} from '../app/composables/useExperiment'
|
|
6
|
+
import { parseFunnelStepsValue } from '../app/composables/useFunnelSteps'
|
|
7
|
+
|
|
8
|
+
// Both shapes reach the inputs: the real value on a form, a JSON string on a
|
|
9
|
+
// table cell. Neither parser may throw — they run while rendering.
|
|
10
|
+
describe('parseFunnelStepsValue', () => {
|
|
11
|
+
it('reads the array and its JSON form alike', () => {
|
|
12
|
+
const steps = [{ kind: 'url' as const, value: '/pricing' }]
|
|
13
|
+
expect(parseFunnelStepsValue(steps)).toEqual(steps)
|
|
14
|
+
expect(parseFunnelStepsValue(JSON.stringify(steps))).toEqual(steps)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('answers an empty list on anything else', () => {
|
|
18
|
+
for (const input of [null, undefined, '', 'not json', '{}', 42]) {
|
|
19
|
+
expect(parseFunnelStepsValue(input)).toEqual([])
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('parseExperimentValue', () => {
|
|
25
|
+
it('reads the object and its JSON form alike', () => {
|
|
26
|
+
const experiment = {
|
|
27
|
+
key: 'cta',
|
|
28
|
+
variations: [{ key: 'a', weight: 1 }],
|
|
29
|
+
runs: [{ startedAt: 1, stoppedAt: null }],
|
|
30
|
+
}
|
|
31
|
+
expect(parseExperimentValue(experiment)).toEqual(experiment)
|
|
32
|
+
expect(parseExperimentValue(JSON.stringify(experiment))).toEqual(experiment)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('reads a value carrying no runs as a split that served no traffic', () => {
|
|
36
|
+
expect(
|
|
37
|
+
parseExperimentValue({ key: 'cta', variations: [] })?.runs,
|
|
38
|
+
).toEqual([])
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('answers null on anything else', () => {
|
|
42
|
+
for (const input of [null, undefined, '', 'not json', 42]) {
|
|
43
|
+
expect(parseExperimentValue(input)).toBeNull()
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('parseExperimentVariationsValue', () => {
|
|
49
|
+
it('answers an empty list on anything that is not an array', () => {
|
|
50
|
+
for (const input of [null, undefined, '', 'not json', '{}']) {
|
|
51
|
+
expect(parseExperimentVariationsValue(input)).toEqual([])
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
import en from '../i18n/locales/marketing-en-GB.json'
|
|
5
|
+
import fr from '../i18n/locales/marketing-fr-FR.json'
|
|
6
|
+
|
|
7
|
+
// vitest runs with the frontend module as its root; the backend sits one level
|
|
8
|
+
// above it.
|
|
9
|
+
const BACKEND_SOURCE_DIR = join(process.cwd(), '..', 'src')
|
|
10
|
+
|
|
11
|
+
// `$page.marketing.a.b` as the backend writes it. A literal that stops on a
|
|
12
|
+
// dot is the prefix of a key completed by a template expression; those are
|
|
13
|
+
// resolved at runtime and cannot be checked here.
|
|
14
|
+
const KEY_LITERAL = /\$page\.marketing\.[\w.]+/g
|
|
15
|
+
const DYNAMIC_KEY_SUFFIX = '.'
|
|
16
|
+
|
|
17
|
+
function keysOf(value: unknown, prefix = ''): string[] {
|
|
18
|
+
if (typeof value !== 'object' || value === null) {
|
|
19
|
+
return [prefix]
|
|
20
|
+
}
|
|
21
|
+
return Object.entries(value).flatMap(([key, child]) =>
|
|
22
|
+
keysOf(child, prefix ? `${prefix}.${key}` : key),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function typescriptFiles(directory: string): string[] {
|
|
27
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
28
|
+
const path = join(directory, entry.name)
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
return typescriptFiles(path)
|
|
31
|
+
}
|
|
32
|
+
return entry.isFile() && path.endsWith('.ts') ? [path] : []
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function backendKeys(): string[] {
|
|
37
|
+
const found = new Set<string>()
|
|
38
|
+
for (const file of typescriptFiles(BACKEND_SOURCE_DIR)) {
|
|
39
|
+
for (const literal of readFileSync(file, 'utf8').match(KEY_LITERAL) ?? []) {
|
|
40
|
+
const key = literal.slice(1)
|
|
41
|
+
if (!key.endsWith(DYNAMIC_KEY_SUFFIX)) {
|
|
42
|
+
found.add(key)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [...found].sort()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveKey(root: unknown, key: string): unknown {
|
|
50
|
+
return key
|
|
51
|
+
.split('.')
|
|
52
|
+
.reduce<unknown>(
|
|
53
|
+
(node, part) =>
|
|
54
|
+
typeof node === 'object' && node !== null
|
|
55
|
+
? (node as Record<string, unknown>)[part]
|
|
56
|
+
: undefined,
|
|
57
|
+
root,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe('locales', () => {
|
|
62
|
+
it('carry the same key set', () => {
|
|
63
|
+
expect(keysOf(fr).sort()).toEqual(keysOf(en).sort())
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('cover every namespace the surfaces use', () => {
|
|
67
|
+
for (const namespace of [
|
|
68
|
+
'overview',
|
|
69
|
+
'pages',
|
|
70
|
+
'campaigns',
|
|
71
|
+
'funnels',
|
|
72
|
+
'settings',
|
|
73
|
+
'websites',
|
|
74
|
+
'errors',
|
|
75
|
+
]) {
|
|
76
|
+
expect(en.page.marketing).toHaveProperty(namespace)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('resolve every static key the backend emits', () => {
|
|
81
|
+
const keys = backendKeys()
|
|
82
|
+
expect(keys.length).toBeGreaterThan(0)
|
|
83
|
+
expect(keys.filter((key) => typeof resolveKey(en, key) !== 'string')).toEqual([])
|
|
84
|
+
expect(keys.filter((key) => typeof resolveKey(fr, key) !== 'string')).toEqual([])
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_PERIOD_DAYS,
|
|
4
|
+
DEFAULT_PERIOD_PRESET,
|
|
5
|
+
ndToPreset,
|
|
6
|
+
periodDays,
|
|
7
|
+
} from '../app/composables/useMarketingPeriod'
|
|
8
|
+
|
|
9
|
+
describe('periodDays', () => {
|
|
10
|
+
it('reads the day count of an Nd window', () => {
|
|
11
|
+
expect(periodDays('30d')).toBe(30)
|
|
12
|
+
expect(periodDays('1d')).toBe(1)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('falls back to the default on anything unparseable', () => {
|
|
16
|
+
for (const input of [undefined, '', 'week', '0d', '-7d']) {
|
|
17
|
+
expect(periodDays(input)).toBe(DEFAULT_PERIOD_DAYS)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('ndToPreset', () => {
|
|
23
|
+
it('maps the offered windows back onto their preset', () => {
|
|
24
|
+
expect(ndToPreset('1d')).toBe('today')
|
|
25
|
+
expect(ndToPreset('7d')).toBe('last-7-days')
|
|
26
|
+
expect(ndToPreset('30d')).toBe('last-30-days')
|
|
27
|
+
expect(ndToPreset('90d')).toBe('last-90-days')
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// An arbitrary window must not silently round to a neighbouring preset.
|
|
31
|
+
it('falls back to the default on a window no preset offers', () => {
|
|
32
|
+
expect(ndToPreset('13d')).toBe(DEFAULT_PERIOD_PRESET)
|
|
33
|
+
expect(ndToPreset(undefined)).toBe(DEFAULT_PERIOD_PRESET)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { expect, it, vi } from 'vitest'
|
|
2
|
+
import frontendModule from '../dms.frontend'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The backend addresses this module's surfaces by component name — the pages
|
|
6
|
+
* through `CustomComponent('DmsMarketing…')`, the DataTypes through their
|
|
7
|
+
* form components — so a name that stops being registered silently falls back
|
|
8
|
+
* to the generic card renderer instead of failing.
|
|
9
|
+
*/
|
|
10
|
+
const BACKEND_ADDRESSED = [
|
|
11
|
+
'DmsMarketingOverviewView',
|
|
12
|
+
'DmsMarketingPagesView',
|
|
13
|
+
'DmsMarketingCampaignsView',
|
|
14
|
+
'DmsMarketingFunnelsView',
|
|
15
|
+
'DmsMarketingFunnelStepsInput',
|
|
16
|
+
'DmsMarketingExperimentInput',
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
it('registers every component under the DmsMarketing prefix', async () => {
|
|
20
|
+
const registerComponent = vi.fn()
|
|
21
|
+
const registerPlugin = vi.fn()
|
|
22
|
+
await frontendModule.setup({
|
|
23
|
+
options: { public: {} },
|
|
24
|
+
registerComponent,
|
|
25
|
+
registerPage: vi.fn(),
|
|
26
|
+
registerDynamicPage: vi.fn(),
|
|
27
|
+
registerLayout: vi.fn(),
|
|
28
|
+
registerErrorPage: vi.fn(),
|
|
29
|
+
registerPlugin,
|
|
30
|
+
registerMiddleware: vi.fn(),
|
|
31
|
+
provide: vi.fn(),
|
|
32
|
+
use: vi.fn(),
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
const names = registerComponent.mock.calls.map(([name]) => name)
|
|
36
|
+
expect(names.length).toBeGreaterThan(0)
|
|
37
|
+
expect(new Set(names).size).toBe(names.length)
|
|
38
|
+
for (const name of names) {
|
|
39
|
+
expect(name.startsWith('DmsMarketing')).toBe(true)
|
|
40
|
+
}
|
|
41
|
+
for (const name of BACKEND_ADDRESSED) {
|
|
42
|
+
expect(names).toContain(name)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('registers the funnel-steps DataType plugin client-side only', async () => {
|
|
47
|
+
const registerPlugin = vi.fn()
|
|
48
|
+
await frontendModule.setup({
|
|
49
|
+
options: { public: {} },
|
|
50
|
+
registerComponent: vi.fn(),
|
|
51
|
+
registerPage: vi.fn(),
|
|
52
|
+
registerDynamicPage: vi.fn(),
|
|
53
|
+
registerLayout: vi.fn(),
|
|
54
|
+
registerErrorPage: vi.fn(),
|
|
55
|
+
registerPlugin,
|
|
56
|
+
registerMiddleware: vi.fn(),
|
|
57
|
+
provide: vi.fn(),
|
|
58
|
+
use: vi.fn(),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(registerPlugin).toHaveBeenCalledTimes(1)
|
|
62
|
+
expect(registerPlugin.mock.calls[0]![1]).toEqual({ clientOnly: true })
|
|
63
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
campaignsLink,
|
|
4
|
+
funnelsLink,
|
|
5
|
+
pagesLink,
|
|
6
|
+
} from '../app/composables/useMarketingRoutes'
|
|
7
|
+
|
|
8
|
+
describe('module links', () => {
|
|
9
|
+
it('stay bare when nothing is selected', () => {
|
|
10
|
+
expect(pagesLink()).toEqual({ path: '/modules/marketing/pages', query: {} })
|
|
11
|
+
expect(campaignsLink()).toEqual({
|
|
12
|
+
path: '/modules/marketing/campaigns',
|
|
13
|
+
query: {},
|
|
14
|
+
})
|
|
15
|
+
expect(funnelsLink()).toEqual({
|
|
16
|
+
path: '/modules/marketing/funnels',
|
|
17
|
+
query: {},
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('carry only the selections that have a value', () => {
|
|
22
|
+
expect(pagesLink({ website: 'w1', path: undefined, period: '7d' })).toEqual({
|
|
23
|
+
path: '/modules/marketing/pages',
|
|
24
|
+
query: { website: 'w1', period: '7d' },
|
|
25
|
+
})
|
|
26
|
+
expect(funnelsLink({ website: 'w1', funnel: null })).toEqual({
|
|
27
|
+
path: '/modules/marketing/funnels',
|
|
28
|
+
query: { website: 'w1' },
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stand-in for the loader-provided `#dms/frontend-module` alias.
|
|
3
|
+
*
|
|
4
|
+
* The real module only exists inside the Vue workspace the DMS frontend
|
|
5
|
+
* loader generates; these tests run the module's own sources directly, so the
|
|
6
|
+
* few SDK helpers they reach are stubbed with the same shapes. The real
|
|
7
|
+
* contract is checked by `pnpm test:frontend`, which builds this module
|
|
8
|
+
* through the loader for real.
|
|
9
|
+
*/
|
|
10
|
+
import type { Component, Plugin } from 'vue'
|
|
11
|
+
|
|
12
|
+
export type DmsPluginSetup = (context: unknown) => void | Promise<void>
|
|
13
|
+
|
|
14
|
+
export interface DmsFrontendSdk {
|
|
15
|
+
options: { public: Record<string, unknown> }
|
|
16
|
+
registerComponent: (name: string, component: Component) => void
|
|
17
|
+
registerPage: (name: string, component: Component) => void
|
|
18
|
+
registerDynamicPage: (name: string, component: Component) => void
|
|
19
|
+
registerLayout: (name: string, component: Component) => void
|
|
20
|
+
registerErrorPage: (component: Component) => void
|
|
21
|
+
registerPlugin: (
|
|
22
|
+
setup: DmsPluginSetup,
|
|
23
|
+
options?: { clientOnly?: boolean },
|
|
24
|
+
) => void
|
|
25
|
+
registerMiddleware: (name: string, handler: unknown) => void
|
|
26
|
+
provide: (key: string, value: unknown) => void
|
|
27
|
+
use: (plugin: Plugin) => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DmsFrontendModule {
|
|
31
|
+
setup(sdk: DmsFrontendSdk): void | Promise<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function defineDmsPlugin(setup: DmsPluginSetup): DmsPluginSetup {
|
|
35
|
+
return setup
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useI18n(): { t: (key: string) => string, te: () => boolean, locale: { value: string } } {
|
|
39
|
+
return { t: (key: string) => key, te: () => false, locale: { value: 'en' } }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function useDmsRoute(): { query: Record<string, string>, path: string } {
|
|
43
|
+
return { query: {}, path: '/' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useDmsRouter(): { replace: () => Promise<void>, push: () => Promise<void> } {
|
|
47
|
+
return { replace: async () => {}, push: async () => {} }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function useDmsRuntimeConfig(): { public: Record<string, unknown> } {
|
|
51
|
+
return { public: {} }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resolveDmsComponent(): Component | undefined {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { fileURLToPath } from 'node:url'
|
|
2
|
+
import vue from '@vitejs/plugin-vue'
|
|
3
|
+
import { defineConfig } from 'vitest/config'
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [vue()],
|
|
7
|
+
resolve: {
|
|
8
|
+
alias: {
|
|
9
|
+
// Provided by the generated workspace at build time; stubbed here so the
|
|
10
|
+
// module's own sources can be imported outside it.
|
|
11
|
+
'#dms/frontend-module': fileURLToPath(
|
|
12
|
+
new URL('./tests/stubs/frontend-module.ts', import.meta.url),
|
|
13
|
+
),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
test: {
|
|
17
|
+
environment: 'jsdom',
|
|
18
|
+
include: ['tests/**/*.test.ts'],
|
|
19
|
+
},
|
|
20
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@antelopejs/dms-marketing",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/AntelopeJS/dms-marketing.git"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"LICENSE",
|
|
11
|
+
"dist",
|
|
12
|
+
"frontend-vue",
|
|
13
|
+
"static"
|
|
14
|
+
],
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./package.json": "./package.json"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"provenance": true,
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@antelopejs/interface-api": ">=0.0.13 <1.0.0",
|
|
31
|
+
"@antelopejs/interface-api-util": ">=0.1.2 <1.0.0",
|
|
32
|
+
"@antelopejs/interface-core": ">=0.0.13 <1.0.0",
|
|
33
|
+
"@antelopejs/interface-data-api": ">=0.1.9 <1.0.0",
|
|
34
|
+
"@antelopejs/interface-database": ">=0.1.7 <1.0.0",
|
|
35
|
+
"@antelopejs/interface-database-decorators": ">=0.1.6 <1.0.0",
|
|
36
|
+
"@antelopejs/interface-dms": ">=0.1.0 <1.0.0",
|
|
37
|
+
"@antelopejs/interface-dms-marketing": ">=0.1.0 <1.0.0",
|
|
38
|
+
"defu": "^6.1.4",
|
|
39
|
+
"maxmind": "^5.0.7",
|
|
40
|
+
"node-cron": "^4.2.1",
|
|
41
|
+
"ua-parser-js": "^2.0.9",
|
|
42
|
+
"zod": "~3.24.4"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@antelopejs/core": ">=1.8.0 <2",
|
|
46
|
+
"@antelopejs/dms": ">=0.3.5 <1.0.0",
|
|
47
|
+
"@antelopejs/dms-frontend": ">=0.2.0 <1.0.0",
|
|
48
|
+
"@antelopejs/interface-mongodb": ">=0.0.3 <1.0.0",
|
|
49
|
+
"@antelopejs/mongodb": "1.3.0",
|
|
50
|
+
"@antelopejs/tooling-configs": ">=0.0.6 <1.0.0",
|
|
51
|
+
"@types/node": "^22.19.15",
|
|
52
|
+
"knip": "^6.34.0",
|
|
53
|
+
"mongodb": "^6.21.0",
|
|
54
|
+
"mongodb-memory-server-core": "^10.4.3",
|
|
55
|
+
"oxfmt": "^0.66.0",
|
|
56
|
+
"oxlint": "1.81.0",
|
|
57
|
+
"oxlint-tsgolint": "^7.0.2001",
|
|
58
|
+
"release-it": "^19.2.4",
|
|
59
|
+
"release-it-changelogen": "^0.1.0",
|
|
60
|
+
"rimraf": "^6.1.3",
|
|
61
|
+
"typescript": "^5.9.3"
|
|
62
|
+
},
|
|
63
|
+
"antelopeJs": {
|
|
64
|
+
"baseUrl": "dist",
|
|
65
|
+
"test": "antelope.test.ts",
|
|
66
|
+
"implements": [
|
|
67
|
+
"@antelopejs/interface-dms-marketing"
|
|
68
|
+
],
|
|
69
|
+
"paths": {
|
|
70
|
+
"@/*": [
|
|
71
|
+
"*"
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"build": "pnpm --filter @antelopejs/interface-dms-marketing build && rimraf dist && tsc -p tsconfig.build.json",
|
|
77
|
+
"test": "pnpm run test:backend && pnpm run test:layer",
|
|
78
|
+
"test:backend": "pnpm run build && ajs module test .",
|
|
79
|
+
"test:layer": "cd frontend-vue && pnpm test",
|
|
80
|
+
"test:frontend": "node scripts/check-frontend.mjs",
|
|
81
|
+
"dev": "ajs project run -w -p playground",
|
|
82
|
+
"frontend:dev": "cd playground && pnpm frontend:dev",
|
|
83
|
+
"lint": "oxlint && oxfmt --check . && cd frontend-vue && pnpm lint",
|
|
84
|
+
"lint:fix": "oxlint --fix --max-warnings=1000000 && oxfmt . && cd frontend-vue && pnpm lint:fix",
|
|
85
|
+
"format": "oxfmt . && cd frontend-vue && pnpm format",
|
|
86
|
+
"release": "pnpm --filter @antelopejs/interface-dms-marketing build && pnpm --dir frontend-vue install --frozen-lockfile && pnpm run lint && release-it",
|
|
87
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
88
|
+
"knip": "knip --dependencies",
|
|
89
|
+
"knip:all": "knip"
|
|
90
|
+
}
|
|
91
|
+
}
|