UBER-803: Fix slow filter (#3634)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-08-25 17:15:41 +07:00 committed by GitHub
parent 97abd2fc12
commit 72c026d505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 28 deletions

View File

@ -224,14 +224,14 @@ export class LiveQuery {
const unsub = liveQuery.query( const unsub = liveQuery.query(
_class, _class,
piplineQuery.query ?? query, query,
(result) => { (result) => {
// If we have one more request after this one, no need to do something. // If we have one more request after this one, no need to do something.
if (id === this.reqId) { if (id === this.reqId) {
callback(result) callback(result)
} }
}, },
piplineQuery.options ?? options options
) )
this.unsubscribe = () => { this.unsubscribe = () => {
unsub() unsub()

View File

@ -17,7 +17,7 @@
import type { DisplayTx, TxViewlet } from '@hcengineering/activity' import type { DisplayTx, TxViewlet } from '@hcengineering/activity'
import attachment from '@hcengineering/attachment' import attachment from '@hcengineering/attachment'
import chunter from '@hcengineering/chunter' import chunter from '@hcengineering/chunter'
import contact, { Employee, PersonAccount, getName } from '@hcengineering/contact' import contact, { Person, PersonAccount, getName } from '@hcengineering/contact'
import core, { AnyAttribute, Class, Doc, Ref, TxCUD, getCurrentAccount } from '@hcengineering/core' import core, { AnyAttribute, Class, Doc, Ref, TxCUD, getCurrentAccount } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform' import { Asset } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
@ -56,7 +56,7 @@
let viewlet: TxDisplayViewlet | undefined let viewlet: TxDisplayViewlet | undefined
let props: any let props: any
let account: PersonAccount | undefined let account: PersonAccount | undefined
let employee: Employee | undefined let person: Person | undefined
let model: AttributeModel[] = [] let model: AttributeModel[] = []
let modelIcon: Asset | undefined = undefined let modelIcon: Asset | undefined = undefined
let iconComponent: AnyComponent | undefined = undefined let iconComponent: AnyComponent | undefined = undefined
@ -67,7 +67,7 @@
$: if (tx.tx._id !== ptx?.tx._id) { $: if (tx.tx._id !== ptx?.tx._id) {
if (tx.tx.modifiedBy !== account?._id) { if (tx.tx.modifiedBy !== account?._id) {
account = undefined account = undefined
employee = undefined person = undefined
} }
viewlet = undefined viewlet = undefined
props = undefined props = undefined
@ -107,10 +107,10 @@
$: account && $: account &&
employeeQuery.query( employeeQuery.query(
contact.mixin.Employee, contact.class.Person,
{ _id: account.person as Ref<Employee> }, { _id: account.person },
(res) => { (res) => {
;[employee] = res ;[person] = res
}, },
{ limit: 1 } { limit: 1 }
) )
@ -191,7 +191,7 @@
{#if showIcon} {#if showIcon}
{#if withAvatar} {#if withAvatar}
<div class="msgactivity-avatar"> <div class="msgactivity-avatar">
<Component is={contact.component.Avatar} props={{ avatar: employee?.avatar, size: 'medium' }} /> <Component is={contact.component.Avatar} props={{ avatar: person?.avatar, size: 'medium' }} />
</div> </div>
{:else} {:else}
<div class="msgactivity-icon"> <div class="msgactivity-icon">
@ -212,8 +212,8 @@
<div class="msgactivity-content__header"> <div class="msgactivity-content__header">
<div class="msgactivity-content__title labels-row"> <div class="msgactivity-content__title labels-row">
<span class={withAvatar ? 'bold' : 'strong'}> <span class={withAvatar ? 'bold' : 'strong'}>
{#if employee} {#if person}
{getName(client.getHierarchy(), employee)} {getName(client.getHierarchy(), person)}
{:else} {:else}
<Label label={core.string.System} /> <Label label={core.string.System} />
{/if} {/if}

View File

@ -96,8 +96,8 @@ export class AggregationMiddleware extends BasePresentationMiddleware implements
const statusFields: Array<Attribute<Doc>> = [] const statusFields: Array<Attribute<Doc>> = []
const allAttrs = h.getAllAttributes(_class) const allAttrs = h.getAllAttributes(_class)
const updatedQuery: DocumentQuery<T> = { ...(ret.query ?? query) } const updatedQuery: DocumentQuery<T> = h.clone(ret.query ?? query)
const finalOptions = { ...(ret.options ?? options ?? {}) } const finalOptions = h.clone(ret.options ?? options ?? {})
await this.updateQueryOptions<T>(allAttrs, h, statusFields, updatedQuery, finalOptions) await this.updateQueryOptions<T>(allAttrs, h, statusFields, updatedQuery, finalOptions)
@ -139,11 +139,13 @@ export class AggregationMiddleware extends BasePresentationMiddleware implements
const docFields: Array<Attribute<Doc>> = [] const docFields: Array<Attribute<Doc>> = []
const h = this.client.getHierarchy() const h = this.client.getHierarchy()
const allAttrs = h.getAllAttributes(_class) const allAttrs = h.getAllAttributes(_class)
const finalOptions = options ?? {} const finalOptions = h.clone(options ?? {})
await this.updateQueryOptions<T>(allAttrs, h, docFields, query, finalOptions) const fquery = h.clone(query ?? {})
const result = await this.provideFindAll(_class, query, finalOptions) await this.updateQueryOptions<T>(allAttrs, h, docFields, fquery, finalOptions)
const result = await this.provideFindAll(_class, fquery, finalOptions)
// We need to add $ // We need to add $
if (docFields.length > 0) { if (docFields.length > 0) {
// We need to update $lookup for doc fields and provide $doc group fields. // We need to update $lookup for doc fields and provide $doc group fields.

View File

@ -24,16 +24,19 @@ import core, {
DocumentQuery, DocumentQuery,
FindOptions, FindOptions,
Hierarchy, Hierarchy,
IdMap,
Ref, Ref,
SortingOrder, SortingOrder,
SortingRules, SortingRules,
Space, Space,
Status, Status,
StatusCategory,
StatusManager, StatusManager,
StatusValue, StatusValue,
Tx, Tx,
WithLookup, WithLookup,
matchQuery matchQuery,
toIdMap
} from '@hcengineering/core' } from '@hcengineering/core'
import { LiveQuery } from '@hcengineering/query' import { LiveQuery } from '@hcengineering/query'
import { AggregationManager, GrouppingManager } from '@hcengineering/view' import { AggregationManager, GrouppingManager } from '@hcengineering/view'
@ -47,8 +50,15 @@ export const statusStore = writable<StatusManager>(new StatusManager([]))
*/ */
export class StatusAggregationManager implements AggregationManager { export class StatusAggregationManager implements AggregationManager {
docs: Doc[] | undefined docs: Doc[] | undefined
docsByName: Map<string, Status[]> = new Map<string, Status[]>()
mgr: StatusManager | Promise<StatusManager> | undefined mgr: StatusManager | Promise<StatusManager> | undefined
statusCategory: IdMap<StatusCategory> = new Map()
query: (() => void) | undefined query: (() => void) | undefined
categoryQuery: (() => void) | undefined
categoryPromise!: Promise<void>
lq: LiveQuery lq: LiveQuery
lqCallback: () => void lqCallback: () => void
@ -68,14 +78,27 @@ export class StatusAggregationManager implements AggregationManager {
} }
return this.mgr return this.mgr
} }
this.categoryPromise = new Promise((resolve) => {
this.categoryQuery = this.lq.query(core.class.StatusCategory, {}, (res) => {
this.statusCategory = toIdMap(res)
resolve()
})
})
this.mgr = new Promise<StatusManager>((resolve) => { this.mgr = new Promise<StatusManager>((resolve) => {
this.query = this.lq.query( this.query = this.lq.query(
core.class.Status, core.class.Status,
{}, {},
(res) => { (res) => {
const first = this.docs === undefined const first = this.docs === undefined
this.docs = res this.docs = res
const newMap = new Map<string, Status[]>()
for (const d of this.docs as Array<WithLookup<Status>>) {
const n = d.name.toLowerCase().trim()
newMap.set(n, [...(newMap.get(n) ?? []), d])
}
this.mgr = new StatusManager(res) this.mgr = new StatusManager(res)
this.docsByName = newMap
statusStore.set(this.mgr) statusStore.set(this.mgr)
if (!first) { if (!first) {
this.lqCallback() this.lqCallback()
@ -83,9 +106,6 @@ export class StatusAggregationManager implements AggregationManager {
resolve(this.mgr) resolve(this.mgr)
}, },
{ {
lookup: {
category: core.class.StatusCategory
},
sort: { sort: {
rank: SortingOrder.Ascending rank: SortingOrder.Ascending
} }
@ -98,6 +118,7 @@ export class StatusAggregationManager implements AggregationManager {
close (): void { close (): void {
this.query?.() this.query?.()
this.categoryQuery?.()
} }
async notifyTx (tx: Tx): Promise<void> { async notifyTx (tx: Tx): Promise<void> {
@ -110,15 +131,13 @@ export class StatusAggregationManager implements AggregationManager {
async categorize (target: Array<Ref<Doc>>, attr: AnyAttribute): Promise<Array<Ref<Doc>>> { async categorize (target: Array<Ref<Doc>>, attr: AnyAttribute): Promise<Array<Ref<Doc>>> {
const mgr = await this.getManager() const mgr = await this.getManager()
const idMap = mgr.getIdMap()
for (const sid of [...target]) { for (const sid of [...target]) {
const s = mgr.getIdMap().get(sid as Ref<Status>) as WithLookup<Status> const s = idMap.get(sid as Ref<Status>) as WithLookup<Status>
if (s !== undefined) { if (s !== undefined) {
let statuses = mgr.getDocs() const statuses = (this.docsByName.get(s.name.toLowerCase().trim()) ?? []).filter(
statuses = statuses.filter( (it) => it.ofAttribute === attr._id && it._id !== s._id
(it) =>
it.ofAttribute === attr._id &&
it.name.toLowerCase().trim() === s.name.toLowerCase().trim() &&
it._id !== s._id
) )
target.push(...statuses.map((it) => it._id)) target.push(...statuses.map((it) => it._id))
} }
@ -140,10 +159,11 @@ export class StatusAggregationManager implements AggregationManager {
// Fill custom sorting. // Fill custom sorting.
let statuses = (await this.getManager()).getDocs() let statuses = (await this.getManager()).getDocs()
statuses = statuses.filter((it) => it.ofAttribute === attr._id) statuses = statuses.filter((it) => it.ofAttribute === attr._id)
await this.categoryPromise
statuses.sort((a, b) => { statuses.sort((a, b) => {
let ret = 0 let ret = 0
if (a.category !== undefined && b.category !== undefined) { if (a.category !== undefined && b.category !== undefined) {
ret = (a.$lookup?.category?.order ?? 0) - (b.$lookup?.category?.order ?? 0) ret = (this.statusCategory.get(a.category)?.order ?? 0) - (this.statusCategory.get(b.category)?.order ?? 0)
} }
if (ret === 0) { if (ret === 0) {
if (a.name.toLowerCase().trim() === b.name.toLowerCase().trim()) { if (a.name.toLowerCase().trim() === b.name.toLowerCase().trim()) {