Compare commits

...

1 Commits

Author SHA1 Message Date
Benjamin Lu
6720175e4c fix: add template search query context 2026-06-23 14:07:44 -07:00
5 changed files with 102 additions and 7 deletions

View File

@@ -6,6 +6,7 @@ import type { IFuseOptions } from 'fuse.js'
import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
import { TemplateIncludeOnDistributionEnum } from '@/platform/workflow/templates/types/template'
import { useTemplateFiltering } from '@/composables/useTemplateFiltering'
import { useSearchQueryTracking } from '@/platform/telemetry/searchQuery/useSearchQueryTracking'
const defaultSettingStore = {
get: vi.fn((key: string) => {
@@ -186,6 +187,47 @@ describe('useTemplateFiltering', () => {
])
})
it('passes active template filters and sort to search query tracking', async () => {
const templates = ref<TemplateInfo[]>([
{
name: 'api-template',
description: 'Enterprise API workflow for video',
mediaType: 'image',
mediaSubtype: 'png',
tags: ['Video'],
models: ['Flux'],
openSource: false
}
])
const { selectedModels, selectedUseCases, selectedRunsOn, sortBy } =
useTemplateFiltering(templates)
const calls = vi.mocked(useSearchQueryTracking).mock.calls
const context = calls[calls.length - 1][3]
expect(calls[calls.length - 1][0]).toBe('templates')
expect(context?.value).toEqual({
filters_applied: [],
sort: 'newest'
})
selectedModels.value = ['Flux', 'SDXL']
selectedUseCases.value = ['Video', 'Portrait']
selectedRunsOn.value = ['External or Remote API']
sortBy.value = 'popular'
await nextTick()
expect(context?.value).toEqual({
filters_applied: [
'model:Flux',
'use_case:Video',
'runs_on:External or Remote API'
],
sort: 'popular'
})
})
it('supports alphabetical, newest, and size-based sorting options', async () => {
const templates = ref<TemplateInfo[]>([
{

View File

@@ -7,6 +7,7 @@ import type { Ref } from 'vue'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useSearchQueryTracking } from '@/platform/telemetry/searchQuery/useSearchQueryTracking'
import type { SearchQueryContextMetadata } from '@/platform/telemetry/types'
import { TemplateIncludeOnDistributionEnum } from '@/platform/workflow/templates/types/template'
import type { TemplateInfo } from '@/platform/workflow/templates/types/template'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
@@ -286,6 +287,15 @@ export function useTemplateFiltering(
const filteredTemplates = computed(() => sortedTemplates.value)
const searchQueryContext = computed<SearchQueryContextMetadata>(() => ({
filters_applied: [
...activeModels.value.map((model) => `model:${model}`),
...activeUseCases.value.map((useCase) => `use_case:${useCase}`),
...selectedRunsOn.value.map((runsOn) => `runs_on:${runsOn}`)
],
sort: sortBy.value
}))
const resetFilters = () => {
searchQuery.value = ''
selectedModels.value = []
@@ -308,7 +318,12 @@ export function useTemplateFiltering(
const filteredCount = computed(() => filteredTemplates.value.length)
const totalCount = computed(() => visibleTemplates.value.length)
useSearchQueryTracking('templates', searchQuery, filteredTemplates)
useSearchQueryTracking(
'templates',
searchQuery,
filteredTemplates,
searchQueryContext
)
// Template filter tracking (debounced to avoid excessive events)
const debouncedTrackFilterChange = debounce(() => {

View File

@@ -2,6 +2,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { EffectScope, Ref } from 'vue'
import { effectScope, ref } from 'vue'
import type { SearchQueryContextMetadata } from '@/platform/telemetry/types'
const hoisted = vi.hoisted(() => ({
trackSearchQuery: vi.fn()
}))
@@ -20,11 +22,12 @@ describe('useSearchQueryTracking', () => {
function track(
query: Ref<string>,
results: Ref<{ length: number }>
results: Ref<{ length: number }>,
context?: Ref<SearchQueryContextMetadata>
): EffectScope {
const scope = effectScope()
scope.run(() => {
useSearchQueryTracking('node_sidebar', query, results)
useSearchQueryTracking('node_sidebar', query, results, context)
})
scopes.push(scope)
return scope
@@ -104,4 +107,29 @@ describe('useSearchQueryTracking', () => {
has_results: true
})
})
it('includes the latest optional search context', async () => {
const query = ref('')
const results = ref<string[]>(['a'])
const context = ref<SearchQueryContextMetadata>({
filters_applied: ['model:Flux'],
sort: 'newest'
})
track(query, results, context)
query.value = 'flux'
context.value = {
filters_applied: ['model:SDXL', 'use_case:Video'],
sort: 'popular'
}
await flush()
expect(hoisted.trackSearchQuery).toHaveBeenCalledExactlyOnceWith({
surface: 'node_sidebar',
query: 'flux',
query_length: 4,
result_count: 1,
has_results: true,
filters_applied: ['model:SDXL', 'use_case:Video'],
sort: 'popular'
})
})
})

View File

@@ -3,7 +3,10 @@ import { onScopeDispose, watch } from 'vue'
import type { Ref } from 'vue'
import { useTelemetry } from '@/platform/telemetry'
import type { SearchSurface } from '@/platform/telemetry/types'
import type {
SearchQueryContextMetadata,
SearchSurface
} from '@/platform/telemetry/types'
const DEBOUNCE_MS = 500
const MAX_QUERY_CHARS = 100
@@ -24,7 +27,8 @@ const MAX_QUERY_CHARS = 100
export function useSearchQueryTracking(
surface: SearchSurface,
query: Ref<string>,
results: Ref<{ length: number }>
results: Ref<{ length: number }>,
context?: Ref<SearchQueryContextMetadata>
): void {
const fire = debounce((value: string) => {
const trimmed = value.trim()
@@ -35,7 +39,8 @@ export function useSearchQueryTracking(
query: trimmed.slice(0, MAX_QUERY_CHARS),
query_length: trimmed.length,
result_count: count,
has_results: count > 0
has_results: count > 0,
...context?.value
})
}, DEBOUNCE_MS)

View File

@@ -294,7 +294,12 @@ export type SearchSurface =
| 'templates'
| 'settings'
export interface SearchQueryMetadata {
export interface SearchQueryContextMetadata {
filters_applied?: string[]
sort?: string
}
export interface SearchQueryMetadata extends SearchQueryContextMetadata {
surface: SearchSurface
query: string
query_length: number