feat(search): omit default sort while searching so results rank by relevance

The web client always sent sort_by/order_by (the view's default sort), so the
backend's userProvidedSort flag was always true and the new BM25 relevance
ranking never engaged from the web app. When a search is active and the user
has not explicitly chosen a sort, omit the sort entirely so the backend ranks
results by relevance. An explicit user sort still suppresses ranking and
non-search browsing is unchanged.
This commit is contained in:
kolaente 2026-06-21 19:36:03 +02:00
parent 6f6f91bd28
commit a2cb2826d0
2 changed files with 91 additions and 0 deletions

View File

@ -0,0 +1,83 @@
import {describe, it, expect, beforeEach, vi} from 'vitest'
import {defineComponent, h, nextTick} from 'vue'
import {mount, flushPromises} from '@vue/test-utils'
import {setActivePinia, createPinia} from 'pinia'
import {createRouter, createMemoryHistory, type Router} from 'vue-router'
const getAll = vi.fn(async () => [])
vi.mock('@/services/taskCollection', async (importOriginal) => {
const actual = await importOriginal<typeof import('@/services/taskCollection')>()
return {
...actual,
default: class {
loading = false
totalPages = 1
getAll = getAll
},
}
})
import {useTaskList} from './useTaskList'
// The second positional argument passed to TaskCollectionService.getAll carries
// the sort_by/order_by the backend uses to decide whether to rank by relevance.
function lastRequestParams(): Record<string, unknown> {
return getAll.mock.calls.at(-1)?.[1] as Record<string, unknown>
}
async function mountTaskList(query: Record<string, string>): Promise<Router> {
const router = createRouter({
history: createMemoryHistory(),
routes: [{path: '/', name: 'home', component: {render: () => null}}],
})
await router.push({path: '/', query})
await router.isReady()
const TestComponent = defineComponent({
setup() {
useTaskList(() => 1, () => 1)
return () => h('div')
},
})
mount(TestComponent, {global: {plugins: [router]}})
await flushPromises()
await nextTick()
return router
}
describe('useTaskList sort handling for relevance ranking', () => {
beforeEach(() => {
setActivePinia(createPinia())
getAll.mockClear()
})
it('omits the sort while searching with the default sort so the backend ranks by relevance', async () => {
await mountTaskList({s: 'find me'})
const params = lastRequestParams()
expect(params.s).toBe('find me')
expect(params.sort_by).toEqual([])
expect(params.order_by).toEqual([])
})
it('keeps an explicit user sort while searching so the user sort is respected', async () => {
await mountTaskList({s: 'find me', sort: 'title:asc'})
const params = lastRequestParams()
expect(params.s).toBe('find me')
expect(params.sort_by).toEqual(['title'])
expect(params.order_by).toEqual(['asc'])
})
it('sends the default sort when not searching', async () => {
await mountTaskList({})
const params = lastRequestParams()
expect(params.s).toBe('')
expect(params.sort_by).not.toHaveLength(0)
// id always sorts last so other sort columns take precedence.
expect(params.sort_by).toEqual(['id'])
expect(params.order_by).toEqual(['desc'])
})
})

View File

@ -122,6 +122,14 @@ export function useTaskList(
const allParams = computed(() => {
const loadParams = {...params.value}
// Relevance ranking only engages when no sort is sent, so omit the default
// sort while searching and let an explicit user sort still take precedence.
if (loadParams.s && !sortQuery.value) {
loadParams.sort_by = []
loadParams.order_by = []
return loadParams
}
return formatSortOrder(sortBy.value, loadParams)
})