mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-03 13:53:42 +00:00
parent
bff122ee8c
commit
9c278d46e4
@ -14,8 +14,8 @@
|
||||
//
|
||||
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Builder, Model, Prop, UX, TypeString, TypeTimestamp } from '@anticrm/model'
|
||||
import type { Domain } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, UX, TypeString, TypeTimestamp, Index } from '@anticrm/model'
|
||||
import { Domain, IndexKind } from '@anticrm/core'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import type { Attachment, Photo } from '@anticrm/attachment'
|
||||
import activity from '@anticrm/activity'
|
||||
@ -31,6 +31,7 @@ export const DOMAIN_ATTACHMENT = 'attachment' as Domain
|
||||
@UX('File' as IntlString)
|
||||
export class TAttachment extends TAttachedDoc implements Attachment {
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeString(), 'File' as IntlString)
|
||||
@ -40,6 +41,7 @@ export class TAttachment extends TAttachedDoc implements Attachment {
|
||||
size!: number
|
||||
|
||||
@Prop(TypeString(), 'Type' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
type!: string
|
||||
|
||||
@Prop(TypeTimestamp(), 'Date' as IntlString)
|
||||
|
@ -59,6 +59,7 @@ export class TContact extends TDoc implements Contact {
|
||||
comments?: number
|
||||
|
||||
@Prop(TypeString(), 'Location' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
city!: string
|
||||
}
|
||||
|
||||
@ -69,6 +70,7 @@ export class TChannel extends TAttachedDoc implements Channel {
|
||||
provider!: Ref<ChannelProvider>
|
||||
|
||||
@Prop(TypeString(), 'Value' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
value!: string
|
||||
|
||||
items?: number
|
||||
|
@ -13,9 +13,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Account, Arr, Ref, Space } from '@anticrm/core'
|
||||
import { DOMAIN_MODEL } from '@anticrm/core'
|
||||
import { Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
|
||||
import { Account, Arr, DOMAIN_MODEL, IndexKind, Ref, Space } from '@anticrm/core'
|
||||
import { Index, Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import core from './component'
|
||||
import { TDoc } from './core'
|
||||
@ -25,9 +24,11 @@ import { TDoc } from './core'
|
||||
@Model(core.class.Space, core.class.Doc, DOMAIN_MODEL)
|
||||
export class TSpace extends TDoc implements Space {
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeString(), 'Description' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: string
|
||||
|
||||
@Prop(TypeBoolean(), 'Private' as IntlString)
|
||||
|
@ -15,12 +15,12 @@
|
||||
//
|
||||
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Builder, Model, TypeString, Prop, ArrOf, TypeBoolean } from '@anticrm/model'
|
||||
import { Builder, Model, TypeString, Prop, ArrOf, TypeBoolean, Index } from '@anticrm/model'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import gmail from './plugin'
|
||||
import type { Message, SharedMessage, SharedMessages } from '@anticrm/gmail'
|
||||
import type { Domain, Type } from '@anticrm/core'
|
||||
import { Domain, IndexKind, Type } from '@anticrm/core'
|
||||
import setting from '@anticrm/setting'
|
||||
import activity from '@anticrm/activity'
|
||||
|
||||
@ -36,24 +36,31 @@ export class TMessage extends TAttachedDoc implements Message {
|
||||
messageId!: string
|
||||
|
||||
@Prop(TypeString(), 'ReplyTo' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
replyTo?: string
|
||||
|
||||
@Prop(TypeString(), 'From' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
from!: string
|
||||
|
||||
@Prop(TypeString(), 'To' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
to!: string
|
||||
|
||||
@Prop(TypeString(), 'Contact' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
contact!: string
|
||||
|
||||
@Prop(TypeString(), 'Subject' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
subject!: string
|
||||
|
||||
@Prop(TypeString(), 'Message' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
content!: string
|
||||
|
||||
@Prop(TypeString(), 'Message' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
textContent!: string
|
||||
|
||||
@Prop(ArrOf(TypeString()), 'Copy' as IntlString)
|
||||
|
@ -14,9 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc, Domain, FindOptions, Ref } from '@anticrm/core'
|
||||
import { Doc, Domain, FindOptions, IndexKind, Ref } from '@anticrm/core'
|
||||
import type { Category, Product, Variant } from '@anticrm/inventory'
|
||||
import { Builder, Collection, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Collection, Index, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
@ -30,6 +30,7 @@ export const DOMAIN_INVENTORY = 'inventory' as Domain
|
||||
@UX(inventory.string.Category, inventory.icon.Categories, undefined, 'name')
|
||||
export class TCategory extends TAttachedDoc implements Category {
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
}
|
||||
|
||||
@ -41,6 +42,7 @@ export class TProduct extends TAttachedDoc implements Product {
|
||||
declare attachedTo: Ref<Category>
|
||||
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(Collection(attachment.class.Photo), attachment.string.Photos)
|
||||
@ -61,9 +63,11 @@ export class TVariant extends TAttachedDoc implements Variant {
|
||||
declare attachedTo: Ref<Product>
|
||||
|
||||
@Prop(TypeString(), 'Name' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeString(), inventory.string.SKU)
|
||||
@Index(IndexKind.FullText)
|
||||
sku!: string
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,9 @@
|
||||
|
||||
// To help typescript locate view plugin properly
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import type { Doc, FindOptions, Lookup, Ref } from '@anticrm/core'
|
||||
import { Doc, FindOptions, IndexKind, Lookup, Ref } from '@anticrm/core'
|
||||
import type { Customer, Funnel, Lead } from '@anticrm/lead'
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import { Builder, Collection, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
@ -41,6 +41,7 @@ export class TLead extends TTask implements Lead {
|
||||
declare attachedTo: Ref<Customer>
|
||||
|
||||
@Prop(TypeString(), 'Title' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), 'Comments' as IntlString)
|
||||
@ -60,6 +61,7 @@ export class TCustomer extends TPerson implements Customer {
|
||||
leads?: number
|
||||
|
||||
@Prop(TypeString(), 'Description' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: string
|
||||
}
|
||||
|
||||
|
@ -14,24 +14,25 @@
|
||||
//
|
||||
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import { Doc, FindOptions, Lookup, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeMarkup, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import { Doc, FindOptions, IndexKind, Lookup, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Index, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeMarkup, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import contact, { TPerson } from '@anticrm/model-contact'
|
||||
import core, { TSpace } from '@anticrm/model-core'
|
||||
import presentation from '@anticrm/model-presentation'
|
||||
import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
||||
import recruit from './plugin'
|
||||
import presentation from '@anticrm/model-presentation'
|
||||
|
||||
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
|
||||
@UX(recruit.string.Vacancy, recruit.icon.Vacancy)
|
||||
export class TVacancy extends TSpaceWithStates implements Vacancy {
|
||||
@Prop(TypeMarkup(), 'Full description' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
fullDescription?: string
|
||||
|
||||
@Prop(Collection(attachment.class.Attachment), 'Attachments' as IntlString)
|
||||
@ -41,9 +42,11 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
|
||||
dueTo?: Timestamp
|
||||
|
||||
@Prop(TypeString(), 'Location' as IntlString, recruit.icon.Location)
|
||||
@Index(IndexKind.FullText)
|
||||
location?: string
|
||||
|
||||
@Prop(TypeString(), 'Company' as IntlString, contact.icon.Company)
|
||||
@Index(IndexKind.FullText)
|
||||
company?: string
|
||||
}
|
||||
|
||||
@ -55,6 +58,7 @@ export class TCandidates extends TSpace implements Candidates {}
|
||||
@UX('Candidate' as IntlString, recruit.icon.RecruitApplication)
|
||||
export class TCandidate extends TPerson implements Candidate {
|
||||
@Prop(TypeString(), 'Title' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
title?: string
|
||||
|
||||
@Prop(Collection(recruit.class.Applicant), 'Applications' as IntlString)
|
||||
@ -67,6 +71,7 @@ export class TCandidate extends TPerson implements Candidate {
|
||||
remote?: boolean
|
||||
|
||||
@Prop(TypeString(), 'Source' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
source?: string
|
||||
}
|
||||
|
||||
@ -313,6 +318,6 @@ export function createModel (builder: Builder): void {
|
||||
})
|
||||
}
|
||||
|
||||
export { default } from './plugin'
|
||||
export { recruitOperation } from './migration'
|
||||
export { createDeps } from './creation'
|
||||
export { recruitOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
|
@ -15,10 +15,11 @@
|
||||
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, IndexKind, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import {
|
||||
Builder,
|
||||
Collection, Hidden, Implements,
|
||||
Index,
|
||||
Mixin,
|
||||
Model,
|
||||
Prop, TypeBoolean,
|
||||
@ -106,6 +107,7 @@ export class TTask extends TAttachedDoc implements Task {
|
||||
@UX(task.string.Todo)
|
||||
export class TTodoItem extends TAttachedDoc implements TodoItem {
|
||||
@Prop(TypeString(), task.string.TodoName, task.icon.Task)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeBoolean(), task.string.TaskDone)
|
||||
@ -130,9 +132,11 @@ export class TIssue extends TTask implements Issue {
|
||||
declare attachedTo: Ref<Doc>
|
||||
|
||||
@Prop(TypeString(), task.string.IssueName)
|
||||
@Index(IndexKind.FullText)
|
||||
name!: string
|
||||
|
||||
@Prop(TypeMarkup(), task.string.TaskDescription)
|
||||
@Index(IndexKind.FullText)
|
||||
description!: string
|
||||
|
||||
@Prop(Collection(chunter.class.Comment), task.string.TaskComments)
|
||||
@ -142,6 +146,7 @@ export class TIssue extends TTask implements Issue {
|
||||
attachments!: number
|
||||
|
||||
@Prop(TypeString(), task.string.TaskLabels)
|
||||
@Index(IndexKind.FullText)
|
||||
labels!: string
|
||||
|
||||
@Prop(TypeRef(contact.class.Employee), task.string.TaskAssignee)
|
||||
@ -193,6 +198,7 @@ export class TLostStateTemplate extends TDoneStateTemplate implements LostStateT
|
||||
@Model(task.class.KanbanTemplate, core.class.Doc, DOMAIN_KANBAN)
|
||||
export class TKanbanTemplate extends TDoc implements KanbanTemplate {
|
||||
@Prop(TypeString(), task.string.KanbanTemplateTitle)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string
|
||||
|
||||
@Prop(Collection(task.class.StateTemplate), task.string.States)
|
||||
|
@ -15,12 +15,12 @@
|
||||
//
|
||||
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf } from '@anticrm/model'
|
||||
import { Builder, Model, TypeString, TypeBoolean, Prop, ArrOf, Index } from '@anticrm/model'
|
||||
import core, { TAttachedDoc } from '@anticrm/model-core'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import telegram from './plugin'
|
||||
import type { TelegramMessage, SharedTelegramMessage, SharedTelegramMessages } from '@anticrm/telegram'
|
||||
import type { Domain, Type } from '@anticrm/core'
|
||||
import { Domain, IndexKind, Type } from '@anticrm/core'
|
||||
import setting from '@anticrm/setting'
|
||||
import activity from '@anticrm/activity'
|
||||
|
||||
@ -33,6 +33,7 @@ function TypeSharedMessage (): Type<SharedTelegramMessage> {
|
||||
@Model(telegram.class.Message, core.class.AttachedDoc, DOMAIN_TELEGRAM)
|
||||
export class TTelegramMessage extends TAttachedDoc implements TelegramMessage {
|
||||
@Prop(TypeString(), 'Content' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
content!: string
|
||||
|
||||
@Prop(TypeBoolean(), 'Incoming' as IntlString)
|
||||
|
@ -14,8 +14,8 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Domain } from '@anticrm/core'
|
||||
import { Builder, Model, Prop, TypeString } from '@anticrm/model'
|
||||
import { Domain, IndexKind } from '@anticrm/core'
|
||||
import { Builder, Index, Model, Prop, TypeString } from '@anticrm/model'
|
||||
import core, { TDoc } from '@anticrm/model-core'
|
||||
import textEditor from '@anticrm/model-text-editor'
|
||||
import { IntlString } from '@anticrm/platform'
|
||||
@ -28,9 +28,11 @@ export const DOMAIN_TEMPLATES = 'templates' as Domain
|
||||
@Model(templates.class.MessageTemplate, core.class.Doc, DOMAIN_TEMPLATES)
|
||||
export class TMessageTemplate extends TDoc implements MessageTemplate {
|
||||
@Prop(TypeString(), 'Title' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
title!: string;
|
||||
|
||||
@Prop(TypeString(), 'Message' as IntlString)
|
||||
@Index(IndexKind.FullText)
|
||||
message!: string;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@
|
||||
import { AttributesBar, createQuery, getClient } from '@anticrm/presentation'
|
||||
import { Vacancy } from '@anticrm/recruit'
|
||||
import { StyledTextBox } from '@anticrm/text-editor'
|
||||
import { Component, EditBox, Grid, Icon, IconClose, Label, ToggleWithLabel, ActionIcon, Scroller } from '@anticrm/ui'
|
||||
import { ActionIcon, Component, EditBox, Grid, Icon, IconClose, Label, Scroller, ToggleWithLabel } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import recruit from '../plugin'
|
||||
|
||||
@ -108,7 +108,7 @@
|
||||
<ToggleWithLabel label={recruit.string.ThisVacancyIsPrivate} description={recruit.string.MakePrivateDescription}/>
|
||||
</Scroller>
|
||||
{:else if selected === 2}
|
||||
<Component is={activity.component.Activity} props={{object, transparent: true}} />
|
||||
<Component is={activity.component.Activity} props={{ object, transparent: true }} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -15,34 +15,38 @@
|
||||
//
|
||||
|
||||
import core, {
|
||||
AttachedDoc, Class, ClassifierKind, Doc, Obj, Ref, TxCreateDoc, TxResult, TxUpdateDoc,
|
||||
AnyAttribute,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
ClassifierKind,
|
||||
Collection,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
FindOptions,
|
||||
FindResult,
|
||||
Hierarchy,
|
||||
IndexKind,
|
||||
MeasureContext,
|
||||
Obj,
|
||||
PropertyType,
|
||||
Ref,
|
||||
Tx,
|
||||
TxBulkWrite,
|
||||
TxCollectionCUD,
|
||||
TxCreateDoc,
|
||||
TxMixin,
|
||||
TxProcessor,
|
||||
TxPutBag,
|
||||
TxRemoveDoc
|
||||
TxRemoveDoc,
|
||||
TxResult,
|
||||
TxUpdateDoc
|
||||
} from '@anticrm/core'
|
||||
import type { FullTextAdapter, IndexedDoc, WithFind } from './types'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const NO_INDEX = [] as AnyAttribute[]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export class FullTextIndex implements WithFind {
|
||||
private readonly indexes = new Map<Ref<Class<Obj>>, AnyAttribute[]>()
|
||||
|
||||
constructor (
|
||||
private readonly hierarchy: Hierarchy,
|
||||
private readonly adapter: FullTextAdapter,
|
||||
@ -138,7 +142,7 @@ export class FullTextIndex implements WithFind {
|
||||
const { _id, $search, ...mainQuery } = query
|
||||
if ($search === undefined) return []
|
||||
const docs = await this.adapter.search(_class, query, options?.limit)
|
||||
const ids: Set<Ref<Doc>> = new Set<Ref<Doc>>(docs.map(p => p.id))
|
||||
const ids: Set<Ref<Doc>> = new Set<Ref<Doc>>(docs.map((p) => p.id))
|
||||
for (const doc of docs) {
|
||||
if (doc.attachedTo !== undefined) {
|
||||
ids.add(doc.attachedTo)
|
||||
@ -147,25 +151,29 @@ export class FullTextIndex implements WithFind {
|
||||
return await this.dbStorage.findAll(ctx, _class, { _id: { $in: Array.from(ids) as any }, ...mainQuery }, options) // TODO: remove `as any`
|
||||
}
|
||||
|
||||
private getFullTextAttributes (clazz: Ref<Class<Obj>>): AnyAttribute[] | undefined {
|
||||
const attributes = this.indexes.get(clazz)
|
||||
if (attributes === undefined) {
|
||||
const allAttributes = this.hierarchy.getAllAttributes(clazz)
|
||||
const result: AnyAttribute[] = []
|
||||
for (const [, attr] of allAttributes) {
|
||||
if (attr.type._class === core.class.TypeString || attr.type._class === core.class.TypeMarkup) {
|
||||
result.push(attr)
|
||||
}
|
||||
private getFullTextAttributes (clazz: Ref<Class<Obj>>, parentDoc?: Doc): AnyAttribute[] {
|
||||
const allAttributes = this.hierarchy.getAllAttributes(clazz)
|
||||
const result: AnyAttribute[] = []
|
||||
for (const [, attr] of allAttributes) {
|
||||
if (isFullTextAttribute(attr)) {
|
||||
result.push(attr)
|
||||
}
|
||||
if (result.length > 0) {
|
||||
this.indexes.set(clazz, result)
|
||||
return result
|
||||
} else {
|
||||
this.indexes.set(clazz, NO_INDEX)
|
||||
}
|
||||
} else if (attributes !== NO_INDEX) {
|
||||
return attributes
|
||||
}
|
||||
|
||||
// We also neex to add all mixin attribues if parent is specified.
|
||||
if (parentDoc !== undefined) {
|
||||
this.hierarchy
|
||||
.getDescendants(clazz)
|
||||
.filter((m) => this.hierarchy.getClass(m).kind === ClassifierKind.MIXIN)
|
||||
.forEach((m) => {
|
||||
for (const [, v] of this.hierarchy.getAllAttributes(m, clazz)) {
|
||||
if (isFullTextAttribute(v) && this.hierarchy.hasMixin(parentDoc, m)) {
|
||||
result.push(v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
protected async txCreateDoc (ctx: MeasureContext, tx: TxCreateDoc<Doc>): Promise<TxResult> {
|
||||
@ -174,15 +182,20 @@ export class FullTextIndex implements WithFind {
|
||||
let parentContent: Record<string, string> = {}
|
||||
if (this.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
||||
const attachedDoc = doc as AttachedDoc
|
||||
const parentDoc = (
|
||||
await this.dbStorage.findAll(ctx, attachedDoc.attachedToClass, { _id: attachedDoc.attachedTo }, { limit: 1 })
|
||||
)[0]
|
||||
if (parentDoc !== undefined) {
|
||||
const parentAttributes = this.getFullTextAttributes(parentDoc._class)
|
||||
parentContent = this.getContent(parentAttributes, parentDoc)
|
||||
if (attachedDoc.attachedToClass !== undefined && attachedDoc.attachedTo !== undefined) {
|
||||
const parentDoc = (
|
||||
await this.dbStorage.findAll(ctx, attachedDoc.attachedToClass, { _id: attachedDoc.attachedTo }, { limit: 1 })
|
||||
)[0]
|
||||
if (parentDoc !== undefined) {
|
||||
const parentAttributes = this.getFullTextAttributes(parentDoc._class, parentDoc)
|
||||
|
||||
if (parentAttributes.length > 0) {
|
||||
parentContent = this.getContent(parentAttributes, parentDoc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (attributes === undefined && Object.keys(parentContent).length === 0) return {}
|
||||
if (attributes.length === 0 && Object.keys(parentContent).length === 0) return {}
|
||||
|
||||
let content = this.getContent(attributes, doc)
|
||||
content = { ...parentContent, ...content }
|
||||
@ -201,7 +214,7 @@ export class FullTextIndex implements WithFind {
|
||||
protected async txUpdateDoc (ctx: MeasureContext, tx: TxUpdateDoc<Doc>): Promise<TxResult> {
|
||||
const attributes = this.getFullTextAttributes(tx.objectClass)
|
||||
let result = {}
|
||||
if (attributes === undefined) return result
|
||||
if (attributes.length === 0) return result
|
||||
const ops: any = tx.operations
|
||||
const update: any = {}
|
||||
let shouldUpdate = false
|
||||
@ -224,22 +237,33 @@ export class FullTextIndex implements WithFind {
|
||||
return result
|
||||
}
|
||||
|
||||
private getContent (attributes: AnyAttribute[] | undefined, doc: Doc): Record<string, string> {
|
||||
private getContent (attributes: AnyAttribute[], doc: Doc): Record<string, string> {
|
||||
const attrs: Record<string, string> = {}
|
||||
|
||||
for (const attr of attributes ?? []) {
|
||||
attrs[attr.name] = (doc as any)[attr.name]?.toString() ?? ''
|
||||
for (const attr of attributes) {
|
||||
const isMixinAttr = this.hierarchy.isMixin(attr.attributeOf)
|
||||
if (isMixinAttr) {
|
||||
attrs[(attr.attributeOf as string) + '.' + attr.name] =
|
||||
((doc as any)[attr.attributeOf] ?? {})[attr.name]?.toString() ?? ''
|
||||
} else {
|
||||
attrs[attr.name] = (doc as any)[attr.name]?.toString() ?? ''
|
||||
}
|
||||
}
|
||||
return attrs
|
||||
}
|
||||
|
||||
private async updateAttachedDocs (ctx: MeasureContext, tx: {objectId: Ref<Doc>, objectClass: Ref<Class<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]
|
||||
if (doc === undefined) return
|
||||
const attributes = this.hierarchy.getAllAttributes(doc._class)
|
||||
|
||||
// Find all mixin atttibutes for document.
|
||||
this.hierarchy.getDescendants(doc._class)
|
||||
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)) {
|
||||
@ -261,7 +285,14 @@ export class FullTextIndex implements WithFind {
|
||||
await this.adapter.update(attached._id, docUpdate)
|
||||
} catch (err: any) {
|
||||
if (((err.message as string) ?? '').includes('document_missing_exception:')) {
|
||||
console.error('missing document in elastic for', tx.objectId, 'attached', attached._id, 'collection', attached.collection)
|
||||
console.error(
|
||||
'missing document in elastic for',
|
||||
tx.objectId,
|
||||
'attached',
|
||||
attached._id,
|
||||
'collection',
|
||||
attached.collection
|
||||
)
|
||||
// We have no document for attached object, so ignore for now. it is probable rebuild of elastic DB.
|
||||
continue
|
||||
}
|
||||
@ -272,3 +303,9 @@ export class FullTextIndex implements WithFind {
|
||||
}
|
||||
}
|
||||
}
|
||||
function isFullTextAttribute (attr: AnyAttribute): boolean {
|
||||
return (
|
||||
attr.index === IndexKind.FullText &&
|
||||
(attr.type._class === core.class.TypeString || attr.type._class === core.class.TypeMarkup)
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
"format": "prettier --write src && eslint --fix src",
|
||||
"ci": "playwright install --with-deps chromium",
|
||||
"test": "",
|
||||
"uitest": "playwright test --browser chromium --reporter list,html"
|
||||
"uitest": "playwright test --browser chromium --reporter list,html -c ./tests/playwright.config.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@anticrm/platform-rig": "~0.6.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { test } from '@playwright/test'
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { openWorkbench } from './utils'
|
||||
|
||||
test.describe('contact tests', () => {
|
||||
@ -22,4 +22,25 @@ test.describe('contact tests', () => {
|
||||
|
||||
await page.locator('.card-container').locator('button:has-text("Create")').click()
|
||||
})
|
||||
test('contact-search', async ({ page }) => {
|
||||
// Create user and workspace
|
||||
await openWorkbench(page)
|
||||
|
||||
await page.locator('[id="app-contact\\:string\\:Contacts"]').click()
|
||||
|
||||
await expect(page.locator('text=Elton John')).toBeVisible()
|
||||
expect(await page.locator('.tr-body').count()).toBeGreaterThan(5)
|
||||
|
||||
const searchBox = page.locator('[placeholder="Search"]')
|
||||
await searchBox.fill('Elton')
|
||||
await searchBox.press('Enter')
|
||||
|
||||
await expect(page.locator('.tr-body')).toHaveCount(1)
|
||||
|
||||
await searchBox.fill('')
|
||||
await searchBox.press('Enter')
|
||||
|
||||
await expect(page.locator('text=Rosamund Chen')).toBeVisible()
|
||||
expect(await page.locator('.tr-body').count()).toBeGreaterThan(5)
|
||||
})
|
||||
})
|
||||
|
7
tests/sanity/tests/playwright.config.ts
Normal file
7
tests/sanity/tests/playwright.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test'
|
||||
const config: PlaywrightTestConfig = {
|
||||
use: {
|
||||
screenshot: 'only-on-failure'
|
||||
}
|
||||
}
|
||||
export default config
|
@ -95,4 +95,28 @@ test.describe('recruit tests', () => {
|
||||
await expect(page.locator('text=John Multiseed').first()).toBeVisible()
|
||||
await expect(page.locator('text=Alex P.').first()).toBeVisible()
|
||||
})
|
||||
|
||||
test('application-search', async ({ page }) => {
|
||||
// Create user and workspace
|
||||
await openWorkbench(page)
|
||||
|
||||
await page.locator('[id="app-recruit\\:string\\:RecruitApplication"]').click()
|
||||
|
||||
await page.click('text=Software Engineer')
|
||||
|
||||
await expect(page.locator('text=Andrey P.')).toBeVisible()
|
||||
expect(await page.locator('.tr-body').count()).toBeGreaterThan(2)
|
||||
|
||||
const searchBox = page.locator('[placeholder="Search"]')
|
||||
await searchBox.fill('Frontend Engineer')
|
||||
await searchBox.press('Enter')
|
||||
|
||||
await expect(page.locator('.tr-body')).toHaveCount(1)
|
||||
|
||||
await searchBox.fill('')
|
||||
await searchBox.press('Enter')
|
||||
|
||||
await expect(page.locator('text=Andrey P.')).toBeVisible()
|
||||
expect(await page.locator('.tr-body').count()).toBeGreaterThan(2)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user