From 331aa7f78f3ff64d0171fc325841f263e019a110 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 27 Jan 2025 17:14:54 +0700 Subject: [PATCH] UBERF-9264: Fix set/unset parent issue (#7799) Signed-off-by: Andrey Sobolev --- models/tracker/src/actions.ts | 24 ++++++++++ models/tracker/src/plugin.ts | 7 +-- .../src/components/ObjectPopup.svelte | 47 ++++++++++++++----- packages/presentation/src/search.ts | 19 +++++--- plugins/tracker-assets/assets/icons.svg | 5 ++ plugins/tracker-assets/lang/cs.json | 3 +- plugins/tracker-assets/lang/de.json | 3 +- plugins/tracker-assets/lang/en.json | 3 +- plugins/tracker-assets/lang/es.json | 3 +- plugins/tracker-assets/lang/fr.json | 3 +- plugins/tracker-assets/lang/it.json | 3 +- plugins/tracker-assets/lang/pt.json | 3 +- plugins/tracker-assets/lang/ru.json | 3 +- plugins/tracker-assets/lang/zh.json | 3 +- plugins/tracker-assets/src/index.ts | 1 + .../SetParentIssueActionPopup.svelte | 15 +----- .../components/issues/edit/EditIssue.svelte | 23 ++++++++- plugins/tracker-resources/src/plugin.ts | 5 ++ plugins/tracker/src/analytics.ts | 4 +- plugins/tracker/src/index.ts | 7 ++- 20 files changed, 133 insertions(+), 51 deletions(-) diff --git a/models/tracker/src/actions.ts b/models/tracker/src/actions.ts index e42af938fa..4d5b66a81e 100644 --- a/models/tracker/src/actions.ts +++ b/models/tracker/src/actions.ts @@ -276,6 +276,30 @@ export function createActions (builder: Builder, issuesId: string, componentsId: }, tracker.action.SetParent ) + createAction( + builder, + { + action: view.actionImpl.UpdateDocument, + actionProps: { + key: 'attachedTo', + value: tracker.ids.NoParent + }, + query: { + attachedTo: { $ne: tracker.ids.NoParent } + }, + label: tracker.string.UnsetParentIssue, + icon: tracker.icon.UnsetParent, + input: 'none', + category: tracker.category.Tracker, + target: tracker.class.Issue, + context: { + mode: ['context'], + application: tracker.app.Tracker, + group: 'associate' + } + }, + tracker.action.UnsetParent + ) createAction( builder, diff --git a/models/tracker/src/plugin.ts b/models/tracker/src/plugin.ts index 080bbd0113..9c84de8eac 100644 --- a/models/tracker/src/plugin.ts +++ b/models/tracker/src/plugin.ts @@ -15,8 +15,7 @@ // import { type DocUpdateMessageViewlet } from '@hcengineering/activity' import { type ChatMessageViewlet } from '@hcengineering/chunter' -import { type StatusCategory, type Doc, type Ref, type DocManager } from '@hcengineering/core' -import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation' +import { type Doc, type DocManager, type Ref, type StatusCategory } from '@hcengineering/core' import { type NotificationGroup, type NotificationType } from '@hcengineering/notification' import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform' import { type ProjectType } from '@hcengineering/task' @@ -87,10 +86,6 @@ export default mergeIds(trackerId, tracker, { MilestoneChatMessageViewlet: '' as Ref, DefaultProjectType: '' as Ref }, - completion: { - IssueQuery: '' as Resource, - IssueCategory: '' as Ref - }, actionImpl: { Move: '' as ViewAction, CopyToClipboard: '' as ViewAction, diff --git a/packages/presentation/src/components/ObjectPopup.svelte b/packages/presentation/src/components/ObjectPopup.svelte index 6985f1134b..4ab8f6da62 100644 --- a/packages/presentation/src/components/ObjectPopup.svelte +++ b/packages/presentation/src/components/ObjectPopup.svelte @@ -24,8 +24,8 @@ import type { IntlString } from '@hcengineering/platform' import { Label } from '@hcengineering/ui' import { createEventDispatcher } from 'svelte' - import presentation from '..' - import { ObjectCreate } from '../types' + import presentation, { searchFor, type SearchItem } from '..' + import { ObjectCreate, type ObjectSearchCategory } from '../types' import { createQuery } from '../utils' import DocPopup from './DocPopup.svelte' @@ -46,7 +46,8 @@ export let width: 'medium' | 'large' | 'full' | 'auto' = 'medium' export let size: 'small' | 'medium' | 'large' = 'large' - export let searchMode: 'field' | 'fulltext' | 'disabled' = 'field' + export let searchMode: 'field' | 'fulltext' | 'disabled' | 'spotlight' = 'field' + export let category: Ref | undefined = undefined export let searchField: string = 'name' export let groupBy = '_class' @@ -74,21 +75,39 @@ let search: string = '' let objects: Doc[] = [] + let extraItems: Ref[] = [] + const query = createQuery() $: noSearchField = searchMode === 'disabled' $: _idExtra = typeof docQuery?._id === 'object' ? docQuery?._id : {} + $: if (searchMode === 'spotlight' && search !== '') { + void searchSpotlight(search).then((items) => { + extraItems = items.map((it) => it.item.id) + }) + } else { + extraItems = [] + } + $: fquery = { + ...(docQuery ?? {}), + ...(() => { + switch (searchMode) { + case 'disabled': + return { _id: { $nin: ignoreObjects, ..._idExtra } } + case 'fulltext': + return { $search: search, _id: { $nin: ignoreObjects, ..._idExtra } } + case 'spotlight': + return extraItems.length > 0 + ? { _id: { $in: extraItems, $nin: ignoreObjects } } + : { _id: { $nin: ignoreObjects, ..._idExtra } } + default: + return { [searchField]: { $like: '%' + search + '%' }, _id: { $nin: ignoreObjects, ..._idExtra } } + } + })() + } $: query.query( _class, - { - ...(docQuery ?? {}), - ...(searchMode !== 'disabled' && search !== '' - ? searchMode === 'fulltext' - ? { $search: search } - : { [searchField]: { $like: '%' + search + '%' } } - : {}), - _id: { $nin: ignoreObjects, ..._idExtra } - }, + fquery, (result) => { result.sort(sort) if (created.length > 0) { @@ -100,6 +119,10 @@ }, { ...(options ?? {}), limit: 200 } ) + + async function searchSpotlight (search: string): Promise { + return (await searchFor('spotlight', search, category, 50)).items + } >, query: string, - categories: ObjectSearchCategory[] + categories: ObjectSearchCategory[], + limit?: number ): Promise { const r = await client.searchFulltext( { @@ -73,7 +74,7 @@ async function searchCategory ( classes: [cl] }, { - limit: 5 + limit: limit ?? 5 } ) const category = findCategoryByClass(categories, cl) @@ -84,12 +85,13 @@ async function doFulltextSearch ( client: TxOperations, classes: Array>>, query: string, - categories: ObjectSearchCategory[] + categories: ObjectSearchCategory[], + limit?: number ): Promise { const sections: SearchSection[] = [] const promises: Array> = [] for (const cl of classes) { - promises.push(searchCategory(client, cl, query, categories)) + promises.push(searchCategory(client, cl, query, categories, limit)) } const resolvedSections = await Promise.all(promises) @@ -113,7 +115,9 @@ const categoriesByContext = new Map() export async function searchFor ( context: 'mention' | 'spotlight', - query: string + query: string, + category?: Ref, + limit?: number ): Promise<{ items: SearchItem[], query: string }> { const client = getClient() let categories = categoriesByContext.get(context) @@ -129,12 +133,13 @@ export async function searchFor ( } const classesToSearch: Array>> = [] - for (const cat of categories) { + const cats = category === undefined ? categories : categories.filter((it) => it._id === category) + for (const cat of cats) { if (cat.classToSearch !== undefined) { classesToSearch.push(cat.classToSearch) } } - const sections = await doFulltextSearch(client, classesToSearch, query, categories) + const sections = await doFulltextSearch(client, classesToSearch, query, categories, limit) return { items: packSearchResultsForListView(sections), query } } diff --git a/plugins/tracker-assets/assets/icons.svg b/plugins/tracker-assets/assets/icons.svg index bae6642ef6..4a1ffef8c5 100644 --- a/plugins/tracker-assets/assets/icons.svg +++ b/plugins/tracker-assets/assets/icons.svg @@ -27,6 +27,11 @@ + + + + + diff --git a/plugins/tracker-assets/lang/cs.json b/plugins/tracker-assets/lang/cs.json index 8e01b52d35..8271f37fc5 100644 --- a/plugins/tracker-assets/lang/cs.json +++ b/plugins/tracker-assets/lang/cs.json @@ -276,7 +276,8 @@ "DefaultIssueStatus": "Výchozí stav úkolu", "IssueStatus": "Stav", "Extensions": "Rozšíření", - "RoleLabel": "Role: {role}" + "RoleLabel": "Role: {role}", + "UnsetParentIssue": "Odebrat nadřazený úkol" }, "status": {} } \ No newline at end of file diff --git a/plugins/tracker-assets/lang/de.json b/plugins/tracker-assets/lang/de.json index 4cf63fb982..eb0074b531 100644 --- a/plugins/tracker-assets/lang/de.json +++ b/plugins/tracker-assets/lang/de.json @@ -286,7 +286,8 @@ "DefaultIssueStatus": "Standard-Aufgabenstatus", "IssueStatus": "Status", "Extensions": "Erweiterungen", - "RoleLabel": "Rolle: {role}" + "RoleLabel": "Rolle: {role}", + "UnsetParentIssue": "Übergeordnete Aufgabe entfernen" }, "status": {} } diff --git a/plugins/tracker-assets/lang/en.json b/plugins/tracker-assets/lang/en.json index c8c0967b12..5d4e53a608 100644 --- a/plugins/tracker-assets/lang/en.json +++ b/plugins/tracker-assets/lang/en.json @@ -286,7 +286,8 @@ "DefaultIssueStatus": "Default issue status", "IssueStatus": "Status", "Extensions": "Extensions", - "RoleLabel": "Role: {role}" + "RoleLabel": "Role: {role}", + "UnsetParentIssue": "Unset parent issue" }, "status": {} } diff --git a/plugins/tracker-assets/lang/es.json b/plugins/tracker-assets/lang/es.json index ed0bb83568..62fa7e7849 100644 --- a/plugins/tracker-assets/lang/es.json +++ b/plugins/tracker-assets/lang/es.json @@ -269,7 +269,8 @@ "DefaultIssueStatus": "Estado de problema predeterminado", "IssueStatus": "Estado", "Extensions": "Extensions", - "RoleLabel": "Role: {role}" + "RoleLabel": "Role: {role}", + "UnsetParentIssue": "Unset parent issue" }, "status": {} } \ No newline at end of file diff --git a/plugins/tracker-assets/lang/fr.json b/plugins/tracker-assets/lang/fr.json index f59b794079..e25981eafc 100644 --- a/plugins/tracker-assets/lang/fr.json +++ b/plugins/tracker-assets/lang/fr.json @@ -269,7 +269,8 @@ "DefaultIssueStatus": "Statut par défaut de l'issue", "IssueStatus": "Statut", "Extensions": "Extensions", - "RoleLabel": "Rôle : {role}" + "RoleLabel": "Rôle : {role}", + "UnsetParentIssue": "Désélectionner l'issue parent" }, "status": {} } \ No newline at end of file diff --git a/plugins/tracker-assets/lang/it.json b/plugins/tracker-assets/lang/it.json index ac06576f8c..ff8babe63d 100644 --- a/plugins/tracker-assets/lang/it.json +++ b/plugins/tracker-assets/lang/it.json @@ -269,7 +269,8 @@ "DefaultIssueStatus": "Stato predefinito della issue", "IssueStatus": "Stato", "Extensions": "Estensioni", - "RoleLabel": "Ruolo: {role}" + "RoleLabel": "Ruolo: {role}", + "UnsetParentIssue": "Annulla l'issue genitore" }, "status": {} } diff --git a/plugins/tracker-assets/lang/pt.json b/plugins/tracker-assets/lang/pt.json index f938635682..2185768cad 100644 --- a/plugins/tracker-assets/lang/pt.json +++ b/plugins/tracker-assets/lang/pt.json @@ -269,7 +269,8 @@ "DefaultIssueStatus": "Estado padrão do problema", "IssueStatus": "Estado", "Extensions": "Extensions", - "RoleLabel": "Cargo: {role}" + "RoleLabel": "Cargo: {role}", + "UnsetParentIssue": "Desmarcar problema pai" }, "status": {} } \ No newline at end of file diff --git a/plugins/tracker-assets/lang/ru.json b/plugins/tracker-assets/lang/ru.json index a94c12aa2a..3b6b5bfa9e 100644 --- a/plugins/tracker-assets/lang/ru.json +++ b/plugins/tracker-assets/lang/ru.json @@ -286,7 +286,8 @@ "DefaultIssueStatus": "Статус по умолчанию", "IssueStatus": "Статус", "Extensions": "Дополнительно", - "RoleLabel": "Роль: {role}" + "RoleLabel": "Роль: {role}", + "UnsetParentIssue": "Снять родительскую задачу" }, "status": {} } diff --git a/plugins/tracker-assets/lang/zh.json b/plugins/tracker-assets/lang/zh.json index 24e9fa0016..cd7cb1cd2a 100644 --- a/plugins/tracker-assets/lang/zh.json +++ b/plugins/tracker-assets/lang/zh.json @@ -286,7 +286,8 @@ "DefaultIssueStatus": "默认问题状态", "IssueStatus": "状态", "Extensions": "扩展", - "RoleLabel": "角色:{role}" + "RoleLabel": "角色:{role}", + "UnsetParentIssue": "取消父问题" }, "status": {} } diff --git a/plugins/tracker-assets/src/index.ts b/plugins/tracker-assets/src/index.ts index c2cf69613b..07aaf1bada 100644 --- a/plugins/tracker-assets/src/index.ts +++ b/plugins/tracker-assets/src/index.ts @@ -36,6 +36,7 @@ loadMetadata(tracker.icon, { Labels: `${icons}#labels`, DueDate: `${icons}#dueDate`, // TODO: add icon Parent: `${icons}#parent-issue`, // TODO: add icon + UnsetParent: `${icons}#unset-parent-issue`, // TODO: add icon Milestone: `${icons}#milestone`, IssueTemplates: `${icons}#issuetemplates`, Start: `${icons}#start`, diff --git a/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte b/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte index a0e55b7c45..f2176f7f36 100644 --- a/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte +++ b/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte @@ -66,24 +66,13 @@ $: selected = !Array.isArray(value) ? ('attachedTo' in value ? value.attachedTo : undefined) : undefined $: ignoreObjects = !Array.isArray(value) ? ('_id' in value ? [value._id] : []) : undefined - $: docQuery = { - 'parents.parentId': { - $nin: [ - ...new Set( - (Array.isArray(value) ? value : [value]) - .map((issue) => ('_id' in issue ? issue._id : null)) - .filter((x): x is Ref => x !== null) - ) - ] - } - } diff --git a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte index 1f4f9ef9bc..c29d45952d 100644 --- a/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/EditIssue.svelte @@ -175,6 +175,13 @@ $: taskType = issue?.kind !== undefined ? $taskTypeStore.get(issue?.kind) : undefined $: projectType = taskType?.parent !== undefined ? $typeStore.get(taskType.parent) : undefined + + async function unsetParentIssue (): Promise { + if (issue === undefined || readonly) return + + await client.update(issue, { attachedTo: tracker.ids.NoParent }) + Analytics.handleEvent(TrackerEvents.IssueParentUnset, { issue: issue.identifier ?? issue._id }) + } {#if !embedded} @@ -276,8 +283,22 @@ {#if hasParentIssue} -
+
+ {#if !readonly} +
+
+ {/if}
{/if} , ComponentIssuesList: '' as Ref }, + completion: { + IssueQuery: '' as Resource, + IssueCategory: '' as Ref + }, string: { More: '' as IntlString, Delete: '' as IntlString, diff --git a/plugins/tracker/src/analytics.ts b/plugins/tracker/src/analytics.ts index 3dd50a49d2..1aba04d397 100644 --- a/plugins/tracker/src/analytics.ts +++ b/plugins/tracker/src/analytics.ts @@ -22,5 +22,7 @@ export enum TrackerEvents { ProjectCreated = 'tracker.project.Created', ProjectDeleted = 'tracker.project.Deleted', - ProjectArchived = 'tracker.project.Archived' + ProjectArchived = 'tracker.project.Archived', + + IssueParentUnset = 'tracker.issue.ParentUnset' } diff --git a/plugins/tracker/src/index.ts b/plugins/tracker/src/index.ts index 1885922696..6f03287a57 100644 --- a/plugins/tracker/src/index.ts +++ b/plugins/tracker/src/index.ts @@ -432,6 +432,7 @@ const pluginState = plugin(trackerId, { Labels: '' as Asset, DueDate: '' as Asset, Parent: '' as Asset, + UnsetParent: '' as Asset, Milestone: '' as Asset, IssueTemplates: '' as Asset, Start: '' as Asset, @@ -495,7 +496,8 @@ const pluginState = plugin(trackerId, { EditProject: '' as Ref, SetMilestone: '' as Ref>, SetLabels: '' as Ref>, - EditRelatedTargets: '' as Ref> + EditRelatedTargets: '' as Ref>, + UnsetParent: '' as Ref> }, project: { DefaultProject: '' as Ref @@ -516,7 +518,8 @@ const pluginState = plugin(trackerId, { Project: '' as IntlString, RelatedIssues: '' as IntlString, Issue: '' as IntlString, - NewProject: '' as IntlString + NewProject: '' as IntlString, + UnsetParentIssue: '' as IntlString }, extensions: { IssueListHeader: '' as ComponentExtensionId,