@gscdump/nuxt 0.22.1 → 0.22.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.
- package/README.md +21 -9
- package/app/components/GscDashboardPage.vue +2 -2
- package/app/components/GscHero.vue +3 -4
- package/app/components/GscLazyTopResult.vue +7 -5
- package/app/components/GscPerformanceChart.vue +4 -4
- package/app/components/GscSourceDebugPanel.vue +2 -1
- package/app/composables/_useGscBackfill.ts +13 -5
- package/app/composables/_useGscResource.ts +144 -56
- package/app/composables/useGscAnalytics.ts +2 -1
- package/app/composables/useGscAnalyticsSourceInfo.ts +11 -5
- package/app/composables/useGscAnalyzer.ts +33 -25
- package/app/composables/useGscAnalyzerDefs.ts +2 -1
- package/app/composables/useGscCountries.ts +5 -6
- package/app/composables/useGscCurrentSite.ts +32 -11
- package/app/composables/useGscInspectionHistory.ts +5 -5
- package/app/composables/useGscInspections.ts +5 -5
- package/app/composables/useGscPanelContext.ts +1 -1
- package/app/composables/useGscQuery.ts +6 -8
- package/app/composables/useGscRequestIndexingInspect.ts +20 -11
- package/app/composables/useGscRollup.ts +66 -81
- package/app/composables/useGscSearchAppearance.ts +5 -6
- package/app/composables/useGscSiteAnalyzer.ts +93 -137
- package/app/composables/useGscSitemapHistory.ts +5 -5
- package/app/composables/useGscSitemaps.ts +5 -5
- package/app/queries/gsc.ts +163 -0
- package/app/utils/gsc-rpc.ts +18 -0
- package/module.ts +1 -1
- package/nuxt.config.ts +20 -0
- package/package.json +139 -8
- package/types.ts +1 -5
- package/app/composables/useGscParquetTable.ts +0 -198
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
// re-boot. Writes progress to the shared map so <GscBootProgress> lights up
|
|
12
12
|
// on boot regardless of mode.
|
|
13
13
|
|
|
14
|
-
import type { AnalysisParams, AnalysisResult } from '@gscdump/analysis'
|
|
15
14
|
import type { AnalysisSourcesResponse, SourceInfoResponse } from '@gscdump/contracts'
|
|
16
15
|
import type { AttachedTablesHandle, BrowserAnalysisRuntime, DuckDBWasmBootResult, QueryResult } from '@gscdump/engine-duckdb-wasm'
|
|
16
|
+
import type { AnalysisParams, AnalysisResult } from '@gscdump/engine/analysis-types'
|
|
17
|
+
import type { AnalyzerRegistry } from '@gscdump/engine/analyzer'
|
|
17
18
|
import type { SiteLoadProgress } from './useGscAnalytics'
|
|
18
|
-
import { defaultAnalyzerRegistry } from '@gscdump/analysis'
|
|
19
19
|
import { coerceRow } from '@gscdump/engine'
|
|
20
|
+
import { gscQueries } from '../queries/gsc'
|
|
21
|
+
import { useGscRpc } from '../utils/gsc-rpc'
|
|
20
22
|
import { useGscSharedSiteResource } from './_useGscSharedSiteResource'
|
|
21
23
|
import { useGscAnalyticsContext } from './useGscAnalytics'
|
|
22
24
|
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
@@ -55,23 +57,13 @@ const DEFAULT_ATTACH_FETCH_CONCURRENCY = 2
|
|
|
55
57
|
const DEFAULT_ATTACH_MAX_FILES = 32
|
|
56
58
|
const DEFAULT_ATTACH_MAX_BYTES = 16 * 1024 * 1024
|
|
57
59
|
|
|
60
|
+
let defaultAnalyzerRegistryPromise: Promise<AnalyzerRegistry> | null = null
|
|
61
|
+
|
|
58
62
|
interface AnalyzerRange {
|
|
59
63
|
start: string
|
|
60
64
|
end: string
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
interface AnalysisSourcesClient {
|
|
64
|
-
getAnalysisSources: (
|
|
65
|
-
siteId: string,
|
|
66
|
-
tables?: string[] | string | { tables?: string[] | string, searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
|
|
67
|
-
options?: { searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
|
|
68
|
-
) => Promise<AnalysisSourcesResponse>
|
|
69
|
-
getSourceInfo: (
|
|
70
|
-
siteId: string,
|
|
71
|
-
options?: { searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
|
|
72
|
-
) => Promise<SourceInfoResponse>
|
|
73
|
-
}
|
|
74
|
-
|
|
75
67
|
function normalizeSearchType(searchType: AnalysisParams['searchType']): NonNullable<AnalysisParams['searchType']> {
|
|
76
68
|
return searchType ?? DEFAULT_SEARCH_TYPE
|
|
77
69
|
}
|
|
@@ -89,18 +81,26 @@ function normalizeRange(range: AnalyzerRange | null | undefined): AnalyzerRange
|
|
|
89
81
|
return range?.start && range?.end ? range : null
|
|
90
82
|
}
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
84
|
+
type AnalyticsClient = ReturnType<typeof useGscAnalyticsClient>
|
|
85
|
+
|
|
86
|
+
function loadSourceInfo(client: AnalyticsClient, siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): Promise<SourceInfoResponse> {
|
|
87
|
+
return client.getSourceInfo(siteId, {
|
|
94
88
|
searchType,
|
|
95
89
|
...(range ? { start: range.start, end: range.end } : {}),
|
|
96
|
-
})
|
|
90
|
+
}) as Promise<SourceInfoResponse>
|
|
97
91
|
}
|
|
98
92
|
|
|
99
|
-
function loadAnalysisSources(siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): Promise<AnalysisSourcesResponse> {
|
|
100
|
-
return
|
|
93
|
+
function loadAnalysisSources(client: AnalyticsClient, siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): Promise<AnalysisSourcesResponse> {
|
|
94
|
+
return client.getAnalysisSources(siteId, undefined, {
|
|
101
95
|
searchType,
|
|
102
96
|
...(range ? { start: range.start, end: range.end } : {}),
|
|
103
|
-
})
|
|
97
|
+
}) as Promise<AnalysisSourcesResponse>
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function loadDefaultAnalyzerRegistry(): Promise<AnalyzerRegistry> {
|
|
101
|
+
defaultAnalyzerRegistryPromise ??= import('@gscdump/analysis/registry')
|
|
102
|
+
.then(m => m.defaultAnalyzerRegistry)
|
|
103
|
+
return defaultAnalyzerRegistryPromise
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
@@ -191,6 +191,8 @@ function createInstance(
|
|
|
191
191
|
const attachedTables = ref<string[]>([])
|
|
192
192
|
const timings = ref<GscAnalyzerTimings | null>(null)
|
|
193
193
|
const manifestVersion = ref<string | undefined>(undefined)
|
|
194
|
+
const client = useGscAnalyticsClient()
|
|
195
|
+
const rpc = useGscRpc()
|
|
194
196
|
|
|
195
197
|
function patch(p: Partial<SiteLoadProgress>): void {
|
|
196
198
|
patchProgress(siteId, { source: 'duckdb', ...p })
|
|
@@ -266,7 +268,7 @@ function createInstance(
|
|
|
266
268
|
patch({ stage: 'manifest', startedAt: Date.now(), filesAttached: 0, filesTotal: 0, error: undefined, endedAt: undefined })
|
|
267
269
|
// Probe the server-resolved source first. Its kind + attachedTables bit
|
|
268
270
|
// decides whether we boot DuckDB-WASM (expensive) or proxy to the server.
|
|
269
|
-
const info = await loadSourceInfo(siteId, searchType, range)
|
|
271
|
+
const info = await loadSourceInfo(client, siteId, searchType, range)
|
|
270
272
|
mode = info.browserAttachEligible ? 'browser-attached' : 'server'
|
|
271
273
|
|
|
272
274
|
if (mode === 'server') {
|
|
@@ -297,7 +299,7 @@ function createInstance(
|
|
|
297
299
|
|
|
298
300
|
patch({ stage: 'manifest' })
|
|
299
301
|
const t1 = performance.now()
|
|
300
|
-
const sources = await loadAnalysisSources(siteId, searchType, range)
|
|
302
|
+
const sources = await loadAnalysisSources(client, siteId, searchType, range)
|
|
301
303
|
const manifestMs = performance.now() - t1
|
|
302
304
|
|
|
303
305
|
const t2 = performance.now()
|
|
@@ -332,7 +334,11 @@ function createInstance(
|
|
|
332
334
|
}
|
|
333
335
|
|
|
334
336
|
async function runServerAnalyze(params: AnalysisParams, _signal?: AbortSignal): Promise<AnalysisResult & { queryMs: number }> {
|
|
335
|
-
const out = await
|
|
337
|
+
const out = await rpc.execute(
|
|
338
|
+
gscQueries.analyze(siteId),
|
|
339
|
+
{ ...params, searchType: params.searchType ?? searchType } as never,
|
|
340
|
+
{ silent: true },
|
|
341
|
+
) as AnalysisResult & { queryMs?: number }
|
|
336
342
|
return {
|
|
337
343
|
results: coerceResults(out.results) as AnalysisResult['results'],
|
|
338
344
|
meta: out.meta as AnalysisResult['meta'],
|
|
@@ -353,7 +359,9 @@ function createInstance(
|
|
|
353
359
|
const p = (async () => {
|
|
354
360
|
if (!rt)
|
|
355
361
|
return runServerAnalyze(scopedParams, opts?.signal)
|
|
356
|
-
const
|
|
362
|
+
const registry = await loadDefaultAnalyzerRegistry()
|
|
363
|
+
opts?.signal?.throwIfAborted?.()
|
|
364
|
+
const out = await rt.analyze(scopedParams as never, registry, { signal: opts?.signal })
|
|
357
365
|
opts?.signal?.throwIfAborted?.()
|
|
358
366
|
return {
|
|
359
367
|
results: coerceResults(out.results) as AnalysisResult['results'],
|
|
@@ -376,7 +384,7 @@ function createInstance(
|
|
|
376
384
|
await boot
|
|
377
385
|
if (mode !== 'browser-attached' || !runtime || !bootedDb)
|
|
378
386
|
return false
|
|
379
|
-
const sources = await loadAnalysisSources(siteId, searchType, range)
|
|
387
|
+
const sources = await loadAnalysisSources(client, siteId, searchType, range)
|
|
380
388
|
if (!runtime.isStale(sources.manifestVersion))
|
|
381
389
|
return false
|
|
382
390
|
// Drop the stale views before swapping in the new partitions. The runtime
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
// `ActionSource` union from `@gscdump/analysis`, so misspelt or unknown
|
|
13
13
|
// sources fail at typecheck instead of at runtime in the priority runner.
|
|
14
14
|
|
|
15
|
-
import type { ActionSource
|
|
15
|
+
import type { ActionSource } from '@gscdump/analysis'
|
|
16
|
+
import type { AnalysisTool } from '@gscdump/engine/analysis-types'
|
|
16
17
|
import type { Component } from '@vue/runtime-core'
|
|
17
18
|
|
|
18
19
|
export type GscAnalyzerKind = 'analyzer' | 'semantic' | 'action'
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { CountriesResponse, CountryRow, GscApiRange } from '@gscdump/contracts'
|
|
6
6
|
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
7
|
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
+
import { gscQueries } from '../queries/gsc'
|
|
8
9
|
import { useGscResource } from './_useGscResource'
|
|
9
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
10
10
|
|
|
11
11
|
export interface UseGscCountriesReturn {
|
|
12
12
|
response: Readonly<Ref<CountriesResponse | null>>
|
|
@@ -14,8 +14,8 @@ export interface UseGscCountriesReturn {
|
|
|
14
14
|
range: ComputedRef<GscApiRange | null>
|
|
15
15
|
source: ComputedRef<CountriesResponse['source'] | null>
|
|
16
16
|
loading: ComputedRef<boolean>
|
|
17
|
-
status: Ref<GscResourceStatus
|
|
18
|
-
error: Ref<Error | null
|
|
17
|
+
status: Readonly<Ref<GscResourceStatus>>
|
|
18
|
+
error: Readonly<Ref<Error | null>>
|
|
19
19
|
refresh: () => Promise<void>
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -23,14 +23,13 @@ export function useGscCountries(
|
|
|
23
23
|
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
24
24
|
range: MaybeRefOrGetter<{ start: string, end: string } | null | undefined>,
|
|
25
25
|
): UseGscCountriesReturn {
|
|
26
|
-
const { data, status, loading, error, refresh } = useGscResource({
|
|
26
|
+
const { data, status, loading, error, refresh } = useGscResource<[string, string, string], CountriesResponse>({
|
|
27
27
|
keys: [
|
|
28
28
|
siteId,
|
|
29
29
|
() => toValue(range)?.start,
|
|
30
30
|
() => toValue(range)?.end,
|
|
31
31
|
] as const,
|
|
32
|
-
|
|
33
|
-
useGscAnalyticsClient().getCountries(id, { start, end }),
|
|
32
|
+
operation: (id, start, end) => gscQueries.countries(id, { start, end }),
|
|
34
33
|
isEmpty: r => r.rows.length === 0,
|
|
35
34
|
})
|
|
36
35
|
|
|
@@ -1,15 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// The analytics layer is otherwise route-agnostic — this composable is the
|
|
5
|
-
// one place the `id` param name is hard-coded. Hosts using a different param
|
|
6
|
-
// name should provide their own equivalent.
|
|
7
|
-
|
|
8
|
-
export function useGscCurrentSite(): {
|
|
1
|
+
import type { ComputedRef, InjectionKey } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface GscCurrentSiteContext {
|
|
9
4
|
siteId: ComputedRef<string>
|
|
10
5
|
site: ReturnType<typeof useGscSite>
|
|
11
|
-
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const gscCurrentSiteKey: InjectionKey<GscCurrentSiteContext> = Symbol('gsc-current-site')
|
|
9
|
+
|
|
10
|
+
function createGscCurrentSiteContext(siteId: MaybeRefOrGetter<string | null | undefined>): GscCurrentSiteContext {
|
|
11
|
+
const resolvedSiteId = computed(() => String(toValue(siteId) ?? ''))
|
|
12
|
+
return { siteId: resolvedSiteId, site: useGscSite(resolvedSiteId) }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Provide the current-site context for a dashboard subtree. Hosts with a route
|
|
17
|
+
* shape other than `/sites/[id]/*` should call this from their layout/page.
|
|
18
|
+
*/
|
|
19
|
+
export function provideGscCurrentSite(siteId: MaybeRefOrGetter<string | null | undefined>): GscCurrentSiteContext {
|
|
20
|
+
const ctx = createGscCurrentSiteContext(siteId)
|
|
21
|
+
provide(gscCurrentSiteKey, ctx)
|
|
22
|
+
return ctx
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Read the current-site context. Falls back to the historical `/sites/[id]/*`
|
|
27
|
+
* route convention for existing hosts that have not added a provider yet.
|
|
28
|
+
*/
|
|
29
|
+
export function useGscCurrentSite(): GscCurrentSiteContext {
|
|
30
|
+
const provided = inject(gscCurrentSiteKey, null)
|
|
31
|
+
if (provided)
|
|
32
|
+
return provided
|
|
33
|
+
|
|
12
34
|
const route = useRoute()
|
|
13
|
-
|
|
14
|
-
return { siteId, site: useGscSite(siteId) }
|
|
35
|
+
return createGscCurrentSiteContext(() => route.params.id as string | undefined)
|
|
15
36
|
}
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
import type { InspectionHistoryRecord, InspectionHistoryResponse } from '@gscdump/contracts'
|
|
6
6
|
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
7
|
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
+
import { gscQueries } from '../queries/gsc'
|
|
8
9
|
import { useGscResource } from './_useGscResource'
|
|
9
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
10
10
|
|
|
11
11
|
export interface UseGscInspectionHistoryReturn {
|
|
12
12
|
response: Readonly<Ref<InspectionHistoryResponse | null>>
|
|
13
13
|
records: ComputedRef<InspectionHistoryRecord[]>
|
|
14
14
|
url: ComputedRef<string | null>
|
|
15
15
|
loading: ComputedRef<boolean>
|
|
16
|
-
status: Ref<GscResourceStatus
|
|
17
|
-
error: Ref<Error | null
|
|
16
|
+
status: Readonly<Ref<GscResourceStatus>>
|
|
17
|
+
error: Readonly<Ref<Error | null>>
|
|
18
18
|
refresh: () => Promise<void>
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -22,9 +22,9 @@ export function useGscInspectionHistory(
|
|
|
22
22
|
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
23
23
|
urlHash: MaybeRefOrGetter<string | null | undefined>,
|
|
24
24
|
): UseGscInspectionHistoryReturn {
|
|
25
|
-
const { data, status, loading, error, refresh } = useGscResource({
|
|
25
|
+
const { data, status, loading, error, refresh } = useGscResource<[string, string], InspectionHistoryResponse>({
|
|
26
26
|
keys: [siteId, urlHash] as const,
|
|
27
|
-
|
|
27
|
+
operation: (id, hash) => gscQueries.inspectionHistory(id, hash),
|
|
28
28
|
})
|
|
29
29
|
|
|
30
30
|
const records = computed(() => data.value?.records ?? [])
|
|
@@ -5,23 +5,23 @@
|
|
|
5
5
|
import type { InspectionHistoryRecord, InspectionIndex } from '@gscdump/contracts'
|
|
6
6
|
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
7
|
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
+
import { gscQueries } from '../queries/gsc'
|
|
8
9
|
import { useGscResource } from './_useGscResource'
|
|
9
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
10
10
|
|
|
11
11
|
export interface UseGscInspectionsReturn {
|
|
12
12
|
index: Readonly<Ref<InspectionIndex | null>>
|
|
13
13
|
records: ComputedRef<InspectionHistoryRecord[]>
|
|
14
14
|
statusCounts: ComputedRef<{ PASS: number, NEUTRAL: number, FAIL: number, unknown: number }>
|
|
15
15
|
loading: ComputedRef<boolean>
|
|
16
|
-
status: Ref<GscResourceStatus
|
|
17
|
-
error: Ref<Error | null
|
|
16
|
+
status: Readonly<Ref<GscResourceStatus>>
|
|
17
|
+
error: Readonly<Ref<Error | null>>
|
|
18
18
|
refresh: () => Promise<void>
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function useGscInspections(siteId: MaybeRefOrGetter<string | null | undefined>): UseGscInspectionsReturn {
|
|
22
|
-
const { data, status, loading, error, refresh } = useGscResource({
|
|
22
|
+
const { data, status, loading, error, refresh } = useGscResource<[string], InspectionIndex>({
|
|
23
23
|
keys: [siteId] as const,
|
|
24
|
-
|
|
24
|
+
operation: (id: string) => gscQueries.inspections(id),
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
const records = computed<InspectionHistoryRecord[]>(() =>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// `/analyze` provides these once; panels inject. Type-only — runtime values
|
|
7
7
|
// are the InjectionKey Symbols themselves.
|
|
8
8
|
|
|
9
|
-
import type { AnalysisParams, AnalysisResult } from '@gscdump/analysis'
|
|
9
|
+
import type { AnalysisParams, AnalysisResult } from '@gscdump/engine/analysis-types'
|
|
10
10
|
import type { InjectionKey } from '@vue/runtime-core'
|
|
11
11
|
|
|
12
12
|
export interface GscPanelRunner {
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
// AnalysisParams to `/api/__gsc/sites/[siteId]/analyze`. Override if your server
|
|
7
7
|
// uses a different contract.
|
|
8
8
|
|
|
9
|
-
import type { AnalysisParams, AnalysisResult } from '@gscdump/analysis'
|
|
9
|
+
import type { AnalysisParams, AnalysisResult } from '@gscdump/engine/analysis-types'
|
|
10
10
|
import type { ComputedRef, Ref, WatchSource } from '@vue/runtime-core'
|
|
11
|
+
import { gscQueries, withDefaultGscSearchType } from '../queries/gsc'
|
|
11
12
|
import { classifyGscError } from '../utils/gsc-error'
|
|
13
|
+
import { useGscRpc } from '../utils/gsc-rpc'
|
|
12
14
|
import { useGscBackfill } from './_useGscBackfill'
|
|
13
15
|
import { useGscQueryDispatcher } from './_useGscQueryDispatcher'
|
|
14
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
15
16
|
import { useGscAnalyzer } from './useGscAnalyzer'
|
|
16
17
|
import { _useGscAuthInternal } from './useGscAuth'
|
|
17
18
|
|
|
@@ -128,17 +129,14 @@ function isEmpty(v: unknown): boolean {
|
|
|
128
129
|
return false
|
|
129
130
|
}
|
|
130
131
|
|
|
131
|
-
async function defaultServerFallback<T>(siteId: string, params: AnalysisParams): Promise<T> {
|
|
132
|
-
return await useGscAnalyticsClient().analyze<T>(siteId, params)
|
|
133
|
-
}
|
|
134
|
-
|
|
135
132
|
const DEFAULT_SEARCH_TYPE: NonNullable<AnalysisParams['searchType']> = 'web'
|
|
136
133
|
|
|
137
134
|
function withDefaultSearchType(params: AnalysisParams): AnalysisParams {
|
|
138
|
-
return
|
|
135
|
+
return withDefaultGscSearchType(params)
|
|
139
136
|
}
|
|
140
137
|
|
|
141
138
|
export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): UseGscQueryReturn<T> {
|
|
139
|
+
const rpc = useGscRpc()
|
|
142
140
|
const querySearchType = computed(() => toValue(opts.params).searchType ?? DEFAULT_SEARCH_TYPE)
|
|
143
141
|
const queryRange = computed(() => {
|
|
144
142
|
const params = toValue(opts.params)
|
|
@@ -176,7 +174,7 @@ export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): Us
|
|
|
176
174
|
|
|
177
175
|
async function runServer(siteId: string): Promise<void> {
|
|
178
176
|
const t0 = performance.now()
|
|
179
|
-
const fn = opts.serverFallback ??
|
|
177
|
+
const fn = opts.serverFallback ?? (async (siteId: string, params: AnalysisParams) => rpc.execute(gscQueries.analyze(siteId), params as never, { silent: true }) as Promise<T>)
|
|
180
178
|
const out = await fn(siteId, withDefaultSearchType(toValue(opts.params))) as T
|
|
181
179
|
data.value = out
|
|
182
180
|
engine.value = 'server'
|
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import type { IndexingInspectRateLimited, IndexingInspectResponse } from '@gscdump/contracts'
|
|
2
|
-
import {
|
|
2
|
+
import { invalidateNuxtQueries } from 'nuxt-use-query/query-cache'
|
|
3
|
+
import { gscQueries } from '../queries/gsc'
|
|
4
|
+
import { useGscRpc } from '../utils/gsc-rpc'
|
|
3
5
|
|
|
4
6
|
export function useGscRequestIndexingInspect(): (
|
|
5
7
|
siteId: string,
|
|
6
8
|
urls: string[],
|
|
7
9
|
) => Promise<IndexingInspectResponse | IndexingInspectRateLimited> {
|
|
8
|
-
const
|
|
10
|
+
const rpc = useGscRpc()
|
|
9
11
|
return async (siteId, urls) => {
|
|
10
|
-
return
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
return rpc.execute(gscQueries.indexingInspect(siteId), { urls }, { silent: true })
|
|
13
|
+
.then((res) => {
|
|
14
|
+
invalidateNuxtQueries(`gsc:inspections:${siteId}`)
|
|
15
|
+
invalidateNuxtQueries(`gsc:inspection-history:${siteId}`)
|
|
16
|
+
return res as IndexingInspectResponse | IndexingInspectRateLimited
|
|
17
|
+
})
|
|
18
|
+
.catch((err: unknown) => {
|
|
19
|
+
const e = err as { type?: string, status?: number, statusCode?: number, response?: { status?: number }, data?: unknown }
|
|
20
|
+
const status = e.status
|
|
21
|
+
?? e.statusCode
|
|
22
|
+
?? e.response?.status
|
|
23
|
+
const data = e.data as IndexingInspectRateLimited | undefined
|
|
24
|
+
if (status === 429 && data && data.error === 'rate_limited')
|
|
25
|
+
return data
|
|
26
|
+
throw err
|
|
27
|
+
})
|
|
19
28
|
}
|
|
20
29
|
}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
import type { RollupEnvelope } from '@gscdump/contracts'
|
|
13
13
|
import type { SiteListItem } from './useGscAnalytics'
|
|
14
|
+
import { gscQueries } from '../queries/gsc'
|
|
15
|
+
import { useGscRpc } from '../utils/gsc-rpc'
|
|
14
16
|
import { useGscResource } from './_useGscResource'
|
|
15
17
|
import { useGscAnalyticsContext } from './useGscAnalytics'
|
|
16
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
17
18
|
|
|
18
19
|
type RollupsInput = MaybeRefOrGetter<string | readonly string[]>
|
|
19
20
|
type SiteLike = string | { id: string } | SiteListItem
|
|
@@ -62,9 +63,11 @@ export function useGscRollup<T = unknown>(
|
|
|
62
63
|
opts: UseGscRollupOptions = {},
|
|
63
64
|
): UseGscRollupReturn<T> {
|
|
64
65
|
const ctx = useGscAnalyticsContext()
|
|
66
|
+
const rpc = useGscRpc()
|
|
65
67
|
const resource = useGscResource<[string, string], RollupEnvelope<T> | null>({
|
|
68
|
+
namespace: 'gsc-rollup',
|
|
66
69
|
keys: [siteId, rollupId],
|
|
67
|
-
fetcher: (id, rid) => fetchOne<T>(id, rid, ctx, toValue(opts.range) ?? null),
|
|
70
|
+
fetcher: (id, rid) => fetchOne<T>(rpc, id, rid, ctx, toValue(opts.range) ?? null),
|
|
68
71
|
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
69
72
|
isEmpty: env => env == null,
|
|
70
73
|
})
|
|
@@ -84,43 +87,31 @@ export function useGscRollups<T = unknown>(
|
|
|
84
87
|
opts: UseGscRollupOptions = {},
|
|
85
88
|
): UseGscRollupsReturn<T> {
|
|
86
89
|
const ctx = useGscAnalyticsContext()
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
loading.value = true
|
|
99
|
-
const range = toValue(opts.range) ?? null
|
|
100
|
-
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
101
|
-
try {
|
|
90
|
+
const rpc = useGscRpc()
|
|
91
|
+
const rollupKey = computed(() => encodeStringList(normaliseRollups(toValue(rollupIds))))
|
|
92
|
+
const resource = useGscResource<[string, string], Record<string, RollupEnvelope<T> | null>>({
|
|
93
|
+
namespace: 'gsc-rollups',
|
|
94
|
+
keys: [siteId, rollupKey],
|
|
95
|
+
fetcher: async (sid, encoded) => {
|
|
96
|
+
const rids = decodeStringList(encoded)
|
|
97
|
+
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
98
|
+
const range = toValue(opts.range) ?? null
|
|
102
99
|
await Promise.all(rids.map(async (rid) => {
|
|
103
|
-
next[rid] = await fetchOne<T>(sid, rid, ctx, range)
|
|
100
|
+
next[rid] = await fetchOne<T>(rpc, sid, rid, ctx, range)
|
|
104
101
|
}))
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
watch(
|
|
113
|
-
() => [toValue(siteId), normaliseRollups(toValue(rollupIds)).join(','), toValue(opts.range)?.start, toValue(opts.range)?.end],
|
|
114
|
-
refresh,
|
|
115
|
-
{ immediate: true },
|
|
116
|
-
)
|
|
102
|
+
return next
|
|
103
|
+
},
|
|
104
|
+
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
105
|
+
isEmpty: value => Object.keys(value).length === 0,
|
|
106
|
+
})
|
|
107
|
+
const envelopes = computed<Record<string, RollupEnvelope<T> | null>>(() => resource.data.value ?? {})
|
|
117
108
|
|
|
118
109
|
return {
|
|
119
110
|
get: (rollupId: string) => envelopes.value[rollupId] ?? null,
|
|
120
111
|
payload: (rollupId: string) => envelopes.value[rollupId]?.payload ?? null,
|
|
121
|
-
envelopes,
|
|
122
|
-
loading: loading as Readonly<Ref<boolean>>,
|
|
123
|
-
refresh,
|
|
112
|
+
envelopes: envelopes as unknown as Ref<Record<string, RollupEnvelope<T> | null>>,
|
|
113
|
+
loading: resource.loading as unknown as Readonly<Ref<boolean>>,
|
|
114
|
+
refresh: resource.refresh,
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
117
|
|
|
@@ -134,56 +125,39 @@ export function useGscRollupFanout<T = unknown>(
|
|
|
134
125
|
opts: UseGscRollupOptions = {},
|
|
135
126
|
): UseGscRollupFanoutReturn<T> {
|
|
136
127
|
const ctx = useGscAnalyticsContext()
|
|
137
|
-
const
|
|
138
|
-
const loading = ref(false)
|
|
128
|
+
const rpc = useGscRpc()
|
|
139
129
|
const progress = ref<{ completed: number, total: number }>({ completed: 0, total: 0 })
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
envelopes.value = {}
|
|
150
|
-
progress.value = { completed: 0, total: 0 }
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
loading.value = true
|
|
154
|
-
// Seed envelopes with nulls so the UI can render skeleton rows before any
|
|
155
|
-
// fetch resolves. Writes land incrementally as each site returns.
|
|
156
|
-
const seeded: Record<string, RollupEnvelope<T> | null> = {}
|
|
157
|
-
for (const sid of siteIds) seeded[sid] = null
|
|
158
|
-
envelopes.value = seeded
|
|
159
|
-
progress.value = { completed: 0, total: siteIds.length }
|
|
160
|
-
const range = toValue(opts.range) ?? null
|
|
161
|
-
try {
|
|
130
|
+
const siteKey = computed(() => encodeStringList(normaliseSites(toValue(sites))))
|
|
131
|
+
const resource = useGscResource<[string, string], Record<string, RollupEnvelope<T> | null>>({
|
|
132
|
+
namespace: 'gsc-rollup-fanout',
|
|
133
|
+
keys: [siteKey, rollupId],
|
|
134
|
+
fetcher: async (encoded, rid) => {
|
|
135
|
+
const siteIds = decodeStringList(encoded)
|
|
136
|
+
progress.value = { completed: 0, total: siteIds.length }
|
|
137
|
+
const range = toValue(opts.range) ?? null
|
|
138
|
+
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
162
139
|
await Promise.all(siteIds.map(async (sid) => {
|
|
163
|
-
|
|
164
|
-
if (token !== runToken)
|
|
165
|
-
return
|
|
166
|
-
envelopes.value = { ...envelopes.value, [sid]: env }
|
|
140
|
+
next[sid] = await fetchOne<T>(rpc, sid, rid, ctx, range)
|
|
167
141
|
progress.value = { completed: progress.value.completed + 1, total: siteIds.length }
|
|
168
142
|
}))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
143
|
+
return next
|
|
144
|
+
},
|
|
145
|
+
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
146
|
+
isEmpty: value => Object.keys(value).length === 0,
|
|
147
|
+
})
|
|
148
|
+
const envelopes = computed<Record<string, RollupEnvelope<T> | null>>(() => resource.data.value ?? {})
|
|
149
|
+
watch(resource.loading, (loading) => {
|
|
150
|
+
if (loading)
|
|
151
|
+
return
|
|
152
|
+
if (!resource.data.value)
|
|
153
|
+
progress.value = { completed: 0, total: 0 }
|
|
154
|
+
}, { immediate: true })
|
|
181
155
|
|
|
182
156
|
return {
|
|
183
|
-
envelopes,
|
|
184
|
-
loading: loading as Readonly<Ref<boolean>>,
|
|
157
|
+
envelopes: envelopes as unknown as Ref<Record<string, RollupEnvelope<T> | null>>,
|
|
158
|
+
loading: resource.loading as unknown as Readonly<Ref<boolean>>,
|
|
185
159
|
progress: progress as Readonly<Ref<{ completed: number, total: number }>>,
|
|
186
|
-
refresh,
|
|
160
|
+
refresh: resource.refresh,
|
|
187
161
|
}
|
|
188
162
|
}
|
|
189
163
|
|
|
@@ -204,7 +178,19 @@ function normaliseSites(input: unknown): string[] {
|
|
|
204
178
|
return out
|
|
205
179
|
}
|
|
206
180
|
|
|
181
|
+
function encodeStringList(values: string[]): string {
|
|
182
|
+
return values.length > 0 ? JSON.stringify(values) : ''
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function decodeStringList(encoded: string): string[] {
|
|
186
|
+
const parsed = JSON.parse(encoded) as unknown
|
|
187
|
+
return Array.isArray(parsed)
|
|
188
|
+
? parsed.filter((v): v is string => typeof v === 'string' && v.length > 0)
|
|
189
|
+
: []
|
|
190
|
+
}
|
|
191
|
+
|
|
207
192
|
async function fetchOne<T>(
|
|
193
|
+
rpc: ReturnType<typeof useGscRpc>,
|
|
208
194
|
siteId: string,
|
|
209
195
|
rollupId: string,
|
|
210
196
|
ctx: ReturnType<typeof useGscAnalyticsContext>,
|
|
@@ -220,11 +206,10 @@ async function fetchOne<T>(
|
|
|
220
206
|
endedAt: undefined,
|
|
221
207
|
})
|
|
222
208
|
try {
|
|
223
|
-
const env = await
|
|
224
|
-
siteId,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
209
|
+
const env = await rpc.query(
|
|
210
|
+
gscQueries.rollup(siteId, rollupId, range ?? undefined),
|
|
211
|
+
{ silent: true },
|
|
212
|
+
) as RollupEnvelope<T>
|
|
228
213
|
ctx.patchProgress(siteId, { stage: 'ready', filesAttached: 1, endedAt: Date.now() })
|
|
229
214
|
return env
|
|
230
215
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { GscApiRange, SearchAppearanceResponse, SearchAppearanceRow } from '@gscdump/contracts'
|
|
6
6
|
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
7
|
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
+
import { gscQueries } from '../queries/gsc'
|
|
8
9
|
import { useGscResource } from './_useGscResource'
|
|
9
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
10
10
|
|
|
11
11
|
export interface UseGscSearchAppearanceReturn {
|
|
12
12
|
response: Readonly<Ref<SearchAppearanceResponse | null>>
|
|
@@ -14,8 +14,8 @@ export interface UseGscSearchAppearanceReturn {
|
|
|
14
14
|
range: ComputedRef<GscApiRange | null>
|
|
15
15
|
source: ComputedRef<SearchAppearanceResponse['source'] | null>
|
|
16
16
|
loading: ComputedRef<boolean>
|
|
17
|
-
status: Ref<GscResourceStatus
|
|
18
|
-
error: Ref<Error | null
|
|
17
|
+
status: Readonly<Ref<GscResourceStatus>>
|
|
18
|
+
error: Readonly<Ref<Error | null>>
|
|
19
19
|
refresh: () => Promise<void>
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -23,14 +23,13 @@ export function useGscSearchAppearance(
|
|
|
23
23
|
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
24
24
|
range: MaybeRefOrGetter<{ start: string, end: string } | null | undefined>,
|
|
25
25
|
): UseGscSearchAppearanceReturn {
|
|
26
|
-
const { data, status, loading, error, refresh } = useGscResource({
|
|
26
|
+
const { data, status, loading, error, refresh } = useGscResource<[string, string, string], SearchAppearanceResponse>({
|
|
27
27
|
keys: [
|
|
28
28
|
siteId,
|
|
29
29
|
() => toValue(range)?.start,
|
|
30
30
|
() => toValue(range)?.end,
|
|
31
31
|
] as const,
|
|
32
|
-
|
|
33
|
-
useGscAnalyticsClient().getSearchAppearance(id, { start, end }),
|
|
32
|
+
operation: (id, start, end) => gscQueries.searchAppearance(id, { start, end }),
|
|
34
33
|
isEmpty: r => r.rows.length === 0,
|
|
35
34
|
})
|
|
36
35
|
|