// // Copyright © 2020, 2021 Anticrm Platform Contributors. // // 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 { Person } from '@anticrm/contact' import core, { AttachedDoc, Class, Doc, DocumentQuery, DOMAIN_TX, MixinData, Ref, TxCollectionCUD, TxCreateDoc, TxMixin, TxOperations, TxUpdateDoc } from '@anticrm/core' import { createOrUpdate, MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' import { DOMAIN_ATTACHMENT } from '@anticrm/model-attachment' import { DOMAIN_COMMENT } from '@anticrm/model-chunter' import contact, { DOMAIN_CONTACT } from '@anticrm/model-contact' import tags, { DOMAIN_TAGS, TagCategory, TagElement } from '@anticrm/model-tags' import { DOMAIN_TASK } from '@anticrm/model-task' import { Candidate } from '@anticrm/recruit' import recruit from './plugin' import { getCategories } from '@anticrm/skillset' function toCandidateData (c: Pick | undefined): MixinData { if (c === undefined) { return {} } const result: MixinData = { onsite: c.onsite, title: c.title, remote: c.remote, source: c.source } // eslint-disable-next-line @typescript-eslint/no-dynamic-delete Object.keys(result).forEach(key => (result as any)[key] == null && delete (result as any)[key]) return result } function findTagCategory (title: string, categories: TagCategory[]): Ref { for (const c of categories) { if (c.tags.findIndex((it) => it.toLowerCase() === title.toLowerCase()) !== -1) { return c._id } } return recruit.category.Other } export const recruitOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { // Move all candidates to mixins. await client.update(DOMAIN_CONTACT, { _class: 'recruit:class:Candidate' as Ref> }, { $rename: { title: `${recruit.mixin.Candidate}.title`, applications: `${recruit.mixin.Candidate}.applications`, remote: `${recruit.mixin.Candidate}.remote`, source: `${recruit.mixin.Candidate}.source`, onsite: `${recruit.mixin.Candidate}.onsite` } }) await client.update(DOMAIN_CONTACT, { _class: 'recruit:class:Candidate' as Ref> }, { _class: contact.class.Person }) await client.update(DOMAIN_TASK, { attachedToClass: 'recruit:class:Candidate' as Ref> }, { attachedToClass: recruit.mixin.Candidate }) await client.update(DOMAIN_ATTACHMENT, { attachedToClass: 'recruit:class:Candidate' as Ref> }, { attachedToClass: recruit.mixin.Candidate }) await client.update(DOMAIN_ATTACHMENT, { attachedToClass: 'recruit:class:Candidate' as Ref> }, { attachedToClass: recruit.mixin.Candidate }) await client.update(DOMAIN_COMMENT, { attachedToClass: 'recruit:class:Candidate' as Ref> }, { attachedToClass: recruit.mixin.Candidate }) // Migrate Create operations. await client.update(DOMAIN_TX, { _class: core.class.TxCreateDoc, objectClass: 'recruit:class:Candidate' as Ref> }, { // objectClass: contact.class.Person, $rename: { 'attributes.title': `attributes.${recruit.mixin.Candidate}.title`, 'attributes.applications': `attributes.${recruit.mixin.Candidate}.applications`, 'attributes.remote': `attributes.${recruit.mixin.Candidate}.remote`, 'attributes.onsite': `attributes.${recruit.mixin.Candidate}.onsite`, 'attributes.source': `attributes.${recruit.mixin.Candidate}.source` } }) await migrateCreateCandidateToPersonAndMixin(client) // Migrate update operations. await client.update(DOMAIN_TX, { _class: core.class.TxUpdateDoc, objectClass: 'recruit:class:Candidate' as Ref> }, { $rename: { 'operations.title': `operations.${recruit.mixin.Candidate}.title`, 'operations.applications': `operations.${recruit.mixin.Candidate}.applications`, 'operations.remote': `operations.${recruit.mixin.Candidate}.remote`, 'operations.onsite': `operations.${recruit.mixin.Candidate}.onsite`, 'operations.source': `operations.${recruit.mixin.Candidate}.source` } }) await migrateUpdateCandidateToPersonAndMixin(client) await migrateTxCollectionCandidateToPerson(client) await client.update(DOMAIN_TX, { _class: core.class.TxRemoveDoc, objectClass: 'recruit:class:Candidate' as Ref> }, { objectClass: contact.class.Person }) // Rename other const categories = await client.find(DOMAIN_TAGS, { _class: tags.class.TagCategory }) const prefix = 'tags:category:Category' for (const c of categories) { if (c._id.startsWith(prefix) || c._id === 'tags:category:Other') { let newCID = c._id.replace(prefix, recruit.category.Category + '.') as Ref if (c._id === 'tags:category:Other') { newCID = recruit.category.Other } await client.delete(DOMAIN_TAGS, c._id) try { await client.create(DOMAIN_TAGS, { ...c, _id: newCID, targetClass: recruit.mixin.Candidate }) } catch (err: any) { // ignore } await client.update(DOMAIN_TAGS, { _class: tags.class.TagElement, category: c._id }, { category: newCID, targetClass: recruit.mixin.Candidate }) } } }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) await createOrUpdate( tx, tags.class.TagCategory, tags.space.Tags, { icon: tags.icon.Tags, label: 'Other', targetClass: recruit.mixin.Candidate, tags: [], default: true }, recruit.category.Other ) for (const c of getCategories()) { await createOrUpdate( tx, tags.class.TagCategory, tags.space.Tags, { icon: tags.icon.Tags, label: c.label, targetClass: recruit.mixin.Candidate, tags: c.skills, default: false }, (recruit.category.Category + '.' + c.id) as Ref ) } const categories = await tx.findAll(tags.class.TagCategory, { targetClass: recruit.mixin.Candidate }) // Find all existing TagElement and update category based on skillset const tagElements = await tx.findAll(tags.class.TagElement, { category: null } as unknown as DocumentQuery) for (const t of tagElements) { if (t.category == null) { const category = findTagCategory(t.title, categories) await tx.update(t, { category: category }) } } } } async function migrateUpdateCandidateToPersonAndMixin (client: MigrationClient): Promise { const updateCandidates = await client.find(DOMAIN_TX, { _class: core.class.TxUpdateDoc, objectClass: 'recruit:class:Candidate' as Ref> }) as TxUpdateDoc[] console.log('Processing update candidate operations:', updateCandidates.length) for (const c of updateCandidates) { const mixinOp: TxMixin = { _class: core.class.TxMixin, _id: (c._id + '.m') as Ref>, objectId: c.objectId, objectClass: contact.class.Person, mixin: recruit.mixin.Candidate, attributes: toCandidateData((c.operations as any)[recruit.mixin.Candidate] as unknown as Candidate), objectSpace: c.objectSpace, modifiedBy: c.modifiedBy, modifiedOn: c.modifiedOn, space: c.space } if (Object.keys(mixinOp.attributes).length > 0) { try { await client.create(DOMAIN_TX, mixinOp) } catch (ex) { // Ignore if existing } } // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete (c.operations as any)[recruit.mixin.Candidate] if (Object.keys(c.operations).length === 0) { // Delete existing transaction, since there is nothing to change inside. await client.delete(DOMAIN_TX, c._id) } else { await client.update(DOMAIN_TX, { _id: c._id }, { $set: { objectClass: contact.class.Person }, $unset: { [`operations.${recruit.mixin.Candidate}`]: '' } }) } } } async function migrateTxCollectionCandidateToPerson (client: MigrationClient): Promise { const collectionCandidates = await client.find(DOMAIN_TX, { _class: core.class.TxCollectionCUD, objectClass: 'recruit:class:Candidate' as Ref> }) as TxCollectionCUD[] console.log('Processing collection candidate operations:', collectionCandidates.length) for (const c of collectionCandidates) { if (c.tx.objectClass === recruit.class.Applicant) { await client.update(DOMAIN_TX, { _id: c._id }, { objectClass: recruit.mixin.Candidate }) } else { await client.update(DOMAIN_TX, { _id: c._id }, { objectClass: contact.class.Person }) } } } async function migrateCreateCandidateToPersonAndMixin (client: MigrationClient): Promise { const createCandidates = await client.find(DOMAIN_TX, { _class: core.class.TxCreateDoc, objectClass: 'recruit:class:Candidate' as Ref>, [`attributes.${recruit.mixin.Candidate}`]: { $exists: true } }) as TxCreateDoc[] console.log('Processing create candidate operations:', createCandidates.length) for (const c of createCandidates) { const mixinOp: TxMixin = { _class: core.class.TxMixin, _id: (c._id + '.m') as Ref>, objectId: c.objectId, objectClass: contact.class.Person, mixin: recruit.mixin.Candidate, attributes: toCandidateData((c.attributes as any)[recruit.mixin.Candidate] as unknown as Candidate), objectSpace: c.objectSpace, modifiedBy: c.modifiedBy, modifiedOn: c.modifiedOn, space: c.space } if (Object.keys(mixinOp.attributes).length > 0) { try { await client.create(DOMAIN_TX, mixinOp) } catch (ex) { // Ignore if existing } } await client.update(DOMAIN_TX, { _id: c._id }, { $set: { objectClass: contact.class.Person }, $unset: { [`attributes.${recruit.mixin.Candidate}`]: '' } }) } }