From 655c46ef9a58fd64ed8db44b89f74ca9e8578043 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Fri, 7 Mar 2025 10:24:33 +0500 Subject: [PATCH] Cards migration (#8164) --- models/all/src/migration.ts | 2 + models/card/src/index.ts | 2 + models/card/src/migration.ts | 95 ++++++++++++++++++++++ server-plugins/card-resources/src/index.ts | 20 +++-- 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 models/card/src/migration.ts diff --git a/models/all/src/migration.ts b/models/all/src/migration.ts index 551c02407e..4786f9cf62 100644 --- a/models/all/src/migration.ts +++ b/models/all/src/migration.ts @@ -54,11 +54,13 @@ import { analyticsCollectorOperation } from '@hcengineering/model-analytics-coll import { workbenchOperation } from '@hcengineering/model-workbench' import { testManagementOperation } from '@hcengineering/model-test-management' import { surveyOperation } from '@hcengineering/model-survey' +import { cardOperation } from '@hcengineering/model-card' import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot' export const migrateOperations: [string, MigrateOperation][] = [ ['core', coreOperation], ['activity', activityOperation], + ['card', cardOperation], ['chunter', chunterOperation], ['calendar', calendarOperation], ['gmail', gmailOperation], diff --git a/models/card/src/index.ts b/models/card/src/index.ts index 266ec64447..ceca23e187 100644 --- a/models/card/src/index.ts +++ b/models/card/src/index.ts @@ -99,6 +99,8 @@ export class MasterTagEditorSection extends TDoc implements MasterTagEditorSecti component!: AnyComponent } +export * from './migration' + export function createModel (builder: Builder): void { builder.createModel(TMasterTag, TTag, TCard, MasterTagEditorSection) diff --git a/models/card/src/migration.ts b/models/card/src/migration.ts new file mode 100644 index 0000000000..101c04c5a9 --- /dev/null +++ b/models/card/src/migration.ts @@ -0,0 +1,95 @@ +// +// Copyright © 2025 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 { DOMAIN_CARD } from '@hcengineering/card' +import { chunterId } from '@hcengineering/chunter' +import core, { type Client, type Data, type Doc, TxOperations } from '@hcengineering/core' +import { + tryMigrate, + tryUpgrade, + type MigrateOperation, + type MigrationClient, + type MigrationUpgradeClient +} from '@hcengineering/model' +import view from '@hcengineering/view' +import card from '.' + +export const cardOperation: MigrateOperation = { + async migrate (client: MigrationClient): Promise { + await tryMigrate(client, chunterId, [ + { + state: 'set-parent-info', + func: setParentInfo + } + ]) + }, + async upgrade (state: Map>, client: () => Promise): Promise { + await tryUpgrade(state, client, chunterId, [ + { + state: 'migrateViewlets', + func: migrateViewlets + } + ]) + } +} + +async function setParentInfo (client: MigrationClient): Promise { + await client.update( + DOMAIN_CARD, + { + parentInfo: { $exists: false } + }, + { + parentInfo: [] + } + ) +} + +function extractObjectProps (doc: T): Data { + const data: any = {} + for (const key in doc) { + if (key === '_id') { + continue + } + data[key] = doc[key] + } + return data as Data +} + +async function migrateViewlets (client: Client): Promise { + const txOp = new TxOperations(client, core.account.System) + const viewlets = await client.findAll(view.class.Viewlet, { attachTo: card.class.Card }) + const masterTags = await client.findAll(card.class.MasterTag, {}) + const currentViewlets = await client.findAll(view.class.Viewlet, { attachTo: { $in: masterTags.map((p) => p._id) } }) + for (const masterTag of masterTags) { + for (const viewlet of viewlets) { + const base = extractObjectProps(viewlet) + const current = currentViewlets.find( + (p) => p.attachTo === masterTag._id && p.variant === viewlet.variant && p.descriptor === viewlet.descriptor + ) + if (current === undefined) { + await txOp.createDoc(view.class.Viewlet, core.space.Model, { + ...base, + attachTo: masterTag._id + }) + } else { + await txOp.diffUpdate(current, { + ...base, + attachTo: masterTag._id + }) + } + } + } +} diff --git a/server-plugins/card-resources/src/index.ts b/server-plugins/card-resources/src/index.ts index b8433e9152..e29185e8a8 100644 --- a/server-plugins/card-resources/src/index.ts +++ b/server-plugins/card-resources/src/index.ts @@ -162,15 +162,17 @@ async function OnMasterTagCreate (ctx: TxCreateDoc[], control: res.push( control.txFactory.createTxMixin(createTx.objectId, core.class.Mixin, core.space.Model, setting.mixin.UserMixin, {}) ) - const viewlets = await control.findAll(control.ctx, view.class.Viewlet, { attachTo: tag.extends }) - for (const viewlet of viewlets) { - const base = extractObjectProps(viewlet) - res.push( - control.txFactory.createTxCreateDoc(view.class.Viewlet, core.space.Model, { - ...base, - attachTo: createTx.objectId - }) - ) + if (tag._class === card.class.MasterTag) { + const viewlets = await control.findAll(control.ctx, view.class.Viewlet, { attachTo: tag.extends }) + for (const viewlet of viewlets) { + const base = extractObjectProps(viewlet) + res.push( + control.txFactory.createTxCreateDoc(view.class.Viewlet, core.space.Model, { + ...base, + attachTo: createTx.objectId + }) + ) + } } return res