// // Copyright © 2022 Hardcore Engineering Inc. // // Licensed under the Eclipse Public License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. You may // obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // // See the License for the specific language governing permissions and // limitations under the License. // import core, { generateId, Ref, TxOperations } from '@anticrm/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' import { IssueStatus, IssueStatusCategory, Team, genRanks } from '@anticrm/tracker' import { DOMAIN_TRACKER } from '.' import tracker from './plugin' enum DeprecatedIssueStatus { Backlog, Todo, InProgress, Done, Canceled } interface CreateTeamIssueStatusesArgs { tx: TxOperations teamId: Ref categories: IssueStatusCategory[] defaultStatusId?: Ref defaultCategoryId?: Ref } const categoryByDeprecatedIssueStatus = { [DeprecatedIssueStatus.Backlog]: tracker.issueStatusCategory.Backlog, [DeprecatedIssueStatus.Todo]: tracker.issueStatusCategory.Unstarted, [DeprecatedIssueStatus.InProgress]: tracker.issueStatusCategory.Started, [DeprecatedIssueStatus.Done]: tracker.issueStatusCategory.Completed, [DeprecatedIssueStatus.Canceled]: tracker.issueStatusCategory.Canceled } as const async function createTeamIssueStatuses ({ tx, teamId: attachedTo, categories, defaultStatusId, defaultCategoryId = tracker.issueStatusCategory.Backlog }: CreateTeamIssueStatusesArgs): Promise { const issueStatusRanks = [...genRanks(categories.length)] for (const [i, statusCategory] of categories.entries()) { const { _id: category, defaultStatusName } = statusCategory const rank = issueStatusRanks[i] await tx.addCollection( tracker.class.IssueStatus, attachedTo, attachedTo, tracker.class.Team, 'issueStatuses', { name: defaultStatusName, category, rank }, category === defaultCategoryId ? defaultStatusId : undefined ) } } async function createDefaultTeam (tx: TxOperations): Promise { const current = await tx.findOne(tracker.class.Team, { _id: tracker.team.DefaultTeam }) const currentDeleted = await tx.findOne(core.class.TxRemoveDoc, { objectId: tracker.team.DefaultTeam }) // Create new if not deleted by customers. if (current === undefined && currentDeleted === undefined) { const defaultStatusId: Ref = generateId() const categories = await tx.findAll(tracker.class.IssueStatusCategory, {}) await tx.createDoc( tracker.class.Team, core.space.Space, { name: 'Default', description: 'Default team', private: false, members: [], archived: false, identifier: 'TSK', sequence: 0, issueStatuses: 0, defaultIssueStatus: defaultStatusId }, tracker.team.DefaultTeam ) await createTeamIssueStatuses({ tx, teamId: tracker.team.DefaultTeam, categories, defaultStatusId }) } } async function upgradeTeamIssueStatuses (tx: TxOperations): Promise { const teams = await tx.findAll(tracker.class.Team, { issueStatuses: undefined }) if (teams.length > 0) { const categories = await tx.findAll(tracker.class.IssueStatusCategory, {}) for (const team of teams) { const defaultStatusId: Ref = generateId() await tx.update(team, { issueStatuses: 0, defaultIssueStatus: defaultStatusId }) await createTeamIssueStatuses({ tx, teamId: team._id, categories, defaultStatusId }) } } } async function upgradeIssueStatuses (tx: TxOperations): Promise { const deprecatedStatuses = [ DeprecatedIssueStatus.Backlog, DeprecatedIssueStatus.Canceled, DeprecatedIssueStatus.Done, DeprecatedIssueStatus.InProgress, DeprecatedIssueStatus.Todo ] const issues = await tx.findAll(tracker.class.Issue, { status: { $in: deprecatedStatuses as any } }) if (issues.length > 0) { const statusByDeprecatedStatus = new Map>() for (const issue of issues) { const deprecatedStatus = issue.status as unknown as DeprecatedIssueStatus if (!statusByDeprecatedStatus.has(deprecatedStatus)) { const category = categoryByDeprecatedIssueStatus[deprecatedStatus] const issueStatus = await tx.findOne(tracker.class.IssueStatus, { category }) if (issueStatus === undefined) { throw new Error(`Could not find a new status for "${DeprecatedIssueStatus[deprecatedStatus]}"`) } statusByDeprecatedStatus.set(deprecatedStatus, issueStatus._id) } await tx.update(issue, { status: statusByDeprecatedStatus.get(deprecatedStatus) }) } } } async function migrateIssueProjects (client: MigrationClient): Promise { const issues = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue, project: { $exists: false } }) if (issues.length === 0) { return } for (const issue of issues) { await client.update(DOMAIN_TRACKER, { _id: issue._id }, { project: null }) } } async function upgradeProjectIcons (tx: TxOperations): Promise { const projects = await tx.findAll(tracker.class.Project, {}) if (projects.length === 0) { return } for (const project of projects) { const icon = project.icon as unknown if (icon !== undefined) { continue } await tx.update(project, { icon: tracker.icon.Projects }) } } async function createDefaults (tx: TxOperations): Promise { await createDefaultTeam(tx) } async function upgradeTeams (tx: TxOperations): Promise { await upgradeTeamIssueStatuses(tx) } async function upgradeIssues (tx: TxOperations): Promise { await upgradeIssueStatuses(tx) } async function upgradeProjects (tx: TxOperations): Promise { await upgradeProjectIcons(tx) } export const trackerOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await Promise.all([migrateIssueProjects(client)]) }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) await createDefaults(tx) await upgradeTeams(tx) await upgradeIssues(tx) await upgradeProjects(tx) } }