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)
export class TSpace extends TDoc implements Space {
@Prop(TypeString(), 'Name' as IntlString)
name!: string
description!: string
private!: boolean
members!: Arr<Ref<Account>>

View File

@ -31,6 +31,7 @@
"deep-equal": "^2.0.5",
"@anticrm/panel": "~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 SocialEditor from './SocialEditor.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 { combineName, formatName, getFirstName, getLastName } from '@anticrm/contact'
@ -88,6 +89,23 @@
</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">
<Attachments objectId={object._id} _class={object._class} space={object.space} {object}/>
</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[]
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 {
if (key.length === 0)

View File

@ -25,6 +25,8 @@ import KanbanView from './components/KanbanView.svelte'
import { getClient } from '@anticrm/presentation'
export { TableView }
async function Delete(object: Doc): Promise<void> {
const client = getClient()
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 core, { Hierarchy, DOMAIN_TX } from '@anticrm/core'
import core, { Hierarchy, DOMAIN_TX, ModelDb } from '@anticrm/core'
import type { FullTextAdapterFactory, FullTextAdapter } from './types'
import { FullTextIndex } from './fulltext'
import { Triggers } from './triggers'
@ -40,7 +40,7 @@ export interface TxAdapter extends DbAdapter {
/**
* @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
@ -73,7 +73,8 @@ class TServerStorage implements ServerStorage {
private readonly adapters: Map<string, DbAdapter>,
private readonly hierarchy: Hierarchy,
private readonly triggers: Triggers,
fulltextAdapter: FullTextAdapter
fulltextAdapter: FullTextAdapter,
private readonly modelDb: ModelDb
) {
this.fulltext = new FullTextIndex(hierarchy, fulltextAdapter, this)
}
@ -125,24 +126,23 @@ class TServerStorage implements ServerStorage {
// maintain hiearachy and triggers
this.hierarchy.tx(tx)
await this.triggers.tx(tx)
return []
} 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
await this.modelDb.tx(tx)
}
// 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 triggers = new Triggers()
const adapters = new Map<string, DbAdapter>()
const modelDb = new ModelDb(hierarchy)
for (const key in conf.adapters) {
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
@ -171,11 +172,15 @@ export async function createServerStorage (conf: DbConfiguration): Promise<Serve
await triggers.tx(tx)
}
for (const tx of model) {
await modelDb.tx(tx)
}
for (const [, adapter] of adapters) {
await adapter.init(model)
}
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 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'
@ -27,7 +27,8 @@ function translateDoc (doc: Doc): Document {
abstract class MongoAdapterBase extends TxProcessor {
constructor (
protected readonly db: Db,
protected readonly hierarchy: Hierarchy
protected readonly hierarchy: Hierarchy,
protected readonly modelDb: ModelDb
) {
super()
}
@ -63,13 +64,16 @@ abstract class MongoAdapterBase extends TxProcessor {
const lookups = options.lookup as any
for (const key in lookups) {
const clazz = lookups[key]
const step = {
from: this.hierarchy.getDomain(clazz),
localField: key,
foreignField: '_id',
as: key + '_lookup'
const domain = this.hierarchy.getDomain(clazz)
if (domain !== DOMAIN_MODEL) {
const step = {
from: domain,
localField: key,
foreignField: '_id',
as: key + '_lookup'
}
pipeline.push({ $lookup: step })
}
pipeline.push({ $lookup: step })
}
const domain = this.hierarchy.getDomain(clazz)
const cursor = this.db.collection(domain).aggregate(pipeline)
@ -78,8 +82,14 @@ abstract class MongoAdapterBase extends TxProcessor {
const object = row as any
object.$lookup = {}
for (const key in lookups) {
const arr = object[key + '_lookup']
object.$lookup[key] = arr[0]
const clazz = lookups[key]
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
@ -214,19 +224,19 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
/**
* @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)
await client.connect()
const db = client.db(dbName)
return new MongoAdapter(db, hierarchy)
return new MongoAdapter(db, hierarchy, modelDb)
}
/**
* @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)
await client.connect()
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.
//
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 { createMongoAdapter, createMongoTxAdapter } from '@anticrm/mongo'
import { createElasticAdapter } from '@anticrm/elastic'
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 { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit'
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
*/
@ -37,7 +47,8 @@ export async function start (dbUrl: string, fullTextUrl: string, port: number, h
startJsonRpc((workspace: string) => {
const conf: DbConfiguration = {
domains: {
[DOMAIN_TX]: 'MongoTx'
[DOMAIN_TX]: 'MongoTx',
[DOMAIN_MODEL]: 'Null'
},
defaultAdapter: 'Mongo',
adapters: {
@ -48,6 +59,10 @@ export async function start (dbUrl: string, fullTextUrl: string, port: number, h
Mongo: {
factory: createMongoAdapter,
url: dbUrl
},
Null: {
factory: createNullAdapter,
url: ''
}
},
fulltextAdapter: {

File diff suppressed because it is too large Load Diff