From 7af0fefa4b9fbf7f4a877faf25bb1ba0e3d2ff2d Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Mon, 8 Jan 2024 21:19:23 +0700
Subject: [PATCH] UBER-1185: Fix TT migration issues (#4320)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 models/task/src/index.ts        | 39 +++++++++++++++++++++++----------
 models/tracker/src/migration.ts |  2 +-
 2 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/models/task/src/index.ts b/models/task/src/index.ts
index 8a0bd4474a..846b1b6341 100644
--- a/models/task/src/index.ts
+++ b/models/task/src/index.ts
@@ -638,7 +638,8 @@ export interface FixTaskResult {
 export async function fixTaskTypes (
   client: MigrationClient,
   descriptor: Ref<ProjectTypeDescriptor>,
-  dataFactory: (t: ProjectType) => Promise<FixTaskData[]>
+  dataFactory: (t: ProjectType) => Promise<FixTaskData[]>,
+  migrateTasks?: (projects: Project[], taskType: TaskType) => Promise<void>
 ): Promise<FixTaskResult> {
   const categoryObj = client.model.findObject(descriptor)
   if (categoryObj === undefined) {
@@ -655,10 +656,6 @@ export async function fixTaskTypes (
   const resultProjects: Project[] = []
 
   for (const t of projectTypes) {
-    if (t.tasks?.length > 0) {
-      // Already migrated.
-      continue
-    }
     t.tasks = [...(t.tasks ?? [])]
     if (t.targetClass === undefined) {
       const targetProjectClassId: Ref<Class<Doc>> = generateId()
@@ -704,12 +701,22 @@ export async function fixTaskTypes (
       )
     }
 
-    const dataTypes = await dataFactory(t)
+    const newTaskTypes = await dataFactory(t)
 
     const projects = await client.find<Project>(DOMAIN_SPACE, { type: t._id })
     resultProjects.push(...projects)
 
-    for (const data of dataTypes) {
+    for (const data of newTaskTypes) {
+      // Check and skip if already had task type for same class
+      const tt = await client.find<TaskType>(DOMAIN_TASK, {
+        _class: task.class.TaskType,
+        ofClass: data.ofClass,
+        parent: t._id
+      })
+      if (tt.length > 0) {
+        continue
+      }
+
       const taskTypeId: Ref<TaskType> = data._id ?? generateId()
       const descr = client.model.getObject(data.descriptor)
 
@@ -718,16 +725,16 @@ export async function fixTaskTypes (
         _class: data.statusClass
       })
 
-      const dStatuses = [...t.statuses.map((it) => it._id)]
+      const dStatuses: Ref<Status>[] = []
       const statusAttr = findStatusAttr(client.hierarchy, data.ofClass)
       // Ensure we have at leas't one item in every category.
       for (const c of data.statusCategories) {
         const category = typeof c === 'string' ? c : c.category
         const cat = await client.model.findOne(core.class.StatusCategory, { _id: category })
 
-        const st = statuses.find((it) => it.category === category)
+        const st = statuses.filter((it) => it.category === category)
         const newStatuses: Ref<Status>[] = []
-        if (st === undefined) {
+        if (st.length === 0 || typeof c === 'object') {
           if (typeof c === 'string') {
             // We need to add new status into missing category
             const statusId: Ref<Status> = generateId()
@@ -773,6 +780,8 @@ export async function fixTaskTypes (
                 })
                 newStatuses.push(statusId)
                 dStatuses.push(statusId)
+              } else {
+                dStatuses.push(st._id)
               }
 
               await client.update(
@@ -785,6 +794,12 @@ export async function fixTaskTypes (
               t.statuses.push(...newStatuses.map((it) => ({ _id: it, taskType: taskTypeId })))
             }
           }
+        } else {
+          for (const sss of st.map((it) => it._id)) {
+            if (!dStatuses.includes(sss)) {
+              dStatuses.push(sss)
+            }
+          }
         }
       }
       const taskType: TaskType = {
@@ -854,11 +869,13 @@ export async function fixTaskTypes (
       )
       t.tasks.push(taskTypeId)
       // Update kind and target classId
+      const projectsToUpdate = projects.filter((it) => it.type === t._id)
       await client.update(
         DOMAIN_TASK,
-        { space: { $in: projects.map((it) => it._id) }, _class: data.ofClass },
+        { space: { $in: projectsToUpdate.map((it) => it._id) }, _class: data.ofClass },
         { $set: { kind: taskTypeId } }
       )
+      await migrateTasks?.(projectsToUpdate, taskType)
     }
 
     // We need to fix project statuses field, for proper icon calculation.
diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts
index 2f809244f7..9c118ff59c 100644
--- a/models/tracker/src/migration.ts
+++ b/models/tracker/src/migration.ts
@@ -248,7 +248,7 @@ async function restoreToDoCategory (client: MigrationClient): Promise<void> {
     const taskTypes = await client.find<TaskType>(DOMAIN_TASK, {
       _class: task.class.TaskType,
       descriptor: tracker.descriptors.Issue,
-      _id: { $in: p.tasks }
+      _id: { $in: p.tasks ?? [] }
     })
     for (const taskType of taskTypes) {
       if (taskType.statusCategories.includes(task.statusCategory.ToDo)) continue