mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-23 19:44:59 +03:00
Elastic fulltext search mixins support (#770)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
7ebc44b8c0
commit
4328fdee9b
@ -52,6 +52,10 @@ class NullFullTextAdapter implements FullTextAdapter {
|
|||||||
async search (query: any): Promise<IndexedDoc[]> {
|
async search (query: any): Promise<IndexedDoc[]> {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async remove (id: Ref<Doc>): Promise<void> {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
||||||
|
@ -36,6 +36,10 @@ class NullFullTextAdapter implements FullTextAdapter {
|
|||||||
async search (query: any): Promise<IndexedDoc[]> {
|
async search (query: any): Promise<IndexedDoc[]> {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async remove (id: Ref<Doc>): Promise<void> {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import type { AttachedDoc, Class, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc } from '@anticrm/core'
|
|
||||||
import core, {
|
import core, {
|
||||||
|
AttachedDoc, Class, ClassifierKind, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc,
|
||||||
AnyAttribute,
|
AnyAttribute,
|
||||||
Collection,
|
Collection,
|
||||||
DocumentQuery,
|
DocumentQuery,
|
||||||
@ -56,12 +56,28 @@ export class FullTextIndex implements WithFind {
|
|||||||
|
|
||||||
protected async txRemoveDoc (ctx: MeasureContext, tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
protected async txRemoveDoc (ctx: MeasureContext, tx: TxRemoveDoc<Doc>): Promise<TxResult> {
|
||||||
// console.log('FullTextIndex.txRemoveDoc: Method not implemented.')
|
// console.log('FullTextIndex.txRemoveDoc: Method not implemented.')
|
||||||
|
await this.adapter.remove(tx.objectId)
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async txMixin (ctx: MeasureContext, tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
protected async txMixin (ctx: MeasureContext, tx: TxMixin<Doc, Doc>): Promise<TxResult> {
|
||||||
console.log('FullTextIndex.txMixin: Method not implemented')
|
const attributes = this.getFullTextAttributes(tx.mixin)
|
||||||
return {}
|
let result = {}
|
||||||
|
if (attributes === undefined) return result
|
||||||
|
const ops: any = tx.attributes
|
||||||
|
const update: any = {}
|
||||||
|
let shouldUpdate = false
|
||||||
|
for (const attr of attributes) {
|
||||||
|
if (ops[attr.name] !== undefined) {
|
||||||
|
update[(tx.mixin as string) + '.' + attr.name] = ops[attr.name]
|
||||||
|
shouldUpdate = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldUpdate) {
|
||||||
|
result = await this.adapter.update(tx.objectId, update)
|
||||||
|
await this.updateAttachedDocs(ctx, tx, update)
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
async tx (ctx: MeasureContext, tx: Tx): Promise<TxResult> {
|
||||||
@ -152,7 +168,7 @@ export class FullTextIndex implements WithFind {
|
|||||||
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
||||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
const attributes = this.getFullTextAttributes(tx.objectClass)
|
||||||
const doc = TxProcessor.createDoc2Doc(tx)
|
const doc = TxProcessor.createDoc2Doc(tx)
|
||||||
let parentContent: any[] = []
|
let parentContent: Record<string, string> = {}
|
||||||
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
||||||
const attachedDoc = doc as AttachedDoc
|
const attachedDoc = doc as AttachedDoc
|
||||||
const parentDoc = (
|
const parentDoc = (
|
||||||
@ -163,9 +179,10 @@ export class FullTextIndex implements WithFind {
|
|||||||
parentContent = this.getContent(parentAttributes, parentDoc)
|
parentContent = this.getContent(parentAttributes, parentDoc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (attributes === undefined && parentContent.length === 0) return {}
|
if (attributes === undefined && Object.keys(parentContent).length === 0) return {}
|
||||||
|
|
||||||
let content = this.getContent(attributes, doc)
|
let content = this.getContent(attributes, doc)
|
||||||
content = content.concat(parentContent)
|
content = { ...parentContent, ...content }
|
||||||
const indexedDoc: IndexedDoc = {
|
const indexedDoc: IndexedDoc = {
|
||||||
id: doc._id,
|
id: doc._id,
|
||||||
_class: doc._class,
|
_class: doc._class,
|
||||||
@ -173,16 +190,7 @@ export class FullTextIndex implements WithFind {
|
|||||||
modifiedOn: doc.modifiedOn,
|
modifiedOn: doc.modifiedOn,
|
||||||
space: doc.space,
|
space: doc.space,
|
||||||
attachedTo: (doc as AttachedDoc).attachedTo,
|
attachedTo: (doc as AttachedDoc).attachedTo,
|
||||||
content0: content[0],
|
...content
|
||||||
content1: content[1],
|
|
||||||
content2: content[2],
|
|
||||||
content3: content[3],
|
|
||||||
content4: content[4],
|
|
||||||
content5: content[5],
|
|
||||||
content6: content[6],
|
|
||||||
content7: content[7],
|
|
||||||
content8: content[8],
|
|
||||||
content9: content[9]
|
|
||||||
}
|
}
|
||||||
return await this.adapter.index(indexedDoc)
|
return await this.adapter.index(indexedDoc)
|
||||||
}
|
}
|
||||||
@ -193,14 +201,12 @@ export class FullTextIndex implements WithFind {
|
|||||||
if (attributes === undefined) return result
|
if (attributes === undefined) return result
|
||||||
const ops: any = tx.operations
|
const ops: any = tx.operations
|
||||||
const update: any = {}
|
const update: any = {}
|
||||||
let i = 0
|
|
||||||
let shouldUpdate = false
|
let shouldUpdate = false
|
||||||
for (const attr of attributes) {
|
for (const attr of attributes) {
|
||||||
if (ops[attr.name] !== undefined) {
|
if (ops[attr.name] !== undefined) {
|
||||||
update[`content${i}`] = ops[attr.name]
|
update[attr.name] = ops[attr.name]
|
||||||
shouldUpdate = true
|
shouldUpdate = true
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
}
|
}
|
||||||
if (tx.operations.space !== undefined) {
|
if (tx.operations.space !== undefined) {
|
||||||
update.space = tx.operations.space
|
update.space = tx.operations.space
|
||||||
@ -213,28 +219,37 @@ export class FullTextIndex implements WithFind {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private getContent (attributes: AnyAttribute[] | undefined, doc: Doc): any[] {
|
private getContent (attributes: AnyAttribute[] | undefined, doc: Doc): Record<string, string> {
|
||||||
if (attributes === undefined) return []
|
const attrs: Record<string, string> = {}
|
||||||
return attributes.map((attr) => (doc as any)[attr.name]?.toString() ?? '')
|
|
||||||
|
for (const attr of attributes ?? []) {
|
||||||
|
attrs[attr.name] = (doc as any)[attr.name]?.toString() ?? ''
|
||||||
|
}
|
||||||
|
return attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateAttachedDocs (ctx: MeasureContext, tx: TxUpdateDoc<Doc>, update: any): Promise<void> {
|
private async updateAttachedDocs (ctx: MeasureContext, tx: {objectId: Ref<Doc>, objectClass: Ref<Class<Doc>>}, update: any): Promise<void> {
|
||||||
const doc = (await this.dbStorage.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
const doc = (await this.dbStorage.findAll(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
|
||||||
if (doc === undefined) return
|
if (doc === undefined) return
|
||||||
const attributes = this.hierarchy.getAllAttributes(doc._class)
|
const attributes = this.hierarchy.getAllAttributes(doc._class)
|
||||||
|
|
||||||
|
// Find all mixin atttibutes for document.
|
||||||
|
this.hierarchy.getDescendants(doc._class)
|
||||||
|
.filter((m) => this.hierarchy.getClass(m).kind === ClassifierKind.MIXIN && this.hierarchy.hasMixin(doc, m))
|
||||||
|
.forEach((m) => {
|
||||||
|
for (const [k, v] of this.hierarchy.getAllAttributes(m, doc._class)) {
|
||||||
|
attributes.set(k, v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
for (const attribute of attributes.values()) {
|
for (const attribute of attributes.values()) {
|
||||||
if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
if (this.hierarchy.isDerived(attribute.type._class, core.class.Collection)) {
|
||||||
const collection = attribute.type as Collection<AttachedDoc>
|
const collection = attribute.type as Collection<AttachedDoc>
|
||||||
const allAttached = await this.dbStorage.findAll(ctx, collection.of, { attachedTo: tx.objectId })
|
const allAttached = await this.dbStorage.findAll(ctx, collection.of, { attachedTo: tx.objectId })
|
||||||
if (allAttached.length === 0) continue
|
if (allAttached.length === 0) continue
|
||||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
|
||||||
const shift = attributes?.length ?? 0
|
|
||||||
const docUpdate: any = {}
|
const docUpdate: any = {}
|
||||||
for (const key in update) {
|
for (const key in update) {
|
||||||
const index = Number.parseInt(key.replace('content', ''))
|
docUpdate[key] = update[key]
|
||||||
if (!isNaN(index)) {
|
|
||||||
docUpdate[`content${index + shift}`] = update[key]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (const attached of allAttached) {
|
for (const attached of allAttached) {
|
||||||
try {
|
try {
|
||||||
|
@ -45,17 +45,8 @@ export interface IndexedDoc {
|
|||||||
modifiedOn: Timestamp
|
modifiedOn: Timestamp
|
||||||
modifiedBy: Ref<Account>
|
modifiedBy: Ref<Account>
|
||||||
attachedTo?: Ref<Doc>
|
attachedTo?: Ref<Doc>
|
||||||
content0?: string
|
|
||||||
content1?: string
|
[key: string]: any
|
||||||
content2?: string
|
|
||||||
content3?: string
|
|
||||||
content4?: string
|
|
||||||
content5?: string
|
|
||||||
content6?: string
|
|
||||||
content7?: string
|
|
||||||
content8?: string
|
|
||||||
content9?: string
|
|
||||||
data?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -64,6 +55,7 @@ export interface IndexedDoc {
|
|||||||
export interface FullTextAdapter {
|
export interface FullTextAdapter {
|
||||||
index: (doc: IndexedDoc) => Promise<TxResult>
|
index: (doc: IndexedDoc) => Promise<TxResult>
|
||||||
update: (id: Ref<Doc>, update: Record<string, any>) => Promise<TxResult>
|
update: (id: Ref<Doc>, update: Record<string, any>) => Promise<TxResult>
|
||||||
|
remove: (id: Ref<Doc>) => Promise<void>
|
||||||
search: (_class: Ref<Class<Doc>>, search: DocumentQuery<Doc>, size: number | undefined) => Promise<IndexedDoc[]>
|
search: (_class: Ref<Class<Doc>>, search: DocumentQuery<Doc>, size: number | undefined) => Promise<IndexedDoc[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,20 +35,7 @@ class ElasticAdapter implements FullTextAdapter {
|
|||||||
must: [
|
must: [
|
||||||
{
|
{
|
||||||
multi_match: {
|
multi_match: {
|
||||||
query: search,
|
query: search
|
||||||
fields: [
|
|
||||||
'content0',
|
|
||||||
'content1',
|
|
||||||
'content2',
|
|
||||||
'content3',
|
|
||||||
'content4',
|
|
||||||
'content5',
|
|
||||||
'content6',
|
|
||||||
'content7',
|
|
||||||
'content8',
|
|
||||||
'content9',
|
|
||||||
'attachment.content'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -124,6 +111,13 @@ class ElasticAdapter implements FullTextAdapter {
|
|||||||
|
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async remove (id: Ref<Doc>): Promise<void> {
|
||||||
|
await this.client.delete({
|
||||||
|
index: this.db,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,6 +78,10 @@ class NullFullTextAdapter implements FullTextAdapter {
|
|||||||
async search (query: any): Promise<IndexedDoc[]> {
|
async search (query: any): Promise<IndexedDoc[]> {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async remove (id: Ref<Doc>): Promise<void> {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
async function createNullFullTextAdapter (): Promise<FullTextAdapter> {
|
||||||
|
Loading…
Reference in New Issue
Block a user