UBERF-7690: Performance fixes (#6336)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-08-15 17:30:25 +07:00 committed by GitHub
parent d502ba86d9
commit 87ded4d797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 200 additions and 103 deletions

View File

@ -67,7 +67,7 @@ async function hydrateNotificationAsYouCan (lastNotification: InboxNotification)
body: '' body: ''
} }
const account = await client.findOne(contact.class.PersonAccount, { _id: lastNotification.modifiedBy as Ref<PersonAccount> }) const account = await client.getModel().findOne(contact.class.PersonAccount, { _id: lastNotification.modifiedBy as Ref<PersonAccount> })
if (account == null) { if (account == null) {
return noPersonData return noPersonData

View File

@ -362,7 +362,6 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_ACTIVITY, domain: DOMAIN_ACTIVITY,
indexes: [{ keys: { attachedTo: 1, createdOn: 1 } }, { keys: { attachedTo: 1, createdOn: -1 } }],
disabled: [ disabled: [
{ modifiedOn: 1 }, { modifiedOn: 1 },
{ createdOn: -1 }, { createdOn: -1 },

View File

@ -199,7 +199,8 @@ export function createModel (builder: Builder): void {
{ modifiedBy: 1 }, { modifiedBy: 1 },
{ createdBy: 1 }, { createdBy: 1 },
{ createdOn: -1 }, { createdOn: -1 },
{ state: 1 } { state: 1 },
{ _class: 1 }
] ]
}) })
} }

View File

@ -135,6 +135,14 @@ export function createModel (builder: Builder): void {
) )
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_BITRIX, domain: DOMAIN_BITRIX,
disabled: [{ _id: 1 }, { _class: 1 }, { space: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }] disabled: [
{ _id: 1 },
{ _class: 1 },
{ space: 1 },
{ modifiedBy: 1 },
{ createdBy: 1 },
{ createdOn: -1 },
{ modifiedOn: 1 }
]
}) })
} }

View File

@ -223,6 +223,7 @@ export class TContactsTab extends TDoc implements ContactsTab {
@Model(contact.class.PersonSpace, core.class.Space) @Model(contact.class.PersonSpace, core.class.Space)
export class TPersonSpace extends TSpace implements PersonSpace { export class TPersonSpace extends TSpace implements PersonSpace {
@Prop(TypeRef(contact.class.Person), contact.string.Person) @Prop(TypeRef(contact.class.Person), contact.string.Person)
@Index(IndexKind.Indexed)
person!: Ref<Person> person!: Ref<Person>
} }
@ -1170,6 +1171,14 @@ export function createModel (builder: Builder): void {
}) })
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_CONTACT, domain: DOMAIN_CONTACT,
indexes: [
{
keys: {
_class: 1,
[contact.mixin.Employee + '.active']: 1
}
}
],
disabled: [{ attachedToClass: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }, { attachedTo: 1 }] disabled: [{ attachedToClass: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdOn: -1 }, { attachedTo: 1 }]
}) })

View File

@ -197,6 +197,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_TX, domain: DOMAIN_TX,
disabled: [ disabled: [
{ _class: 1 },
{ space: 1 }, { space: 1 },
{ objectClass: 1 }, { objectClass: 1 },
{ createdBy: 1 }, { createdBy: 1 },
@ -267,7 +268,14 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_STATUS, domain: DOMAIN_STATUS,
disabled: [{ modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdBy: -1 }, { createdOn: -1 }] disabled: [
{ modifiedOn: 1 },
{ modifiedBy: 1 },
{ createdBy: 1 },
{ createdBy: -1 },
{ createdOn: -1 },
{ space: 1 }
]
}) })
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_SPACE, domain: DOMAIN_SPACE,
@ -276,7 +284,15 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_BLOB, domain: DOMAIN_BLOB,
disabled: [{ _class: 1 }, { space: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { createdBy: -1 }, { createdOn: -1 }] disabled: [
{ _class: 1 },
{ space: 1 },
{ modifiedBy: 1 },
{ createdBy: 1 },
{ createdBy: -1 },
{ createdOn: -1 },
{ modifiedOn: 1 }
]
}) })
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {

View File

@ -14,22 +14,22 @@
// //
import { import {
DOMAIN_MODEL,
IndexKind,
type Account, type Account,
type AccountRole, type AccountRole,
type Arr, type Arr,
type Class,
type CollectionSize,
type Domain, type Domain,
DOMAIN_MODEL, type Permission,
IndexKind,
type Ref, type Ref,
type Role,
type RolesAssignment,
type Space, type Space,
type TypedSpace,
type SpaceType, type SpaceType,
type SpaceTypeDescriptor, type SpaceTypeDescriptor,
type Role, type TypedSpace
type Class,
type Permission,
type CollectionSize,
type RolesAssignment
} from '@hcengineering/core' } from '@hcengineering/core'
import { import {
ArrOf, ArrOf,
@ -46,7 +46,7 @@ import {
} from '@hcengineering/model' } from '@hcengineering/model'
import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform' import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform'
import core from './component' import core from './component'
import { TDoc, TAttachedDoc } from './core' import { TAttachedDoc, TDoc } from './core'
export const DOMAIN_SPACE = 'space' as Domain export const DOMAIN_SPACE = 'space' as Domain
@ -67,6 +67,7 @@ export class TSpace extends TDoc implements Space {
private!: boolean private!: boolean
@Prop(TypeBoolean(), core.string.Archived) @Prop(TypeBoolean(), core.string.Archived)
@Index(IndexKind.Indexed)
archived!: boolean archived!: boolean
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members) @Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)

View File

@ -254,7 +254,8 @@ export function createModel (builder: Builder): void {
{ modifiedBy: 1 }, { modifiedBy: 1 },
{ createdBy: 1 }, { createdBy: 1 },
{ attachedToClass: 1 }, { attachedToClass: 1 },
{ createdOn: -1 } { createdOn: -1 },
{ modifiedOn: 1 }
] ]
}) })

View File

@ -16,6 +16,7 @@
import activity, { type ActivityMessage } from '@hcengineering/activity' import activity, { type ActivityMessage } from '@hcengineering/activity'
import chunter from '@hcengineering/chunter' import chunter from '@hcengineering/chunter'
import { type PersonSpace } from '@hcengineering/contact'
import { import {
AccountRole, AccountRole,
DOMAIN_MODEL, DOMAIN_MODEL,
@ -49,7 +50,6 @@ import {
UX, UX,
type Builder type Builder
} from '@hcengineering/model' } from '@hcengineering/model'
import { type PersonSpace } from '@hcengineering/contact'
import core, { TClass, TDoc } from '@hcengineering/model-core' import core, { TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference' import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction, template } from '@hcengineering/model-view' import view, { createAction, template } from '@hcengineering/model-view'
@ -201,7 +201,6 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext {
objectId!: Ref<Doc> objectId!: Ref<Doc>
@Prop(TypeRef(core.class.Class), core.string.Class) @Prop(TypeRef(core.class.Class), core.string.Class)
@Index(IndexKind.Indexed)
objectClass!: Ref<Class<Doc>> objectClass!: Ref<Class<Doc>>
@Prop(TypeRef(core.class.Space), core.string.Space) @Prop(TypeRef(core.class.Space), core.string.Space)
@ -632,7 +631,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_NOTIFICATION, domain: DOMAIN_NOTIFICATION,
indexes: [{ keys: { user: 1, archived: 1 } }], indexes: [{ keys: { user: 1, archived: 1, space: 1 } }],
disabled: [{ modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { isViewed: 1 }, { hidden: 1 }] disabled: [{ modifiedOn: 1 }, { modifiedBy: 1 }, { createdBy: 1 }, { isViewed: 1 }, { hidden: 1 }]
}) })
@ -647,7 +646,8 @@ export function createModel (builder: Builder): void {
{ isViewed: 1 }, { isViewed: 1 },
{ hidden: 1 }, { hidden: 1 },
{ createdOn: -1 }, { createdOn: -1 },
{ attachedTo: 1 } { attachedTo: 1 },
{ space: 1 }
] ]
}) })
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {

View File

@ -480,7 +480,7 @@ export function createModel (builder: Builder): void {
sortable: true sortable: true
}, },
baseQuery: { baseQuery: {
isDone: { $ne: true }, isDone: false,
'$lookup.space.archived': false '$lookup.space.archived': false
} }
}, },
@ -500,7 +500,7 @@ export function createModel (builder: Builder): void {
} }
}, },
baseQuery: { baseQuery: {
isDone: { $ne: true }, isDone: false,
'$lookup.space.archived': false '$lookup.space.archived': false
} }
}, },
@ -796,7 +796,7 @@ export function createModel (builder: Builder): void {
descriptor: task.viewlet.Kanban, descriptor: task.viewlet.Kanban,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
baseQuery: { baseQuery: {
isDone: { $ne: true }, isDone: false,
'$lookup.space.archived': false '$lookup.space.archived': false
}, },
viewOptions: { viewOptions: {

View File

@ -60,6 +60,16 @@ export const recruitOperation: MigrateOperation = {
func: async (client: MigrationClient) => { func: async (client: MigrationClient) => {
await migrateSpace(client, 'recruit:space:Reviews' as Ref<Space>, core.space.Workspace, [DOMAIN_CALENDAR]) await migrateSpace(client, 'recruit:space:Reviews' as Ref<Space>, core.space.Workspace, [DOMAIN_CALENDAR])
} }
},
{
state: 'migrate-applicants',
func: async (client: MigrationClient) => {
await client.update(
DOMAIN_TASK,
{ _class: recruit.class.Applicant, isDone: { $nin: [false, true] } },
{ isDone: false }
)
}
} }
]) ])
}, },

View File

@ -15,6 +15,7 @@
import { getEmbeddedLabel, IntlString } from '@hcengineering/platform' import { getEmbeddedLabel, IntlString } from '@hcengineering/platform'
import { deepEqual } from 'fast-equals' import { deepEqual } from 'fast-equals'
import { DOMAIN_BENCHMARK } from './benchmark'
import { import {
Account, Account,
AccountRole, AccountRole,
@ -46,7 +47,6 @@ import { TxOperations } from './operations'
import { isPredicate } from './predicate' import { isPredicate } from './predicate'
import { DocumentQuery, FindResult } from './storage' import { DocumentQuery, FindResult } from './storage'
import { DOMAIN_TX } from './tx' import { DOMAIN_TX } from './tx'
import { DOMAIN_BENCHMARK } from './benchmark'
function toHex (value: number, chars: number): string { function toHex (value: number, chars: number): string {
const result = value.toString(16) const result = value.toString(16)
@ -604,9 +604,10 @@ export const isEnum =
export async function checkPermission ( export async function checkPermission (
client: TxOperations, client: TxOperations,
_id: Ref<Permission>, _id: Ref<Permission>,
_space: Ref<TypedSpace> _space: Ref<TypedSpace>,
space?: TypedSpace
): Promise<boolean> { ): Promise<boolean> {
const space = await client.findOne(core.class.TypedSpace, { _id: _space }) space = space ?? (await client.findOne(core.class.TypedSpace, { _id: _space }))
const type = await client const type = await client
.getModel() .getModel()
.findOne(core.class.SpaceType, { _id: space?.type }, { lookup: { _id: { roles: core.class.Role } } }) .findOne(core.class.SpaceType, { _id: space?.type }, { lookup: { _id: { roles: core.class.Role } } })

View File

@ -16,7 +16,7 @@ import type { AttachedDoc, Class, Doc, Markup, Mixin, Ref, SystemSpace, Timestam
import { NotificationType } from '@hcengineering/notification' import { NotificationType } from '@hcengineering/notification'
import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform' import type { Asset, IntlString, Metadata, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform'
import type { Handler, IntegrationType } from '@hcengineering/setting' import { Handler, IntegrationType } from '@hcengineering/setting'
import { AnyComponent, ComponentExtensionId } from '@hcengineering/ui' import { AnyComponent, ComponentExtensionId } from '@hcengineering/ui'
/** /**

View File

@ -320,7 +320,7 @@ function fillStores (): void {
const accountPersonQuery = createQuery(true) const accountPersonQuery = createQuery(true)
const query = createQuery(true) const query = createQuery(true)
query.query(contact.mixin.Employee, {}, (res) => { query.query(contact.mixin.Employee, { [contact.mixin.Employee + '.active']: { $in: [true, false] } }, (res) => {
employeesStore.set(res) employeesStore.set(res)
employeeByIdStore.set(toIdMap(res)) employeeByIdStore.set(toIdMap(res))
}) })
@ -331,13 +331,10 @@ function fillStores (): void {
const persons = res.map((it) => it.person) const persons = res.map((it) => it.person)
accountPersonQuery.query<Person>( accountPersonQuery.query<Person>(contact.class.Person, { _id: { $in: persons } }, (res) => {
contact.class.Person, const personIn = toIdMap(res)
{ _id: { $in: persons }, [contact.mixin.Employee]: { $exists: false } }, personAccountPersonByIdStore.set(personIn)
(res) => { })
personAccountPersonByIdStore.set(toIdMap(res))
}
)
}) })
const providerQuery = createQuery(true) const providerQuery = createQuery(true)

View File

@ -112,12 +112,8 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
user: getCurrentAccount()._id user: getCurrentAccount()._id
}, },
(result: InboxNotification[]) => { (result: InboxNotification[]) => {
result.sort((a, b) => (b.createdOn ?? b.modifiedOn) - (a.createdOn ?? a.modifiedOn))
this.otherInboxNotifications.set(result) this.otherInboxNotifications.set(result)
},
{
sort: {
createdOn: SortingOrder.Descending
}
} }
) )

View File

@ -14,7 +14,7 @@
--> -->
<script lang="ts"> <script lang="ts">
import { DocumentQuery, Ref } from '@hcengineering/core' import { DocumentQuery, Ref } from '@hcengineering/core'
import type { IntlString, Asset } from '@hcengineering/platform' import type { Asset, IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker' import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { IModeSelector, resolvedLocationStore } from '@hcengineering/ui' import { IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
@ -41,18 +41,20 @@
let query: DocumentQuery<Issue> | undefined = undefined let query: DocumentQuery<Issue> | undefined = undefined
let modeSelectorProps: IModeSelector | undefined = undefined let modeSelectorProps: IModeSelector | undefined = undefined
const archivedProjectQuery = createQuery() const allProjectQuery = createQuery()
let archived: Ref<Project>[] = [] let allProjects: Pick<Project, '_id' | '_class' | 'archived'>[] = []
archivedProjectQuery.query( allProjectQuery.query(
tracker.class.Project, tracker.class.Project,
{ archived: true }, {},
(res) => { (res) => {
archived = res.map((it) => it._id) allProjects = res
}, },
{ projection: { _id: 1 } } { projection: { _id: 1, archived: 1 } }
) )
$: spaceQuery = currentSpace ? { space: currentSpace } : { space: { $nin: archived } } $: spaceQuery = currentSpace
? { space: currentSpace }
: { space: { $in: allProjects.filter((it) => !it.archived).map((it) => it._id) } }
$: all = { ...baseQuery, ...spaceQuery } $: all = { ...baseQuery, ...spaceQuery }

View File

@ -75,16 +75,16 @@
{ sort: { _id: 1 }, projection: { _id: 1 } } { sort: { _id: 1 }, projection: { _id: 1 } }
) )
const archivedProjectQuery = createQuery() const allProjectQuery = createQuery()
let archived: Ref<Project>[] = [] let allProjects: Pick<Project, '_class' | '_id' | 'archived'>[] = []
archivedProjectQuery.query( allProjectQuery.query(
tracker.class.Project, tracker.class.Project,
{ archived: true }, {},
(res) => { (res) => {
archived = res.map((it) => it._id) allProjects = res
}, },
{ projection: { _id: 1 } } { projection: { _id: 1, archived: 1 } }
) )
$: queries = { assigned, active, backlog, created, subscribed } $: queries = { assigned, active, backlog, created, subscribed }
@ -95,7 +95,7 @@
$: if (mode !== undefined) { $: if (mode !== undefined) {
query = { ...(queries as any)[mode] } query = { ...(queries as any)[mode] }
if (query?.space === undefined) { if (query?.space === undefined) {
query = { ...query, space: { $nin: archived } } query = { ...query, space: { $in: allProjects.filter((it) => !it.archived).map((it) => it._id) } }
} }
modeSelectorProps = { modeSelectorProps = {
config, config,

View File

@ -1496,6 +1496,14 @@ export const permissionsStore = writable<PermissionsStore>({
whitelist: new Set() whitelist: new Set()
}) })
const spaceSpaceQuery = createQuery(true)
export const spaceSpace = writable<TypedSpace | undefined>(undefined)
spaceSpaceQuery.query(core.class.TypedSpace, { _id: core.space.Space }, (res) => {
spaceSpace.set(res[0])
})
const spaceTypesQuery = createQuery(true) const spaceTypesQuery = createQuery(true)
const permissionsQuery = createQuery(true) const permissionsQuery = createQuery(true)
type TargetClassesProjection = Record<Ref<Class<Space>>, number> type TargetClassesProjection = Record<Ref<Class<Space>>, number>

View File

@ -13,8 +13,17 @@
// limitations under the License. // limitations under the License.
// //
import core, { checkPermission, type Space, type Doc, type TypedSpace, getCurrentAccount } from '@hcengineering/core' import core, {
checkPermission,
getCurrentAccount,
toIdMap,
type Doc,
type Space,
type TypedSpace
} from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { get } from 'svelte/store'
import { spaceSpace } from './utils'
function isTypedSpace (space: Space): space is TypedSpace { function isTypedSpace (space: Space): space is TypedSpace {
return getClient().getHierarchy().isDerived(space._class, core.class.TypedSpace) return getClient().getHierarchy().isDerived(space._class, core.class.TypedSpace)
@ -28,14 +37,14 @@ export async function canDeleteObject (doc?: Doc | Doc[]): Promise<boolean> {
const client = getClient() const client = getClient()
const targets = Array.isArray(doc) ? doc : [doc] const targets = Array.isArray(doc) ? doc : [doc]
// Note: allow deleting objects in NOT typed spaces for now // Note: allow deleting objects in NOT typed spaces for now
const targetSpaces = (await client.findAll(core.class.Space, { _id: { $in: targets.map((t) => t.space) } })).filter( const targetSpaces = toIdMap(
isTypedSpace (await client.findAll(core.class.Space, { _id: { $in: targets.map((t) => t.space) } })).filter(isTypedSpace)
) )
return !( return !(
await Promise.all( await Promise.all(
Array.from(new Set(targetSpaces.map((t) => t._id))).map( Array.from(targetSpaces.entries()).map(
async (s) => await checkPermission(client, core.permission.ForbidDeleteObject, s) async (s) => await checkPermission(client, core.permission.ForbidDeleteObject, s[0], s[1])
) )
) )
).some((r) => r) ).some((r) => r)
@ -54,11 +63,13 @@ export async function canEditSpace (doc?: Doc | Doc[]): Promise<boolean> {
const client = getClient() const client = getClient()
if (await checkPermission(client, core.permission.UpdateObject, core.space.Space)) { const _spaceSpace = get(spaceSpace) ?? (await client.findOne(core.class.TypedSpace, { _id: core.space.Space }))
if (await checkPermission(client, core.permission.UpdateObject, core.space.Space, _spaceSpace)) {
return true return true
} }
if (isTypedSpace(space) && (await checkPermission(client, core.permission.UpdateSpace, space._id))) { if (isTypedSpace(space) && (await checkPermission(client, core.permission.UpdateSpace, space._id, space))) {
return true return true
} }
@ -78,11 +89,13 @@ export async function canArchiveSpace (doc?: Doc | Doc[]): Promise<boolean> {
const client = getClient() const client = getClient()
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space)) { const _spaceSpace = get(spaceSpace) ?? (await client.findOne(core.class.TypedSpace, { _id: core.space.Space }))
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space, _spaceSpace)) {
return true return true
} }
if (isTypedSpace(space) && (await checkPermission(client, core.permission.ArchiveSpace, space._id))) { if (isTypedSpace(space) && (await checkPermission(client, core.permission.ArchiveSpace, space._id, space))) {
return true return true
} }
@ -102,7 +115,9 @@ export async function canDeleteSpace (doc?: Doc | Doc[]): Promise<boolean> {
const client = getClient() const client = getClient()
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space)) { const _spaceSpace = get(spaceSpace) ?? (await client.findOne(core.class.TypedSpace, { _id: core.space.Space }))
if (await checkPermission(client, core.permission.DeleteObject, core.space.Space, _spaceSpace)) {
return true return true
} }

View File

@ -41,12 +41,14 @@
let shownSpaces: Space[] = [] let shownSpaces: Space[] = []
$: if (model) { $: if (model) {
const classes = getSpecialSpaceClass(model).flatMap((c) => hierarchy.getDescendants(c)) const classes = Array.from(new Set(getSpecialSpaceClass(model).flatMap((c) => hierarchy.getDescendants(c)))).filter(
(it) => !hierarchy.isMixin(it)
)
if (classes.length > 0) { if (classes.length > 0) {
query.query( query.query(
core.class.Space, classes.length === 1 ? classes[0] : core.class.Space,
{ {
_class: classes.length === 1 ? classes[0] : { $in: classes }, ...(classes.length === 1 ? {} : { _class: { $in: classes } }),
members: getCurrentAccount()._id members: getCurrentAccount()._id
}, },
(result) => { (result) => {

View File

@ -2,7 +2,7 @@ FROM node:20
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm RUN npm install --ignore-scripts=false --verbose bufferutil utf-8-validate @mongodb-js/zstd snappy --unsafe-perm
RUN npm install --ignore-scripts=false --verbose uNetworking/uWebSockets.js#v20.43.0 RUN npm install --ignore-scripts=false --verbose uNetworking/uWebSockets.js#v20.47.0
RUN apt-get update RUN apt-get update
RUN apt-get install libjemalloc2 RUN apt-get install libjemalloc2

View File

@ -122,10 +122,11 @@ async function handleVacancyUpdate (control: TriggerControl, cud: TxCUD<Doc>, re
const updateTx = cud as TxUpdateDoc<Vacancy> const updateTx = cud as TxUpdateDoc<Vacancy>
if (updateTx.operations.company !== undefined) { if (updateTx.operations.company !== undefined) {
// It could be null or new value // It could be null or new value
const txes = await control.findAll(core.class.TxCUD, { const txes = (
objectId: updateTx.objectId, await control.findAll(core.class.TxCUD, {
_id: { $nin: [updateTx._id] } objectId: updateTx.objectId
}) })
).filter((it) => it._id !== updateTx._id)
const vacancy = TxProcessor.buildDoc2Doc(txes) as Vacancy const vacancy = TxProcessor.buildDoc2Doc(txes) as Vacancy
if (vacancy.company != null) { if (vacancy.company != null) {
// We have old value // We have old value
@ -162,10 +163,11 @@ async function handleVacancyRemove (control: TriggerControl, cud: TxCUD<Doc>, ac
if (control.hierarchy.isDerived(cud.objectClass, recruit.class.Vacancy)) { if (control.hierarchy.isDerived(cud.objectClass, recruit.class.Vacancy)) {
const removeTx = actualTx as TxRemoveDoc<Vacancy> const removeTx = actualTx as TxRemoveDoc<Vacancy>
// It could be null or new value // It could be null or new value
const txes = await control.findAll(core.class.TxCUD, { const txes = (
objectId: removeTx.objectId, await control.findAll(core.class.TxCUD, {
_id: { $nin: [removeTx._id] } objectId: removeTx.objectId
}) })
).filter((it) => it._id !== removeTx._id)
const vacancy = TxProcessor.buildDoc2Doc(txes) as Vacancy const vacancy = TxProcessor.buildDoc2Doc(txes) as Vacancy
const res: Tx[] = [] const res: Tx[] = []
if (vacancy.company != null) { if (vacancy.company != null) {

View File

@ -44,11 +44,9 @@ export async function getValue (control: TriggerControl, context: Record<string,
} }
async function getEmployee (control: TriggerControl, _id: Ref<Account>): Promise<Person | undefined> { async function getEmployee (control: TriggerControl, _id: Ref<Account>): Promise<Person | undefined> {
const employeeAccount = ( const employeeAccount = control.modelDb.findAllSync(contact.class.PersonAccount, {
await control.modelDb.findAll(contact.class.PersonAccount, { _id: _id as Ref<PersonAccount>
_id: _id as Ref<PersonAccount> })[0]
})
)[0]
if (employeeAccount !== undefined) { if (employeeAccount !== undefined) {
const employee = ( const employee = (
await control.findAll(contact.class.Person, { await control.findAll(contact.class.Person, {

View File

@ -321,11 +321,12 @@ async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control:
if (upd.operations.value !== undefined) { if (upd.operations.value !== undefined) {
const logTxes = Array.from( const logTxes = Array.from(
await control.findAll(core.class.TxCollectionCUD, { await control.findAll(core.class.TxCollectionCUD, {
'tx.objectId': cud.objectId, 'tx.objectId': cud.objectId
_id: { $nin: [parentTx._id] }
}) })
)
.filter((it) => it._id !== parentTx._id)
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
).map(TxProcessor.extractTx) .map(TxProcessor.extractTx)
const doc: TimeSpendReport | undefined = TxProcessor.buildDoc2Doc(logTxes) const doc: TimeSpendReport | undefined = TxProcessor.buildDoc2Doc(logTxes)
const res: Tx[] = [] const res: Tx[] = []
@ -357,11 +358,12 @@ async function doTimeReportUpdate (cud: TxCUD<TimeSpendReport>, tx: Tx, control:
if (!control.removedMap.has(parentTx.objectId)) { if (!control.removedMap.has(parentTx.objectId)) {
const logTxes = Array.from( const logTxes = Array.from(
await control.findAll(core.class.TxCollectionCUD, { await control.findAll(core.class.TxCollectionCUD, {
'tx.objectId': cud.objectId, 'tx.objectId': cud.objectId
_id: { $nin: [parentTx._id] }
}) })
)
.filter((it) => it._id !== parentTx._id)
// eslint-disable-next-line @typescript-eslint/unbound-method // eslint-disable-next-line @typescript-eslint/unbound-method
).map(TxProcessor.extractTx) .map(TxProcessor.extractTx)
const doc: TimeSpendReport | undefined = TxProcessor.buildDoc2Doc(logTxes) const doc: TimeSpendReport | undefined = TxProcessor.buildDoc2Doc(logTxes)
if (doc !== undefined) { if (doc !== undefined) {
const [currentIssue] = await control.findAll(tracker.class.Issue, { _id: parentTx.objectId }, { limit: 1 }) const [currentIssue] = await control.findAll(tracker.class.Issue, { _id: parentTx.objectId }, { limit: 1 })

View File

@ -44,6 +44,10 @@ export class DomainIndexHelperImpl implements DomainHelper {
const attrs = hierarchy.getAllAttributes(c._id) const attrs = hierarchy.getAllAttributes(c._id)
const domainAttrs = this.domains.get(domain) ?? new Set<FieldIndexConfig<Doc>>() const domainAttrs = this.domains.get(domain) ?? new Set<FieldIndexConfig<Doc>>()
for (const a of attrs.values()) { for (const a of attrs.values()) {
if (a.isCustom === true) {
// Skip custom attribute indexes
continue
}
if (a.index !== undefined && a.index !== IndexKind.FullText) { if (a.index !== undefined && a.index !== IndexKind.FullText) {
domainAttrs.add({ domainAttrs.add({
keys: { keys: {

View File

@ -115,7 +115,19 @@ interface LookupStep {
} }
export async function toArray<T> (cursor: AbstractCursor<T>): Promise<T[]> { export async function toArray<T> (cursor: AbstractCursor<T>): Promise<T[]> {
const data = await cursor.toArray() const data: T[] = []
while (true) {
const d = await cursor.next()
if (d === null) {
break
}
data.push(d)
const batch = cursor.readBufferedDocuments()
if (batch.length > 0) {
data.push(...batch)
}
}
await cursor.close() await cursor.close()
return data return data
} }

View File

@ -125,14 +125,26 @@ export function getMongoClient (uri: string, options?: MongoClientOptions): Mong
const key = `${uri}${process.env.MONGO_OPTIONS ?? '{}'}_${JSON.stringify(options ?? {})}` const key = `${uri}${process.env.MONGO_OPTIONS ?? '{}'}_${JSON.stringify(options ?? {})}`
let existing = connections.get(key) let existing = connections.get(key)
const allOptions: MongoClientOptions = {
...options,
...extraOptions
}
// Make poll size stable
if (allOptions.maxPoolSize !== undefined) {
allOptions.minPoolSize = allOptions.maxPoolSize
}
allOptions.monitorCommands = false
allOptions.noDelay = true
// If not created or closed // If not created or closed
if (existing === undefined) { if (existing === undefined) {
existing = new MongoClientReferenceImpl( existing = new MongoClientReferenceImpl(
MongoClient.connect(uri, { MongoClient.connect(uri, {
appName: 'transactor', appName: 'transactor',
...options, enableUtf8Validation: false,
...extraOptions,
enableUtf8Validation: false ...allOptions
}), }),
() => { () => {
connections.delete(key) connections.delete(key)

View File

@ -385,7 +385,7 @@ export async function upgradeModel (
await tryMigrate(migrateClient, coreId, [ await tryMigrate(migrateClient, coreId, [
{ {
state: 'indexes-v3', state: 'indexes-v4',
func: upgradeIndexes func: upgradeIndexes
} }
]) ])

View File

@ -2,9 +2,9 @@
mkdir -p ./.build mkdir -p ./.build
cd ./.build cd ./.build
if ! test -f ./v20.43.0.zip; then if ! test -f ./v20.47.0.zip; then
wget --quiet https://github.com/uNetworking/uWebSockets.js/archive/refs/tags/v20.43.0.zip wget --quiet https://github.com/uNetworking/uWebSockets.js/archive/refs/tags/v20.47.0.zip
fi fi
if ! test -f ../lib/uws.js; then if ! test -f ../lib/uws.js; then
unzip -qq -j -o ./v20.43.0.zip -d ../lib unzip -qq -j -o ./v20.47.0.zip -d ../lib
fi fi

View File

@ -1,2 +1,2 @@
v20.43.0.zip v*.zip
src/uws src/uws

View File

@ -80,12 +80,13 @@ export function startHttpServer (
const token = req.query.token as string const token = req.query.token as string
const payload = decodeToken(token) const payload = decodeToken(token)
const admin = payload.extra?.admin === 'true' const admin = payload.extra?.admin === 'true'
res.writeHead(200, { 'Content-Type': 'application/json' }) const jsonData = {
const json = JSON.stringify({
...getStatistics(ctx, sessions, admin), ...getStatistics(ctx, sessions, admin),
users: getUsers, users: getUsers(),
admin admin
}) }
const json = JSON.stringify(jsonData)
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(json) res.end(json)
} catch (err: any) { } catch (err: any) {
Analytics.handleError(err) Analytics.handleError(err)

View File

@ -21,10 +21,10 @@ import core, {
type Class, type Class,
type TxMixin type TxMixin
} from '@hcengineering/core' } from '@hcengineering/core'
import github, { DocSyncInfo, GithubProject } from '@hcengineering/github'
import { TriggerControl } from '@hcengineering/server-core' import { TriggerControl } from '@hcengineering/server-core'
import time, { ToDo } from '@hcengineering/time' import time, { ToDo } from '@hcengineering/time'
import tracker from '@hcengineering/tracker' import tracker from '@hcengineering/tracker'
import github, { DocSyncInfo, GithubProject } from '@hcengineering/github'
/** /**
* @public * @public
@ -124,7 +124,7 @@ async function updateDocSyncInfo (
} }
} }
const [account] = await control.modelDb.findAll(contact.class.PersonAccount, { const [account] = control.modelDb.findAllSync(contact.class.PersonAccount, {
_id: tx.modifiedBy as Ref<PersonAccount> _id: tx.modifiedBy as Ref<PersonAccount>
}) })
// Do not modify state if is modified by github service. // Do not modify state if is modified by github service.