@antelopejs/dms-marketing 0.2.3 → 0.2.4
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.
|
@@ -81,6 +81,7 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
81
81
|
const {
|
|
82
82
|
data: inventory,
|
|
83
83
|
loading: loadingPages,
|
|
84
|
+
settled: pagesSettled,
|
|
84
85
|
failed: pagesFailed,
|
|
85
86
|
run: loadPages,
|
|
86
87
|
} = useLatestRequest<MarketingPages>(() =>
|
|
@@ -100,6 +101,16 @@ const trackerEnabled = computed(() => inventory.value?.trackerEnabled ?? true)
|
|
|
100
101
|
// attempt, and must not clear the one that says the site list never loaded.
|
|
101
102
|
const failed = computed(() => websitesFailed.value || pagesFailed.value)
|
|
102
103
|
|
|
104
|
+
// A selected site means an inventory request is expected. Keep the surface
|
|
105
|
+
// visibly loading through the first answer instead of rendering an empty
|
|
106
|
+
// master/detail shell while websites, pages and the preview requests settle.
|
|
107
|
+
const loading = computed(
|
|
108
|
+
() =>
|
|
109
|
+
loadingWebsites.value
|
|
110
|
+
|| loadingPages.value
|
|
111
|
+
|| (!!selectedWebsiteId.value && !pagesSettled.value),
|
|
112
|
+
)
|
|
113
|
+
|
|
103
114
|
// Land on something rather than on an empty pane, but never override a path
|
|
104
115
|
// that was linked to or typed. Watching the answer rather than writing from
|
|
105
116
|
// the fetch keeps superseded ones out: only the newest ever reaches here.
|
|
@@ -334,8 +345,10 @@ async function resetSiteHeatmaps() {
|
|
|
334
345
|
/>
|
|
335
346
|
|
|
336
347
|
<template v-else>
|
|
348
|
+
<USkeleton v-if="loading" class="h-96 w-full" />
|
|
349
|
+
|
|
337
350
|
<UAlert
|
|
338
|
-
v-if="truncated"
|
|
351
|
+
v-else-if="truncated"
|
|
339
352
|
icon="i-ph-list"
|
|
340
353
|
color="neutral"
|
|
341
354
|
variant="subtle"
|
|
@@ -353,7 +366,7 @@ async function resetSiteHeatmaps() {
|
|
|
353
366
|
/>
|
|
354
367
|
|
|
355
368
|
<DmsMasterDetail
|
|
356
|
-
v-else
|
|
369
|
+
v-else-if="!loading"
|
|
357
370
|
v-model="selectedPath"
|
|
358
371
|
:items="listItems"
|
|
359
372
|
:list-label="t('page.marketing.pages.inventory')"
|
|
@@ -16,12 +16,15 @@ interface PagesLink {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** Query without the empty keys — a bare link must stay bare. */
|
|
19
|
-
function withQuery(base: string, query: Record<string, string | null | undefined>) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
function withQuery(base: string, query: Record<string, string | null | undefined>): string {
|
|
20
|
+
const params = new URLSearchParams()
|
|
21
|
+
for (const [key, value] of Object.entries(query)) {
|
|
22
|
+
if (value) {
|
|
23
|
+
params.set(key, value)
|
|
24
|
+
}
|
|
24
25
|
}
|
|
26
|
+
const suffix = params.toString()
|
|
27
|
+
return suffix ? `${base}?${suffix}` : base
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/** The tracked-pages surface, optionally on a given site, path and window. */
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { nextTick } from 'vue'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
import { useLatestRequest } from '../app/composables/useLatestRequest'
|
|
4
|
+
|
|
5
|
+
describe('useLatestRequest loading states', () => {
|
|
6
|
+
it('is unsettled immediately and settles with the fetched path inventory', async () => {
|
|
7
|
+
let resolve: ((value: { pages: { path: string }[] }) => void) | undefined
|
|
8
|
+
const request = useLatestRequest(() => new Promise((done) => {
|
|
9
|
+
resolve = done
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
const pending = request.run()
|
|
13
|
+
expect(request.settled.value).toBe(false)
|
|
14
|
+
expect(request.loading.value).toBe(true)
|
|
15
|
+
expect(request.data.value).toBeNull()
|
|
16
|
+
|
|
17
|
+
resolve?.({ pages: [{ path: '/pricing' }] })
|
|
18
|
+
await pending
|
|
19
|
+
await nextTick()
|
|
20
|
+
|
|
21
|
+
expect(request.settled.value).toBe(true)
|
|
22
|
+
expect(request.loading.value).toBe(false)
|
|
23
|
+
expect(request.data.value?.pages[0]?.path).toBe('/pricing')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('settles an empty response without leaving the empty path state loading', async () => {
|
|
27
|
+
const request = useLatestRequest(async () => ({ pages: [] }))
|
|
28
|
+
|
|
29
|
+
await request.run()
|
|
30
|
+
|
|
31
|
+
expect(request.settled.value).toBe(true)
|
|
32
|
+
expect(request.loading.value).toBe(false)
|
|
33
|
+
expect(request.data.value?.pages).toEqual([])
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -7,25 +7,17 @@ import {
|
|
|
7
7
|
|
|
8
8
|
describe('module links', () => {
|
|
9
9
|
it('stay bare when nothing is selected', () => {
|
|
10
|
-
expect(pagesLink()).
|
|
11
|
-
expect(campaignsLink()).
|
|
12
|
-
|
|
13
|
-
query: {},
|
|
14
|
-
})
|
|
15
|
-
expect(funnelsLink()).toEqual({
|
|
16
|
-
path: '/modules/marketing/funnels',
|
|
17
|
-
query: {},
|
|
18
|
-
})
|
|
10
|
+
expect(pagesLink()).toBe('/modules/marketing/pages')
|
|
11
|
+
expect(campaignsLink()).toBe('/modules/marketing/campaigns')
|
|
12
|
+
expect(funnelsLink()).toBe('/modules/marketing/funnels')
|
|
19
13
|
})
|
|
20
14
|
|
|
21
|
-
it('
|
|
22
|
-
expect(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
query: { website: 'w1' },
|
|
29
|
-
})
|
|
15
|
+
it('serializes the exact button route and encodes selected values', () => {
|
|
16
|
+
expect(funnelsLink({ website: 'test-site-1' })).toBe(
|
|
17
|
+
'/modules/marketing/funnels?website=test-site-1',
|
|
18
|
+
)
|
|
19
|
+
expect(pagesLink({ website: 'w 1', path: '/pricing?x=1', period: '7d' })).toBe(
|
|
20
|
+
'/modules/marketing/pages?website=w+1&path=%2Fpricing%3Fx%3D1&period=7d',
|
|
21
|
+
)
|
|
30
22
|
})
|
|
31
23
|
})
|