diff --git a/models/task/src/index.ts b/models/task/src/index.ts index 896939c330..8a0bd4474a 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -61,7 +61,7 @@ import view, { template, actionTemplates as viewTemplates } from '@hcengineering/model-view' -import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/platform' +import { getEmbeddedLabel, type Asset, type IntlString, type Resource } from '@hcengineering/platform' import setting from '@hcengineering/setting' import tags from '@hcengineering/tags' import { @@ -171,6 +171,7 @@ export class TTaskTypeDescriptor extends TDoc implements TaskTypeDescriptor { // If specified, will allow to be created by users, system type overwize allowCreate!: boolean + statusCategoriesFunc?: Resource<(project: ProjectType) => Ref[]> } @Mixin(task.mixin.TaskTypeClass, core.class.Class) @@ -507,12 +508,26 @@ export function createModel (builder: Builder): void { core.space.Model, { ofAttribute: task.attribute.State, - label: task.string.StateActive, + label: task.string.StateUnstarted, icon: task.icon.TaskState, color: PaletteColorIndexes.Porpoise, - defaultStatusName: 'New state', + defaultStatusName: 'Todo', order: 1 }, + task.statusCategory.ToDo + ) + + builder.createDoc( + core.class.StatusCategory, + core.space.Model, + { + ofAttribute: task.attribute.State, + label: task.string.StateActive, + icon: task.icon.TaskState, + color: PaletteColorIndexes.Cerulean, + defaultStatusName: 'New state', + order: 2 + }, task.statusCategory.Active ) @@ -525,7 +540,7 @@ export function createModel (builder: Builder): void { icon: task.icon.TaskState, color: PaletteColorIndexes.Grass, defaultStatusName: 'Won', - order: 2 + order: 3 }, task.statusCategory.Won ) @@ -539,7 +554,7 @@ export function createModel (builder: Builder): void { icon: task.icon.TaskState, color: PaletteColorIndexes.Coin, defaultStatusName: 'Lost', - order: 3 + order: 4 }, task.statusCategory.Lost ) diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts index 939af4afe3..240abc7ca2 100644 --- a/models/task/src/migration.ts +++ b/models/task/src/migration.ts @@ -44,6 +44,7 @@ async function reorderStates (_client: MigrationUpgradeClient): Promise { const states = toIdMap(await client.findAll(core.class.Status, {})) const order = [ task.statusCategory.UnStarted, + task.statusCategory.ToDo, task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost diff --git a/models/task/src/plugin.ts b/models/task/src/plugin.ts index c0de8d6984..250db11bfb 100644 --- a/models/task/src/plugin.ts +++ b/models/task/src/plugin.ts @@ -79,6 +79,7 @@ export default mergeIds(taskId, task, { string: { ManageProjects: '' as IntlString, StateBacklog: '' as IntlString, - StateActive: '' as IntlString + StateActive: '' as IntlString, + StateUnstarted: '' as IntlString } }) diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 4117b6f2c0..775e34c695 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -663,7 +663,8 @@ export function createModel (builder: Builder): void { allowCreate: true, description: tracker.string.Issue, icon: tracker.icon.Issue, - name: tracker.string.Issue + name: tracker.string.Issue, + statusCategoriesFunc: tracker.function.GetIssueStatusCategories }, tracker.descriptors.Issue ) diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts index cec63c0230..04ada9323c 100644 --- a/models/tracker/src/migration.ts +++ b/models/tracker/src/migration.ts @@ -20,7 +20,12 @@ import core, { generateId, type Data, type Ref, - type Status + type Status, + toIdMap, + DOMAIN_TX, + type TxCreateDoc, + TxProcessor, + type Tx } from '@hcengineering/core' import { createOrUpdate, @@ -34,12 +39,14 @@ import { DOMAIN_TASK, createProjectType, fixTaskTypes } from '@hcengineering/mod import tags from '@hcengineering/tags' import task, { type ProjectType, type TaskType } from '@hcengineering/task' import { + type IssueStatus, TimeReportDayType, baseIssueTaskStatuses, classicIssueTaskStatuses, createStatesData } from '@hcengineering/tracker' import tracker from './plugin' +import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' async function createDefaultProject (tx: TxOperations): Promise { const current = await tx.findOne(tracker.class.Project, { @@ -132,7 +139,7 @@ async function createDefaultProject (tx: TxOperations): Promise { defaultIssueStatus: state._id, defaultTimeReportDay: TimeReportDayType.PreviousWorkDay, defaultAssignee: undefined, - type: tracker.ids.DefaultProjectType + type: tracker.ids.ClassingProjectType }, tracker.project.DefaultProject ) @@ -176,6 +183,156 @@ async function fixTrackerTaskTypes (client: MigrationClient): Promise { }) } +async function tryCreateStatus (client: MigrationClient): Promise> { + const exists = await client.find(DOMAIN_STATUS, { + _class: tracker.class.IssueStatus, + name: 'Todo', + ofAttribute: tracker.attribute.IssueStatus + }) + if (exists.length > 0) return exists[0]._id + const newStatus: IssueStatus = { + ofAttribute: tracker.attribute.IssueStatus, + name: 'Todo', + _id: generateId(), + category: task.statusCategory.ToDo, + color: PaletteColorIndexes.Porpoise, + space: task.space.Statuses, + modifiedOn: Date.now(), + createdBy: core.account.System, + createdOn: Date.now(), + modifiedBy: core.account.System, + _class: tracker.class.IssueStatus + } + await client.create(DOMAIN_STATUS, newStatus) + const tx: TxCreateDoc = { + modifiedOn: Date.now(), + createdBy: core.account.System, + createdOn: Date.now(), + modifiedBy: core.account.System, + _id: generateId(), + objectClass: newStatus._class, + objectSpace: newStatus.space, + objectId: newStatus._id, + _class: core.class.TxCreateDoc, + space: core.space.Tx, + attributes: { + category: newStatus.category, + color: newStatus.color, + ofAttribute: newStatus.ofAttribute, + name: newStatus.name + } + } + await client.create(DOMAIN_TX, tx) + return newStatus._id +} + +async function restoreToDoCategory (client: MigrationClient): Promise { + const updatedStatus = new Set>() + const allStatuses = await client.find( + DOMAIN_STATUS, + { _class: tracker.class.IssueStatus }, + { projection: { name: 1, _id: 1 } } + ) + const statusMap = toIdMap(allStatuses) + const projects = await client.find(DOMAIN_SPACE, { + _class: task.class.ProjectType, + descriptor: tracker.descriptors.ProjectType, + classic: true + }) + for (const p of projects) { + const changed = new Map, Ref>() + const pushStatuses: { + _id: Ref + taskType: Ref + }[] = [] + const taskTypes = await client.find(DOMAIN_TASK, { + _class: task.class.TaskType, + descriptor: tracker.descriptors.Issue, + _id: { $in: p.tasks } + }) + for (const taskType of taskTypes) { + if (taskType.statusCategories.includes(task.statusCategory.ToDo)) continue + const activeIndexes: number[] = [] + for (let index = 0; index < taskType.statuses.length; index++) { + const status = taskType.statuses[index] + const st = statusMap.get(status) + if (st === undefined) continue + if (st.category !== task.statusCategory.Active) continue + activeIndexes.push(index) + } + if (activeIndexes.length < 2) { + // we should create new status + const newStatus = await tryCreateStatus(client) + pushStatuses.push({ + _id: newStatus, + taskType: taskType._id + }) + taskType.statuses.splice(activeIndexes[0] ?? 0, 0, newStatus) + } else { + // let's try to find ToDo status + let changed = false + for (const index of activeIndexes) { + const status = taskType.statuses[index] + const st = statusMap.get(status) + if (st === undefined) continue + const ownTxes = await client.find(DOMAIN_TX, { objectId: status }) + const attachedTxes = await client.find(DOMAIN_TX, { 'tx.objectId': status }) + const original = TxProcessor.buildDoc2Doc([...ownTxes, ...attachedTxes]) + if (original === undefined) continue + if (original.category === tracker.issueStatusCategory.Unstarted) { + // We need to update status + if (!updatedStatus.has(status)) { + await client.update( + DOMAIN_STATUS, + { _id: status }, + { $set: { category: task.statusCategory.ToDo } } + ) + updatedStatus.add(status) + } + changed = true + } + } + if (!changed) { + // we should create new status + const newStatus = await tryCreateStatus(client) + pushStatuses.push({ + _id: newStatus, + taskType: taskType._id + }) + taskType.statuses.splice(activeIndexes[0] ?? 0, 0, newStatus) + } + } + await client.update( + DOMAIN_TASK, + { _id: taskType._id }, + { + $set: { + statusCategories: [ + task.statusCategory.UnStarted, + task.statusCategory.ToDo, + task.statusCategory.Active, + task.statusCategory.Won, + task.statusCategory.Lost + ], + statuses: taskType.statuses + } + } + ) + } + if (changed.size > 0) { + const statuses = p.statuses + .map((it) => { + return { + ...it, + _id: changed.get(it._id) ?? it._id + } + }) + .concat(pushStatuses) + await client.update(DOMAIN_SPACE, { _id: p._id }, { $set: { statuses } }) + } + } +} + export const trackerOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await tryMigrate(client, 'tracker', [ @@ -205,7 +362,7 @@ export const trackerOperation: MigrateOperation = { await client.update( DOMAIN_STATUS, { _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Unstarted }, - { $set: { category: task.statusCategory.Active } } + { $set: { category: task.statusCategory.ToDo } } ) await client.update( DOMAIN_STATUS, @@ -238,6 +395,7 @@ export const trackerOperation: MigrateOperation = { // We need to replace category tt.statusCategories = [ task.statusCategory.UnStarted, + task.statusCategory.ToDo, task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost @@ -250,6 +408,7 @@ export const trackerOperation: MigrateOperation = { const toRemove: Ref[] = [] for (const c of [ task.statusCategory.UnStarted, + task.statusCategory.ToDo, task.statusCategory.Active, task.statusCategory.Won, task.statusCategory.Lost @@ -301,6 +460,10 @@ export const trackerOperation: MigrateOperation = { { state: 'fixTaskTypes', func: fixTrackerTaskTypes + }, + { + state: 'restoreToDoCategory', + func: restoreToDoCategory } ]) }, diff --git a/plugins/task-assets/lang/en.json b/plugins/task-assets/lang/en.json index 1b19600890..3a6842f945 100644 --- a/plugins/task-assets/lang/en.json +++ b/plugins/task-assets/lang/en.json @@ -55,6 +55,7 @@ "DoneStatesWon": "Done status / Won", "DoneStatesLost": "Done status / Lost", "StateBacklog": "Backlog", + "StateUnstarted": "Unstarted", "StateActive": "Active", "AllStates": "All states", "DoneStates": "Done states", diff --git a/plugins/task-assets/lang/ru.json b/plugins/task-assets/lang/ru.json index 73a96e2a06..a04179c04f 100644 --- a/plugins/task-assets/lang/ru.json +++ b/plugins/task-assets/lang/ru.json @@ -55,6 +55,7 @@ "DoneStatesWon": "Завершено / Выиграно", "DoneStatesLost": "Завершено / Потеряно", "StateBacklog": "Пул задач", + "StateUnstarted": "Не запущенные", "StateActive": "Активные", "AllStates": "Все статусы", "DoneStates": "Завершенные статусы", diff --git a/plugins/task-resources/src/components/state/StatePresenter.svelte b/plugins/task-resources/src/components/state/StatePresenter.svelte index cb3a556f15..6533e81894 100644 --- a/plugins/task-resources/src/components/state/StatePresenter.svelte +++ b/plugins/task-resources/src/components/state/StatePresenter.svelte @@ -31,6 +31,7 @@ import { createEventDispatcher, onMount } from 'svelte' import { typeStore } from '../..' import IconBacklog from '../icons/IconBacklog.svelte' + import IconUnstarted from '../icons/IconUnstarted.svelte' import IconCanceled from '../icons/IconCanceled.svelte' import IconCompleted from '../icons/IconCompleted.svelte' import IconStarted from '../icons/IconStarted.svelte' @@ -104,6 +105,7 @@ const categoryIcons = { [task.statusCategory.UnStarted]: IconBacklog, + [task.statusCategory.ToDo]: IconUnstarted, [task.statusCategory.Active]: IconStarted, [task.statusCategory.Won]: IconCompleted, [task.statusCategory.Lost]: IconCanceled diff --git a/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte b/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte index 5d01036b75..0c28243d91 100644 --- a/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte +++ b/plugins/task-resources/src/components/taskTypes/CreateTaskType.svelte @@ -14,7 +14,7 @@ -->