Model lookup (#259)

Signed-off-by: Andrey Platov <andrey@hardcoreeng.com>
This commit is contained in:
Andrey Platov 2021-10-13 10:38:35 +02:00 committed by GitHub
parent 56deb3a4b0
commit 771188e72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1010 additions and 876 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,9 @@ export const DOMAIN_STATE = 'state' as Domain
@Model(core.class.Space, core.class.Doc, DOMAIN_MODEL) @Model(core.class.Space, core.class.Doc, DOMAIN_MODEL)
export class TSpace extends TDoc implements Space { export class TSpace extends TDoc implements Space {
@Prop(TypeString(), 'Name' as IntlString)
name!: string name!: string
description!: string description!: string
private!: boolean private!: boolean
members!: Arr<Ref<Account>> members!: Arr<Ref<Account>>

View File

@ -31,6 +31,7 @@
"deep-equal": "^2.0.5", "deep-equal": "^2.0.5",
"@anticrm/panel": "~0.6.0", "@anticrm/panel": "~0.6.0",
"@anticrm/chunter-resources": "~0.6.0", "@anticrm/chunter-resources": "~0.6.0",
"@anticrm/view": "~0.6.0" "@anticrm/view": "~0.6.0",
"@anticrm/view-resources": "~0.6.0"
} }
} }

View File

@ -28,8 +28,9 @@
import Edit from './icons/Edit.svelte' import Edit from './icons/Edit.svelte'
import SocialEditor from './SocialEditor.svelte' import SocialEditor from './SocialEditor.svelte'
import AttributesBar from './AttributesBar.svelte' import AttributesBar from './AttributesBar.svelte'
import { TableView } from '@anticrm/view-resources'
import chunter from '@anticrm/chunter' import core from '@anticrm/core'
import recruit from '../plugin' import recruit from '../plugin'
import { combineName, formatName, getFirstName, getLastName } from '@anticrm/contact' import { combineName, formatName, getFirstName, getLastName } from '@anticrm/contact'
@ -88,6 +89,23 @@
</div> </div>
</div> </div>
Applications
<TableView
_class={recruit.class.Applicant}
config={['$lookup.candidate', '$lookup.state', '$lookup.candidate.city', '$lookup.space.name']}
options={
{
lookup: {
candidate: recruit.class.Candidate,
state: core.class.State,
space: core.class.Space
}
}
}
search=""
/>
<div class="attachments"> <div class="attachments">
<Attachments objectId={object._id} _class={object._class} space={object.space} {object}/> <Attachments objectId={object._id} _class={object._class} space={object.space} {object}/>
</div> </div>

View File

@ -0,0 +1,24 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Space } from '@anticrm/core'
export let value: Space
</script>
{value.name}

View File

@ -38,7 +38,7 @@
let objects: Doc[] let objects: Doc[]
const query = createQuery() const query = createQuery()
$: query.query(_class, search === '' ? { space } : { $search: search }, result => { objects = result }, { sort: { [sortKey]: sortOrder }, ...options }) $: query.query(_class, search === '' ? (space ? { space } : {}) : { $search: search }, result => { objects = result }, { sort: { [sortKey]: sortOrder }, ...options })
function getValue(doc: Doc, key: string): any { function getValue(doc: Doc, key: string): any {
if (key.length === 0) if (key.length === 0)

View File

@ -25,6 +25,8 @@ import KanbanView from './components/KanbanView.svelte'
import { getClient } from '@anticrm/presentation' import { getClient } from '@anticrm/presentation'
export { TableView }
async function Delete(object: Doc): Promise<void> { async function Delete(object: Doc): Promise<void> {
const client = getClient() const client = getClient()
await client.removeDoc(object._class, object.space, object._id) await client.removeDoc(object._class, object.space, object._id)

View File

@ -15,7 +15,7 @@
// //
import type { ServerStorage, Domain, Tx, TxCUD, Doc, Ref, Class, DocumentQuery, FindResult, FindOptions, Storage, TxBulkWrite } from '@anticrm/core' import type { ServerStorage, Domain, Tx, TxCUD, Doc, Ref, Class, DocumentQuery, FindResult, FindOptions, Storage, TxBulkWrite } from '@anticrm/core'
import core, { Hierarchy, DOMAIN_TX } from '@anticrm/core' import core, { Hierarchy, DOMAIN_TX, ModelDb } from '@anticrm/core'
import type { FullTextAdapterFactory, FullTextAdapter } from './types' import type { FullTextAdapterFactory, FullTextAdapter } from './types'
import { FullTextIndex } from './fulltext' import { FullTextIndex } from './fulltext'
import { Triggers } from './triggers' import { Triggers } from './triggers'
@ -40,7 +40,7 @@ export interface TxAdapter extends DbAdapter {
/** /**
* @public * @public
*/ */
export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string) => Promise<DbAdapter> export type DbAdapterFactory = (hierarchy: Hierarchy, url: string, db: string, modelDb: ModelDb) => Promise<DbAdapter>
/** /**
* @public * @public
@ -73,7 +73,8 @@ class TServerStorage implements ServerStorage {
private readonly adapters: Map<string, DbAdapter>, private readonly adapters: Map<string, DbAdapter>,
private readonly hierarchy: Hierarchy, private readonly hierarchy: Hierarchy,
private readonly triggers: Triggers, private readonly triggers: Triggers,
fulltextAdapter: FullTextAdapter fulltextAdapter: FullTextAdapter,
private readonly modelDb: ModelDb
) { ) {
this.fulltext = new FullTextIndex(hierarchy, fulltextAdapter, this) this.fulltext = new FullTextIndex(hierarchy, fulltextAdapter, this)
} }
@ -125,24 +126,23 @@ class TServerStorage implements ServerStorage {
// maintain hiearachy and triggers // maintain hiearachy and triggers
this.hierarchy.tx(tx) this.hierarchy.tx(tx)
await this.triggers.tx(tx) await this.triggers.tx(tx)
return [] await this.modelDb.tx(tx)
} else {
// store object
await this.routeTx(tx)
// invoke triggers and store derived objects
const derived = await this.triggers.apply(tx.modifiedBy, tx, this.findAll.bind(this), this.hierarchy)
for (const tx of derived) {
await this.routeTx(tx)
}
// index object
await this.fulltext.tx(tx)
// index derived objects
for (const tx of derived) {
await this.fulltext.tx(tx)
}
return derived
} }
// store object
await this.routeTx(tx)
// invoke triggers and store derived objects
const derived = await this.triggers.apply(tx.modifiedBy, tx, this.findAll.bind(this), this.hierarchy)
for (const tx of derived) {
await this.routeTx(tx)
}
// index object
await this.fulltext.tx(tx)
// index derived objects
for (const tx of derived) {
await this.fulltext.tx(tx)
}
return derived
} }
} }
@ -153,10 +153,11 @@ export async function createServerStorage (conf: DbConfiguration): Promise<Serve
const hierarchy = new Hierarchy() const hierarchy = new Hierarchy()
const triggers = new Triggers() const triggers = new Triggers()
const adapters = new Map<string, DbAdapter>() const adapters = new Map<string, DbAdapter>()
const modelDb = new ModelDb(hierarchy)
for (const key in conf.adapters) { for (const key in conf.adapters) {
const adapterConf = conf.adapters[key] const adapterConf = conf.adapters[key]
adapters.set(key, await adapterConf.factory(hierarchy, adapterConf.url, conf.workspace)) adapters.set(key, await adapterConf.factory(hierarchy, adapterConf.url, conf.workspace, modelDb))
} }
const txAdapter = adapters.get(conf.domains[DOMAIN_TX]) as TxAdapter const txAdapter = adapters.get(conf.domains[DOMAIN_TX]) as TxAdapter
@ -171,11 +172,15 @@ export async function createServerStorage (conf: DbConfiguration): Promise<Serve
await triggers.tx(tx) await triggers.tx(tx)
} }
for (const tx of model) {
await modelDb.tx(tx)
}
for (const [, adapter] of adapters) { for (const [, adapter] of adapters) {
await adapter.init(model) await adapter.init(model)
} }
const fulltextAdapter = await conf.fulltextAdapter.factory(conf.fulltextAdapter.url, conf.workspace) const fulltextAdapter = await conf.fulltextAdapter.factory(conf.fulltextAdapter.url, conf.workspace)
return new TServerStorage(conf.domains, conf.defaultAdapter, adapters, hierarchy, triggers, fulltextAdapter) return new TServerStorage(conf.domains, conf.defaultAdapter, adapters, hierarchy, triggers, fulltextAdapter, modelDb)
} }

View File

@ -14,7 +14,7 @@
// //
import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc, TxUpdateDoc, TxMixin, TxPutBag, TxRemoveDoc } from '@anticrm/core' import type { Tx, Ref, Doc, Class, DocumentQuery, FindResult, FindOptions, TxCreateDoc, TxUpdateDoc, TxMixin, TxPutBag, TxRemoveDoc } from '@anticrm/core'
import core, { DOMAIN_TX, SortingOrder, TxProcessor, Hierarchy, isOperator } from '@anticrm/core' import core, { DOMAIN_TX, DOMAIN_MODEL, SortingOrder, TxProcessor, Hierarchy, isOperator, ModelDb } from '@anticrm/core'
import type { DbAdapter, TxAdapter } from '@anticrm/server-core' import type { DbAdapter, TxAdapter } from '@anticrm/server-core'
@ -27,7 +27,8 @@ function translateDoc (doc: Doc): Document {
abstract class MongoAdapterBase extends TxProcessor { abstract class MongoAdapterBase extends TxProcessor {
constructor ( constructor (
protected readonly db: Db, protected readonly db: Db,
protected readonly hierarchy: Hierarchy protected readonly hierarchy: Hierarchy,
protected readonly modelDb: ModelDb
) { ) {
super() super()
} }
@ -63,13 +64,16 @@ abstract class MongoAdapterBase extends TxProcessor {
const lookups = options.lookup as any const lookups = options.lookup as any
for (const key in lookups) { for (const key in lookups) {
const clazz = lookups[key] const clazz = lookups[key]
const step = { const domain = this.hierarchy.getDomain(clazz)
from: this.hierarchy.getDomain(clazz), if (domain !== DOMAIN_MODEL) {
localField: key, const step = {
foreignField: '_id', from: domain,
as: key + '_lookup' localField: key,
foreignField: '_id',
as: key + '_lookup'
}
pipeline.push({ $lookup: step })
} }
pipeline.push({ $lookup: step })
} }
const domain = this.hierarchy.getDomain(clazz) const domain = this.hierarchy.getDomain(clazz)
const cursor = this.db.collection(domain).aggregate(pipeline) const cursor = this.db.collection(domain).aggregate(pipeline)
@ -78,8 +82,14 @@ abstract class MongoAdapterBase extends TxProcessor {
const object = row as any const object = row as any
object.$lookup = {} object.$lookup = {}
for (const key in lookups) { for (const key in lookups) {
const arr = object[key + '_lookup'] const clazz = lookups[key]
object.$lookup[key] = arr[0] const domain = this.hierarchy.getDomain(clazz)
if (domain !== DOMAIN_MODEL) {
const arr = object[key + '_lookup']
object.$lookup[key] = arr[0]
} else {
object.$lookup[key] = this.modelDb.getObject(object[key])
}
} }
} }
return result return result
@ -214,19 +224,19 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
/** /**
* @public * @public
*/ */
export async function createMongoAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<DbAdapter> { export async function createMongoAdapter (hierarchy: Hierarchy, url: string, dbName: string, modelDb: ModelDb): Promise<DbAdapter> {
const client = new MongoClient(url) const client = new MongoClient(url)
await client.connect() await client.connect()
const db = client.db(dbName) const db = client.db(dbName)
return new MongoAdapter(db, hierarchy) return new MongoAdapter(db, hierarchy, modelDb)
} }
/** /**
* @public * @public
*/ */
export async function createMongoTxAdapter (hierarchy: Hierarchy, url: string, dbName: string): Promise<TxAdapter> { export async function createMongoTxAdapter (hierarchy: Hierarchy, url: string, dbName: string, modelDb: ModelDb): Promise<TxAdapter> {
const client = new MongoClient(url) const client = new MongoClient(url)
await client.connect() await client.connect()
const db = client.db(dbName) const db = client.db(dbName)
return new MongoTxAdapter(db, hierarchy) return new MongoTxAdapter(db, hierarchy, modelDb)
} }

View File

@ -14,18 +14,28 @@
// limitations under the License. // limitations under the License.
// //
import { DOMAIN_TX } from '@anticrm/core' import { Class, Doc, DocumentQuery, DOMAIN_MODEL, DOMAIN_TX, FindOptions, FindResult, Hierarchy, ModelDb, Ref, Tx } from '@anticrm/core'
import { start as startJsonRpc } from '@anticrm/server-ws' import { start as startJsonRpc } from '@anticrm/server-ws'
import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo' import { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
import { createElasticAdapter } from '@anticrm/elastic' import { createElasticAdapter } from '@anticrm/elastic'
import { createServerStorage } from '@anticrm/server-core' import { createServerStorage } from '@anticrm/server-core'
import type { DbConfiguration } from '@anticrm/server-core' import type { DbConfiguration, DbAdapter } from '@anticrm/server-core'
import { addLocation } from '@anticrm/platform' import { addLocation } from '@anticrm/platform'
import { serverChunterId } from '@anticrm/server-chunter' import { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit' import { serverRecruitId } from '@anticrm/server-recruit'
import { serverViewId } from '@anticrm/server-view' import { serverViewId } from '@anticrm/server-view'
class NullDbAdapter implements DbAdapter {
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 tx (tx: Tx): Promise<void> {}
}
async function createNullAdapter (hierarchy: Hierarchy, url: string, db: string, modelDb: ModelDb): Promise<DbAdapter> {
return new NullDbAdapter()
}
/** /**
* @public * @public
*/ */
@ -37,7 +47,8 @@ export async function start (dbUrl: string, fullTextUrl: string, port: number, h
startJsonRpc((workspace: string) => { startJsonRpc((workspace: string) => {
const conf: DbConfiguration = { const conf: DbConfiguration = {
domains: { domains: {
[DOMAIN_TX]: 'MongoTx' [DOMAIN_TX]: 'MongoTx',
[DOMAIN_MODEL]: 'Null'
}, },
defaultAdapter: 'Mongo', defaultAdapter: 'Mongo',
adapters: { adapters: {
@ -48,6 +59,10 @@ export async function start (dbUrl: string, fullTextUrl: string, port: number, h
Mongo: { Mongo: {
factory: createMongoAdapter, factory: createMongoAdapter,
url: dbUrl url: dbUrl
},
Null: {
factory: createNullAdapter,
url: ''
} }
}, },
fulltextAdapter: { fulltextAdapter: {

File diff suppressed because it is too large Load Diff