UBERF-7600: Reduce number of $in operators and fix account service is… (#6080)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-07-17 16:38:45 +07:00 committed by GitHub
parent 89417c7f84
commit 42c2719581
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 125 additions and 127 deletions

View File

@ -729,7 +729,7 @@ export function createActions (builder: Builder, issuesId: string, componentsId:
category: tracker.category.Tracker,
target: core.class.Space,
query: {
_class: { $nin: [tracker.class.Project] }
_class: { $ne: tracker.class.Project }
},
context: {
mode: ['context'],

View File

@ -236,6 +236,8 @@ export function getClient (): TxOperations & Client & OptimisticTxes {
return client
}
let txQueue: Tx[] = []
/**
* @public
*/
@ -265,8 +267,15 @@ export async function setClient (_client: Client): Promise<void> {
client = uiClient
const notifyCaller = reduceCalls(async () => {
const t = txQueue
txQueue = []
await uiClient.doNotify(...t)
})
_client.notify = (...tx: Tx[]) => {
void uiClient.doNotify(...tx)
txQueue.push(...tx)
void notifyCaller()
}
if (needRefresh || globalQueries.length > 0) {
await refreshClient(true)

View File

@ -78,7 +78,7 @@
let resultQuery: DocumentQuery<Card>
$: resultQuery = { ...query, isArchived: { $nin: [true] }, space }
$: resultQuery = { ...query, isArchived: { $ne: true }, space }
const cardQuery = createQuery()
let cards: Card[] = []

View File

@ -11,7 +11,7 @@
export let space: Ref<Project>
export let options: FindOptions<Card> | undefined
const isArchived = { $nin: [true] }
const isArchived = { $ne: true }
const query = createQuery()
let states: Ref<State>[] = []
$: query.query(task.class.State, { space, isArchived }, (result) => {

View File

@ -33,6 +33,7 @@
_class?: Ref<Class<Doc>>
label: IntlString
objects: Doc[]
count: number
}
const client = getClient()
@ -41,9 +42,9 @@
const contextByDocStore = inboxClient.contextByDoc
const contextsQuery = createQuery()
const objectsQueryByClass = new Map<Ref<Class<Doc>>, LiveQuery>()
const objectsQueryByClass = new Map<Ref<Class<Doc>>, { query: LiveQuery, limit: number }>()
let objectsByClass = new Map<Ref<Class<Doc>>, Doc[]>()
let objectsByClass = new Map<Ref<Class<Doc>>, { docs: Doc[], total: number }>()
let contexts: DocNotifyContext[] = []
let shouldPushObject = false
@ -84,24 +85,26 @@
for (const [_class, ctx] of contextsByClass.entries()) {
const ids = ctx.map(({ attachedTo }) => attachedTo)
const query = objectsQueryByClass.get(_class) ?? createQuery()
const { query, limit } = objectsQueryByClass.get(_class) ?? {
query: createQuery(),
limit: model.maxSectionItems ?? 5
}
objectsQueryByClass.set(_class, query)
objectsQueryByClass.set(_class, { query, limit: limit ?? model.maxSectionItems ?? 5 })
query.query(_class, { _id: { $in: ids } }, (res: Doc[]) => {
objectsByClass = objectsByClass.set(_class, res)
query.query(_class, { _id: { $in: limit !== -1 ? ids.slice(0, limit) : ids } }, (res: Doc[]) => {
objectsByClass = objectsByClass.set(_class, { docs: res, total: ids.length })
})
}
for (const [classRef, query] of objectsQueryByClass.entries()) {
if (!contextsByClass.has(classRef)) {
query.unsubscribe()
query.query.unsubscribe()
objectsQueryByClass.delete(classRef)
objectsByClass.delete(classRef)
objectsByClass = objectsByClass
}
}
objectsByClass = objectsByClass
}
function getObjectGroup (object: Doc): ChatGroup {
@ -118,7 +121,7 @@
const getSections = reduceCalls(
async (
objectsByClass: Map<Ref<Class<Doc>>, Doc[]>,
objectsByClass: Map<Ref<Class<Doc>>, { docs: Doc[], total: number }>,
model: ChatNavGroupModel,
object: { _id: Doc['_id'], _class: Doc['_class'] } | undefined,
getPushObj: () => Doc,
@ -129,8 +132,9 @@
if (!model.wrap) {
result.push({
id: model.id,
objects: Array.from(objectsByClass.values()).flat(),
label: model.label ?? chunter.string.Channels
objects: Array.from(Array.from(objectsByClass.values()).map((it) => it.docs)).flat(),
label: model.label ?? chunter.string.Channels,
count: Array.from(Array.from(objectsByClass.values()).map((it) => it.total)).reduceRight((a, b) => a + b, 0)
})
handler(result)
@ -140,27 +144,29 @@
let isObjectPushed = false
if (
Array.from(objectsByClass.values())
Array.from(Array.from(objectsByClass.values()).map((it) => it.docs))
.flat()
.some((o) => o._id === object?._id)
) {
isObjectPushed = true
}
for (const [_class, objects] of objectsByClass.entries()) {
for (let [_class, { docs: objects, total }] of objectsByClass.entries()) {
const clazz = hierarchy.getClass(_class)
const sectionObjects = [...objects]
if (object !== undefined && _class === object._class && !objects.some(({ _id }) => _id === object._id)) {
isObjectPushed = true
sectionObjects.push(getPushObj())
total++
}
result.push({
id: _class,
_class,
objects: sectionObjects,
label: clazz.pluralLabel ?? clazz.label
label: clazz.pluralLabel ?? clazz.label,
count: total
})
}
@ -171,7 +177,8 @@
id: object._id,
_class: object._class,
objects: [getPushObj()],
label: clazz.pluralLabel ?? clazz.label
label: clazz.pluralLabel ?? clazz.label,
count: 1
})
}
@ -203,7 +210,16 @@
header={section.label}
actions={getSectionActions(section, contexts)}
sortFn={model.sortFn}
maxItems={model.maxSectionItems}
itemsCount={section.count}
on:show-more={() => {
if (section._class !== undefined) {
const query = objectsQueryByClass.get(section._class)
if (query !== undefined) {
query.limit += 50
loadObjects(contexts)
}
}
}}
on:select
/>
{/each}

View File

@ -23,6 +23,7 @@
import view from '@hcengineering/view'
import { getDocTitle } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import chunter from '../../../plugin'
import { getChannelName, getObjectIcon } from '../../../utils'
import { ChatNavItemModel, SortFnOptions } from '../types'
@ -31,9 +32,9 @@
export let id: string
export let header: IntlString
export let objects: Doc[]
export let itemsCount: number
export let contexts: DocNotifyContext[]
export let actions: Action[] = []
export let maxItems: number | undefined = undefined
export let objectId: Ref<Doc> | undefined
export let sortFn: (items: ChatNavItemModel[], options: SortFnOptions) => ChatNavItemModel[]
@ -42,10 +43,10 @@
let sortedItems: ChatNavItemModel[] = []
let items: ChatNavItemModel[] = []
let visibleItems: ChatNavItemModel[] = []
const dispatcher = createEventDispatcher()
let canShowMore = false
let isShownMore = false
$: void getChatNavItems(objects, (res) => {
items = res
@ -56,9 +57,7 @@
userStatusByAccount: $statusByUserStore,
personAccountById: $personAccountByIdStore
})
$: canShowMore = !!maxItems && items.length > maxItems
$: visibleItems = getVisibleItems(canShowMore, isShownMore, maxItems, sortedItems, objectId)
$: canShowMore = itemsCount > items.length
const getChatNavItems = reduceCalls(
async (objects: Doc[], handler: (items: ChatNavItemModel[]) => void): Promise<void> => {
@ -102,46 +101,13 @@
)
function onShowMore (): void {
isShownMore = !isShownMore
dispatcher('show-more')
}
function getVisibleItems (
canShowMore: boolean,
isShownMore: boolean,
maxItems: number | undefined,
items: ChatNavItemModel[],
selectedObjectId: Ref<Doc> | undefined
): ChatNavItemModel[] {
if (!canShowMore || isShownMore) {
return items
}
const result = items.slice(0, maxItems)
if (selectedObjectId === undefined) {
return result
}
const exists = result.some(({ id }) => id === selectedObjectId)
if (exists) {
return result
}
const selectedItem = items.find(({ id }) => id === selectedObjectId)
if (selectedItem === undefined) {
return result
}
result.push(selectedItem)
return result
}
$: visibleItem = visibleItems.find(({ id }) => id === objectId)
$: visibleItem = sortedItems.find(({ id }) => id === objectId)
</script>
{#if visibleItems.length > 0 && contexts.length > 0}
{#if sortedItems.length > 0 && contexts.length > 0}
<NavGroup
_id={id}
label={header}
@ -149,23 +115,17 @@
{actions}
highlighted={items.some((it) => it.id === objectId)}
isFold
empty={visibleItems.length === 0}
empty={sortedItems.length === 0}
visible={visibleItem !== undefined}
noDivider
>
{#each visibleItems as item (item.id)}
{#each sortedItems as item (item.id)}
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
<ChatNavItem {context} isSelected={objectId === item.id} {item} type={'type-object'} on:select />
{/each}
{#if canShowMore}
<div class="showMore">
<ModernButton
label={isShownMore ? ui.string.ShowLess : ui.string.ShowMore}
kind="tertiary"
inheritFont
size="extra-small"
on:click={onShowMore}
/>
<ModernButton label={ui.string.ShowMore} kind="tertiary" inheritFont size="extra-small" on:click={onShowMore} />
</div>
{/if}
<svelte:fragment slot="visible" let:isOpen>

View File

@ -58,10 +58,7 @@ export class PresentationClientHook implements ClientHook {
if (this.notifyEnabled) {
const rtx = tx.filter((tx) => (tx as any).objectClass !== core.class.BenchmarkDoc)
if (rtx.length > 0) {
console.debug(
'devmodel# notify=>',
testing ? JSON.stringify(cutObjectArray(rtx)).slice(0, 160) : rtx.length === 1 ? rtx[0] : tx
)
console.debug('devmodel# notify=>', testing ? cutObjectArray(rtx) : rtx.length === 1 ? rtx[0] : tx)
}
}
})

View File

@ -103,7 +103,7 @@
archivedOtherNotificationsQuery.query(
notification.class.InboxNotification,
{ _class: { $nin: [notification.class.ActivityInboxNotification] }, archived: true, user: me._id },
{ _class: { $ne: notification.class.ActivityInboxNotification }, archived: true, user: me._id },
(res) => {
archivedOtherNotifications = res
},

View File

@ -103,7 +103,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
this.otherInboxNotificationsQuery.query(
notification.class.InboxNotification,
{
_class: { $nin: [notification.class.ActivityInboxNotification] },
_class: { $ne: notification.class.ActivityInboxNotification },
archived: { $ne: true },
user: getCurrentAccount()._id
},

View File

@ -35,7 +35,8 @@
Ref,
toIdMap,
TxProcessor,
WithLookup
WithLookup,
type Blob
} from '@hcengineering/core'
import { getMetadata, getResource, setPlatformStatus, unknownError } from '@hcengineering/platform'
import presentation, {
@ -184,7 +185,7 @@
)
})
async function createCandidate () {
async function createCandidate (): Promise<void> {
const _id: Ref<Person> = generateId()
const candidate: Data<Person> = {
name: combineName(object.firstName ?? '', object.lastName ?? ''),
@ -220,7 +221,7 @@
}
}
const applyOps = client.apply(_id)
const applyOps = client.apply(_id, 'create-candidate')
await applyOps.createDoc(contact.class.Person, contact.space.Contacts, candidate, _id)
await applyOps.createMixin(
@ -233,7 +234,7 @@
if (object.resumeUuid !== undefined) {
const resume = resumeDraft() as resumeFile
applyOps.addCollection(
await applyOps.addCollection(
attachment.class.Attachment,
contact.space.Contacts,
_id,
@ -241,7 +242,7 @@
'attachments',
{
name: resume.name,
file: resume.uuid,
file: resume.uuid as Ref<Blob>,
size: resume.size,
type: resume.type,
lastModified: resume.lastModified

View File

@ -58,7 +58,7 @@
$: query.query(
calendar.class.Event,
{
_class: { $nin: [calendar.class.ReccuringEvent] },
_class: { $ne: calendar.class.ReccuringEvent },
calendar: { $in: calendarIds },
date: { $lte: toDate },
dueDate: { $gte: fromDate },

View File

@ -261,9 +261,14 @@ export function start (
app.use(bp.json())
app.use(bp.urlencoded({ extended: true }))
const childLogger = ctx.logger.childLogger?.('requests', {
enableConsole: 'false'
})
const requests = ctx.newChild('requests', {}, {}, childLogger)
class MyStream {
write (text: string): void {
ctx.info(text)
requests.info(text)
}
}

View File

@ -524,7 +524,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
await this.waitInit()
const domain = this.storage.hierarchy.getDomain(_class)
const newQuery = query
const newQuery = { ...query }
const account = await getUser(this.storage, ctx)
const isSpace = this.storage.hierarchy.isDerived(_class, core.class.Space)
const field = this.getKey(domain)
@ -532,10 +532,20 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
if (!isSystem(account) && account.role !== AccountRole.DocGuest) {
if (!isOwner(account, ctx) || !isSpace) {
if (query[field] !== undefined) {
;(newQuery as any)[field] = await this.mergeQuery(account, query[field], domain, isSpace)
const res = await this.mergeQuery(account, query[field], domain, isSpace)
;(newQuery as any)[field] = res
if (typeof res === 'object') {
if (Array.isArray(res.$in) && res.$in.length === 1 && Object.keys(res).length === 1) {
;(newQuery as any)[field] = res.$in[0]
}
}
} else {
const spaces = await this.filterByDomain(domain, this.getAllAllowedSpaces(account, !isSpace))
;(newQuery as any)[field] = { $in: spaces }
if (spaces.length === 1) {
;(newQuery as any)[field] = spaces[0]
} else {
;(newQuery as any)[field] = { $in: spaces }
}
}
}
}

View File

@ -613,7 +613,11 @@ abstract class MongoAdapterBase implements DbAdapter {
}
): Promise<FindResult<T>> {
if (options != null && (options?.lookup != null || this.isEnumSort(_class, options) || this.isRulesSort(options))) {
return await ctx.with('pipeline', {}, async (ctx) => await this.findWithPipeline(ctx, _class, query, options))
return await ctx.with('pipeline', {}, async (ctx) => await this.findWithPipeline(ctx, _class, query, options), {
_class,
query,
options
})
}
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
const coll = this.collection(domain)

View File

@ -32,7 +32,7 @@ import core, {
} from '@hcengineering/core'
import { unknownError, type Status } from '@hcengineering/platform'
import { type HelloRequest, type HelloResponse, type Request, type Response } from '@hcengineering/rpc'
import type { Pipeline, StorageAdapter, PipelineFactory } from '@hcengineering/server-core'
import type { Pipeline, PipelineFactory, StorageAdapter } from '@hcengineering/server-core'
import { type Token } from '@hcengineering/server-token'
import {
@ -220,36 +220,37 @@ class TSessionManager implements SessionManager {
}
@withContext('get-workspace-info')
async getWorkspaceInfo (ctx: MeasureContext, accounts: string, token: string): Promise<WorkspaceLoginInfo> {
for (let i = 0; i < 10; i++) {
try {
const userInfo = await (
await fetch(accounts, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
method: 'getWorkspaceInfo',
params: [true]
})
async getWorkspaceInfo (
ctx: MeasureContext,
accounts: string,
token: string
): Promise<WorkspaceLoginInfo | undefined> {
try {
const userInfo = await (
await fetch(accounts, {
method: 'POST',
headers: {
Authorization: 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
method: 'getWorkspaceInfo',
params: [true]
})
).json()
})
).json()
if (userInfo.error !== undefined) {
ctx.error('Error response from account service', { error: JSON.stringify(userInfo) })
throw new Error(JSON.stringify(userInfo.error))
}
return { ...userInfo.result, upgrade: userInfo.upgrade }
} catch (err: any) {
if (i === 9) {
throw err
}
await new Promise<void>((resolve) => setTimeout(resolve, 100))
if (userInfo.error !== undefined) {
ctx.error('Error response from account service', { error: JSON.stringify(userInfo) })
throw new Error(JSON.stringify(userInfo.error))
}
return { ...userInfo.result, upgrade: userInfo.upgrade }
} catch (err: any) {
if (err?.cause?.code === 'ECONNRESET' || err?.cause?.code === 'ECONNREFUSED') {
return undefined
}
throw err
}
throw new Error('')
}
@withContext('📲 add-session')
@ -270,17 +271,12 @@ class TSessionManager implements SessionManager {
const wsString = toWorkspaceString(token.workspace, '@')
let workspaceInfo: WorkspaceLoginInfo | undefined
for (let i = 0; i < 5; i++) {
try {
workspaceInfo =
accountsUrl !== '' ? await this.getWorkspaceInfo(ctx, accountsUrl, rawToken) : this.wsFromToken(token)
break
} catch (err: any) {
if (i === 4) {
throw err
}
await new Promise((resolve) => setTimeout(resolve, 10))
}
workspaceInfo =
accountsUrl !== '' ? await this.getWorkspaceInfo(ctx, accountsUrl, rawToken) : this.wsFromToken(token)
if (workspaceInfo === undefined) {
// No connection to account service, retry from client.
return { upgrade: true }
}
if (workspaceInfo?.creating === true && token.email !== systemAccountEmail) {