mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
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:
parent
89417c7f84
commit
42c2719581
@ -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'],
|
||||
|
@ -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)
|
||||
|
@ -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[] = []
|
||||
|
@ -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) => {
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user