mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-20 15:20:18 +00:00
FindResult {total: number} (#1320)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
13f0569127
commit
f44c9a59ca
@ -22,6 +22,7 @@ import { ModelDb } from './memdb'
|
|||||||
import type { DocumentQuery, FindOptions, FindResult, Storage, TxResult, WithLookup } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, Storage, TxResult, WithLookup } from './storage'
|
||||||
import { SortingOrder } from './storage'
|
import { SortingOrder } from './storage'
|
||||||
import { Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from './tx'
|
import { Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from './tx'
|
||||||
|
import { toFindResult } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -73,7 +74,7 @@ class ClientImpl implements Client {
|
|||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
): Promise<FindResult<T>> {
|
): Promise<FindResult<T>> {
|
||||||
const domain = this.hierarchy.getDomain(_class)
|
const domain = this.hierarchy.getDomain(_class)
|
||||||
let result =
|
const data =
|
||||||
domain === DOMAIN_MODEL
|
domain === DOMAIN_MODEL
|
||||||
? await this.model.findAll(_class, query, options)
|
? await this.model.findAll(_class, query, options)
|
||||||
: await this.conn.findAll(_class, query, options)
|
: await this.conn.findAll(_class, query, options)
|
||||||
@ -81,10 +82,10 @@ class ClientImpl implements Client {
|
|||||||
// In case of mixin we need to create mixin proxies.
|
// In case of mixin we need to create mixin proxies.
|
||||||
|
|
||||||
// Update mixins & lookups
|
// Update mixins & lookups
|
||||||
result = result.map((v) => {
|
const result = data.map((v) => {
|
||||||
return this.hierarchy.updateLookupMixin(_class, v, options)
|
return this.hierarchy.updateLookupMixin(_class, v, options)
|
||||||
})
|
})
|
||||||
return result
|
return toFindResult(result, data.total)
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne<T extends Doc>(
|
async findOne<T extends Doc>(
|
||||||
@ -162,9 +163,7 @@ export async function createClient (
|
|||||||
if (t._class === core.class.TxCreateDoc) {
|
if (t._class === core.class.TxCreateDoc) {
|
||||||
const ct = t as TxCreateDoc<Doc>
|
const ct = t as TxCreateDoc<Doc>
|
||||||
if (ct.objectClass === core.class.PluginConfiguration) {
|
if (ct.objectClass === core.class.PluginConfiguration) {
|
||||||
configs.set(ct.objectId as Ref<PluginConfiguration>,
|
configs.set(ct.objectId as Ref<PluginConfiguration>, TxProcessor.createDoc2Doc(ct) as PluginConfiguration)
|
||||||
TxProcessor.createDoc2Doc(ct) as PluginConfiguration
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else if (t._class === core.class.TxUpdateDoc) {
|
} else if (t._class === core.class.TxUpdateDoc) {
|
||||||
const ut = t as TxUpdateDoc<Doc>
|
const ut = t as TxUpdateDoc<Doc>
|
||||||
@ -177,7 +176,7 @@ export async function createClient (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const excludedPlugins = Array.from(configs.values()).filter(it => !allowedPlugins.includes(it.pluginId as Plugin))
|
const excludedPlugins = Array.from(configs.values()).filter((it) => !allowedPlugins.includes(it.pluginId as Plugin))
|
||||||
|
|
||||||
for (const a of excludedPlugins) {
|
for (const a of excludedPlugins) {
|
||||||
for (const c of configs.values()) {
|
for (const c of configs.values()) {
|
||||||
@ -186,9 +185,9 @@ export async function createClient (
|
|||||||
for (const id of c.transactions) {
|
for (const id of c.transactions) {
|
||||||
excluded.add(id as Ref<Tx>)
|
excluded.add(id as Ref<Tx>)
|
||||||
}
|
}
|
||||||
const exclude = systemTx.filter(t => excluded.has(t._id))
|
const exclude = systemTx.filter((t) => excluded.has(t._id))
|
||||||
console.log('exclude plugin', c.pluginId, exclude.length)
|
console.log('exclude plugin', c.pluginId, exclude.length)
|
||||||
systemTx = systemTx.filter(t => !excluded.has(t._id))
|
systemTx = systemTx.filter((t) => !excluded.has(t._id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import { matchQuery, resultSort } from './query'
|
|||||||
import type { DocumentQuery, FindOptions, FindResult, LookupData, Storage, TxResult, WithLookup } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, LookupData, Storage, TxResult, WithLookup } from './storage'
|
||||||
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
|
||||||
import { TxProcessor } from './tx'
|
import { TxProcessor } from './tx'
|
||||||
|
import { toFindResult } from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -77,7 +78,7 @@ export abstract class MemDb extends TxProcessor {
|
|||||||
return doc as T
|
return doc as T
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLookupValue<T extends Doc> (doc: T, lookup: Lookup<T>, result: LookupData<T>): Promise<void> {
|
private async getLookupValue<T extends Doc>(doc: T, lookup: Lookup<T>, result: LookupData<T>): Promise<void> {
|
||||||
for (const key in lookup) {
|
for (const key in lookup) {
|
||||||
if (key === '_id') {
|
if (key === '_id') {
|
||||||
await this.getReverseLookupValue(doc, lookup, result)
|
await this.getReverseLookupValue(doc, lookup, result)
|
||||||
@ -101,7 +102,11 @@ export abstract class MemDb extends TxProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getReverseLookupValue<T extends Doc> (doc: T, lookup: ReverseLookups, result: LookupData<T>): Promise<void> {
|
private async getReverseLookupValue<T extends Doc>(
|
||||||
|
doc: T,
|
||||||
|
lookup: ReverseLookups,
|
||||||
|
result: LookupData<T>
|
||||||
|
): Promise<void> {
|
||||||
for (const key in lookup._id) {
|
for (const key in lookup._id) {
|
||||||
const value = lookup._id[key]
|
const value = lookup._id[key]
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
@ -129,7 +134,7 @@ export abstract class MemDb extends TxProcessor {
|
|||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T>
|
options?: FindOptions<T>
|
||||||
): Promise<FindResult<T>> {
|
): Promise<FindResult<T>> {
|
||||||
let result: Doc[]
|
let result: WithLookup<Doc>[]
|
||||||
const baseClass = this.hierarchy.getBaseClass(_class)
|
const baseClass = this.hierarchy.getBaseClass(_class)
|
||||||
if (
|
if (
|
||||||
Object.prototype.hasOwnProperty.call(query, '_id') &&
|
Object.prototype.hasOwnProperty.call(query, '_id') &&
|
||||||
@ -144,16 +149,17 @@ export abstract class MemDb extends TxProcessor {
|
|||||||
|
|
||||||
if (baseClass !== _class) {
|
if (baseClass !== _class) {
|
||||||
// We need to filter instances without mixin was set
|
// We need to filter instances without mixin was set
|
||||||
result = result.filter(r => (r as any)[_class] !== undefined)
|
result = result.filter((r) => (r as any)[_class] !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options?.lookup !== undefined) result = await this.lookup(result as T[], options.lookup)
|
if (options?.lookup !== undefined) result = await this.lookup(result as T[], options.lookup)
|
||||||
|
|
||||||
if (options?.sort !== undefined) resultSort(result, options?.sort)
|
if (options?.sort !== undefined) resultSort(result, options?.sort)
|
||||||
|
const total = result.length
|
||||||
result = result.slice(0, options?.limit)
|
result = result.slice(0, options?.limit)
|
||||||
const tresult = clone(result) as T[]
|
const tresult = clone(result) as WithLookup<T>[]
|
||||||
return tresult.map(it => this.hierarchy.updateLookupMixin(_class, it, options))
|
const res = tresult.map((it) => this.hierarchy.updateLookupMixin(_class, it, options))
|
||||||
|
return toFindResult(res, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
addDoc (doc: Doc): void {
|
addDoc (doc: Doc): void {
|
||||||
|
@ -145,7 +145,9 @@ export type WithLookup<T extends Doc> = T & {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export type FindResult<T extends Doc> = WithLookup<T>[]
|
export type FindResult<T extends Doc> = WithLookup<T>[] & {
|
||||||
|
total: number
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import type { Account, Doc, Ref } from './classes'
|
import type { Account, Doc, Ref } from './classes'
|
||||||
|
import { FindResult } from './storage'
|
||||||
|
|
||||||
function toHex (value: number, chars: number): string {
|
function toHex (value: number, chars: number): string {
|
||||||
const result = value.toString(16)
|
const result = value.toString(16)
|
||||||
@ -50,7 +51,9 @@ let currentAccount: Account
|
|||||||
* @public
|
* @public
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function getCurrentAccount (): Account { return currentAccount }
|
export function getCurrentAccount (): Account {
|
||||||
|
return currentAccount
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -65,3 +68,11 @@ export function setCurrentAccount (account: Account): void {
|
|||||||
export function escapeLikeForRegexp (value: string): string {
|
export function escapeLikeForRegexp (value: string): string {
|
||||||
return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
return value.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export function toFindResult<T extends Doc> (docs: T[], total?: number): FindResult<T> {
|
||||||
|
const length = total ?? docs.length
|
||||||
|
return Object.assign(docs, { total: length })
|
||||||
|
}
|
||||||
|
@ -41,7 +41,8 @@ import core, {
|
|||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxResult,
|
TxResult,
|
||||||
TxUpdateDoc,
|
TxUpdateDoc,
|
||||||
WithLookup
|
WithLookup,
|
||||||
|
toFindResult
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import justClone from 'just-clone'
|
import justClone from 'just-clone'
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ interface Query {
|
|||||||
query: DocumentQuery<Doc>
|
query: DocumentQuery<Doc>
|
||||||
result: Doc[] | Promise<Doc[]>
|
result: Doc[] | Promise<Doc[]>
|
||||||
options?: FindOptions<Doc>
|
options?: FindOptions<Doc>
|
||||||
|
total: number
|
||||||
callback: (result: FindResult<Doc>) => void
|
callback: (result: FindResult<Doc>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,12 +126,14 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
_class,
|
_class,
|
||||||
query,
|
query,
|
||||||
result,
|
result,
|
||||||
|
total: 0,
|
||||||
options: options as FindOptions<Doc>,
|
options: options as FindOptions<Doc>,
|
||||||
callback: callback as (result: Doc[]) => void
|
callback: callback as (result: Doc[]) => void
|
||||||
}
|
}
|
||||||
this.queries.push(q)
|
this.queries.push(q)
|
||||||
result
|
result
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
|
q.total = result.total
|
||||||
q.callback(result)
|
q.callback(result)
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@ -155,7 +159,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
doc[tx.bag] = bag = {}
|
doc[tx.bag] = bag = {}
|
||||||
}
|
}
|
||||||
bag[tx.key] = tx.value
|
bag[tx.key] = tx.value
|
||||||
await this.callback(updatedDoc, q)
|
await this.updatedDocCallback(updatedDoc, q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {}
|
return {}
|
||||||
@ -175,7 +179,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
if (updatedDoc !== undefined) {
|
if (updatedDoc !== undefined) {
|
||||||
// Create or apply mixin value
|
// Create or apply mixin value
|
||||||
updatedDoc = TxProcessor.updateMixin4Doc(updatedDoc, tx.mixin, tx.attributes)
|
updatedDoc = TxProcessor.updateMixin4Doc(updatedDoc, tx.mixin, tx.attributes)
|
||||||
await this.callback(updatedDoc, q)
|
await this.updatedDocCallback(updatedDoc, q)
|
||||||
} else {
|
} else {
|
||||||
if (this.getHierarchy().isDerived(tx.mixin, q._class)) {
|
if (this.getHierarchy().isDerived(tx.mixin, q._class)) {
|
||||||
// Mixin potentially added to object we doesn't have in out results
|
// Mixin potentially added to object we doesn't have in out results
|
||||||
@ -241,6 +245,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
const match = await this.findOne(q._class, { $search: q.query.$search, _id: tx.objectId }, q.options)
|
const match = await this.findOne(q._class, { $search: q.query.$search, _id: tx.objectId }, q.options)
|
||||||
if (match === undefined) {
|
if (match === undefined) {
|
||||||
q.result.splice(pos, 1)
|
q.result.splice(pos, 1)
|
||||||
|
q.total--
|
||||||
} else {
|
} else {
|
||||||
q.result[pos] = match
|
q.result[pos] = match
|
||||||
}
|
}
|
||||||
@ -253,18 +258,20 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
q.result[pos] = current
|
q.result[pos] = current
|
||||||
} else {
|
} else {
|
||||||
q.result.splice(pos, 1)
|
q.result.splice(pos, 1)
|
||||||
|
q.total--
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.__updateDoc(q, updatedDoc, tx)
|
await this.__updateDoc(q, updatedDoc, tx)
|
||||||
if (!this.match(q, updatedDoc)) {
|
if (!this.match(q, updatedDoc)) {
|
||||||
q.result.splice(pos, 1)
|
q.result.splice(pos, 1)
|
||||||
|
q.total--
|
||||||
} else {
|
} else {
|
||||||
q.result[pos] = updatedDoc
|
q.result[pos] = updatedDoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.sort(q, tx)
|
this.sort(q, tx)
|
||||||
await this.callback(q.result[pos], q)
|
await this.updatedDocCallback(q.result[pos], q)
|
||||||
} else if (this.matchQuery(q, tx)) {
|
} else if (this.matchQuery(q, tx)) {
|
||||||
return await this.refresh(q)
|
return await this.refresh(q)
|
||||||
}
|
}
|
||||||
@ -284,7 +291,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
if (q.options?.sort !== undefined) {
|
if (q.options?.sort !== undefined) {
|
||||||
resultSort(q.result, q.options?.sort)
|
resultSort(q.result, q.options?.sort)
|
||||||
}
|
}
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +335,10 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async refresh (q: Query): Promise<void> {
|
private async refresh (q: Query): Promise<void> {
|
||||||
q.result = this.client.findAll(q._class, q.query, q.options)
|
const res = await this.client.findAll(q._class, q.query, q.options)
|
||||||
q.result = await q.result
|
q.result = res
|
||||||
q.callback(this.clone(q.result))
|
q.total = res.total
|
||||||
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if query is partially matched.
|
// Check if query is partially matched.
|
||||||
@ -442,6 +450,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
if (match === undefined) return
|
if (match === undefined) return
|
||||||
}
|
}
|
||||||
q.result.push(doc)
|
q.result.push(doc)
|
||||||
|
q.total++
|
||||||
|
|
||||||
if (q.options?.sort !== undefined) {
|
if (q.options?.sort !== undefined) {
|
||||||
resultSort(q.result, q.options?.sort)
|
resultSort(q.result, q.options?.sort)
|
||||||
@ -449,16 +458,24 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
|
|
||||||
if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
|
if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
|
||||||
if (q.result.pop()?._id !== doc._id) {
|
if (q.result.pop()?._id !== doc._id) {
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.handleDocAddLookup(q, doc)
|
await this.handleDocAddLookup(q, doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async callback (q: Query): Promise<void> {
|
||||||
|
if (q.result instanceof Promise) {
|
||||||
|
q.result = await q.result
|
||||||
|
}
|
||||||
|
const clone = this.clone(q.result)
|
||||||
|
q.callback(toFindResult(clone, q.total))
|
||||||
|
}
|
||||||
|
|
||||||
private async handleDocAddLookup (q: Query, doc: Doc): Promise<void> {
|
private async handleDocAddLookup (q: Query, doc: Doc): Promise<void> {
|
||||||
if (q.options?.lookup === undefined) return
|
if (q.options?.lookup === undefined) return
|
||||||
const lookup = q.options.lookup
|
const lookup = q.options.lookup
|
||||||
@ -472,7 +489,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
if (q.options?.sort !== undefined) {
|
if (q.options?.sort !== undefined) {
|
||||||
resultSort(q.result, q.options?.sort)
|
resultSort(q.result, q.options?.sort)
|
||||||
}
|
}
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,7 +546,8 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
const index = q.result.findIndex((p) => p._id === tx.objectId)
|
const index = q.result.findIndex((p) => p._id === tx.objectId)
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
q.result.splice(index, 1)
|
q.result.splice(index, 1)
|
||||||
q.callback(this.clone(q.result))
|
q.total--
|
||||||
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
await this.handleDocRemoveLookup(q, tx)
|
await this.handleDocRemoveLookup(q, tx)
|
||||||
}
|
}
|
||||||
@ -568,7 +586,7 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
if (q.options?.sort !== undefined) {
|
if (q.options?.sort !== undefined) {
|
||||||
resultSort(q.result, q.options?.sort)
|
resultSort(q.result, q.options?.sort)
|
||||||
}
|
}
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -717,16 +735,18 @@ export class LiveQuery extends TxProcessor implements Client {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private async callback (updatedDoc: Doc, q: Query): Promise<void> {
|
private async updatedDocCallback (updatedDoc: Doc, q: Query): Promise<void> {
|
||||||
q.result = q.result as Doc[]
|
q.result = q.result as Doc[]
|
||||||
|
|
||||||
if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
|
if (q.options?.limit !== undefined && q.result.length > q.options.limit) {
|
||||||
if (q.result[q.options?.limit]._id === updatedDoc._id) {
|
if (q.result[q.options?.limit]._id === updatedDoc._id) {
|
||||||
return await this.refresh(q)
|
return await this.refresh(q)
|
||||||
}
|
}
|
||||||
if (q.result.pop()?._id !== updatedDoc._id) q.callback(q.result)
|
if (q.result.pop()?._id !== updatedDoc._id) {
|
||||||
|
await this.callback(q)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
q.callback(this.clone(q.result))
|
await this.callback(q)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
|
|
||||||
let channels: AttachedData<Channel>[] = []
|
let channels: AttachedData<Channel>[] = []
|
||||||
|
|
||||||
let matches: FindResult<Person> = []
|
let matches: Person[] = []
|
||||||
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
|
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
|
||||||
matches = p
|
matches = p
|
||||||
})
|
})
|
||||||
|
@ -13,7 +13,19 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { Account, AttachedData, AttachedDoc, Class, Client, Data, Doc, FindResult, Ref, Space, UXObject } from '@anticrm/core'
|
import {
|
||||||
|
Account,
|
||||||
|
AttachedData,
|
||||||
|
AttachedDoc,
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
Data,
|
||||||
|
Doc,
|
||||||
|
FindResult,
|
||||||
|
Ref,
|
||||||
|
Space,
|
||||||
|
UXObject
|
||||||
|
} from '@anticrm/core'
|
||||||
import type { Asset, Plugin } from '@anticrm/platform'
|
import type { Asset, Plugin } from '@anticrm/platform'
|
||||||
import { IntlString, plugin } from '@anticrm/platform'
|
import { IntlString, plugin } from '@anticrm/platform'
|
||||||
import type { AnyComponent } from '@anticrm/ui'
|
import type { AnyComponent } from '@anticrm/ui'
|
||||||
@ -61,15 +73,12 @@ export interface Contact extends Doc {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface Person extends Contact {
|
export interface Person extends Contact {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export interface Organization extends Contact {
|
export interface Organization extends Contact {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
@ -180,37 +189,47 @@ export default contactPlugin
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function findPerson (client: Client, person: Data<Person>, channels: AttachedData<Channel>[]): Promise<FindResult<Person>> {
|
export async function findPerson (
|
||||||
|
client: Client,
|
||||||
|
person: Data<Person>,
|
||||||
|
channels: AttachedData<Channel>[]
|
||||||
|
): Promise<Person[]> {
|
||||||
if (channels.length === 0 || person.name.length === 0) {
|
if (channels.length === 0 || person.name.length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
// Take only first part of first name for match.
|
// Take only first part of first name for match.
|
||||||
const values = channels.map(it => it.value)
|
const values = channels.map((it) => it.value)
|
||||||
|
|
||||||
// Same name persons
|
// Same name persons
|
||||||
|
|
||||||
const potentialChannels = await client.findAll(contactPlugin.class.Channel, { value: { $in: values } })
|
const potentialChannels = await client.findAll(contactPlugin.class.Channel, { value: { $in: values } })
|
||||||
let potentialPersonIds = Array.from(new Set(potentialChannels.map(it => it.attachedTo as Ref<Person>)).values())
|
let potentialPersonIds = Array.from(new Set(potentialChannels.map((it) => it.attachedTo as Ref<Person>)).values())
|
||||||
|
|
||||||
if (potentialPersonIds.length === 0) {
|
if (potentialPersonIds.length === 0) {
|
||||||
const firstName = getFirstName(person.name).split(' ').shift() ?? ''
|
const firstName = getFirstName(person.name).split(' ').shift() ?? ''
|
||||||
const lastName = getLastName(person.name)
|
const lastName = getLastName(person.name)
|
||||||
// try match using just first/last name
|
// try match using just first/last name
|
||||||
potentialPersonIds = (await client.findAll(contactPlugin.class.Person, { name: { $like: `${lastName}%${firstName}%` } })).map(it => it._id)
|
potentialPersonIds = (
|
||||||
|
await client.findAll(contactPlugin.class.Person, { name: { $like: `${lastName}%${firstName}%` } })
|
||||||
|
).map((it) => it._id)
|
||||||
if (potentialPersonIds.length === 0) {
|
if (potentialPersonIds.length === 0) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const potentialPersons: FindResult<Person> = await client.findAll(contactPlugin.class.Person, { _id: { $in: potentialPersonIds } }, {
|
const potentialPersons: FindResult<Person> = await client.findAll(
|
||||||
lookup: {
|
contactPlugin.class.Person,
|
||||||
_id: {
|
{ _id: { $in: potentialPersonIds } },
|
||||||
channels: contactPlugin.class.Channel
|
{
|
||||||
|
lookup: {
|
||||||
|
_id: {
|
||||||
|
channels: contactPlugin.class.Channel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
const result: FindResult<Person> = []
|
const result: Person[] = []
|
||||||
|
|
||||||
for (const c of potentialPersons) {
|
for (const c of potentialPersons) {
|
||||||
let matches = 0
|
let matches = 0
|
||||||
@ -220,7 +239,7 @@ export async function findPerson (client: Client, person: Data<Person>, channels
|
|||||||
if (c.city === person.city) {
|
if (c.city === person.city) {
|
||||||
matches++
|
matches++
|
||||||
}
|
}
|
||||||
for (const ch of c.$lookup?.channels as Channel[] ?? []) {
|
for (const ch of (c.$lookup?.channels as Channel[]) ?? []) {
|
||||||
for (const chc of channels) {
|
for (const chc of channels) {
|
||||||
if (chc.provider === ch.provider && chc.value === ch.value.trim()) {
|
if (chc.provider === ch.provider && chc.value === ch.value.trim()) {
|
||||||
// We have matched value
|
// We have matched value
|
||||||
|
@ -13,13 +13,27 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import core, {
|
||||||
|
Class,
|
||||||
|
Client,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
FindOptions,
|
||||||
|
FindResult,
|
||||||
|
Hierarchy,
|
||||||
|
ModelDb,
|
||||||
|
Ref,
|
||||||
|
toFindResult,
|
||||||
|
Tx,
|
||||||
|
TxResult,
|
||||||
|
WithLookup
|
||||||
|
} from '@anticrm/core'
|
||||||
|
import { Builder } from '@anticrm/model'
|
||||||
import { getMetadata, IntlString, Resources } from '@anticrm/platform'
|
import { getMetadata, IntlString, Resources } from '@anticrm/platform'
|
||||||
|
import view from '@anticrm/view'
|
||||||
|
import workbench from '@anticrm/workbench'
|
||||||
import ModelView from './components/ModelView.svelte'
|
import ModelView from './components/ModelView.svelte'
|
||||||
import QueryView from './components/QueryView.svelte'
|
import QueryView from './components/QueryView.svelte'
|
||||||
import core, { Class, Client, Doc, DocumentQuery, FindOptions, Ref, FindResult, Hierarchy, ModelDb, Tx, TxResult, WithLookup, Metrics } from '@anticrm/core'
|
|
||||||
import { Builder } from '@anticrm/model'
|
|
||||||
import workbench from '@anticrm/workbench'
|
|
||||||
import view from '@anticrm/view'
|
|
||||||
import devmodel from './plugin'
|
import devmodel from './plugin'
|
||||||
|
|
||||||
export interface TxWitHResult {
|
export interface TxWitHResult {
|
||||||
@ -61,19 +75,53 @@ class ModelClient implements Client {
|
|||||||
return this.client.getModel()
|
return this.client.getModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOne <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<WithLookup<T> | undefined> {
|
async findOne<T extends Doc>(
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T>
|
||||||
|
): Promise<WithLookup<T> | undefined> {
|
||||||
const result = await this.client.findOne(_class, query, options)
|
const result = await this.client.findOne(_class, query, options)
|
||||||
console.info('devmodel# findOne=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel(), getMetadata(devmodel.metadata.DevModel))
|
console.info(
|
||||||
queries.push({ _class, query, options: options as FindOptions<Doc>, result: result !== undefined ? [result] : [], findOne: true })
|
'devmodel# findOne=>',
|
||||||
|
_class,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
'result => ',
|
||||||
|
result,
|
||||||
|
' =>model',
|
||||||
|
this.client.getModel(),
|
||||||
|
getMetadata(devmodel.metadata.DevModel)
|
||||||
|
)
|
||||||
|
queries.push({
|
||||||
|
_class,
|
||||||
|
query,
|
||||||
|
options: options as FindOptions<Doc>,
|
||||||
|
result: toFindResult(result !== undefined ? [result] : []),
|
||||||
|
findOne: true
|
||||||
|
})
|
||||||
if (queries.length > 100) {
|
if (queries.length > 100) {
|
||||||
queries.shift()
|
queries.shift()
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async findAll<T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<FindResult<T>> {
|
async findAll<T extends Doc>(
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T>
|
||||||
|
): Promise<FindResult<T>> {
|
||||||
const result = await this.client.findAll(_class, query, options)
|
const result = await this.client.findAll(_class, query, options)
|
||||||
console.info('devmodel# findAll=>', _class, query, options, 'result => ', result, ' =>model', this.client.getModel(), getMetadata(devmodel.metadata.DevModel))
|
console.info(
|
||||||
|
'devmodel# findAll=>',
|
||||||
|
_class,
|
||||||
|
query,
|
||||||
|
options,
|
||||||
|
'result => ',
|
||||||
|
result,
|
||||||
|
' =>model',
|
||||||
|
this.client.getModel(),
|
||||||
|
getMetadata(devmodel.metadata.DevModel)
|
||||||
|
)
|
||||||
queries.push({ _class, query, options: options as FindOptions<Doc>, result, findOne: false })
|
queries.push({ _class, query, options: options as FindOptions<Doc>, result, findOne: false })
|
||||||
if (queries.length > 100) {
|
if (queries.length > 100) {
|
||||||
queries.shift()
|
queries.shift()
|
||||||
@ -101,29 +149,33 @@ export async function Hook (client: Client): Promise<Client> {
|
|||||||
// Client is alive here, we could hook with some model extensions special for DevModel plugin.
|
// Client is alive here, we could hook with some model extensions special for DevModel plugin.
|
||||||
const builder = new Builder()
|
const builder = new Builder()
|
||||||
|
|
||||||
builder.createDoc(workbench.class.Application, core.space.Model, {
|
builder.createDoc(
|
||||||
label: 'DevModel' as IntlString,
|
workbench.class.Application,
|
||||||
icon: view.icon.Table,
|
core.space.Model,
|
||||||
hidden: false,
|
{
|
||||||
navigatorModel: {
|
label: 'DevModel' as IntlString,
|
||||||
spaces: [
|
icon: view.icon.Table,
|
||||||
],
|
hidden: false,
|
||||||
specials: [
|
navigatorModel: {
|
||||||
{
|
spaces: [],
|
||||||
label: 'Transactions' as IntlString,
|
specials: [
|
||||||
icon: view.icon.Table,
|
{
|
||||||
id: 'transactions',
|
label: 'Transactions' as IntlString,
|
||||||
component: devmodel.component.ModelView
|
icon: view.icon.Table,
|
||||||
},
|
id: 'transactions',
|
||||||
{
|
component: devmodel.component.ModelView
|
||||||
label: 'Queries' as IntlString,
|
},
|
||||||
icon: view.icon.Table,
|
{
|
||||||
id: 'queries',
|
label: 'Queries' as IntlString,
|
||||||
component: devmodel.component.QueryView
|
icon: view.icon.Table,
|
||||||
}
|
id: 'queries',
|
||||||
]
|
component: devmodel.component.QueryView
|
||||||
}
|
}
|
||||||
}, devmodel.ids.DevModelApp)
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
devmodel.ids.DevModelApp
|
||||||
|
)
|
||||||
|
|
||||||
const model = client.getModel()
|
const model = client.getModel()
|
||||||
for (const tx of builder.getTxes()) {
|
for (const tx of builder.getTxes()) {
|
||||||
|
@ -378,7 +378,7 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let matches: FindResult<Person> = []
|
let matches: Person[] = []
|
||||||
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
|
$: findPerson(client, { ...object, name: combineName(firstName, lastName) }, channels).then((p) => {
|
||||||
matches = p
|
matches = p
|
||||||
})
|
})
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
import type { Candidate, Review } from '@anticrm/recruit'
|
import type { Candidate, Review } from '@anticrm/recruit'
|
||||||
import task, { SpaceWithStates } from '@anticrm/task'
|
import task, { SpaceWithStates } from '@anticrm/task'
|
||||||
import { StyledTextBox } from '@anticrm/text-editor'
|
import { StyledTextBox } from '@anticrm/text-editor'
|
||||||
import ui, { DateRangePicker, Grid, Status as StatusControl, StylishEdit, EditBox, Row } from '@anticrm/ui'
|
import { DateRangePicker, Grid, Status as StatusControl, EditBox, Row } from '@anticrm/ui'
|
||||||
import view from '@anticrm/view'
|
import view from '@anticrm/view'
|
||||||
import { createEventDispatcher } from 'svelte'
|
import { createEventDispatcher } from 'svelte'
|
||||||
import recruit from '../../plugin'
|
import recruit from '../../plugin'
|
||||||
|
@ -93,15 +93,21 @@ async function UnarchiveSpace (object: SpaceWithStates): Promise<void> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryTask<D extends Task> (_class: Ref<Class<D>>, client: Client, search: string): Promise<ObjectSearchResult[]> {
|
export async function queryTask<D extends Task> (
|
||||||
|
_class: Ref<Class<D>>,
|
||||||
|
client: Client,
|
||||||
|
search: string
|
||||||
|
): Promise<ObjectSearchResult[]> {
|
||||||
const cl = client.getHierarchy().getClass(_class)
|
const cl = client.getHierarchy().getClass(_class)
|
||||||
const shortLabel = (await translate(cl.shortLabel ?? '' as IntlString, {})).toUpperCase()
|
const shortLabel = (await translate(cl.shortLabel ?? ('' as IntlString), {})).toUpperCase()
|
||||||
|
|
||||||
// Check number pattern
|
// Check number pattern
|
||||||
|
|
||||||
const sequence = (await client.findOne(task.class.Sequence, { attachedTo: _class }))?.sequence ?? 0
|
const sequence = (await client.findOne(task.class.Sequence, { attachedTo: _class }))?.sequence ?? 0
|
||||||
|
|
||||||
const named = new Map((await client.findAll(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map(e => [e._id, e]))
|
const named = new Map(
|
||||||
|
(await client.findAll<Task>(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map((e) => [e._id, e])
|
||||||
|
)
|
||||||
const nids: number[] = []
|
const nids: number[] = []
|
||||||
if (sequence > 0) {
|
if (sequence > 0) {
|
||||||
for (let n = 0; n < sequence; n++) {
|
for (let n = 0; n < sequence; n++) {
|
||||||
@ -110,7 +116,7 @@ export async function queryTask<D extends Task> (_class: Ref<Class<D>>, client:
|
|||||||
nids.push(n)
|
nids.push(n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const numbered = await client.findAll<Task>(_class, { number: { $in: nids } }, { limit: 200 }) as D[]
|
const numbered = await client.findAll<Task>(_class, { number: { $in: nids } }, { limit: 200 })
|
||||||
for (const d of numbered) {
|
for (const d of numbered) {
|
||||||
if (!named.has(d._id)) {
|
if (!named.has(d._id)) {
|
||||||
named.set(d._id, d)
|
named.set(d._id, d)
|
||||||
@ -118,7 +124,7 @@ export async function queryTask<D extends Task> (_class: Ref<Class<D>>, client:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(named.values()).map(e => ({
|
return Array.from(named.values()).map((e) => ({
|
||||||
doc: e,
|
doc: e,
|
||||||
title: `${shortLabel}-${e.number}`,
|
title: `${shortLabel}-${e.number}`,
|
||||||
icon: task.icon.Task,
|
icon: task.icon.Task,
|
||||||
|
@ -40,7 +40,8 @@ import core, {
|
|||||||
TxPutBag,
|
TxPutBag,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxResult,
|
TxResult,
|
||||||
TxUpdateDoc
|
TxUpdateDoc,
|
||||||
|
toFindResult
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import type { FullTextAdapter, IndexedDoc, WithFind } from './types'
|
import type { FullTextAdapter, IndexedDoc, WithFind } from './types'
|
||||||
|
|
||||||
@ -141,13 +142,14 @@ export class FullTextIndex implements WithFind {
|
|||||||
): Promise<FindResult<T>> {
|
): Promise<FindResult<T>> {
|
||||||
console.log('search', query)
|
console.log('search', query)
|
||||||
const { _id, $search, ...mainQuery } = query
|
const { _id, $search, ...mainQuery } = query
|
||||||
if ($search === undefined) return []
|
if ($search === undefined) return toFindResult([])
|
||||||
|
|
||||||
let skip = 0
|
let skip = 0
|
||||||
const result: FindResult<T> = []
|
const result: FindResult<T> = toFindResult([])
|
||||||
while (true) {
|
while (true) {
|
||||||
const docs = await this.adapter.search(_class, query, options?.limit, skip)
|
const docs = await this.adapter.search(_class, query, options?.limit, skip)
|
||||||
if (docs.length === 0) {
|
if (docs.length === 0) {
|
||||||
|
result.total = result.length
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
skip += docs.length
|
skip += docs.length
|
||||||
@ -158,7 +160,9 @@ export class FullTextIndex implements WithFind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const resultIds = getResultIds(ids, _id)
|
const resultIds = getResultIds(ids, _id)
|
||||||
result.push(...await this.dbStorage.findAll(ctx, _class, { _id: { $in: resultIds }, ...mainQuery }, options))
|
const current = await this.dbStorage.findAll(ctx, _class, { _id: { $in: resultIds }, ...mainQuery }, options)
|
||||||
|
result.push(...current)
|
||||||
|
result.total += current.total
|
||||||
if (result.length > 0 && result.length >= (options?.limit ?? 0)) {
|
if (result.length > 0 && result.length >= (options?.limit ?? 0)) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,16 @@ import core, {
|
|||||||
FindOptions,
|
FindOptions,
|
||||||
FindResult,
|
FindResult,
|
||||||
generateId,
|
generateId,
|
||||||
Hierarchy, MeasureMetricsContext, ModelDb, Ref,
|
Hierarchy,
|
||||||
|
MeasureMetricsContext,
|
||||||
|
ModelDb,
|
||||||
|
Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
Space,
|
Space,
|
||||||
Tx,
|
Tx,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxResult
|
TxResult,
|
||||||
|
toFindResult
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import { createServerStorage, DbAdapter, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
import { createServerStorage, DbAdapter, DbConfiguration, FullTextAdapter, IndexedDoc } from '@anticrm/server-core'
|
||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
@ -50,7 +54,7 @@ class NullDbAdapter implements DbAdapter {
|
|||||||
query: DocumentQuery<T>,
|
query: DocumentQuery<T>,
|
||||||
options?: FindOptions<T> | undefined
|
options?: FindOptions<T> | undefined
|
||||||
): Promise<FindResult<T>> {
|
): Promise<FindResult<T>> {
|
||||||
return []
|
return toFindResult([])
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (tx: Tx): Promise<TxResult> {
|
async tx (tx: Tx): Promise<TxResult> {
|
||||||
@ -78,9 +82,7 @@ class NullFullTextAdapter implements FullTextAdapter {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove (id: Ref<Doc>): Promise<void> {
|
async remove (id: Ref<Doc>): Promise<void> {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async close (): Promise<void> {}
|
async close (): Promise<void> {}
|
||||||
}
|
}
|
||||||
@ -276,43 +278,76 @@ describe('mongo operations', () => {
|
|||||||
rate: 20
|
rate: 20
|
||||||
})
|
})
|
||||||
|
|
||||||
const commentId = await operations.addCollection(taskPlugin.class.TaskComment, '' as Ref<Space>, docId, taskPlugin.class.Task, 'tasks', {
|
const commentId = await operations.addCollection(
|
||||||
message: 'my-msg',
|
taskPlugin.class.TaskComment,
|
||||||
date: new Date()
|
'' as Ref<Space>,
|
||||||
})
|
docId,
|
||||||
|
taskPlugin.class.Task,
|
||||||
await operations.addCollection(taskPlugin.class.TaskComment, '' as Ref<Space>, docId, taskPlugin.class.Task, 'tasks', {
|
'tasks',
|
||||||
message: 'my-msg2',
|
{
|
||||||
date: new Date()
|
message: 'my-msg',
|
||||||
})
|
date: new Date()
|
||||||
|
|
||||||
const r2 = await client.findAll<TaskComment>(taskPlugin.class.TaskComment, {}, {
|
|
||||||
lookup: {
|
|
||||||
attachedTo: taskPlugin.class.Task
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
|
await operations.addCollection(
|
||||||
|
taskPlugin.class.TaskComment,
|
||||||
|
'' as Ref<Space>,
|
||||||
|
docId,
|
||||||
|
taskPlugin.class.Task,
|
||||||
|
'tasks',
|
||||||
|
{
|
||||||
|
message: 'my-msg2',
|
||||||
|
date: new Date()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const r2 = await client.findAll<TaskComment>(
|
||||||
|
taskPlugin.class.TaskComment,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
attachedTo: taskPlugin.class.Task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
expect(r2.length).toEqual(2)
|
expect(r2.length).toEqual(2)
|
||||||
expect((r2[0].$lookup?.attachedTo as Task)?._id).toEqual(docId)
|
expect((r2[0].$lookup?.attachedTo as Task)?._id).toEqual(docId)
|
||||||
|
|
||||||
const r3 = await client.findAll<Task>(taskPlugin.class.Task, {}, {
|
const r3 = await client.findAll<Task>(
|
||||||
lookup: {
|
taskPlugin.class.Task,
|
||||||
_id: { comment: taskPlugin.class.TaskComment }
|
{},
|
||||||
|
{
|
||||||
|
lookup: {
|
||||||
|
_id: { comment: taskPlugin.class.TaskComment }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
expect(r3).toHaveLength(1)
|
expect(r3).toHaveLength(1)
|
||||||
expect((r3[0].$lookup as any).comment).toHaveLength(2)
|
expect((r3[0].$lookup as any).comment).toHaveLength(2)
|
||||||
|
|
||||||
const comment2Id = await operations.addCollection(taskPlugin.class.TaskComment, '' as Ref<Space>, commentId, taskPlugin.class.TaskComment, 'comments', {
|
const comment2Id = await operations.addCollection(
|
||||||
message: 'my-msg3',
|
taskPlugin.class.TaskComment,
|
||||||
date: new Date()
|
'' as Ref<Space>,
|
||||||
})
|
commentId,
|
||||||
|
taskPlugin.class.TaskComment,
|
||||||
|
'comments',
|
||||||
|
{
|
||||||
|
message: 'my-msg3',
|
||||||
|
date: new Date()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const r4 = await client.findAll<TaskComment>(taskPlugin.class.TaskComment, {
|
const r4 = await client.findAll<TaskComment>(
|
||||||
_id: comment2Id
|
taskPlugin.class.TaskComment,
|
||||||
}, {
|
{
|
||||||
lookup: { attachedTo: [taskPlugin.class.TaskComment, { attachedTo: taskPlugin.class.Task } as any] }
|
_id: comment2Id
|
||||||
})
|
},
|
||||||
|
{
|
||||||
|
lookup: { attachedTo: [taskPlugin.class.TaskComment, { attachedTo: taskPlugin.class.Task } as any] }
|
||||||
|
}
|
||||||
|
)
|
||||||
expect((r4[0].$lookup?.attachedTo as TaskComment)?._id).toEqual(commentId)
|
expect((r4[0].$lookup?.attachedTo as TaskComment)?._id).toEqual(commentId)
|
||||||
expect(((r4[0].$lookup?.attachedTo as any)?.$lookup.attachedTo as Task)?._id).toEqual(docId)
|
expect(((r4[0].$lookup?.attachedTo as any)?.$lookup.attachedTo as Task)?._id).toEqual(docId)
|
||||||
})
|
})
|
||||||
|
@ -16,12 +16,29 @@
|
|||||||
import core, {
|
import core, {
|
||||||
Class,
|
Class,
|
||||||
Doc,
|
Doc,
|
||||||
DocumentQuery, DOMAIN_MODEL, DOMAIN_TX, escapeLikeForRegexp, FindOptions, FindResult, Hierarchy, isOperator, Lookup, Mixin, ModelDb, Ref, ReverseLookups, SortingOrder, Tx,
|
DocumentQuery,
|
||||||
|
DOMAIN_MODEL,
|
||||||
|
DOMAIN_TX,
|
||||||
|
escapeLikeForRegexp,
|
||||||
|
FindOptions,
|
||||||
|
FindResult,
|
||||||
|
Hierarchy,
|
||||||
|
isOperator,
|
||||||
|
Lookup,
|
||||||
|
Mixin,
|
||||||
|
ModelDb,
|
||||||
|
Ref,
|
||||||
|
ReverseLookups,
|
||||||
|
SortingOrder,
|
||||||
|
Tx,
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxMixin, TxProcessor, TxPutBag,
|
TxMixin,
|
||||||
|
TxProcessor,
|
||||||
|
TxPutBag,
|
||||||
TxRemoveDoc,
|
TxRemoveDoc,
|
||||||
TxResult,
|
TxResult,
|
||||||
TxUpdateDoc
|
TxUpdateDoc,
|
||||||
|
toFindResult
|
||||||
} from '@anticrm/core'
|
} from '@anticrm/core'
|
||||||
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
|
||||||
import { Collection, Db, Document, Filter, MongoClient, Sort } from 'mongodb'
|
import { Collection, Db, Document, Filter, MongoClient, Sort } from 'mongodb'
|
||||||
@ -39,7 +56,12 @@ interface LookupStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class MongoAdapterBase extends TxProcessor {
|
abstract class MongoAdapterBase extends TxProcessor {
|
||||||
constructor (protected readonly db: Db, protected readonly hierarchy: Hierarchy, protected readonly modelDb: ModelDb, protected readonly client: MongoClient) {
|
constructor (
|
||||||
|
protected readonly db: Db,
|
||||||
|
protected readonly hierarchy: Hierarchy,
|
||||||
|
protected readonly modelDb: ModelDb,
|
||||||
|
protected readonly client: MongoClient
|
||||||
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +82,10 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
if (keys[0] === '$like') {
|
if (keys[0] === '$like') {
|
||||||
const pattern = value.$like as string
|
const pattern = value.$like as string
|
||||||
translated[tkey] = {
|
translated[tkey] = {
|
||||||
$regex: `^${pattern.split('%').map(it => escapeLikeForRegexp(it)).join('.*')}$`,
|
$regex: `^${pattern
|
||||||
|
.split('%')
|
||||||
|
.map((it) => escapeLikeForRegexp(it))
|
||||||
|
.join('.*')}$`,
|
||||||
$options: 'i'
|
$options: 'i'
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@ -84,7 +109,7 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
return translated
|
return translated
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLookupValue<T extends Doc> (lookup: Lookup<T>, result: LookupStep[], parent?: string): Promise<void> {
|
private async getLookupValue<T extends Doc>(lookup: Lookup<T>, result: LookupStep[], parent?: string): Promise<void> {
|
||||||
for (const key in lookup) {
|
for (const key in lookup) {
|
||||||
if (key === '_id') {
|
if (key === '_id') {
|
||||||
await this.getReverseLookupValue(lookup, result, parent)
|
await this.getReverseLookupValue(lookup, result, parent)
|
||||||
@ -119,7 +144,11 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getReverseLookupValue (lookup: ReverseLookups, result: LookupStep[], parent?: string): Promise<any | undefined> {
|
private async getReverseLookupValue (
|
||||||
|
lookup: ReverseLookups,
|
||||||
|
result: LookupStep[],
|
||||||
|
parent?: string
|
||||||
|
): Promise<any | undefined> {
|
||||||
const fullKey = parent !== undefined ? parent + '.' + '_id' : '_id'
|
const fullKey = parent !== undefined ? parent + '.' + '_id' : '_id'
|
||||||
for (const key in lookup._id) {
|
for (const key in lookup._id) {
|
||||||
const as = parent !== undefined ? parent + key : key
|
const as = parent !== undefined ? parent + key : key
|
||||||
@ -147,14 +176,20 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLookups<T extends Doc> (lookup: Lookup<T> | undefined, parent?: string): Promise<LookupStep[]> {
|
private async getLookups<T extends Doc>(lookup: Lookup<T> | undefined, parent?: string): Promise<LookupStep[]> {
|
||||||
if (lookup === undefined) return []
|
if (lookup === undefined) return []
|
||||||
const result: [] = []
|
const result: [] = []
|
||||||
await this.getLookupValue(lookup, result, parent)
|
await this.getLookupValue(lookup, result, parent)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fillLookup<T extends Doc> (_class: Ref<Class<T>>, object: any, key: string, fullKey: string, targetObject: any): Promise<void> {
|
private async fillLookup<T extends Doc>(
|
||||||
|
_class: Ref<Class<T>>,
|
||||||
|
object: any,
|
||||||
|
key: string,
|
||||||
|
fullKey: string,
|
||||||
|
targetObject: any
|
||||||
|
): Promise<void> {
|
||||||
if (targetObject.$lookup === undefined) {
|
if (targetObject.$lookup === undefined) {
|
||||||
targetObject.$lookup = {}
|
targetObject.$lookup = {}
|
||||||
}
|
}
|
||||||
@ -173,7 +208,12 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fillLookupValue<T extends Doc> (lookup: Lookup<T> | undefined, object: any, parent?: string, parentObject?: any): Promise<void> {
|
private async fillLookupValue<T extends Doc>(
|
||||||
|
lookup: Lookup<T> | undefined,
|
||||||
|
object: any,
|
||||||
|
parent?: string,
|
||||||
|
parentObject?: any
|
||||||
|
): Promise<void> {
|
||||||
if (lookup === undefined) return
|
if (lookup === undefined) return
|
||||||
for (const key in lookup) {
|
for (const key in lookup) {
|
||||||
if (key === '_id') {
|
if (key === '_id') {
|
||||||
@ -193,7 +233,12 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fillReverseLookup (lookup: ReverseLookups, object: any, parent?: string, parentObject?: any): Promise<void> {
|
private async fillReverseLookup (
|
||||||
|
lookup: ReverseLookups,
|
||||||
|
object: any,
|
||||||
|
parent?: string,
|
||||||
|
parentObject?: any
|
||||||
|
): Promise<void> {
|
||||||
const targetObject = parentObject ?? object
|
const targetObject = parentObject ?? object
|
||||||
if (targetObject.$lookup === undefined) {
|
if (targetObject.$lookup === undefined) {
|
||||||
targetObject.$lookup = {}
|
targetObject.$lookup = {}
|
||||||
@ -316,7 +361,7 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
if (options?.projection !== undefined) {
|
if (options?.projection !== undefined) {
|
||||||
cursor = cursor.project(options.projection)
|
cursor = cursor.project(options.projection)
|
||||||
}
|
}
|
||||||
|
let total: number | undefined
|
||||||
if (options !== null && options !== undefined) {
|
if (options !== null && options !== undefined) {
|
||||||
if (options.sort !== undefined) {
|
if (options.sort !== undefined) {
|
||||||
const sort: Sort = {}
|
const sort: Sort = {}
|
||||||
@ -328,10 +373,12 @@ abstract class MongoAdapterBase extends TxProcessor {
|
|||||||
cursor = cursor.sort(sort)
|
cursor = cursor.sort(sort)
|
||||||
}
|
}
|
||||||
if (options.limit !== undefined) {
|
if (options.limit !== undefined) {
|
||||||
|
total = await cursor.count()
|
||||||
cursor = cursor.limit(options.limit)
|
cursor = cursor.limit(options.limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await cursor.toArray()
|
const res = await cursor.toArray()
|
||||||
|
return toFindResult(res, total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,18 +447,16 @@ class MongoAdapter extends MongoAdapterBase {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return await this.db
|
return await this.db.collection(domain).updateOne(
|
||||||
.collection(domain)
|
{ _id: tx.objectId },
|
||||||
.updateOne(
|
{
|
||||||
{ _id: tx.objectId },
|
$set: {
|
||||||
{
|
...this.translateMixinAttrs(tx.mixin, tx.attributes),
|
||||||
$set: {
|
modifiedBy: tx.modifiedBy,
|
||||||
...this.translateMixinAttrs(tx.mixin, tx.attributes),
|
modifiedOn: tx.modifiedOn
|
||||||
modifiedBy: tx.modifiedBy,
|
|
||||||
modifiedOn: tx.modifiedOn
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,12 +614,16 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getModel (): Promise<Tx[]> {
|
async getModel (): Promise<Tx[]> {
|
||||||
const model = await this.db.collection(DOMAIN_TX).find<Tx>({ objectSpace: core.space.Model }).sort({ _id: 1 }).toArray()
|
const model = await this.db
|
||||||
|
.collection(DOMAIN_TX)
|
||||||
|
.find<Tx>({ objectSpace: core.space.Model })
|
||||||
|
.sort({ _id: 1 })
|
||||||
|
.toArray()
|
||||||
// We need to put all core.account.System transactions first
|
// We need to put all core.account.System transactions first
|
||||||
const systemTr: Tx[] = []
|
const systemTr: Tx[] = []
|
||||||
const userTx: Tx[] = []
|
const userTx: Tx[] = []
|
||||||
|
|
||||||
model.forEach(tx => ((tx.modifiedBy === core.account.System) ? systemTr : userTx).push(tx))
|
model.forEach((tx) => (tx.modifiedBy === core.account.System ? systemTr : userTx).push(tx))
|
||||||
|
|
||||||
return systemTr.concat(userTx)
|
return systemTr.concat(userTx)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,21 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import { Client as MinioClient } from 'minio'
|
import { Client as MinioClient } from 'minio'
|
||||||
import { Class, Doc, DocumentQuery, DOMAIN_MODEL, DOMAIN_TX, FindOptions, FindResult, Hierarchy, ModelDb, Ref, Tx, TxResult } from '@anticrm/core'
|
import {
|
||||||
|
Class,
|
||||||
|
Doc,
|
||||||
|
DocumentQuery,
|
||||||
|
DOMAIN_MODEL,
|
||||||
|
DOMAIN_TX,
|
||||||
|
FindOptions,
|
||||||
|
FindResult,
|
||||||
|
Hierarchy,
|
||||||
|
ModelDb,
|
||||||
|
Ref,
|
||||||
|
Tx,
|
||||||
|
TxResult,
|
||||||
|
toFindResult
|
||||||
|
} from '@anticrm/core'
|
||||||
import { createElasticAdapter } from '@anticrm/elastic'
|
import { createElasticAdapter } from '@anticrm/elastic'
|
||||||
import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
|
import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
|
||||||
import type { DbAdapter, DbConfiguration } from '@anticrm/server-core'
|
import type { DbAdapter, DbConfiguration } from '@anticrm/server-core'
|
||||||
@ -41,8 +55,18 @@ import { metricsContext } from './metrics'
|
|||||||
|
|
||||||
class NullDbAdapter implements DbAdapter {
|
class NullDbAdapter implements DbAdapter {
|
||||||
async init (model: Tx[]): Promise<void> {}
|
async init (model: Tx[]): Promise<void> {}
|
||||||
async findAll <T extends Doc>(_class: Ref<Class<T>>, query: DocumentQuery<T>, options?: FindOptions<T> | undefined): Promise<FindResult<T>> { return [] }
|
async findAll<T extends Doc>(
|
||||||
async tx (tx: Tx): Promise<TxResult> { return {} }
|
_class: Ref<Class<T>>,
|
||||||
|
query: DocumentQuery<T>,
|
||||||
|
options?: FindOptions<T> | undefined
|
||||||
|
): Promise<FindResult<T>> {
|
||||||
|
return toFindResult([])
|
||||||
|
}
|
||||||
|
|
||||||
|
async tx (tx: Tx): Promise<TxResult> {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
async close (): Promise<void> {}
|
async close (): Promise<void> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +86,13 @@ export interface MinioConfig {
|
|||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export function start (dbUrl: string, fullTextUrl: string, minioConf: MinioConfig, port: number, host?: string): () => void {
|
export function start (
|
||||||
|
dbUrl: string,
|
||||||
|
fullTextUrl: string,
|
||||||
|
minioConf: MinioConfig,
|
||||||
|
port: number,
|
||||||
|
host?: string
|
||||||
|
): () => void {
|
||||||
addLocation(serverAttachmentId, () => import('@anticrm/server-attachment-resources'))
|
addLocation(serverAttachmentId, () => import('@anticrm/server-attachment-resources'))
|
||||||
addLocation(serverContactId, () => import('@anticrm/server-contact-resources'))
|
addLocation(serverContactId, () => import('@anticrm/server-contact-resources'))
|
||||||
addLocation(serverNotificationId, () => import('@anticrm/server-notification-resources'))
|
addLocation(serverNotificationId, () => import('@anticrm/server-notification-resources'))
|
||||||
@ -77,38 +107,44 @@ export function start (dbUrl: string, fullTextUrl: string, minioConf: MinioConfi
|
|||||||
addLocation(serverGmailId, () => import('@anticrm/server-gmail-resources'))
|
addLocation(serverGmailId, () => import('@anticrm/server-gmail-resources'))
|
||||||
addLocation(serverTelegramId, () => import('@anticrm/server-telegram-resources'))
|
addLocation(serverTelegramId, () => import('@anticrm/server-telegram-resources'))
|
||||||
|
|
||||||
return startJsonRpc(metricsContext, (workspace: string) => {
|
return startJsonRpc(
|
||||||
const conf: DbConfiguration = {
|
metricsContext,
|
||||||
domains: {
|
(workspace: string) => {
|
||||||
[DOMAIN_TX]: 'MongoTx',
|
const conf: DbConfiguration = {
|
||||||
[DOMAIN_MODEL]: 'Null'
|
domains: {
|
||||||
},
|
[DOMAIN_TX]: 'MongoTx',
|
||||||
defaultAdapter: 'Mongo',
|
[DOMAIN_MODEL]: 'Null'
|
||||||
adapters: {
|
|
||||||
MongoTx: {
|
|
||||||
factory: createMongoTxAdapter,
|
|
||||||
url: dbUrl
|
|
||||||
},
|
},
|
||||||
Mongo: {
|
defaultAdapter: 'Mongo',
|
||||||
factory: createMongoAdapter,
|
adapters: {
|
||||||
url: dbUrl
|
MongoTx: {
|
||||||
|
factory: createMongoTxAdapter,
|
||||||
|
url: dbUrl
|
||||||
|
},
|
||||||
|
Mongo: {
|
||||||
|
factory: createMongoAdapter,
|
||||||
|
url: dbUrl
|
||||||
|
},
|
||||||
|
Null: {
|
||||||
|
factory: createNullAdapter,
|
||||||
|
url: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Null: {
|
fulltextAdapter: {
|
||||||
factory: createNullAdapter,
|
factory: createElasticAdapter,
|
||||||
url: ''
|
url: fullTextUrl
|
||||||
}
|
},
|
||||||
},
|
storageFactory: () =>
|
||||||
fulltextAdapter: {
|
new MinioClient({
|
||||||
factory: createElasticAdapter,
|
...minioConf,
|
||||||
url: fullTextUrl
|
port: 9000,
|
||||||
},
|
useSSL: false
|
||||||
storageFactory: () => new MinioClient({
|
}),
|
||||||
...minioConf,
|
workspace
|
||||||
port: 9000,
|
}
|
||||||
useSSL: false
|
return createServerStorage(conf)
|
||||||
}),
|
},
|
||||||
workspace
|
port,
|
||||||
}
|
host
|
||||||
return createServerStorage(conf)
|
)
|
||||||
}, port, host)
|
|
||||||
}
|
}
|
||||||
|
@ -19,22 +19,36 @@ import { start, disableLogging } from '../server'
|
|||||||
import { generateToken } from '@anticrm/server-token'
|
import { generateToken } from '@anticrm/server-token'
|
||||||
import WebSocket from 'ws'
|
import WebSocket from 'ws'
|
||||||
|
|
||||||
import type { Doc, Ref, Class, DocumentQuery, FindOptions, FindResult, Tx, TxResult, MeasureContext } from '@anticrm/core'
|
import type {
|
||||||
import { MeasureMetricsContext } from '@anticrm/core'
|
Doc,
|
||||||
|
Ref,
|
||||||
|
Class,
|
||||||
|
DocumentQuery,
|
||||||
|
FindOptions,
|
||||||
|
FindResult,
|
||||||
|
Tx,
|
||||||
|
TxResult,
|
||||||
|
MeasureContext
|
||||||
|
} from '@anticrm/core'
|
||||||
|
import { MeasureMetricsContext, toFindResult } from '@anticrm/core'
|
||||||
|
|
||||||
describe('server', () => {
|
describe('server', () => {
|
||||||
disableLogging()
|
disableLogging()
|
||||||
|
|
||||||
start(new MeasureMetricsContext('test', {}), async () => ({
|
start(
|
||||||
findAll: async <T extends Doc>(
|
new MeasureMetricsContext('test', {}),
|
||||||
ctx: MeasureContext,
|
async () => ({
|
||||||
_class: Ref<Class<T>>,
|
findAll: async <T extends Doc>(
|
||||||
query: DocumentQuery<T>,
|
ctx: MeasureContext,
|
||||||
options?: FindOptions<T>
|
_class: Ref<Class<T>>,
|
||||||
): Promise<FindResult<T>> => ([]),
|
query: DocumentQuery<T>,
|
||||||
tx: async (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> => ([{}, []]),
|
options?: FindOptions<T>
|
||||||
close: async () => {}
|
): Promise<FindResult<T>> => toFindResult([]),
|
||||||
}), 3333)
|
tx: async (ctx: MeasureContext, tx: Tx): Promise<[TxResult, Tx[]]> => [{}, []],
|
||||||
|
close: async () => {}
|
||||||
|
}),
|
||||||
|
3333
|
||||||
|
)
|
||||||
|
|
||||||
function connect (): WebSocket {
|
function connect (): WebSocket {
|
||||||
const token: string = generateToken('', 'latest')
|
const token: string = generateToken('', 'latest')
|
||||||
@ -46,7 +60,9 @@ describe('server', () => {
|
|||||||
conn.on('open', () => {
|
conn.on('open', () => {
|
||||||
conn.close()
|
conn.close()
|
||||||
})
|
})
|
||||||
conn.on('close', () => { done() })
|
conn.on('close', () => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not connect to server without token', (done) => {
|
it('should not connect to server without token', (done) => {
|
||||||
@ -54,7 +70,9 @@ describe('server', () => {
|
|||||||
conn.on('error', () => {
|
conn.on('error', () => {
|
||||||
conn.close()
|
conn.close()
|
||||||
})
|
})
|
||||||
conn.on('close', () => { done() })
|
conn.on('close', () => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should send many requests', (done) => {
|
it('should send many requests', (done) => {
|
||||||
@ -74,6 +92,8 @@ describe('server', () => {
|
|||||||
conn.close()
|
conn.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
conn.on('close', () => { done() })
|
conn.on('close', () => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user