mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-07 08:21:08 +00:00
separate mention category for employees (#5095)
Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
parent
16204edaac
commit
72a93ad388
@ -836,7 +836,8 @@ export function createModel (builder: Builder): void {
|
||||
label: contact.string.SearchEmployee,
|
||||
title: contact.string.Employees,
|
||||
query: contact.completion.EmployeeQuery,
|
||||
context: ['search']
|
||||
context: ['search', 'mention'],
|
||||
classToSearch: contact.mixin.Employee
|
||||
},
|
||||
contact.completion.EmployeeCategory
|
||||
)
|
||||
@ -849,7 +850,7 @@ export function createModel (builder: Builder): void {
|
||||
label: contact.string.SearchPerson,
|
||||
title: contact.string.People,
|
||||
query: contact.completion.PersonQuery,
|
||||
context: ['search', 'mention', 'spotlight'],
|
||||
context: ['search', 'spotlight'],
|
||||
classToSearch: contact.class.Person
|
||||
},
|
||||
contact.completion.PersonCategory
|
||||
|
@ -521,7 +521,7 @@ export interface DocIndexState extends Doc {
|
||||
|
||||
// Indexable attributes, including child ones.
|
||||
attributes: Record<string, any>
|
||||
|
||||
mixins?: Ref<Class<Doc>>[]
|
||||
// Full Summary
|
||||
fullSummary?: Markup | null
|
||||
shortSummary?: Markup | null
|
||||
|
@ -141,6 +141,21 @@ export class Hierarchy {
|
||||
return result
|
||||
}
|
||||
|
||||
findAllMixins<D extends Doc, M extends D>(doc: Doc): Ref<Class<M>>[] {
|
||||
const _doc = _toDoc(doc)
|
||||
const resultSet = new Set<Ref<Class<M>>>()
|
||||
for (const [k, v] of Object.entries(_doc)) {
|
||||
if (typeof v === 'object' && this.classifiers.has(k as Ref<Classifier>)) {
|
||||
if (this.isMixin(k as Ref<Classifier>)) {
|
||||
if (!resultSet.has(k as Ref<Classifier>)) {
|
||||
resultSet.add(k as Ref<Classifier>)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(resultSet)
|
||||
}
|
||||
|
||||
isMixin (_class: Ref<Class<Doc>>): boolean {
|
||||
const data = this.classifiers.get(_class)
|
||||
return data !== undefined && this._isMixin(data)
|
||||
|
@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Class, Ref, Doc, SearchResultDoc, TxOperations, SearchResult } from '@hcengineering/core'
|
||||
import type { Class, Ref, Doc, SearchResultDoc, TxOperations } from '@hcengineering/core'
|
||||
import { type ObjectSearchCategory } from './types'
|
||||
import plugin from './plugin'
|
||||
import { getClient } from './utils'
|
||||
@ -67,7 +67,7 @@ async function doFulltextSearch (
|
||||
query: string,
|
||||
categories: ObjectSearchCategory[]
|
||||
): Promise<SearchSection[]> {
|
||||
let result: SearchResult | undefined
|
||||
const sections: SearchSection[] = []
|
||||
for (const cl of classes) {
|
||||
const r = await client.searchFulltext(
|
||||
{
|
||||
@ -78,31 +78,10 @@ async function doFulltextSearch (
|
||||
limit: 5
|
||||
}
|
||||
)
|
||||
if (result === undefined) {
|
||||
result = r
|
||||
} else {
|
||||
result.docs.push(...r.docs)
|
||||
result.total = (result?.total ?? 0) + (r.total ?? 0)
|
||||
}
|
||||
const category = findCategoryByClass(categories, cl)
|
||||
if (category !== undefined) sections.push({ category, items: r.docs })
|
||||
}
|
||||
|
||||
const itemsByClass = new Map<Ref<Class<Doc>>, SearchResultDoc[]>()
|
||||
for (const item of result?.docs ?? []) {
|
||||
const list = itemsByClass.get(item.doc._class)
|
||||
if (list === undefined) {
|
||||
itemsByClass.set(item.doc._class, [item])
|
||||
} else {
|
||||
list.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
const sections: SearchSection[] = []
|
||||
for (const [_class, items] of itemsByClass.entries()) {
|
||||
const category = findCategoryByClass(categories, _class)
|
||||
if (category !== undefined) {
|
||||
sections.push({ category, items })
|
||||
}
|
||||
}
|
||||
return sections.sort((a, b) => {
|
||||
const maxScoreA = Math.max(...(a?.items ?? []).map((obj) => obj?.score ?? 0))
|
||||
const maxScoreB = Math.max(...(b?.items ?? []).map((obj) => obj?.score ?? 0))
|
||||
@ -112,7 +91,10 @@ async function doFulltextSearch (
|
||||
|
||||
const categoriesByContext = new Map<string, ObjectSearchCategory[]>()
|
||||
|
||||
export async function searchFor (context: 'mention' | 'spotlight', query: string): Promise<SearchItem[]> {
|
||||
export async function searchFor (
|
||||
context: 'mention' | 'spotlight',
|
||||
query: string
|
||||
): Promise<{ items: SearchItem[], query: string }> {
|
||||
const client = getClient()
|
||||
let categories = categoriesByContext.get(context)
|
||||
if (categories === undefined) {
|
||||
@ -121,7 +103,7 @@ export async function searchFor (context: 'mention' | 'spotlight', query: string
|
||||
}
|
||||
|
||||
if (categories === undefined) {
|
||||
return []
|
||||
return { items: [], query }
|
||||
}
|
||||
|
||||
const classesToSearch: Array<Ref<Class<Doc>>> = []
|
||||
@ -132,5 +114,5 @@ export async function searchFor (context: 'mention' | 'spotlight', query: string
|
||||
}
|
||||
|
||||
const sections = await doFulltextSearch(client, classesToSearch, query, categories)
|
||||
return packSearchResultsForListView(sections)
|
||||
return { items: packSearchResultsForListView(sections), query }
|
||||
}
|
||||
|
@ -67,8 +67,11 @@
|
||||
return false
|
||||
}
|
||||
|
||||
async function updateItems (query: string): Promise<void> {
|
||||
items = await searchFor('mention', query)
|
||||
async function updateItems (localQuery: string): Promise<void> {
|
||||
const r = await searchFor('mention', localQuery)
|
||||
if (r.query === query) {
|
||||
items = r.items
|
||||
}
|
||||
}
|
||||
$: void updateItems(query)
|
||||
</script>
|
||||
|
@ -244,7 +244,7 @@
|
||||
async function updateItems (query: string, filteredActions: Array<WithLookup<Action>>): Promise<void> {
|
||||
let searchItems: SearchItem[] = []
|
||||
if (query !== '' && query.indexOf('/') !== 0) {
|
||||
searchItems = await searchFor('spotlight', query)
|
||||
searchItems = (await searchFor('spotlight', query)).items
|
||||
}
|
||||
items = packSearchAndActions(searchItems, filteredActions)
|
||||
}
|
||||
|
@ -114,16 +114,19 @@ export class FullTextIndex implements WithFind {
|
||||
const old = stDocs.get(cud.objectId as Ref<DocIndexState>)
|
||||
if (cud._class === core.class.TxRemoveDoc && old?.create !== undefined) {
|
||||
// Object created and deleted, skip index
|
||||
stDocs.delete(cud.objectId as Ref<DocIndexState>)
|
||||
continue
|
||||
} else if (old !== undefined) {
|
||||
// Create and update
|
||||
// Skip update
|
||||
continue
|
||||
if (old.removed) continue
|
||||
else {
|
||||
stDocs.set(cud.objectId as Ref<DocIndexState>, {
|
||||
...old,
|
||||
updated: cud._class !== core.class.TxRemoveDoc,
|
||||
removed: cud._class === core.class.TxRemoveDoc
|
||||
})
|
||||
}
|
||||
}
|
||||
stDocs.set(cud.objectId as Ref<DocIndexState>, {
|
||||
updated: cud._class !== core.class.TxRemoveDoc,
|
||||
removed: cud._class === core.class.TxRemoveDoc
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -207,7 +210,7 @@ export class FullTextIndex implements WithFind {
|
||||
const indexedDocMap = new Map<Ref<Doc>, IndexedDoc>()
|
||||
|
||||
for (const doc of docs) {
|
||||
if (this.hierarchy.isDerived(doc._class, baseClass)) {
|
||||
if (doc._class.some((cl) => this.hierarchy.isDerived(cl, baseClass))) {
|
||||
ids.add(doc.id)
|
||||
indexedDocMap.set(doc.id, doc)
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ export class IndexedFieldStage implements FullTextPipelineStage {
|
||||
const docUpdate: DocumentUpdate<DocIndexState> = {}
|
||||
|
||||
let changes = 0
|
||||
|
||||
docUpdate.mixins = pipeline.hierarchy.findAllMixins(doc as Doc)
|
||||
// Convert previous child fields to just
|
||||
for (const [k] of Object.entries(docState.attributes)) {
|
||||
const { attr, docId, _class } = extractDocKey(k)
|
||||
|
@ -242,7 +242,7 @@ export class FullTextPushStage implements FullTextPipelineStage {
|
||||
})
|
||||
)
|
||||
|
||||
const allAttributes = pipeline.hierarchy.getAllAttributes(elasticDoc._class)
|
||||
const allAttributes = pipeline.hierarchy.getAllAttributes(doc.objectClass)
|
||||
|
||||
// Include child ref attributes
|
||||
await this.indexRefAttributes(allAttributes, doc, elasticDoc, ctx)
|
||||
@ -290,7 +290,7 @@ export class FullTextPushStage implements FullTextPipelineStage {
|
||||
export function createElasticDoc (upd: DocIndexState): IndexedDoc {
|
||||
const doc = {
|
||||
id: upd._id,
|
||||
_class: upd.objectClass,
|
||||
_class: [upd.objectClass, ...(upd.mixins ?? [])],
|
||||
modifiedBy: upd.modifiedBy,
|
||||
modifiedOn: upd.modifiedOn,
|
||||
space: upd.space,
|
||||
|
@ -102,9 +102,9 @@ export const contentStageId = 'cnt-v2b'
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const fieldStateId = 'fld-v12'
|
||||
export const fieldStateId = 'fld-v13a'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const fullTextPushStageId = 'fts-v10b'
|
||||
export const fullTextPushStageId = 'fts-v11a'
|
||||
|
@ -112,12 +112,12 @@ export async function updateDocWithPresenter (
|
||||
spaceDoc: DocIndexState | undefined
|
||||
}
|
||||
): Promise<void> {
|
||||
const searchPresenter = findSearchPresenter(hierarchy, elasticDoc._class)
|
||||
const searchPresenter = findSearchPresenter(hierarchy, doc.objectClass)
|
||||
if (searchPresenter === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const reader = createIndexedReader(elasticDoc._class, hierarchy, doc, {
|
||||
const reader = createIndexedReader(doc.objectClass, hierarchy, doc, {
|
||||
space: refDocs.spaceDoc,
|
||||
attachedTo: refDocs.parentDoc
|
||||
})
|
||||
@ -156,7 +156,7 @@ export async function updateDocWithPresenter (
|
||||
} else if (prop.provider !== undefined) {
|
||||
const func = await getResource(Object.values(prop.provider)[0] as Resource<any>)
|
||||
const renderProps = await readAndMapProps(reader, prop.config.props)
|
||||
value = func(hierarchy, { _class: elasticDoc._class, ...renderProps })
|
||||
value = func(hierarchy, { _class: doc.objectClass, ...renderProps })
|
||||
} else if (prop.name === 'searchIcon') {
|
||||
value = await readAndMapProps(reader, prop.config.props)
|
||||
}
|
||||
@ -186,7 +186,7 @@ export function mapSearchResultDoc (hierarchy: Hierarchy, raw: IndexedDoc): Sear
|
||||
iconProps: raw.searchIcon,
|
||||
doc: {
|
||||
_id: raw.id,
|
||||
_class: raw._class
|
||||
_class: raw._class[0]
|
||||
},
|
||||
score: raw._score
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ export interface EmbeddingSearchOption {
|
||||
*/
|
||||
export interface IndexedDoc {
|
||||
id: Ref<Doc>
|
||||
_class: Ref<Class<Doc>>
|
||||
_class: Ref<Class<Doc>>[]
|
||||
space: Ref<Space>
|
||||
modifiedOn: Timestamp
|
||||
modifiedBy: Ref<Account>
|
||||
|
@ -41,7 +41,7 @@ describe('Elastic Adapter', () => {
|
||||
it('should create document', async () => {
|
||||
const doc: IndexedDoc = {
|
||||
id: 'doc1' as Ref<Doc>,
|
||||
_class: 'class1' as Ref<Class<Doc>>,
|
||||
_class: ['class1' as Ref<Class<Doc>>],
|
||||
modifiedBy: 'andrey' as Ref<Account>,
|
||||
modifiedOn: 0,
|
||||
space: 'space1' as Ref<Space>,
|
||||
|
Loading…
Reference in New Issue
Block a user