perf: search (#1091)

This commit is contained in:
Jonas Plum
2024-08-03 14:58:55 +02:00
committed by GitHub
parent b929100d30
commit 84ae933cfb
6 changed files with 90 additions and 42 deletions

11
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
coverage:
status:
project:
default:
threshold: 5%
patch: off
comment:
layout: diff
parsers:
go:
partials_as_hits: true

View File

@@ -0,0 +1,49 @@
package migrations
import (
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
)
const searchViewName = "ticket_search"
const searchViewQuery = `
SELECT
tickets.id,
tickets.name,
tickets.created,
tickets.description,
tickets.open,
tickets.type,
tickets.state,
users.name as owner_name,
group_concat(comments.message) as comment_messages,
group_concat(files.name) as file_names,
group_concat(links.name) as link_names,
group_concat(links.url) as link_urls,
group_concat(tasks.name) as task_names,
group_concat(timeline.message) as timeline_messages
FROM tickets
LEFT JOIN comments ON comments.ticket = tickets.id
LEFT JOIN files ON files.ticket = tickets.id
LEFT JOIN links ON links.ticket = tickets.id
LEFT JOIN tasks ON tasks.ticket = tickets.id
LEFT JOIN timeline ON timeline.ticket = tickets.id
LEFT JOIN users ON users.id = tickets.owner
GROUP BY tickets.id
`
func searchViewUp(db dbx.Builder) error {
return daos.New(db).SaveCollection(internalView(searchViewName, searchViewQuery))
}
func searchViewDown(db dbx.Builder) error {
dao := daos.New(db)
id, err := dao.FindCollectionByNameOrId(searchViewName)
if err != nil {
return err
}
return dao.DeleteCollection(id)
}

View File

@@ -11,4 +11,5 @@ func Register() {
migrations.Register(viewsUp, viewsDown, "1700000004_views.go")
migrations.Register(reactionsUp, reactionsDown, "1700000005_reactions.go")
migrations.Register(systemuserUp, systemuserDown, "1700000006_systemuser.go")
migrations.Register(searchViewUp, searchViewDown, "1700000007_search_view.go")
}

View File

@@ -17,9 +17,8 @@ import {
import { ScrollArea } from '@/components/ui/scroll-area'
import { Separator } from '@/components/ui/separator'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import { Info, LoaderCircle, Search } from 'lucide-vue-next'
import { LoaderCircle, Search } from 'lucide-vue-next'
import { useQuery } from '@tanstack/vue-query'
import debounce from 'lodash.debounce'
@@ -28,7 +27,7 @@ import { computed, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { pb } from '@/lib/pocketbase'
import type { Ticket, Type } from '@/lib/types'
import type { SearchTicket, Type } from '@/lib/types'
const router = useRouter()
const route = useRoute()
@@ -48,15 +47,13 @@ const filter = computed(() => {
let raws: Array<string> = [
'name ~ {:search}',
'description ~ {:search}',
'owner.name ~ {:search}',
'owner.email ~ {:search}',
'links_via_ticket.name ~ {:search}',
'links_via_ticket.url ~ {:search}',
'tasks_via_ticket.name ~ {:search}',
'comments_via_ticket.message ~ {:search}',
'files_via_ticket.name ~ {:search}',
'timeline_via_ticket.message ~ {:search}',
'state.severity ~ {:search}'
'owner_name ~ {:search}',
'comment_messages ~ {:search}',
'file_names ~ {:search}',
'link_names ~ {:search}',
'link_urls ~ {:search}',
'task_names ~ {:search}',
'timeline_messages ~ {:search}'
]
Object.keys(props.selectedType.schema.properties).forEach((key) => {
@@ -96,12 +93,10 @@ const {
refetch
} = useQuery({
queryKey: ['tickets', filter.value],
queryFn: (): Promise<ListResult<Ticket>> =>
pb.collection('tickets').getList(page.value, perPage.value, {
queryFn: (): Promise<ListResult<SearchTicket>> =>
pb.collection('ticket_search').getList(page.value, perPage.value, {
sort: '-created',
filter: filter.value,
expand:
'type,owner,comments_via_ticket.author,files_via_ticket,timeline_via_ticket,links_via_ticket,tasks_via_ticket.owner'
filter: filter.value
})
})
@@ -139,9 +134,9 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
<Tabs v-model="tab" class="flex flex-1 flex-col overflow-hidden">
<div class="flex items-center justify-between px-4 pt-2">
<TabsList>
<TabsTrigger value="all"> All</TabsTrigger>
<TabsTrigger value="open"> Open</TabsTrigger>
<TabsTrigger value="closed"> Closed</TabsTrigger>
<TabsTrigger value="all">All</TabsTrigger>
<TabsTrigger value="open">Open</TabsTrigger>
<TabsTrigger value="closed">Closed</TabsTrigger>
</TabsList>
<!-- Button variant="outline" size="sm" class="h-7 gap-1 rounded-md px-3">
<ListFilter class="h-3.5 w-3.5" />
@@ -155,23 +150,6 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
<span class="absolute inset-y-0 start-0 flex items-center justify-center px-2">
<Search class="size-4 text-muted-foreground" />
</span>
<div>
<TooltipProvider :delay-duration="0">
<Tooltip>
<TooltipTrigger as-child>
<Info class="ml-2 size-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p class="w-64">
Search name, description, or owner. Links, tasks, comments, files, and
timeline messages are also searched, but cause unreliable results if there are
more than 1000 records.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
</form>
</div>
@@ -222,7 +200,6 @@ watch([tab, props.selectedType, page, perPage], () => refetch())
</PaginationListItem>
<PaginationEllipsis v-else :key="item.type" :index="index" />
</template>
<PaginationNext />
<PaginationLast />
</PaginationList>

View File

@@ -3,12 +3,12 @@ import ResourceListElement from '@/components/common/ResourceListElement.vue'
import { useRoute } from 'vue-router'
import type { Ticket } from '@/lib/types'
import type { SearchTicket } from '@/lib/types'
const route = useRoute()
defineProps<{
tickets: Array<Ticket>
tickets: Array<SearchTicket>
}>()
</script>
@@ -19,10 +19,10 @@ defineProps<{
:key="item.id"
:title="item.name"
:created="item.created"
:subtitle="item.expand.owner ? item.expand.owner.name : ''"
:subtitle="item.owner_name"
:description="item.description ? item.description.substring(0, 300) : ''"
:active="route.params.id === item.id"
:to="`/tickets/${item.expand.type.id}/${item.id}`"
:to="`/tickets/${item.type}/${item.id}`"
:open="item.open"
/>
</div>

View File

@@ -32,6 +32,16 @@ export interface Ticket {
}
}
export interface SearchTicket {
id: string
name: string
created: string
description: string
open: boolean
type: string
owner_name: string
}
export interface Task {
id: string