diff --git a/plugins/tracker-assets/lang/en.json b/plugins/tracker-assets/lang/en.json index 49b5e359bc..751fbe9a89 100644 --- a/plugins/tracker-assets/lang/en.json +++ b/plugins/tracker-assets/lang/en.json @@ -294,7 +294,11 @@ "ConfigDescription": "Extension to manage work items and do all jobs done.", "ProjectColor": "Choose project icon color", "ProjectIconCategory": "Icon", - "ProjectEmojiiCategory": "Emojii" + "ProjectEmojiiCategory": "Emojii", + + "NoStatusFound": "No matching status found", + "CreateMissingStatus": "Create missing status", + "UnsetParent": "Parent issue will be unset" }, "status": {} } diff --git a/plugins/tracker-assets/lang/ru.json b/plugins/tracker-assets/lang/ru.json index 3679a3e5c0..dfa7bfe97f 100644 --- a/plugins/tracker-assets/lang/ru.json +++ b/plugins/tracker-assets/lang/ru.json @@ -294,7 +294,10 @@ "ConfigDescription": "Расширение по управлению задачами, чтобы все было в срок.", "ProjectColor": "Выберете цвет проекта", "ProjectIconCategory": "Иконка", - "ProjectEmojiiCategory": "Эмодзи" + "ProjectEmojiiCategory": "Эмодзи", + "NoStatusFound": "Статус не найдет", + "CreateMissingStatus": "Создать отсутствущий статус", + "UnsetParent": "Родительская задача будет убрана" }, "status": {} } diff --git a/plugins/tracker-resources/src/components/ComponentSelector.svelte b/plugins/tracker-resources/src/components/ComponentSelector.svelte index d807d74f6d..e5a88d31a8 100644 --- a/plugins/tracker-resources/src/components/ComponentSelector.svelte +++ b/plugins/tracker-resources/src/components/ComponentSelector.svelte @@ -14,11 +14,11 @@ --> @@ -61,6 +67,7 @@ {shape} width={compression ? 'min-content' : width} {justify} + {space} {isEditable} {shouldShowLabel} {popupPlaceholder} diff --git a/plugins/tracker-resources/src/components/components/ComponentPresenter.svelte b/plugins/tracker-resources/src/components/components/ComponentPresenter.svelte index 907cfe032d..796efc9cfb 100644 --- a/plugins/tracker-resources/src/components/components/ComponentPresenter.svelte +++ b/plugins/tracker-resources/src/components/components/ComponentPresenter.svelte @@ -20,7 +20,7 @@ export let value: WithLookup export let shouldShowAvatar = true - export let onClick: () => void | undefined + export let onClick: (() => void) | undefined = undefined export let disabled = false export let inline: boolean = false diff --git a/plugins/tracker-resources/src/components/issues/Move.svelte b/plugins/tracker-resources/src/components/issues/Move.svelte index 94e89bdc35..04b3b3aae5 100644 --- a/plugins/tracker-resources/src/components/issues/Move.svelte +++ b/plugins/tracker-resources/src/components/issues/Move.svelte @@ -14,16 +14,20 @@ // limitations under the License. -->
@@ -64,19 +127,57 @@
- {#await getSpace() then} - {#if currentSpace && _class} - - {/if} - {/await} + {#if currentSpace && _class} + + {/if}
+
+
+
+ {#if loading} + + {:else if toMove.length > 0 && currentSpace} + {#each toMove as issue} + {@const upd = issueToUpdate.get(issue._id) ?? {}} +
+
+ +
+ +
+
+ {#if issue.space !== currentSpace._id} + {#key upd.status} + + {/key} + {#key upd.component} + + {/key} + {#if upd.attachedTo === tracker.ids.NoParent && issue.attachedTo !== tracker.ids.NoParent} +
+
+ {/if} + {/if} +
+ {/each} + {/if} +
+ + {#if currentSpace !== undefined} + + + {/if} + @@ -93,7 +195,7 @@ display: flex; flex-direction: column; padding: 2rem 1.75rem 1.75rem; - width: 25rem; + width: 55rem; max-width: 40rem; background: var(--popup-bg-color); border-radius: 1.25rem; @@ -117,5 +219,20 @@ margin-top: 1rem; column-gap: 0.5rem; } + .issues-move { + height: 30rem; + overflow: auto; + } + .issue-move { + border: 1px solid var(--popup-divider); + } + + .status-option { + border: 1px solid var(--popup-divider); + } + } + + .unset-parent { + background-color: var(--accent-bg-color); } diff --git a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte index 2881fe29fc..f75c6eba8a 100644 --- a/plugins/tracker-resources/src/components/issues/StatusEditor.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusEditor.svelte @@ -14,13 +14,12 @@ --> {#if value} @@ -34,7 +35,15 @@ style:max-width={showParent ? `${value.parents.length !== 0 ? 95 : 100}%` : '100%'} title={value.title} > - + {value.title} diff --git a/plugins/tracker-resources/src/components/issues/move/ComponentMove.svelte b/plugins/tracker-resources/src/components/issues/move/ComponentMove.svelte new file mode 100644 index 0000000000..a7df28082f --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/move/ComponentMove.svelte @@ -0,0 +1,73 @@ + + + +{#if missingComponents.length > 0} +
+
+
+ {#each missingComponents as comp} +
+
+
+
+ +
+
+ {/each} +
+{/if} diff --git a/plugins/tracker-resources/src/components/issues/move/ComponentMovePresenter.svelte b/plugins/tracker-resources/src/components/issues/move/ComponentMovePresenter.svelte new file mode 100644 index 0000000000..ccfd088df6 --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/move/ComponentMovePresenter.svelte @@ -0,0 +1,69 @@ + + + +{#if currentComponent !== undefined} +
+
+ +
+ +
+ => + + {#if targetComponent === undefined} +
+
+
+ => +
+ {/if} + issueToUpdate.set(issue._id, { ...issueToUpdate.get(issue._id), status: evt.detail })} + /> +
+
+{/if} + + diff --git a/plugins/tracker-resources/src/components/issues/move/StatusMove.svelte b/plugins/tracker-resources/src/components/issues/move/StatusMove.svelte new file mode 100644 index 0000000000..fba1c02355 --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/move/StatusMove.svelte @@ -0,0 +1,70 @@ + + + +{#if missingStatuses.length > 0} +
+
+
+ {#each missingStatuses as st} +
+
+
+
+ +
+
+ {/each} +
+{/if} diff --git a/plugins/tracker-resources/src/components/issues/move/StatusMovePresenter.svelte b/plugins/tracker-resources/src/components/issues/move/StatusMovePresenter.svelte new file mode 100644 index 0000000000..084071c5de --- /dev/null +++ b/plugins/tracker-resources/src/components/issues/move/StatusMovePresenter.svelte @@ -0,0 +1,63 @@ + + + +
+
+ +
+ +
+ => + + {#if targetStatus === undefined} +
+
+ {/if} + issueToUpdate.set(issue._id, { ...issueToUpdate.get(issue._id), status: evt.detail })} + /> +
+
+ + diff --git a/plugins/tracker-resources/src/components/sprints/SprintFilter.svelte b/plugins/tracker-resources/src/components/sprints/SprintFilter.svelte index 8ae3226864..68c12a2147 100644 --- a/plugins/tracker-resources/src/components/sprints/SprintFilter.svelte +++ b/plugins/tracker-resources/src/components/sprints/SprintFilter.svelte @@ -17,7 +17,7 @@ import { translate } from '@hcengineering/platform' import presentation, { getClient } from '@hcengineering/presentation' import { Project, Sprint, SprintStatus } from '@hcengineering/tracker' - import ui, { deviceOptionsStore, Icon, Label, CheckBox, Loading, resizeObserver } from '@hcengineering/ui' + import ui, { CheckBox, Icon, Label, Loading, deviceOptionsStore, resizeObserver } from '@hcengineering/ui' import view, { Filter } from '@hcengineering/view' import { createEventDispatcher, onMount } from 'svelte' import tracker from '../../plugin' diff --git a/plugins/tracker-resources/src/plugin.ts b/plugins/tracker-resources/src/plugin.ts index db08c4513c..bf1155617e 100644 --- a/plugins/tracker-resources/src/plugin.ts +++ b/plugins/tracker-resources/src/plugin.ts @@ -314,7 +314,11 @@ export default mergeIds(trackerId, tracker, { ProjectColor: '' as IntlString, ProjectIconCategory: '' as IntlString, - ProjectEmojiiCategory: '' as IntlString + ProjectEmojiiCategory: '' as IntlString, + + NoStatusFound: '' as IntlString, + CreateMissingStatus: '' as IntlString, + UnsetParent: '' as IntlString }, component: { NopeComponent: '' as AnyComponent, diff --git a/plugins/tracker-resources/src/utils.ts b/plugins/tracker-resources/src/utils.ts index 95985fa971..99c0863246 100644 --- a/plugins/tracker-resources/src/utils.ts +++ b/plugins/tracker-resources/src/utils.ts @@ -16,6 +16,7 @@ import { Employee } from '@hcengineering/contact' import core, { ApplyOperations, + AttachedData, AttachedDoc, Class, Collection, @@ -24,7 +25,9 @@ import core, { DocumentUpdate, Ref, SortingOrder, + Status, StatusCategory, + StatusManager, StatusValue, toIdMap, TxCollectionCUD, @@ -535,8 +538,9 @@ async function updateIssuesOnMove ( client: TxOperations, applyOps: ApplyOperations, doc: Doc, - space: Ref, - extra?: DocumentUpdate + space: Project, + extra: DocumentUpdate, + updates: Map, DocumentUpdate> ): Promise { const hierarchy = client.getHierarchy() const attributes = hierarchy.getAllAttributes(doc._class) @@ -545,29 +549,37 @@ async function updateIssuesOnMove ( const collection = attribute.type as Collection const allAttached = await client.findAll(collection.of, { attachedTo: doc._id }) for (const attached of allAttached) { - // Do not use extra for childs. if (hierarchy.isDerived(collection.of, tracker.class.Issue)) { const lastOne = await client.findOne(tracker.class.Issue, {}, { sort: { rank: SortingOrder.Descending } }) const incResult = await client.updateDoc( tracker.class.Project, core.space.Space, - space, + space._id, { $inc: { sequence: 1 } }, true ) - await updateIssuesOnMove(client, applyOps, attached, space, { - ...extra, - rank: calcRank(lastOne, undefined), - number: (incResult as any).object.sequence - }) - } else await updateIssuesOnMove(client, applyOps, attached, space) + await updateIssuesOnMove( + client, + applyOps, + attached, + space, + { + ...updates.get(attached._id as Ref), + rank: calcRank(lastOne, undefined), + number: (incResult as any).object.sequence + }, + updates + ) + } else { + await updateIssuesOnMove(client, applyOps, attached, space, {}, updates) + } } } } await applyOps.update(doc, { - space, + space: space._id, ...extra }) } @@ -577,9 +589,9 @@ async function updateIssuesOnMove ( */ export async function moveIssueToSpace ( client: TxOperations, - docs: Doc[], - space: Ref, - extra?: DocumentUpdate + docs: Issue[], + space: Project, + updates: Map, DocumentUpdate> ): Promise { const applyOps = client.apply(docs[0]._id) for (const doc of docs) { @@ -587,17 +599,98 @@ export async function moveIssueToSpace ( const incResult = await client.updateDoc( tracker.class.Project, core.space.Space, - space, + space._id, { $inc: { sequence: 1 } }, true ) - await updateIssuesOnMove(client, applyOps, doc, space, { - ...extra, - rank: calcRank(lastOne, undefined), - number: (incResult as any).object.sequence - }) + await updateIssuesOnMove( + client, + applyOps, + doc, + space, + { + ...updates.get(doc._id), + rank: calcRank(lastOne, undefined), + number: (incResult as any).object.sequence + }, + updates + ) } await applyOps.commit() } + +/** + * @public + * + * Will collect all issues to be moved. + */ +export async function collectIssues (client: TxOperations, docs: Doc[]): Promise { + const result: Issue[] = [] + const hierarchy = client.getHierarchy() + for (const doc of docs) { + if (hierarchy.isDerived(doc._class, tracker.class.Issue)) { + result.push(doc as Issue) + } + + const attributes = hierarchy.getAllAttributes(doc._class) + for (const attribute of attributes.values()) { + if (hierarchy.isDerived(attribute.type._class, core.class.Collection)) { + const collection = attribute.type as Collection + const allAttached = await client.findAll(collection.of, { attachedTo: doc._id }) + for (const attached of allAttached) { + if (hierarchy.isDerived(collection.of, tracker.class.Issue)) { + if (result.find((it) => it._id === attached._id) === undefined) { + result.push(attached as Issue) + } + } + + const subIssues = await collectIssues(client, [attached]) + if (subIssues.length > 0) { + for (const s of subIssues) { + if (result.find((it) => it._id === s._id) === undefined) { + result.push(s) + } + } + } + } + } + } + } + return result +} + +/** + * @public + */ +export function findTargetStatus ( + mgr: StatusManager, + status: Ref, + targetProject: Ref, + useCategory = false +): Ref | undefined { + const s = mgr.get(status) + let targetStatus = mgr + .filter( + (it) => + it.space === targetProject && + it.ofAttribute === s?.ofAttribute && + (it.name ?? '').trim().toLowerCase() === (s?.name ?? '').trim().toLowerCase() + ) + .shift() + if (targetStatus === undefined && useCategory) { + targetStatus = mgr + .filter((it) => it.space === targetProject && it.ofAttribute === s?.ofAttribute && s?.category === it.category) + .shift() + } + return targetStatus?._id +} + +/** + * @public + */ +export function issueToAttachedData (issue: Issue): AttachedData { + const { _id, _class, space, ...data } = issue + return { ...data } +} diff --git a/pods/collaborator/package.json b/pods/collaborator/package.json index 5a7532f420..cd5f659206 100644 --- a/pods/collaborator/package.json +++ b/pods/collaborator/package.json @@ -44,7 +44,6 @@ "@hcengineering/server-token": "^0.6.4", "@hcengineering/server-core": "^0.6.1", "lib0": "~0.2.52", - "lodash.debounce": "~4.0.8", "y-protocols": "^1.0.5", "ws": "^8.10.0", "yjs": "^13.5.52", diff --git a/server-plugins/tracker-resources/src/index.ts b/server-plugins/tracker-resources/src/index.ts index ff54a9bbba..c543068014 100644 --- a/server-plugins/tracker-resources/src/index.ts +++ b/server-plugins/tracker-resources/src/index.ts @@ -257,7 +257,6 @@ async function doIssueUpdate ( { limit: 1 } ) - const updatedComponent = newParent !== undefined ? newParent.component : null const updatedParents = newParent !== undefined ? [{ parentId: newParent._id, parentTitle: newParent.title }, ...newParent.parents] : [] @@ -268,13 +267,12 @@ async function doIssueUpdate ( ? {} : { parents: [...issue.parents].slice(0, parentInfoIndex + 1).concat(updatedParents) } - return { ...parentsUpdate, component: updatedComponent } + return { ...parentsUpdate } } res.push( control.txFactory.createTxUpdateDoc(updateTx.objectClass, updateTx.objectSpace, updateTx.objectId, { - parents: updatedParents, - component: updatedComponent + parents: updatedParents }), ...(await updateSubIssues(updateTx, control, update)) ) @@ -284,14 +282,6 @@ async function doIssueUpdate ( updateIssueParentEstimations(issue, res, control, issue.parents, updatedParents) } - if (Object.prototype.hasOwnProperty.call(updateTx.operations, 'component')) { - res.push( - ...(await updateSubIssues(updateTx, control, { - component: updateTx.operations.component - })) - ) - } - if ( Object.prototype.hasOwnProperty.call(updateTx.operations, 'estimation') || Object.prototype.hasOwnProperty.call(updateTx.operations, 'reportedTime')