// // Copyright © 2023 Hardcore Engineering Inc. // /* eslint-disable @typescript-eslint/no-unused-vars */ import chunter from '@hcengineering/chunter' import core, { Doc, DocumentUpdate, Hierarchy, Ref, Space, Storage, Tx, TxCUD, TxProcessor, TxUpdateDoc, systemAccountUuid, type Class, type TxMixin } from '@hcengineering/core' import github, { DocSyncInfo, GithubProject } from '@hcengineering/github' import { TriggerControl } from '@hcengineering/server-core' import time, { ToDo } from '@hcengineering/time' import tracker from '@hcengineering/tracker' /** * @public */ export async function OnGithubBroadcast (txes: Tx[], control: TriggerControl): Promise { // Enhance broadcast to send DocSyncInfo change only to system account. control.ctx.contextData.broadcast.targets.github = (it) => { if (TxProcessor.isExtendsCUD(it._class)) { if ((it as TxCUD).objectClass === github.class.DocSyncInfo) { return [systemAccountUuid] } } } return [] } /** * @public */ export async function OnProjectChanges (txes: Tx[], control: TriggerControl): Promise { // Enhance broadcast to send DocSyncInfo change only to system account. await OnGithubBroadcast(txes, control) const result: Tx[] = [] const cache = new Map() const toApply: Tx[] = [] for (const ltx of txes) { if (ltx._class === core.class.TxMixin && (ltx as TxMixin).mixin === github.mixin.GithubIssue) { const mix = ltx as TxMixin // Do not spend time to wait for trigger processing await updateDocSyncInfo(control, ltx, mix.objectSpace, mix, cache, toApply) continue } if (TxProcessor.isExtendsCUD(ltx._class)) { const cud = ltx as TxCUD let space: Ref = cud.objectSpace if (cud._class === core.class.TxUpdateDoc) { const upd = cud as TxUpdateDoc if (upd.operations.space != null) { space = upd.operations.space } } if (isDocSyncUpdateRequired(control.hierarchy, cud)) { await updateDocSyncInfo(control, ltx, space, cud, cache, toApply) } if (control.hierarchy.isDerived(cud.objectClass, time.class.ToDo)) { if (cud.attachedToClass !== undefined && cud.attachedTo !== undefined) { if (control.hierarchy.isDerived(cud.attachedToClass, github.class.GithubPullRequest)) { // Ok we got todo change for pull request, let's mark it for sync. result.push( control.txFactory.createTxUpdateDoc( github.class.DocSyncInfo, ltx.objectSpace, cud.attachedTo as Ref, { needSync: '' } ) ) } } } } } if (toApply.length > 0) { await control.apply(control.ctx, toApply) } return result } /** * @public */ export async function OnProjectRemove (txes: Tx[], control: TriggerControl): Promise { const result: Tx[] = [] for (const ltx of txes) { if (ltx._class === core.class.TxRemoveDoc) { const cud = ltx as TxCUD if (control.hierarchy.isDerived(cud.objectClass, tracker.class.Project)) { const project = control.removedMap.get(cud.objectId) if (project === undefined) { continue } if (control.hierarchy.hasMixin(project, github.mixin.GithubProject)) { const repos = await control.findAll(control.ctx, github.class.GithubIntegrationRepository, { githubProject: cud.objectId as Ref }) for (const repo of repos) { result.push( control.txFactory.createTxUpdateDoc(repo._class, repo.space, repo._id, { enabled: false, githubProject: null }) ) } const syncDocs = control.modelDb.findAllSync(github.class.DocSyncInfo, { space: cud.objectId as Ref }) for (const syncDoc of syncDocs) { result.push(control.txFactory.createTxRemoveDoc(syncDoc._class, syncDoc.space, syncDoc._id)) } } } } } if (result.length > 0) { await OnGithubBroadcast(txes, control) } return result } // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ trigger: { OnProjectChanges, OnProjectRemove, OnGithubBroadcast }, functions: { TodoDoneTester } }) async function TodoDoneTester ( client: { findAll: Storage['findAll'] hierarchy: Hierarchy }, todo: ToDo ): Promise { if (client.hierarchy.hasMixin(todo, github.mixin.GithubTodo)) { return false } return true } async function updateDocSyncInfo ( control: TriggerControl, tx: Tx, space: Ref, cud: { _class: Ref> objectId: Ref objectClass: Ref> }, cache: Map, toApply: Tx[] ): Promise { // TODO: FIXME // throw new Error('Not implemented') // const checkTx = (tx: Tx): boolean => // control.hierarchy.isDerived(tx._class, core.class.TxCUD) && // (tx as TxCUD).objectClass === github.class.DocSyncInfo && // (tx as TxCUD).objectId === cud.objectId // const txes = [...control.txes, ...control.ctx.contextData.broadcast.txes, ...toApply] // // Check already captured Txes // for (const i of txes) { // if (checkTx(i)) { // // We have sync doc create request already. // return // } // } // const [account] = control.modelDb.findAllSync(contact.class.PersonAccount, { // _id: tx.modifiedBy as PersonId // }) // // Do not modify state if is modified by github service. // if (account === undefined) { // return // } // const projects = // (cache.get('projects') as GithubProject[]) ?? // (await control.queryFind(control.ctx, github.mixin.GithubProject, {}, { projection: { _id: 1 } })) // cache.set('projects', projects) // if (projects.some((it) => it._id === (space as Ref))) { // const sdoc = // (cache.get(cud.objectId) as DocSyncInfo) ?? // ( // await control.findAll(control.ctx, github.class.DocSyncInfo, { // _id: cud.objectId as Ref // }) // ).shift() // // We need to check if sync doc is already exists. // if (sdoc === undefined) { // // Created by non github integration // // We need to create the doc sync info // createSyncDoc(control, cud, tx, space, toApply) // } else { // cache.set(cud.objectId, sdoc) // // We need to create the doc sync info // updateSyncDoc(control, cud, space, sdoc, toApply) // } // } } function isDocSyncUpdateRequired (h: Hierarchy, coll: TxCUD): boolean { return ( h.isDerived(coll.objectClass, tracker.class.Issue) || h.isDerived(coll.objectClass, chunter.class.ChatMessage) || h.isDerived(coll.objectClass, github.class.GithubReviewComment) || h.isDerived(coll.objectClass, github.class.GithubReview) || h.isDerived(coll.objectClass, github.class.GithubReviewThread) || h.isDerived(coll.objectClass, tracker.class.Milestone) ) } function updateSyncDoc ( control: TriggerControl, cud: { _class: Ref> objectId: Ref objectClass: Ref> }, space: Ref, info: DocSyncInfo, toApply: Tx[] ): void { const data: DocumentUpdate = cud._class === core.class.TxRemoveDoc ? { needSync: '', deleted: true } : { needSync: '' } if (info.space !== space) { data.externalVersion = '#' // We need to put this one to handle new documents.) data.space = space } toApply.push( control.txFactory.createTxUpdateDoc( github.class.DocSyncInfo, info.space, cud.objectId as Ref, data ) ) } function createSyncDoc ( control: TriggerControl, cud: { _class: Ref> objectId: Ref objectClass: Ref> }, tx: Tx, space: Ref, toApply: Tx[] ): void { const data: DocumentUpdate = { url: '', githubNumber: 0, repository: null, objectClass: cud.objectClass, externalVersion: '#', // We need to put this one to handle new documents. needSync: '', derivedVersion: '' } if ((tx as TxCUD).attachedTo !== undefined) { // Collection CUD, we could assign attachedTo data.attachedTo = (tx as TxCUD).attachedTo } toApply.push( control.txFactory.createTxCreateDoc( github.class.DocSyncInfo, space, data, cud.objectId as Ref ) ) }