From 8c874204950936ac9752a733081af65fc23c6c2f Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 5 May 2025 17:19:02 +0700 Subject: [PATCH 1/3] UBERF-10481: Fix external changes are missed Signed-off-by: Andrey Sobolev --- .../github/pod-github/src/sync/issueBase.ts | 65 ++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/services/github/pod-github/src/sync/issueBase.ts b/services/github/pod-github/src/sync/issueBase.ts index 1b1b989a1eb..b240e0d4481 100644 --- a/services/github/pod-github/src/sync/issueBase.ts +++ b/services/github/pod-github/src/sync/issueBase.ts @@ -69,6 +69,7 @@ import { errorToObj, getCreateStatus, getType, + guessStatus, isGHWriteAllowed } from './utils' @@ -1070,6 +1071,58 @@ export abstract class IssueSyncManagerBase { return issueUpdate } + async performDocumentExternalSync ( + ctx: MeasureContext, + info: DocSyncInfo, + previousExternal: IssueExternalData, + issueExternal: IssueExternalData, + derivedClient: TxOperations + ): Promise { + // TODO: Since Github integeration need to be re-written to use cards, so this is quick fix to not loose data in case of external sync while service was offline. + + const update: IssueUpdate = {} + const du: DocumentUpdate = {} + const account = (await this.provider.getAccount(issueExternal.author))?._id ?? core.account.System + + const container = await this.provider.getContainer(info.space) + if (container == null) { + return + } + const type = await this.provider.getTaskTypeOf(container.project.type, tracker.class.Issue) + const statuses = await this.provider.getStatuses(type?._id) + + if (previousExternal.state !== issueExternal.state) { + update.status = ( + await guessStatus({ state: issueExternal.state, stateReason: issueExternal.stateReason }, statuses) + )._id + } + if (previousExternal.title !== issueExternal.title) { + update.title = issueExternal.title + } + if (previousExternal.body !== issueExternal.body) { + update.description = await this.provider.getMarkupSafe( + container.container, + issueExternal.body, + this.stripGuestLink + ) + du.markdown = await this.provider.getMarkdown(update.description) + } + if (!deepEqual(previousExternal.assignees, issueExternal.assignees)) { + const assignees = await this.getAssignees(issueExternal) + update.assignee = assignees?.[0]?.person ?? null + } + if (Object.keys(update).length > 0) { + await this.handleUpdate( + issueExternal, + derivedClient, + update, + account, + container.project, + false + ) + } + } + async syncIssues ( _class: Ref>, repo: GithubIntegrationRepository, @@ -1120,7 +1173,17 @@ export abstract class IssueSyncManagerBase { } const externalEqual = deepEqual(existing.external, issue) if (!externalEqual || existing.externalVersion !== githubExternalSyncVersion) { - this.ctx.info('Update sync doc', { url: issue.url, workspace: this.provider.getWorkspaceId().name }) + this.ctx.info('Update sync doc(extarnal changes)', { + url: issue.url, + workspace: this.provider.getWorkspaceId().name + }) + + if (existing.needSync === githubSyncVersion) { + // Sync external if and only if no changes from platform. + // We need to apply changes from Github, while service was offline. + await this.performDocumentExternalSync(this.ctx, existing, existing.external, issue, derivedClient) + } + await ops.diffUpdate( existing, { From ceb0d57fdf1240094092897b936359ff05fb1097 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 5 May 2025 19:10:13 +0700 Subject: [PATCH 2/3] UBERF-10494: Allow to reintegrate same project again Signed-off-by: Andrey Sobolev --- .../src/components/ConnectProject.svelte | 27 ++++++++++++++----- .../src/components/GithubIntegerations.svelte | 10 ++++--- .../src/components/GithubRepositories.svelte | 4 +-- .../github/pod-github/src/sync/comments.ts | 11 +++++--- .../github/pod-github/src/sync/issueBase.ts | 18 +++++-------- services/github/pod-github/src/sync/utils.ts | 3 ++- 6 files changed, 45 insertions(+), 28 deletions(-) diff --git a/services/github/github-resources/src/components/ConnectProject.svelte b/services/github/github-resources/src/components/ConnectProject.svelte index 966bb1cadcd..9cfcec25f71 100644 --- a/services/github/github-resources/src/components/ConnectProject.svelte +++ b/services/github/github-resources/src/components/ConnectProject.svelte @@ -13,12 +13,18 @@ showPopup } from '@hcengineering/ui' import DropdownLabelsPopup from '@hcengineering/ui/src/components/DropdownLabelsPopup.svelte' - import { GithubIntegration, GithubIntegrationRepository, githubPullRequestStates } from '@hcengineering/github' + import { + GithubIntegration, + GithubIntegrationRepository, + githubPullRequestStates, + type GithubProject + } from '@hcengineering/github' import github from '../plugin' export let integration: WithLookup export let repository: GithubIntegrationRepository export let projects: Project[] = [] + export let orphanProjects: GithubProject[] = [] /** * @public @@ -112,16 +118,23 @@ const githubProject = client.getHierarchy().as(projectInst, github.mixin.GithubProject) - void getClient().update(githubProject, { + if (githubProject.integration !== integration._id) { + await getClient().update(githubProject, { + integration: integration._id + }) + } + await getClient().update(githubProject, { $push: { repositories: repository._id } }) - void getClient().update(repository, { githubProject: githubProject._id, enabled: true }) + await getClient().update(repository, { githubProject: githubProject._id, enabled: true }) } - $: allowedProjects = projects.filter( - (it) => - (client.getHierarchy().asIf(it, github.mixin.GithubProject)?.integration ?? integration._id) === integration._id - ) + $: allowedProjects = projects + .filter( + (it) => + (client.getHierarchy().asIf(it, github.mixin.GithubProject)?.integration ?? integration._id) === integration._id + ) + .concat(orphanProjects) async function selectProject (event: MouseEvent): Promise { showPopup( DropdownLabelsPopup, diff --git a/services/github/github-resources/src/components/GithubIntegerations.svelte b/services/github/github-resources/src/components/GithubIntegerations.svelte index 532900efa77..6a7ce689bd7 100644 --- a/services/github/github-resources/src/components/GithubIntegerations.svelte +++ b/services/github/github-resources/src/components/GithubIntegerations.svelte @@ -1,11 +1,11 @@ {#if integrations.length > 0} @@ -23,7 +27,7 @@ {@const giprj = githubProjects.filter((it) => it.integration === gi._id)}
- +
{/each} diff --git a/services/github/github-resources/src/components/GithubRepositories.svelte b/services/github/github-resources/src/components/GithubRepositories.svelte index 018e39de53d..6cd4b08bfad 100644 --- a/services/github/github-resources/src/components/GithubRepositories.svelte +++ b/services/github/github-resources/src/components/GithubRepositories.svelte @@ -26,11 +26,11 @@ import ConnectProject from './ConnectProject.svelte' import { githubLanguageColors } from './languageColors' import { sendGHServiceRequest } from './utils' - import { BackgroundColor } from '@hcengineering/text' export let integration: WithLookup export let projects: Project[] = [] export let giProjects: GithubProject[] = [] + export let orphanProjects: GithubProject[] = [] const client = getClient() @@ -235,7 +235,7 @@ /> {:else} - + {/if} diff --git a/services/github/pod-github/src/sync/comments.ts b/services/github/pod-github/src/sync/comments.ts index 4d1a5defcf4..b4648f935eb 100644 --- a/services/github/pod-github/src/sync/comments.ts +++ b/services/github/pod-github/src/sync/comments.ts @@ -531,7 +531,7 @@ export class CommentSyncManager implements DocSyncManager { } const syncInfo = await this.client.findAll(github.class.DocSyncInfo, { space: repo.githubProject, - repository: repo._id, + // repository: repo._id, // If we skip repository, we will find orphaned comments, so we could connect them on. objectClass: chunter.class.ChatMessage, url: { $in: comments.map((it) => (it.url ?? '').toLowerCase()) } }) @@ -553,14 +553,19 @@ export class CommentSyncManager implements DocSyncManager { lastModified }) } else { - if (!deepEqual(existing.external, comment) || existing.externalVersion !== githubExternalSyncVersion) { + if ( + !deepEqual(existing.external, comment) || + existing.externalVersion !== githubExternalSyncVersion || + existing.repository !== repo._id + ) { await derivedClient.diffUpdate( existing, { needSync: '', external: comment, externalVersion: githubExternalSyncVersion, - lastModified + lastModified, + repository: repo._id }, lastModified ) diff --git a/services/github/pod-github/src/sync/issueBase.ts b/services/github/pod-github/src/sync/issueBase.ts index b240e0d4481..67406689f1c 100644 --- a/services/github/pod-github/src/sync/issueBase.ts +++ b/services/github/pod-github/src/sync/issueBase.ts @@ -1112,14 +1112,7 @@ export abstract class IssueSyncManagerBase { update.assignee = assignees?.[0]?.person ?? null } if (Object.keys(update).length > 0) { - await this.handleUpdate( - issueExternal, - derivedClient, - update, - account, - container.project, - false - ) + await this.handleUpdate(issueExternal, derivedClient, update, account, container.project, false) } } @@ -1137,7 +1130,7 @@ export abstract class IssueSyncManagerBase { syncDocs ?? (await this.client.findAll(github.class.DocSyncInfo, { space: repo.githubProject, - repository: repo._id, + // repository: repo._id, // If we skip repository, we will find orphaned issues, so we could connect them on. objectClass: _class, url: { $in: issues.map((it) => (it.url ?? '').toLowerCase()) } })) @@ -1171,15 +1164,15 @@ export abstract class IssueSyncManagerBase { if (syncDocs !== undefined) { syncDocs = syncDocs.filter((it) => it._id !== existing._id) } - const externalEqual = deepEqual(existing.external, issue) + const externalEqual = deepEqual(existing.external, issue) && existing.repository === repo._id if (!externalEqual || existing.externalVersion !== githubExternalSyncVersion) { this.ctx.info('Update sync doc(extarnal changes)', { url: issue.url, workspace: this.provider.getWorkspaceId().name }) - if (existing.needSync === githubSyncVersion) { - // Sync external if and only if no changes from platform. + if (existing.needSync === githubSyncVersion || existing.repository !== repo._id) { + // Sync external if and only if no changes from platform or we do resync from github. // We need to apply changes from Github, while service was offline. await this.performDocumentExternalSync(this.ctx, existing, existing.external, issue, derivedClient) } @@ -1192,6 +1185,7 @@ export abstract class IssueSyncManagerBase { externalVersion: githubExternalSyncVersion, derivedVersion: '', // Clear derived state to recalculate it. externalVersionSince: '', + repository: repo._id, lastModified: new Date(issue.updatedAt).getTime() }, Date.now() diff --git a/services/github/pod-github/src/sync/utils.ts b/services/github/pod-github/src/sync/utils.ts index dd6d2c257ce..719be174f38 100644 --- a/services/github/pod-github/src/sync/utils.ts +++ b/services/github/pod-github/src/sync/utils.ts @@ -370,12 +370,13 @@ export async function syncDerivedDocuments ( }) } else { processed.add(existing._id) - if (!deepEqual(existing.external, r)) { + if (!deepEqual(existing.external, r) || existing.repository !== repo._id) { // Only update if had changes. await derivedClient.update(existing, { external: r, needSync: '', // We need to check if we had any changes. derivedVersion: '', + repository: repo._id, externalVersion: githubExternalSyncVersion, lastModified: new Date(r.updatedAt ?? r.createdAt).getTime(), ...extra From 8284dcaac10fd846a1bb324d1ece4ecae8864720 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 12 May 2025 12:41:08 +0700 Subject: [PATCH 3/3] Remove unstable/unused Github project support Signed-off-by: Andrey Sobolev --- services/github/github/src/index.ts | 62 +- services/github/model-github/src/index.ts | 38 +- .../github/pod-github/src/sync/comments.ts | 9 - .../github/pod-github/src/sync/githubTypes.ts | 172 --- .../github/pod-github/src/sync/issueBase.ts | 664 +--------- services/github/pod-github/src/sync/issues.ts | 121 +- .../github/pod-github/src/sync/projects.ts | 1176 ----------------- .../pod-github/src/sync/pullrequests.ts | 121 +- .../pod-github/src/sync/reviewComments.ts | 11 +- .../pod-github/src/sync/reviewThreads.ts | 9 - .../github/pod-github/src/sync/reviews.ts | 9 - .../github/pod-github/src/sync/syncConfig.ts | 10 - services/github/pod-github/src/sync/utils.ts | 97 +- services/github/pod-github/src/types.ts | 4 - services/github/pod-github/src/worker.ts | 9 +- 15 files changed, 35 insertions(+), 2477 deletions(-) delete mode 100644 services/github/pod-github/src/sync/projects.ts delete mode 100644 services/github/pod-github/src/sync/syncConfig.ts diff --git a/services/github/github/src/index.ts b/services/github/github/src/index.ts index a6ab60b0e32..2498bbb78a9 100644 --- a/services/github/github/src/index.ts +++ b/services/github/github/src/index.ts @@ -4,24 +4,12 @@ import { ActivityMessage, ActivityMessageViewlet } from '@hcengineering/activity' import { Attachment } from '@hcengineering/attachment' import { Person } from '@hcengineering/contact' -import { - Account, - AnyAttribute, - AttachedDoc, - Class, - Data, - Doc, - Hyperlink, - Markup, - Mixin, - Ref, - Timestamp -} from '@hcengineering/core' +import { Account, AttachedDoc, Class, Data, Doc, Hyperlink, Markup, Mixin, Ref, Timestamp } from '@hcengineering/core' import { Asset, IntlString, Metadata, Plugin, plugin } from '@hcengineering/platform' import { Preference } from '@hcengineering/preference' import task, { ProjectTypeDescriptor, TaskStatusFactory, TaskTypeDescriptor } from '@hcengineering/task' import { ToDo } from '@hcengineering/time' -import { Component, Issue, Milestone, Project } from '@hcengineering/tracker' +import { Component, Issue, Project } from '@hcengineering/tracker' import { AnyComponent } from '@hcengineering/ui' import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' @@ -456,59 +444,16 @@ export interface GithubUserInfo extends Doc { avatarUrl?: string } -/** - * @public - */ -export interface GithubFieldMapping { - // Platform field - _id: Ref - name: string - _class: Ref> - - // Github information - githubId: string // It could be fieldName or fieldId -} - -/** - * @public - */ -export interface GithubProjectSyncData { - // Project NodeId - projectNodeId?: string - projectNumber?: number - - githubProjectName?: string - - // Mapping of all fields in this project. - mappings: GithubFieldMapping[] - - // Update mapping - githubUpdatedAt?: string -} - /** * @public * * Mixin to ordinary project, to allow github repository mapping into it. */ -export interface GithubProject extends Project, GithubProjectSyncData { +export interface GithubProject extends Project { integration: Ref // A list of mapped repositories we synchronized into this project. repositories: Ref[] - - // Mixin to store all github custom attributes in - mixinClass: Ref> -} - -/** - * @public - * - * Mixin for milestone to represent a github project for it. - */ -export interface GithubMilestone extends Milestone, GithubProjectSyncData { - // A link to github project. - url: Hyperlink } export interface GithubPullRequestReviewThread extends Doc { @@ -566,7 +511,6 @@ export default plugin(githubId, { mixin: { GithubIssue: '' as Ref>, GithubProject: '' as Ref>, - GithubMilestone: '' as Ref>, GithubComponent: '' as Ref>, GithubUser: '' as Ref>, GithubTodo: '' as Ref> diff --git a/services/github/model-github/src/index.ts b/services/github/model-github/src/index.ts index 9bcb3cf7ce1..5c242c76c5b 100644 --- a/services/github/model-github/src/index.ts +++ b/services/github/model-github/src/index.ts @@ -43,7 +43,7 @@ import { import { type Person } from '@hcengineering/contact' import contact, { TContact } from '@hcengineering/model-contact' import presentation from '@hcengineering/model-presentation' -import tracker, { TComponent, TIssue, TMilestone, TProject, issuesOptions } from '@hcengineering/model-tracker' +import tracker, { TComponent, TIssue, TProject, issuesOptions } from '@hcengineering/model-tracker' import view, { classPresenter } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' import { getEmbeddedLabel } from '@hcengineering/platform' @@ -54,11 +54,9 @@ import { type DocSyncInfo, type GithubAuthentication, type GithubComponent, - type GithubFieldMapping, type GithubIntegration, type GithubIntegrationRepository, type GithubIssue, - type GithubMilestone, type GithubPatch, type GithubProject, type GithubPullRequest, @@ -374,16 +372,6 @@ export class TGithubProject extends TProject implements GithubProject { @ReadOnly() @Hidden() projectNumber!: number - - @Prop(TypeRef(core.class.Class), getEmbeddedLabel('Attribute Class')) - @ReadOnly() - @Hidden() - mixinClass!: Ref> - - @Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Field mappings')) - @Hidden() - // Mapping of all fields in this project. - mappings!: GithubFieldMapping[] } @Mixin(github.mixin.GithubIssue, tracker.class.Issue) @@ -433,29 +421,6 @@ export class TGithubComponent extends TComponent implements GithubComponent { represent!: boolean } -@Mixin(github.mixin.GithubMilestone, tracker.class.Milestone) -@UX(github.string.GithubMilestone) -export class TGithubMilestone extends TMilestone implements GithubMilestone { - @Prop(TypeHyperlink(), getEmbeddedLabel('Github Project URL')) - @Index(IndexKind.FullText) - @ReadOnly() - url!: Hyperlink - - @Prop(TypeString(), getEmbeddedLabel('NodeID')) - @Hidden() - @ReadOnly() - projectNodeId!: string - - @Prop(TypeNumber(), getEmbeddedLabel('Number')) - @Hidden() - @ReadOnly() - projectNumber!: number - - @Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Field mappings')) - // Mapping of all fields in this project. - mappings!: GithubFieldMapping[] -} - @Model(github.class.GithubPullRequest, tracker.class.Issue) @UX(github.string.PullRequest, github.icon.PullRequest, undefined, undefined, undefined, github.string.PullRequests) export class TGithubPullRequest extends TIssue implements GithubPullRequest { @@ -603,7 +568,6 @@ export function createModel (builder: Builder): void { TGithubIntegrationRepository, TGithubPatch, TGithubUserInfo, - TGithubMilestone, TGithubComponent, TGithubUser, TGithubTodo diff --git a/services/github/pod-github/src/sync/comments.ts b/services/github/pod-github/src/sync/comments.ts index b4648f935eb..fbe1a5b0d21 100644 --- a/services/github/pod-github/src/sync/comments.ts +++ b/services/github/pod-github/src/sync/comments.ts @@ -29,7 +29,6 @@ import { collectUpdate, deleteObjects, errorToObj, getSince, isGHWriteAllowed } import { Analytics } from '@hcengineering/analytics' import { IssueComment, IssueCommentCreatedEvent, IssueCommentEvent } from '@octokit/webhooks-types' import config from '../config' -import { syncConfig } from './syncConfig' interface MessageData { message: string @@ -89,14 +88,6 @@ export class CommentSyncManager implements DocSyncManager { if (container === undefined) { return false } - if ( - container?.container === undefined || - ((container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject) - ) { - return false - } const commentExternal = info.external as CommentExternalData | undefined diff --git a/services/github/pod-github/src/sync/githubTypes.ts b/services/github/pod-github/src/sync/githubTypes.ts index e72395795e0..7269cd772c4 100644 --- a/services/github/pod-github/src/sync/githubTypes.ts +++ b/services/github/pod-github/src/sync/githubTypes.ts @@ -6,159 +6,6 @@ import { PullRequestMergeable } from '@hcengineering/github' -/** - * @public - */ -export type GithubDataType = 'SINGLE_SELECT' | 'TEXT' | 'DATE' | 'NUMBER' - -/** - * @public - */ -export interface GithubProjectV2 { - projectV2: { - id: string - title: string - description: string - updatedAt: string - fields: { - edges: GithubProjectV2Field[] - } - } -} - -/** - * @public - */ -export interface GithubProjectV2FieldOption { - name: string - color: string - description: string - id: string -} -/** - * @public - */ -export interface GithubProjectV2Field { - node: { - dataType: GithubDataType - updatedAt: string - - id: string - name: string - options?: GithubProjectV2FieldOption[] - } & Record -} -/** - * @public - */ -export interface GithubProjectV2ItemFieldValue { - id: string - // Date - date?: string - // Number - number?: number - // Single select - color?: string - description?: string - optionId?: string - // Text - text?: string - field: { - id: string - name: string - dataType: GithubDataType - } -} - -export interface GithubProjectV2Item { - id: string - type: 'ISSUE' | 'PULL_REQUEST' | 'DRAFT_ISSUE' | 'REDACTED' - project: { - id: string - number: number - } - fieldValues: { - nodes: (GithubProjectV2ItemFieldValue | any)[] - } -} - -export const projectV2Field = ` - ... on ProjectV2Field { - id - name - updatedAt - dataType - } - ... on ProjectV2IterationField { - id - name - dataType - updatedAt - } - ... on ProjectV2SingleSelectField { - id - name - options { - name - id - color - description - } - dataType - updatedAt - } -` - -export const projectV2ItemFields = ` - ... on ProjectV2ItemFieldDateValue { - id - date - field { - ... on ProjectV2Field { - id - name - dataType - } - } - } - ... on ProjectV2ItemFieldNumberValue { - id - number - field { - ... on ProjectV2Field { - id - name - dataType - } - } - } - ... on ProjectV2ItemFieldSingleSelectValue { - id - name - color - description - optionId - field { - ... on ProjectV2SingleSelectField { - id - name - dataType - } - } - } - ... on ProjectV2ItemFieldTextValue { - id - text - field { - ... on ProjectV2Field { - id - name - dataType - } - } - } -` - export const assigneesField = ` assignees(first: 10) { nodes { @@ -245,22 +92,6 @@ title updatedAt url ${reactionsField} -projectItems(first: 10, includeArchived: true) { - nodes { - id - type - project { - id - url - number - } - fieldValues(first: 50) { - nodes { - ${projectV2ItemFields} - } - } - } -} lastEditedAt publishedAt ` @@ -307,9 +138,6 @@ export interface IssueExternalData { } }[] } - projectItems: { - nodes: GithubProjectV2Item[] - } lastEditedAt: string publishedAt: string } diff --git a/services/github/pod-github/src/sync/issueBase.ts b/services/github/pod-github/src/sync/issueBase.ts index 67406689f1c..9cf68796a5e 100644 --- a/services/github/pod-github/src/sync/issueBase.ts +++ b/services/github/pod-github/src/sync/issueBase.ts @@ -20,58 +20,29 @@ import core, { MeasureContext, Ref, Space, - Status, TxOperations, makeDocCollabId } from '@hcengineering/core' import github, { DocSyncInfo, - GithubFieldMapping, GithubIntegrationRepository, GithubIssue, GithubIssue as GithubIssueP, - GithubMilestone, GithubProject } from '@hcengineering/github' import { IntlString } from '@hcengineering/platform' import { LiveQuery } from '@hcengineering/query' import { getPublicLink } from '@hcengineering/server-guest-resources' -import task, { TaskType, type Task } from '@hcengineering/task' +import task, { type Task } from '@hcengineering/task' import { MarkupNode, MarkupNodeType, areEqualMarkups, markupToJSON, traverseNode } from '@hcengineering/text' import time, { type ToDo } from '@hcengineering/time' -import tracker, { Issue, IssuePriority } from '@hcengineering/tracker' -import { ProjectsV2ItemEvent } from '@octokit/webhooks-types' +import tracker, { Issue } from '@hcengineering/tracker' import { deepEqual } from 'fast-equals' import { Octokit } from 'octokit' -import { - ContainerFocus, - IntegrationContainer, - IntegrationManager, - githubExternalSyncVersion, - githubSyncVersion -} from '../types' -import { - GithubDataType, - GithubProjectV2FieldOption, - GithubProjectV2Item, - GithubProjectV2ItemFieldValue, - IssueExternalData, - fieldValues, - projectValue, - supportedGithubTypes -} from './githubTypes' +import { ContainerFocus, IntegrationManager, githubExternalSyncVersion, githubSyncVersion } from '../types' +import { IssueExternalData } from './githubTypes' import { stripGuestLink } from './guest' -import { syncConfig } from './syncConfig' -import { - collectUpdate, - compareMarkdown, - deleteObjects, - errorToObj, - getCreateStatus, - getType, - guessStatus, - isGHWriteAllowed -} from './utils' +import { collectUpdate, compareMarkdown, deleteObjects, errorToObj, guessStatus } from './utils' /** * @public @@ -116,16 +87,6 @@ Record */ export type IssueUpdate = DocumentUpdate> -/** - * @public - */ -export interface IssueSyncTarget { - project: GithubProject - mappings: GithubFieldMapping[] - target: GithubProject | GithubMilestone - prjData?: GithubProjectV2Item -} - export abstract class IssueSyncManagerBase { provider!: IntegrationManager constructor ( @@ -152,189 +113,6 @@ export abstract class IssueSyncManagerBase { return assignees } - async processProjectV2Event ( - integration: IntegrationContainer, - event: ProjectsV2ItemEvent, - derivedClient: TxOperations, - prj: GithubProject - ): Promise { - const account = (await this.provider.getAccountU(event.sender))?._id ?? core.account.System - switch (event.action) { - case 'edited': { - const itemId = event.projects_v2_item.node_id - const projectId = event.projects_v2_item.project_node_id - - try { - const actualContent: { - node: { - id: string - content: { - id: string - url: string - number: number - } - fieldValues: { - nodes: GithubProjectV2ItemFieldValue[] - } - } - } = (await integration.octokit?.graphql( - `query listIssue($nodeId: ID!) { - node(id: $nodeId) { - ... on ProjectV2Item { - id - content { - ... on Issue { - id - number - url - } - ... on PullRequest { - id - number - url - } - } - ${fieldValues} - } - } - }`, - { - nodeId: itemId - } - )) as any - const syncData = await this.client.findOne(github.class.DocSyncInfo, { - space: prj._id, - url: (actualContent.node.content.url ?? '').toLowerCase() - }) - - if (syncData !== undefined) { - const milestone = await this.client.findOne(github.mixin.GithubMilestone, { - projectNodeId: projectId - }) - - const target: IssueSyncTarget | undefined = - milestone !== undefined - ? { - mappings: milestone.mappings ?? [], - project: prj, - target: milestone - } - : prj.projectNodeId === projectId - ? this.getProjectIssueTarget(prj) - : undefined - - if (target === undefined) { - // Not our project, we should just update external - return - } - - this.ctx.info('event for issue', { url: syncData.url, workspace: this.provider.getWorkspaceId().name }) - const externalData = syncData.external as IssueExternalData - // We need to replace field values we retrieved - target.prjData = externalData.projectItems.nodes.find( - (it) => it.project.id === event.projects_v2_item.project_node_id - ) - - if (target.prjData === undefined) { - target.prjData = { - fieldValues: actualContent.node.fieldValues, - id: event.projects_v2_item.node_id, - type: 'ISSUE', - project: { - id: prj.projectNodeId as string, - number: prj.projectNumber as number - } - } - externalData.projectItems.nodes.push(target.prjData) - } else { - target.prjData.fieldValues = actualContent.node.fieldValues - } - // Store github values - await derivedClient.update(syncData, { - external: externalData, - externalVersion: githubExternalSyncVersion - }) - - if (event.changes.field_value === undefined) { - this.ctx.info('No changes for change event', { event, workspace: this.provider.getWorkspaceId().name }) - return - } - - let needProjectRefresh = false - const update: DocumentUpdate> & Record = {} - - let structure = integration.projectStructure.get(target.target._id) - - for (const f of target.prjData.fieldValues?.nodes ?? []) { - if (!('id' in f)) { - continue - } - // Check if we need to update project structure - if (structure !== undefined) { - const ff = structure.projectV2.fields.edges.find((it) => it.node.id === f.field.id) - if (ff === undefined && supportedGithubTypes.has(f.field.dataType)) { - // We have missing field. - needProjectRefresh = true - } - } - if (needProjectRefresh && syncData.repository != null) { - const repo = await this.provider.liveQuery.findOne( - github.class.GithubIntegrationRepository, - { - _id: syncData.repository - } - ) - - if (repo !== undefined) { - await this.provider.handleEvent(github.class.GithubIntegration, integration.installationId, repo, {}) - structure = integration.projectStructure.get(prj._id) - } - } - - const taskTypes = ( - await this.provider.liveQuery.queryFind(task.class.TaskType, { parent: prj.type }) - ).filter((it) => this.client.getHierarchy().isDerived(it.targetClass, syncData.objectClass)) - - // TODO: Use GithubProject configuration to specify target type for issues - if (taskTypes.length === 0) { - // Missing required task type - this.ctx.error('Missing required task type, in Event.') - } - - if (event.changes.field_value.field_node_id === f.field.id && taskTypes.length > 0) { - const ff = await this.toPlatformField( - { - container: integration, - project: prj - }, - f, - target, - taskTypes[0] - ) - if (ff === undefined) { - continue - } - const { value, mapping } = ff - if (value !== undefined) { - update[mapping.name] = value - } - continue - } - } - if (Object.keys(update).length > 0) { - await this.handleUpdate(externalData, derivedClient, update, account, prj, false, syncData) - } - } - } catch (err: any) { - Analytics.handleError(err) - this.ctx.error(err, event) - } - - break - } - } - } - async handleUpdate ( external: IssueExternalData, derivedClient: TxOperations, @@ -420,12 +198,7 @@ export abstract class IssueSyncManagerBase { }, lastModified ) - await this.client.diffUpdate( - this.client.getHierarchy().as(doc, prj.mixinClass), - issueData, - lastModified, - account - ) + await this.client.diffUpdate(doc, issueData, lastModified, account) this.provider.sync() } } @@ -434,229 +207,6 @@ export abstract class IssueSyncManagerBase { } } - async addIssueToProject ( - container: ContainerFocus, - okit: Octokit, - issue: IssueExternalData, - projectTarget: string - ): Promise { - const query = `mutation addIssueToProject($project: ID!, $contentId: ID!) { - addProjectV2ItemById(input: {projectId: $project, contentId: $contentId}) { - item { - ${projectValue} - id - type - ${fieldValues} - } - } - }` - if (isGHWriteAllowed()) { - const response: any = await okit.graphql(query, { project: projectTarget, contentId: issue.id }) - return response.addProjectV2ItemById.item - } - } - - async removeIssueFromProject (okit: Octokit, projectTarget: string, issueId: string): Promise { - try { - const query = `mutation removeIssueToProject($project: ID!, $contentId: ID!) { - deleteProjectV2Item(input: {projectId: $project, itemId: $contentId}) { - deletedItemId - } - }` - if (isGHWriteAllowed()) { - await okit.graphql(query, { project: projectTarget, contentId: issueId }) - } - } catch (err: any) { - Analytics.handleError(err) - this.ctx.error(err) - } - } - - findOption ( - container: ContainerFocus, - field: GithubProjectV2ItemFieldValue, - target: GithubProject | GithubMilestone - ): GithubProjectV2FieldOption | undefined { - const structure = container.container.projectStructure.get(target._id) - if (structure === undefined) { - return { - id: field.field.id, - name: field.field.name, - color: field.color ?? '', - description: field.description ?? '' - } - } - const pField = structure.projectV2.fields.edges.find((it) => it.node.id === field.field.id) - if (pField === undefined) { - return { - id: field.field.id, - name: field.field.name, - color: field.color ?? '', - description: field.description ?? '' - } - } - return (pField.node.options ?? []).find((it) => it.id === field.optionId) - } - - findOptionId ( - container: ContainerFocus, - fieldId: string, - value: string | null, - target: IssueSyncTarget - ): string | undefined { - if (value == null) { - return - } - const structure = container.container.projectStructure.get(target.target._id) - if (structure === undefined) { - return - } - const pField = structure.projectV2.fields.edges.find((it) => it.node.id === fieldId) - if (pField === undefined) { - return undefined - } - return (pField.node.options ?? []).find((it) => it.name?.toLowerCase() === value.toLowerCase())?.id - } - - async toPlatformField ( - container: ContainerFocus, - // eslint-disable-next-line @typescript-eslint/ban-types - field: GithubProjectV2ItemFieldValue | {}, - target: IssueSyncTarget, - taskType: TaskType - ): Promise<{ value: any, mapping: GithubFieldMapping } | undefined> { - if (!('field' in field)) { - return - } - const mapping = target.mappings.find((it) => it.githubId === field.field.id) - if (mapping === undefined) { - return undefined - } - - if (mapping.name === 'status') { - const option = this.findOption(container, field, target.target) - if (option === undefined) { - return - } - return { - value: await getCreateStatus( - this.ctx, - this.provider, - this.client, - container.project, - option?.name, - option.description, - option.color, - taskType - ), - mapping - } - } - - if (mapping.name === 'priority') { - const values: Record = { - '': IssuePriority.NoPriority, - High: IssuePriority.High, - Medium: IssuePriority.Medium, - Low: IssuePriority.Low, - Urgent: IssuePriority.Urgent - } - const option = this.findOption(container, field, target.target) - return { value: values[option?.name ?? ''] ?? IssuePriority.NoPriority, mapping } - } - - switch (field.field.dataType) { - case 'DATE': - return { value: field.date !== undefined ? new Date(field.date).getTime() : null, mapping } - case 'NUMBER': - return { value: field.number, mapping } - case 'TEXT': - return { value: field.text, mapping } - case 'SINGLE_SELECT': { - const option = this.findOption(container, field, target.target) - return { value: option?.name, mapping } - } - } - } - - async fillProjectV2Fields ( - target: IssueSyncTarget, - container: ContainerFocus, - issueData: Record, - taskType: TaskType - ): Promise { - for (const f of target.prjData?.fieldValues?.nodes ?? []) { - const ff = await this.toPlatformField(container, f, target, taskType) - if (ff === undefined) { - continue - } - const { value, mapping } = ff - if (value !== undefined) { - ;(issueData as any)[mapping.name] = value - } - } - } - - async updateIssueValues ( - target: IssueSyncTarget, - okit: Octokit, - values: { id: string, value: any, dataType: GithubDataType }[] - ): Promise<{ error: any, response: any }[]> { - function getValue (val: { id: string, value: any, dataType: GithubDataType }): string { - switch (val.dataType) { - case 'SINGLE_SELECT': - return `singleSelectOptionId: "${val.value as string}"` - case 'DATE': - return `date: "${new Date(val.value).toISOString()}"` - case 'NUMBER': - return `number: ${val.value as number}` - case 'TEXT': - return `text: "${val.value as string}"` - } - } - const errors: any[] = [] - const itm = ` { - projectV2Item { - id - type - ${projectValue} - ${fieldValues} - } - }\n` - let response: any = {} - if (isGHWriteAllowed()) { - for (const val of values) { - const q = ` - mutation updateField($project: ID!, $itemId: ID!) { - updateProjectV2ItemFieldValue( - input: {projectId: $project, itemId: $itemId, fieldId: "${val.id}", value: {${getValue(val)}}} - ) - ${itm} - }` - try { - response = await okit.graphql(q, { - project: target.target.projectNodeId, - itemId: target.prjData?.id as string - }) - } catch (err: any) { - if (err.errors?.[0]?.type === 'NOT_FOUND') { - errors.push({ error: err, response }) - return errors - } - Analytics.handleError(err) - // Failed to update one particular value, skip it. - this.ctx.error('error during field update', { - error: err, - response, - workspace: this.provider.getWorkspaceId().name - }) - errors.push({ error: err, response }) - } - } - } - return errors - } - abstract fillBackChanges (update: DocumentUpdate, existing: GithubIssue, external: any): Promise async addConnectToMessage ( @@ -697,15 +247,13 @@ export abstract class IssueSyncManagerBase { abstract afterSync (existing: Issue, account: Ref, issueExternal: any, info: DocSyncInfo): Promise async handleDiffUpdate ( - target: IssueSyncTarget, + container: ContainerFocus, existing: WithMarkup, info: DocSyncInfo, issueData: GithubIssueData, - container: ContainerFocus, issueExternal: IssueExternalData, account: Ref, - accountGH: Ref, - syncToProject: boolean + accountGH: Ref ): Promise> { let needUpdate = false if (!this.client.getHierarchy().hasMixin(existing, github.mixin.GithubIssue)) { @@ -741,35 +289,18 @@ export abstract class IssueSyncManagerBase { await this.notifyConnected(container, info, existing, issueExternal) } } - if (!this.client.getHierarchy().hasMixin(existing, container.project.mixinClass)) { - await this.ctx.withLog( - 'create mixin issue', - {}, - () => - this.client.createMixin( - existing._id as Ref, - existing._class, - existing.space, - container.project.mixinClass, - {} - ), - { identifier: existing.identifier, url: issueExternal.url } - ) - // Re iterate to have existing value with mixin inside. - needUpdate = true - } if (needUpdate) { return { needSync: '' } } - const existingIssue = this.client.getHierarchy().as(existing, container.project.mixinClass) + const existingIssue = this.client.getHierarchy().as(existing, github.mixin.GithubIssue) const previousData: GithubIssueData = info.current ?? ({} as unknown as GithubIssueData) - const type = await this.provider.getTaskTypeOf(container.project.type, existing._class) - const stst = await this.provider.getStatuses(type?._id) + // const type = await this.provider.getTaskTypeOf(container.project.type, existing._class) + // const stst = await this.provider.getStatuses(type?._id) const update = collectUpdate(previousData, issueData, Object.keys(issueData)) - const allAttributes = this.client.getHierarchy().getAllAttributes(container.project.mixinClass) + const allAttributes = this.client.getHierarchy().getAllAttributes(existingIssue._class) const platformUpdate = collectUpdate(previousData, existingIssue, Array.from(allAttributes.keys())) const okit = (await this.provider.getOctokit(account as Ref)) ?? container.container.octokit @@ -821,107 +352,12 @@ export abstract class IssueSyncManagerBase { okit, account ) - - const fieldsUpdate: { id: string, value: any, dataType: GithubDataType }[] = [] - - // Collect field update. - for (const [k, v] of Object.entries(platformUpdate)) { - const mapping = target.mappings.filter((it) => it != null).find((it) => it.name === k) - if (mapping === undefined) { - continue - } - const attr = this.client.getHierarchy().getAttribute(mapping._class, mapping.name) - - if (attr.name === 'status') { - // Handle status field - const status = stst.find((it) => it._id === v) as Status - const optionId = this.findOptionId(container, mapping.githubId, status.name, target) - if (optionId !== undefined) { - fieldsUpdate.push({ - id: mapping.githubId, - dataType: 'SINGLE_SELECT', - value: optionId - }) - this.ctx.info(' => prepare issue status update', { - url: issueExternal.url, - name: status.name, - workspace: this.provider.getWorkspaceId().name - }) - continue - } - } - if (attr.name === 'priority') { - const values: Record = { - [IssuePriority.NoPriority]: '', - [IssuePriority.High]: 'High', - [IssuePriority.Medium]: 'Medium', - [IssuePriority.Low]: 'Low', - [IssuePriority.Urgent]: 'Urgent' - } - // Handle priority field TODO: Add clear of field - const priorityName = values[v as IssuePriority] - const optionId = this.findOptionId(container, mapping.githubId, priorityName, target) - if (optionId !== undefined) { - fieldsUpdate.push({ - id: mapping.githubId, - dataType: 'SINGLE_SELECT', - value: optionId - }) - this.ctx.info(' => prepare issue priority update', { - url: issueExternal.url, - priority: priorityName, - workspace: this.provider.getWorkspaceId().name - }) - continue - } - } - - const dataType = getType(attr) - if (dataType === 'SINGLE_SELECT') { - // Handle status field - const optionId = this.findOptionId(container, mapping.githubId, v, target) - if (optionId !== undefined) { - fieldsUpdate.push({ - id: mapping.githubId, - dataType: 'SINGLE_SELECT', - value: optionId - }) - this.ctx.info(` => prepare issue field ${attr.label} update`, { - url: issueExternal.url, - value: v, - workspace: this.provider.getWorkspaceId().name - }) - continue - } - } - - if (dataType === undefined) { - continue - } - fieldsUpdate.push({ - id: mapping.githubId, - dataType, - value: v - }) - this.ctx.info(`=> prepare issue field ${attr.label} update`, { - url: issueExternal.url, - value: v, - workspace: this.provider.getWorkspaceId().name - }) - } - if (fieldsUpdate.length > 0 && syncToProject && target.prjData !== undefined) { - const errors = await this.updateIssueValues(target, okit, fieldsUpdate) - if (errors.length === 0) { - needExternalSync = true - } - } - // TODO: Add support for labels, milestone, assignees } // We need remove all readonly field values for (const k of Object.keys(update)) { // Skip readonly fields - const attr = this.client.getHierarchy().findAttribute(target.project.mixinClass, k) + const attr = this.client.getHierarchy().findAttribute(existingIssue._class, k) if (attr?.readonly === true) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete (update as any)[k] @@ -1209,40 +645,6 @@ export abstract class IssueSyncManagerBase { this.provider.sync() } - async getMilestoneIssueTarget ( - project: GithubProject, - container: IntegrationContainer, - existingIssue: Issue | undefined, - external: IssueExternalData - ): Promise { - if (existingIssue !== undefined) { - // Select a milestone project - if (existingIssue.milestone != null) { - const milestone = await this.provider.liveQuery.findOne(github.mixin.GithubMilestone, { - _id: existingIssue.milestone as Ref - }) - if (milestone === undefined) { - return - } - return { - project, - mappings: milestone.mappings ?? [], - target: milestone, - prjData: external.projectItems.nodes.find((it) => it.project.id === milestone.projectNodeId) - } - } - } - } - - getProjectIssueTarget (project: GithubProject, external?: IssueExternalData): IssueSyncTarget { - return { - project, - mappings: project.mappings ?? [], - target: project, - prjData: external?.projectItems.nodes.find((it) => it.project.id === project.projectNodeId) - } - } - abstract deleteGithubDocument (container: ContainerFocus, account: Ref, id: string): Promise async handleDelete ( @@ -1255,14 +657,6 @@ export abstract class IssueSyncManagerBase { if (container === undefined) { return false } - if ( - container?.container === undefined || - ((container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject) - ) { - return false - } const issueExternal = info.external as IssueExternalData | undefined @@ -1272,38 +666,6 @@ export abstract class IssueSyncManagerBase { } const account = existing?.createdBy ?? (await this.provider.getAccount(issueExternal.author))?._id ?? core.account.System - const okit = (await this.provider.getOctokit(account as Ref)) ?? container.container.octokit - - if (existing !== undefined && issueExternal !== undefined) { - let target = await this.getMilestoneIssueTarget( - container.project, - container.container, - existing as Issue, - issueExternal - ) - if (target === null) { - // We need to wait, no milestone data yet. - return false - } - if (target === undefined) { - target = this.getProjectIssueTarget(container.project, issueExternal) - } - const isProjectProjectTarget = target.target.projectNodeId === target.project.projectNodeId - const supportProjects = - (isProjectProjectTarget && syncConfig.MainProject) || (!isProjectProjectTarget && syncConfig.SupportMilestones) - - // A target node id - const targetNodeId: string | undefined = info.targetNodeId as string - - if (targetNodeId !== undefined && supportProjects) { - const itemNode = issueExternal.projectItems.nodes.find((it) => it.project.id === targetNodeId) - if (itemNode !== undefined) { - await this.removeIssueFromProject(okit, targetNodeId, itemNode.id) - } - // Clear external project items - info.external.projectItems = [] - } - } if (issueExternal !== undefined) { try { diff --git a/services/github/pod-github/src/sync/issues.ts b/services/github/pod-github/src/sync/issues.ts index d0fb59daf9e..dfb3f96950f 100644 --- a/services/github/pod-github/src/sync/issues.ts +++ b/services/github/pod-github/src/sync/issues.ts @@ -46,8 +46,7 @@ import { githubSyncVersion } from '../types' import { IssueExternalData, issueDetails } from './githubTypes' -import { GithubIssueData, IssueSyncManagerBase, IssueSyncTarget, IssueUpdate, WithMarkup } from './issueBase' -import { syncConfig } from './syncConfig' +import { GithubIssueData, IssueSyncManagerBase, IssueUpdate, WithMarkup } from './issueBase' import { getSince, gqlp, guessStatus, isGHWriteAllowed, syncRunner } from './utils' export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncManager { @@ -93,35 +92,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan const projectV2Event = (event as ProjectsV2ItemEvent).projects_v2_item?.id !== undefined if (projectV2Event) { - const projectV2Event = event as ProjectsV2ItemEvent - - const githubProjects = await this.provider.liveQuery.findAll(github.mixin.GithubProject, { - archived: false - }) - let prj = githubProjects.find((it) => it.projectNodeId === projectV2Event.projects_v2_item.project_node_id) - if (prj === undefined) { - // Checking for milestones - const m = await this.provider.liveQuery.findOne(github.mixin.GithubMilestone, { - projectNodeId: projectV2Event.projects_v2_item.project_node_id - }) - if (m !== undefined) { - prj = githubProjects.find((it) => it._id === m.space) - } - } - - if (prj === undefined) { - this.ctx.info('Event from unknown v2 project', { - nodeId: projectV2Event.projects_v2_item.project_node_id, - workspace: this.provider.getWorkspaceId().name - }) - return - } - - const urlId = projectV2Event.projects_v2_item.node_id - - await syncRunner.exec(urlId, async () => { - await this.processProjectV2Event(integration, projectV2Event, derivedClient, prj as GithubProject) - }) + // Just ignore } else { const issueEvent = event as IssuesEvent const { project, repository } = await this.provider.getProjectAndRepository(issueEvent.repository.node_id) @@ -336,15 +307,6 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan let needCreateConnectedAtHuly = info.addHulyLink === true - if ( - (container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject - ) { - this.ctx.error('Not syncing no structure', { url: info.url }) - return { needSync: '' } - } - if (info.repository == null && existing !== undefined) { if (this.client.getHierarchy().hasMixin(existing, github.mixin.GithubIssue)) { const repositoryId = this.client.getHierarchy().as(existing, github.mixin.GithubIssue).repository @@ -428,17 +390,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan return { needSync: githubSyncVersion } } - let target = await this.getMilestoneIssueTarget( - container.project, - container.container, - existing as Issue, - issueExternal - ) - if (target === undefined) { - target = this.getProjectIssueTarget(container.project, issueExternal) - } - - const syncResult = await this.syncToTarget(target, container, existing, issueExternal, derivedClient, info) + const syncResult = await this.syncToTarget(container, existing, issueExternal, derivedClient, info) if (externalWasCreated && existing !== undefined) { // Create child documents @@ -466,13 +418,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan return { ...syncResult, - issueExternal, - targetNodeId: target.target.projectNodeId + issueExternal } } async syncToTarget ( - target: IssueSyncTarget, container: ContainerFocus, existing: Doc | undefined, issueExternal: IssueExternalData, @@ -484,15 +434,6 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan const accountGH = info.lastGithubUser ?? (await this.provider.getAccount(issueExternal.author))?._id ?? core.account.System - const isProjectProjectTarget = target.target.projectNodeId === target.project.projectNodeId - const supportProjects = - (isProjectProjectTarget && syncConfig.MainProject) || (!isProjectProjectTarget && syncConfig.SupportMilestones) - - // A target node id - const targetNodeId: string | undefined = info.targetNodeId as string - - const okit = (await this.provider.getOctokit(account as Ref)) ?? container.container.octokit - const type = await this.provider.getTaskTypeOf(container.project.type, tracker.class.Issue) const statuses = await this.provider.getStatuses(type?._id) // collaborators: assignees.map((it) => it._id), @@ -519,53 +460,6 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan return { needSync: githubSyncVersion } } - await this.fillProjectV2Fields(target, container, issueData, taskTypes[0]) - - if ( - targetNodeId !== undefined && - target.target.projectNodeId !== undefined && - targetNodeId !== target.target.projectNodeId && - supportProjects - ) { - const itemNode = issueExternal.projectItems.nodes.find((it) => it.project.id === targetNodeId) - if (itemNode !== undefined) { - await this.removeIssueFromProject(okit, targetNodeId, itemNode.id) - // remove data - issueExternal.projectItems.nodes = issueExternal.projectItems.nodes.filter((it) => it.id !== targetNodeId) - target.prjData = undefined - await derivedClient.update(info, { - external: issueExternal, - externalVersion: githubExternalSyncVersion - }) - // We need to sync from platform as new to new project. - // We need to remove current sync - info.current = {} - } - } - - if (target.prjData === undefined && okit !== undefined && syncConfig.IssuesInProject && supportProjects) { - try { - this.ctx.info('add issue to project v2', { - url: issueExternal.url, - workspace: this.provider.getWorkspaceId().name - }) - target.prjData = await this.ctx.withLog('add issue to project v2', {}, () => - this.addIssueToProject(container, okit, issueExternal, target.target.projectNodeId as string) - ) - if (target.prjData !== undefined) { - issueExternal.projectItems.nodes.push(target.prjData) - } - - await derivedClient.update(info, { - external: issueExternal, - externalVersion: githubExternalSyncVersion - }) - } catch (err: any) { - Analytics.handleError(err) - this.ctx.error('Error add project v2', { err }) - return { needSync: githubSyncVersion, error: JSON.stringify(err) } - } - } if (existing === undefined) { try { this.ctx.info('create platform issue', { @@ -639,15 +533,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan {}, async () => await this.handleDiffUpdate( - target, + container, { ...(existing as any), description }, info, issueData, - container, issueExternal, account, - accountGH, - supportProjects + accountGH ), { url: issueExternal.url } ) @@ -956,7 +848,6 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan repository: repo, descriptionLocked: isDescriptionLocked }) - await this.client.createMixin(issueId, github.mixin.GithubIssue, prj._id, prj.mixinClass, {}) await this.addConnectToMessage( github.string.IssueConnectedActivityInfo, diff --git a/services/github/pod-github/src/sync/projects.ts b/services/github/pod-github/src/sync/projects.ts deleted file mode 100644 index faf3811a9de..00000000000 --- a/services/github/pod-github/src/sync/projects.ts +++ /dev/null @@ -1,1176 +0,0 @@ -import { Analytics } from '@hcengineering/analytics' -import { PersonAccount } from '@hcengineering/contact' -import core, { - AnyAttribute, - Class, - Data, - Doc, - DocumentUpdate, - EnumOf, - MeasureContext, - Ref, - TxOperations, - generateId -} from '@hcengineering/core' -import github, { - DocSyncInfo, - GithubFieldMapping, - GithubIntegrationRepository, - GithubMilestone, - GithubProject, - GithubProjectSyncData -} from '@hcengineering/github' -import { getEmbeddedLabel, translate } from '@hcengineering/platform' -import { LiveQuery } from '@hcengineering/query' -import task from '@hcengineering/task' -import tracker, { Milestone } from '@hcengineering/tracker' -import { RepositoryEvent } from '@octokit/webhooks-types' -import { deepEqual } from 'fast-equals' -import { Octokit } from 'octokit' -import { - ContainerFocus, - DocSyncManager, - ExternalSyncField, - IntegrationContainer, - IntegrationManager, - githubExternalSyncVersion, - githubSyncVersion -} from '../types' -import { - GithubDataType, - GithubProjectV2, - GithubProjectV2Field, - GithubProjectV2ItemFieldValue, - IssueExternalData, - projectV2Field, - projectV2ItemFields, - supportedGithubTypes -} from './githubTypes' -import { syncConfig } from './syncConfig' -import { - collectUpdate, - errorToObj, - getPlatformType, - getType, - gqlp, - hashCode, - isGHWriteAllowed, - syncRunner -} from './utils' - -const githubColors = ['GRAY', 'BLUE', 'GREEN', 'YELLOW', 'ORANGE', 'RED', 'PINK', 'PURPLE'] - -const categoryColors = { - [task.statusCategory.UnStarted]: githubColors[0], - [task.statusCategory.ToDo]: githubColors[3], - [task.statusCategory.Active]: githubColors[1], - [task.statusCategory.Won]: githubColors[2], - [task.statusCategory.Lost]: githubColors[7] -} - -interface GithubMilestoneExternalData { - url: string - projectNumber: number - projectId: string - label: string - description: string - updatedAt: string -} - -interface MilestoneData { - label: string - description: string -} - -export class ProjectsSyncManager implements DocSyncManager { - provider!: IntegrationManager - - externalDerivedSync = false - - constructor ( - readonly ctx: MeasureContext, - readonly client: TxOperations, - readonly lq: LiveQuery - ) {} - - async init (provider: IntegrationManager): Promise { - this.provider = provider - } - - async sync ( - existing: Doc | undefined, - info: DocSyncInfo, - parent?: DocSyncInfo - ): Promise | undefined> { - const container = await this.provider.getContainer(info.space) - if (container?.container === undefined) { - return { needSync: githubSyncVersion } - } - - const okit = await this.provider.getOctokit(container.project.createdBy as Ref) - if (okit === undefined) { - this.ctx.info('No Authentication for author, waiting for authentication.', { - workspace: this.provider.getWorkspaceId().name - }) - return { needSync: githubSyncVersion, error: 'Need authentication for user' } - } - - let checkStructure = false - - if ( - existing !== undefined && - this.client.getHierarchy().isDerived(existing._class, tracker.class.Milestone) && - container.container.type === 'Organization' - ) { - // If no external project for milestone exists, let's create it. - const milestone = existing as Milestone - if (info.external === undefined) { - checkStructure = true - try { - await this.ctx.withLog( - 'Create Milestone projectV2', - {}, - () => this.createMilestone(container.container, container.project, okit, milestone, info), - { label: milestone.label } - ) - } catch (err: any) { - this.ctx.error('failed create milestone', err) - return { needSync: githubSyncVersion, error: errorToObj(err) } - } - } - - if (checkStructure) { - const m = (await this.client.findOne(github.mixin.GithubMilestone, { - _id: milestone._id as Ref - })) as GithubMilestone - - let { projectStructure, wasUpdates } = await this.ctx.withLog( - 'update project structure', - {}, - () => - syncRunner.exec(m._id, () => - this.updateFieldMappings(container.container, container.project, m, container.project.mixinClass, okit) - ), - { label: milestone.label } - ) - - // Retrieve updated field - if (wasUpdates) { - projectStructure = (await this.ctx.withLog( - 'update project structure(sync/second step)', - {}, - () => this.queryProjectStructure(container.container, m), - { - label: m.label - } - )) as GithubProjectV2 - } - container.container.projectStructure.set(m._id, projectStructure) - } - const milestoneExternal = info.external as GithubMilestoneExternalData - - const messageData: MilestoneData = { - label: milestoneExternal.label, - description: await this.provider.getMarkupSafe(container.container, milestoneExternal.description) - } - - await this.handleDiffUpdateMilestone(existing, info, messageData, container, milestoneExternal) - - return { current: messageData, needSync: githubSyncVersion } - } - - return { needSync: githubSyncVersion } - } - - private async createMilestone ( - integration: IntegrationContainer, - project: GithubProject, - okit: Octokit, - milestone: Milestone, - info: DocSyncInfo | undefined - ): Promise { - if (integration.type !== 'Organization') { - return - } - const response = await this.createProjectV2(integration, okit, milestone.label) - - if (response !== undefined) { - const data: GithubMilestoneExternalData = { - projectId: response.projectNodeId, - projectNumber: response.projectNumber, - url: response.url, - label: milestone.label, - description: '', - updatedAt: new Date().toISOString() - } - - if (info !== undefined) { - info.external = data - } - - await this.client.createMixin( - milestone._id, - milestone._class, - milestone.space, - github.mixin.GithubMilestone, - { - mappings: [], - url: response.url, - projectNodeId: response.projectNodeId, - projectNumber: response.projectNumber, - githubProjectName: milestone.label - } - ) - - const derivedClient = new TxOperations(this.client, core.account.System, true) - - if (info !== undefined) { - await derivedClient.update(info, { - external: data, - needSync: '' - }) - - // We also need to notify all issues with milestone set to this milestone. - const milestonedIds = await this.client.findAll( - tracker.class.Issue, - { milestone: milestone._id }, - { projection: { _id: 1 } } - ) - while (milestonedIds.length > 0) { - const part = milestonedIds.splice(0, 100) - const docInfos = await this.client.findAll( - github.class.DocSyncInfo, - { _id: { $in: part.map((it) => it._id as unknown as Ref) } }, - { projection: { _id: 1 } } - ) - if (docInfos.length > 0) { - const ops = derivedClient.apply() - for (const d of docInfos) { - await ops.update(d, { needSync: '' }) - } - await ops.commit() - } - } - } - } - } - - async handleDiffUpdateMilestone ( - existing: Doc, - info: DocSyncInfo, - issueData: MilestoneData, - container: ContainerFocus, - issueExternal: GithubMilestoneExternalData - ): Promise> { - const existingMilestone = existing as Milestone - const previousData: MilestoneData = info.current ?? ({} as unknown as MilestoneData) - - const update = collectUpdate(previousData, issueData, Object.keys(issueData)) - - const allAttributes = this.client.getHierarchy().getAllAttributes(tracker.class.Milestone) - const platformUpdate = collectUpdate(previousData, existingMilestone, Array.from(allAttributes.keys())) - - const okit = - (await this.provider.getOctokit(existing.modifiedBy as Ref)) ?? container.container.octokit - - // Remove current same values from update - for (const [k, v] of Object.entries(update)) { - if ((existingMilestone as any)[k] === v) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete (update as any)[k] - } - } - - for (const [k, v] of Object.entries(update)) { - const pv = (platformUpdate as any)[k] - if (pv != null && pv !== v) { - // We have conflict of values. - this.ctx.error('conflict', { identifier: existingMilestone._id, k, v, pv }) - - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete (update as any)[k] - continue - } - } - - if (container !== undefined && okit !== undefined) { - if (platformUpdate.label !== undefined || platformUpdate.description !== undefined) { - await this.updateProjectV2(okit, issueExternal.projectId, { - title: platformUpdate.label, - readme: - platformUpdate.description !== undefined - ? await this.provider.getMarkdown(platformUpdate.description) - : undefined - }) - } - } - - if (Object.keys(update).length > 0) { - // We have some fields to update of existing from external - await this.client.update(existingMilestone, update, false, new Date(issueExternal.updatedAt).getTime()) - } - - // We need to trigger external version retrieval, via sync or event, to prevent move sync operations from platform before we will be sure all is updated on github. - return { current: issueData, needSync: githubSyncVersion } - } - - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { - const event = evt as RepositoryEvent - - const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id) - - if (project === undefined || repository === undefined) { - this.ctx.error('Unable to find project and repository for event', { - name: event.repository.name, - workspace: this.provider.getWorkspaceId().name - }) - return - } - - if (project !== undefined) { - const projectStructure = (await this.ctx.withLog( - 'update project structure(handleEvent)', - { prj: project.name }, - () => this.queryProjectStructure(integration, project) - )) as GithubProjectV2 - - integration.projectStructure.set(project._id, projectStructure) - } - } - - async handleDelete ( - existing: Doc | undefined, - info: DocSyncInfo, - derivedClient: TxOperations, - deleteExisting: boolean - ): Promise { - return true - } - - async externalSync ( - integration: IntegrationContainer, - derivedClient: TxOperations, - kind: ExternalSyncField, - syncDocs: DocSyncInfo[], - repository: GithubIntegrationRepository, - project: GithubProject - ): Promise { - for (const d of syncDocs) { - if (d.objectClass === tracker.class.Milestone) { - // no external data for doc - await derivedClient.update(d, { - externalVersion: githubExternalSyncVersion - }) - } - } - } - - repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void { - integration.synchronized.delete(`${repo._id}:issues`) - } - - async externalFullSync ( - integration: IntegrationContainer, - derivedClient: TxOperations, - projects: GithubProject[], - repositories: GithubIntegrationRepository[] - ): Promise { - for (const prj of projects) { - if (this.provider.isClosing()) { - break - } - // Wait global project sync - await integration.syncLock.get(prj._id) - - const syncKey = `project_structure${prj._id}` - if ( - prj === undefined || - integration.synchronized.has(syncKey) || - integration.octokit === undefined || - integration.integration.createdBy === undefined - ) { - continue - } - - const okit = await this.provider.getOctokit(integration.integration.createdBy as Ref) - if (okit === undefined) { - this.ctx.info('No Authentication for author, waiting for authentication.', { - workspace: this.provider.getWorkspaceId().name - }) - continue - } - - // Check if project skill exists, on github - if (syncConfig.MainProject && integration.type === 'Organization') { - if (prj.projectNumber === undefined) { - try { - await this.ctx.withLog('Create projectV2', { prj: prj.name }, async () => { - const response = await this.createProjectV2(integration, okit, prj.name) - if (response !== undefined) { - prj.projectNumber = response.projectNumber - prj.projectNodeId = response.projectNodeId - - await this.client.update(prj, response) - } - }) - } catch (err: any) { - this.ctx.error('failed to create project', { prj: prj.name }) - continue - } - } - - try { - let { projectStructure, wasUpdates } = await this.ctx.withLog( - 'update project structure', - { prj: prj.name }, - () => this.updateFieldMappings(integration, prj, prj, prj.mixinClass, okit) - ) - - // Check if we have any changes in project, during our inactivity. - await this.ctx.withLog('check project v2 changes:', { prj: prj.name }, () => - this.checkChanges(projectStructure, prj, prj._id, integration, derivedClient) - ) - - // Retrieve updated field - if (wasUpdates) { - projectStructure = (await this.ctx.withLog('update project structure(second pass)', { prj: prj.name }, () => - this.queryProjectStructure(integration, prj) - )) as GithubProjectV2 - } - - integration.projectStructure.set(prj._id, projectStructure) - } catch (err: any) { - this.ctx.error('filed to query project structure', err) - } - } - - if (syncConfig.SupportMilestones && integration.type === 'Organization') { - // Check project milestones and sync their structure as well. - const milestones = await this.provider.liveQuery.findAll(github.mixin.GithubMilestone, { - space: prj._id - }) - for (const m of milestones) { - if (this.provider.isClosing()) { - break - } - try { - let { projectStructure, wasUpdates } = await this.ctx.withLog( - 'update project structure', - { prj: m.label }, - () => - syncRunner.exec( - m._id, - async () => await this.updateFieldMappings(integration, prj, m, prj.mixinClass, okit) - ) - ) - - // Check if we have any changes in project, during our inactivity. - await this.ctx.withLog('check project v2 changes', { prj: prj.name }, () => - this.checkChanges(projectStructure, m, prj._id, integration, derivedClient) - ) - - // Retrieve updated field - if (wasUpdates) { - projectStructure = (await this.ctx.withLog( - 'update project structure(second pass)', - { prj: prj.name }, - () => this.queryProjectStructure(integration, m) - )) as GithubProjectV2 - } - - integration.projectStructure.set(m._id, projectStructure) - } catch (err: any) { - this.ctx.error('filed to query project structure', err) - } - } - } - - integration.synchronized.add(syncKey) - } - } - - private async checkChanges ( - projectStructure: GithubProjectV2, - prj: GithubProject | GithubMilestone, - space: Ref, - integration: IntegrationContainer, - derivedClient: TxOperations - ): Promise { - if (projectStructure.projectV2.updatedAt !== prj.githubUpdatedAt) { - // ok, we need to synchronize all project items. - const { query, params, root } = this.queryProject(integration, prj) - const i = integration.octokit.graphql.paginate.iterator(query, params) - - // We need to collect a list of all uris of documents, and check if we have some missing ones. - const checkId = generateId() - try { - for await (const data of i) { - const items: { - id: string - type: string - updatedAt: string - fieldValues: { - nodes: GithubProjectV2ItemFieldValue[] - } - content: { - id: string - url: string - } - }[] = data[root].projectV2.items.nodes - - const syncInfos = await this.client.findAll(github.class.DocSyncInfo, { - space, - objectClass: tracker.class.Issue, - url: { $in: items.map((it) => (it.content.url ?? '').toLowerCase()) } - }) - - for (const item of items) { - let needSync = false - const itemSyncData = syncInfos.find((it) => it.url === item.content.url.toLocaleLowerCase()) - if (itemSyncData !== undefined) { - // We had item already, let's check our project field content and request update. - const external = itemSyncData.external as IssueExternalData - const dataIdx = external.projectItems.nodes.findIndex((it) => it.project.id === prj.projectNodeId) - if (dataIdx !== -1) { - const data = external.projectItems.nodes[dataIdx] - if (!deepEqual(data?.fieldValues.nodes, item.fieldValues.nodes)) { - // TODO: replace value - data.fieldValues = item.fieldValues - needSync = true - } - } else { - // No project information - needSync = true - } - // Mark all our existing sync documents, so we could find any missing ones. - await derivedClient.update( - itemSyncData, - needSync - ? { - external: itemSyncData.external, - externalCheckId: checkId, - needSync: '' - } - : { externalCheckId: checkId } - ) - } - } - - this.provider.sync() - } - } catch (err: any) { - this.ctx.error('filed in checkChanges', err) - Analytics.handleError(err) - } - - while (true) { - // Find all missing items - const missingInfos = await this.client.findAll( - github.class.DocSyncInfo, - { - space, - objectClass: tracker.class.Issue, - externalCheckId: { $ne: checkId }, - targetNodeId: prj.projectNodeId, - external: { $exists: true } // Skip not created items yet - }, - { limit: 50 } - ) - for (const u of missingInfos) { - // We need to sync - const udata = u.external as IssueExternalData - if (udata.projectItems !== undefined) { - udata.projectItems = { - nodes: (udata.projectItems?.nodes ?? []).filter((it) => it.project.id !== prj.projectNodeId) - } - } - await derivedClient.update(u, { needSync: '', external: u.external, externalCheckId: checkId }) - } - if (missingInfos.length === 0) { - break - } - } - this.provider.sync() - - await this.client.update(prj, { - githubUpdatedAt: projectStructure.projectV2.updatedAt - }) - } - } - - updateSet = new Map>() - - private async updateFieldMappings ( - integration: IntegrationContainer, - prj: GithubProject, - target: GithubProject | GithubMilestone, - mixinClass: Ref>, - okit: Octokit - ): Promise<{ projectStructure: GithubProjectV2, wasUpdates: boolean, mappings: GithubFieldMapping[] }> { - let projectStructure = await this.queryProjectStructure(integration, target) - let mappings = target.mappings ?? [] - - if (projectStructure === undefined) { - if (this.client.getHierarchy().isDerived(tracker.class.Project, target._class)) { - // We need to re-create project. - const project = target as GithubProject - await this.ctx.withLog( - 'Create projectV2', - { name: 'name' in target ? target.name : target.label }, - async () => { - const response = await this.createProjectV2(integration, okit, project.name) - if (response !== undefined) { - target.projectNumber = response.projectNumber - target.projectNodeId = response.projectNodeId - } - - mappings = [] - await this.client.update(target, { ...response, mappings: [] }) - } - ) - } else { - const milestone = target as GithubMilestone - try { - await this.ctx.withLog('Create Milestone projectV2', { label: milestone.label }, async () => { - await this.createMilestone(integration, prj, okit, milestone, undefined) - mappings = [] - }) - } catch (err: any) { - Analytics.handleError(err) - this.ctx.error('Error', { err }) - } - } - projectStructure = (await this.queryProjectStructure(integration, target)) as GithubProjectV2 - } - - const h = this.client.getHierarchy() - const allFields = h.getOwnAttributes(mixinClass) - - const githubFields: GithubProjectV2Field[] = projectStructure.projectV2.fields.edges - - const mHash = JSON.stringify(mappings) - // Create any platform field into matching github field - for (const [, f] of allFields.entries()) { - const existingField = mappings.find((it) => it._id === f._id) - if (f.hidden === true) { - continue - } - if (f.isCustom === true && existingField === undefined) { - await this.createUpdateSimpleAttribute(f, githubFields, okit, target, mappings) - } - } - const statusF = h.getAttribute(tracker.class.Issue, 'status') - const f = await this.createUpdateStatus(githubFields, statusF, okit, target, prj) - if (f !== undefined) { - await this.pushMapping(target, mappings, statusF, f) - } - const priorityF = h.getAttribute(tracker.class.Issue, 'priority') - const pf = await this.createUpdatePriority(githubFields, priorityF, okit, target) - if (pf !== undefined) { - await this.pushMapping(target, mappings, priorityF, pf) - } - - await this.createUpdateSimpleAttribute( - h.getAttribute(tracker.class.Issue, 'estimation'), - githubFields, - okit, - target, - mappings - ) - - await this.createUpdateSimpleAttribute( - h.getAttribute(tracker.class.Issue, 'reportedTime'), - githubFields, - okit, - target, - mappings - ) - - await this.createUpdateSimpleAttribute( - h.getAttribute(tracker.class.Issue, 'remainingTime'), - githubFields, - okit, - target, - mappings - ) - - for (const fieldNode of githubFields) { - const existingField = (target.mappings ?? []).find((it) => it.githubId === fieldNode.node.id) - if (existingField !== undefined) { - continue - } - - if (supportedGithubTypes.has(fieldNode.node.dataType)) { - // try to find existing attribute - let matchedField: AnyAttribute | undefined - for (const [k, attr] of allFields) { - if (attr.type._class !== getPlatformType(fieldNode.node.dataType)) { - // Skip non matched fields. - continue - } - if (k.toLowerCase() === fieldNode.node.name.toLowerCase()) { - matchedField = attr - break - } - const labelValue = await translate(attr.label, {}) - if (labelValue.toLowerCase() === fieldNode.node.name.toLowerCase()) { - matchedField = attr - break - } - } - - if (matchedField !== undefined) { - // Ok we have field matched. - await this.pushMapping( - prj, - mappings, - { _id: matchedField._id, name: matchedField.name, attributeOf: matchedField.attributeOf }, - fieldNode - ) - continue - } - - if (fieldNode.node.dataType === 'SINGLE_SELECT') { - // TODO: Add enum update's - await this.createEnumAttribute(fieldNode, target, mappings, mixinClass) - } else if (fieldNode.node.dataType === 'NUMBER') { - await this.createSimpleAttribute(fieldNode, target, mappings, '0', mixinClass) - } else if (fieldNode.node.dataType === 'TEXT') { - await this.createSimpleAttribute(fieldNode, target, mappings, '', mixinClass) - } else if (fieldNode.node.dataType === 'DATE') { - await this.createSimpleAttribute(fieldNode, target, mappings, '', mixinClass) - } else if (fieldNode.node.dataType === 'ITERATION') { - // TODO: Handle Iteration data type. - } - } - } - return { projectStructure, wasUpdates: mHash !== JSON.stringify(target.mappings), mappings } - } - - private async createUpdateSimpleAttribute ( - field: AnyAttribute, - githubFields: GithubProjectV2Field[], - okit: Octokit, - target: GithubProject | GithubMilestone, - mappings: GithubFieldMapping[] - ): Promise { - const v = await this.createUpdateCustomField(githubFields, field, okit, target) - if (v !== undefined) { - await this.pushMapping(target, mappings, field, v) - } - } - - private async createEnumAttribute ( - fieldNode: GithubProjectV2Field, - prj: GithubProject | GithubMilestone, - mappings: GithubFieldMapping[], - mixinClass: Ref> - ): Promise { - const enumValues = (fieldNode.node.options ?? []).map((it) => it.name) - const enumId = await this.client.createDoc(core.class.Enum, core.space.Model, { - name: `Github_${'name' in prj ? prj.name : prj.label}_${fieldNode.node.name}`, - enumValues - }) - const enumType: EnumOf = { - _class: core.class.EnumOf, - of: enumId, - label: getEmbeddedLabel(fieldNode.node.name) - } - const data: Data = { - attributeOf: mixinClass, - name: fieldNode.node.id, - label: getEmbeddedLabel(fieldNode.node.name), - isCustom: true, - type: enumType, - defaultValue: enumValues[0] - } - // Create new attribute - const attrId = await this.client.createDoc( - core.class.Attribute, - core.space.Model, - data, - undefined, - Date.now(), - prj.createdBy - ) - await this.pushMapping(prj, mappings, { _id: attrId, name: data.name, attributeOf: data.attributeOf }, fieldNode) - } - - private async createSimpleAttribute ( - fieldNode: GithubProjectV2Field, - prj: GithubProject | GithubMilestone, - mappings: GithubFieldMapping[], - defaultValue: string, - mixinClass: Ref> - ): Promise { - const data: Data = { - attributeOf: mixinClass, - name: fieldNode.node.id, // Use github field id as name - label: getEmbeddedLabel(fieldNode.node.name), - isCustom: true, - type: { - _class: getPlatformType(fieldNode.node.dataType), - label: getEmbeddedLabel(fieldNode.node.name) - }, - defaultValue - } - // Create new attribute - const attrId = await this.client.createDoc( - core.class.Attribute, - core.space.Model, - data, - undefined, - Date.now(), - prj.createdBy - ) - await this.pushMapping(prj, mappings, { _id: attrId, name: data.name, attributeOf: data.attributeOf }, fieldNode) - } - - private async pushMapping ( - prj: GithubProject | GithubMilestone, - mappings: GithubFieldMapping[], - f: Pick, - node: GithubProjectV2Field - ): Promise { - const field = mappings.find((it) => it._id === f._id) - if (field !== undefined) { - return - } - const m = { - _id: f._id, - name: f.name, - _class: f.attributeOf, - githubId: node.node.id - } - mappings.push(m) - await this.client.update(prj, { - $push: { - mappings: m - } - }) - } - - private async createUpdateStatus ( - githubFields: GithubProjectV2Field[], - statusAttr: AnyAttribute, - okit: Octokit, - prj: GithubProject | GithubMilestone, - project: GithubProject - ): Promise { - // TODO: A support of field upgrade - const githubAttr = githubFields - .map((it) => it) - .find( - (it) => - it.node.name.toLowerCase() === 'uber' + statusAttr.name.toLowerCase() && it.node.dataType === 'SINGLE_SELECT' - ) - if (githubAttr !== undefined) { - return githubAttr - } - - // Let's find all platform status fields - const statusFields = await this.provider.getProjectStatuses(project.type) - - const opts: { name: string, color: string, description: string }[] = [] - for (const fi of statusFields) { - opts.push({ - name: fi.name, - description: fi.description ?? '', - color: categoryColors[fi.category ?? task.statusCategory.UnStarted] - }) - } - - if (isGHWriteAllowed()) { - const fieldUpdateResponse: any = await okit.graphql( - `mutation createProjectField { - ${this.addProjectField( - prj.projectNodeId as string, - 'Uber' + statusAttr.name[0].toUpperCase() + statusAttr.name.slice(1), - 'SINGLE_SELECT', - opts - )} - } - ` - ) - return { node: fieldUpdateResponse.createProjectV2Field.projectV2Field as GithubProjectV2Field['node'] } - } - } - - private async createUpdateCustomField ( - githubFields: GithubProjectV2Field[], - attr: AnyAttribute, - okit: Octokit, - prj: GithubProject | GithubMilestone - ): Promise { - const attrType = getType(attr) - if (attrType === undefined) { - return undefined - } - // TODO: A support of field upgrade - let githubAttr = githubFields - .map((it) => it) - .find((it) => it.node.name.toLowerCase() === attr.name.toLowerCase() && it.node.dataType === attrType) - if (githubAttr !== undefined) { - return githubAttr - } - - // Find using label - - const labelValue = await translate(attr.label, {}) - - githubAttr = githubFields - .map((it) => it) - .find((it) => it.node.name.toLowerCase() === labelValue.toLowerCase() && it.node.dataType === attrType) - - if (githubAttr !== undefined) { - return githubAttr - } - - let opts: { name: string, color: string, description: string }[] | undefined - - if (attrType === 'SINGLE_SELECT') { - const typeOf = attr.type as EnumOf - const enumClass = await this.client.findOne(core.class.Enum, { _id: typeOf.of }) - opts = [] - for (const fi of enumClass?.enumValues ?? []) { - opts.push({ - name: fi, - description: '', - color: githubColors[Math.abs(hashCode(fi)) % githubColors.length] - }) - } - } - - if (isGHWriteAllowed()) { - const fieldUpdateResponse: any = await okit.graphql( - `mutation createProjectField { - ${this.addProjectField( - prj.projectNodeId as string, - labelValue[0].toUpperCase() + labelValue.slice(1), - attrType, - opts - )} - } - ` - ) - return { node: fieldUpdateResponse.createProjectV2Field.projectV2Field as GithubProjectV2Field['node'] } - } - } - - private async createUpdatePriority ( - githubFields: GithubProjectV2Field[], - attr: AnyAttribute, - okit: Octokit, - prj: GithubProject | GithubMilestone - ): Promise { - // TODO: A support of field upgrade - const githubAttr = githubFields - .map((it) => it) - .find((it) => it.node.name.toLowerCase() === attr.name.toLowerCase() && it.node.dataType === 'SINGLE_SELECT') - if (githubAttr !== undefined) { - return githubAttr - } - - const opts: { name: string, color: string, description: string }[] = [] - - for (const fi of ['Urgent', 'High', 'Medium', 'Low']) { - opts.push({ - name: fi, - description: '', - color: githubColors[Math.abs(hashCode(fi)) % githubColors.length] - }) - } - if (isGHWriteAllowed()) { - const fieldUpdateResponse: any = await okit.graphql( - `mutation createProjectField { - ${this.addProjectField( - prj.projectNodeId as string, - attr.name[0].toUpperCase() + attr.name.slice(1), - 'SINGLE_SELECT', - opts - )} - } - ` - ) - return { node: fieldUpdateResponse.createProjectV2Field.projectV2Field as GithubProjectV2Field['node'] } - } - } - - private async queryProjectStructure ( - integration: IntegrationContainer, - prj: GithubProjectSyncData - ): Promise { - const root = `${integration.type === 'Organization' ? 'organization' : 'user'}` - return ( - (await integration.octokit?.graphql( - ` - query projectStructureQuery($login: String!, $prjNumber: Int!) { - ${root}(login: $login) { - projectV2(number: $prjNumber) { - id - updatedAt - title - readme - fields(last: 100) { - edges { - node { - ${projectV2Field} - } - } - } - } - } - }`, - { - login: integration.login, - prjNumber: prj.projectNumber - } - )) as any - )[root] - } - - private queryProject ( - integration: IntegrationContainer, - prj: GithubProjectSyncData - ): { query: string, params: any, root: string } { - const root = `${integration.type === 'Organization' ? 'organization' : 'user'}` - return { - query: ` - query queryProjectContents($login: String!, $prjNumber: Int!, $cursor: String) { - ${root}(login: $login) { - projectV2(number: $prjNumber) { - items(first: 99, after: $cursor) { - nodes { - id - type - updatedAt - fieldValues(first: 50) { - nodes { - ${projectV2ItemFields} - } - } - content { - ... on Issue { - id - url - } - ... on PullRequest { - id - url - } - } - } - pageInfo { - startCursor - hasNextPage - endCursor - } - totalCount - } - } - } - } - `, - params: { - login: integration.login, - prjNumber: prj.projectNumber - }, - root - } - } - - private async createProjectV2 ( - integration: IntegrationContainer, - octokit: Octokit, - prjName: string - ): Promise<{ projectNumber: number, projectNodeId: string, url: string } | undefined> { - if (isGHWriteAllowed()) { - const response: any = await octokit.graphql( - ` - mutation createProjectV2($owner: ID!, $title: String!) { - createProjectV2(input: {ownerId: $owner, title: $title}) { - projectV2 { - url - id - number - } - } - }`, - { - owner: integration.loginNodeId, - title: prjName - } - ) - - return { - projectNumber: response.createProjectV2.projectV2.number, - projectNodeId: response.createProjectV2.projectV2.id, - url: response.createProjectV2.projectV2.url - } - } - } - - private async updateProjectV2 ( - octokit: Octokit, - projectId: string, - options: { - title?: string - shortDescription?: string - readme?: string - } - ): Promise { - if (isGHWriteAllowed()) { - await octokit.graphql( - ` - mutation createProjectV2($projectID: ID!) { - updateProjectV2(input: { - projectId: $projectID - ${gqlp(options)} - }) { - projectV2 { - url - id - number - } - } - }`, - { - projectID: projectId - } - ) - } - } - - private addProjectField ( - projectId: string, - name: string, - type: GithubDataType, - options?: { - name: string - color: string - description: string - }[] - ): string { - return ` - createProjectV2Field( - input: { - dataType: ${type}, - name: "${name}", - ${ - options !== undefined - ? `singleSelectOptions: [ - ${options - .map((it) => `{name: "${it.name}", color: ${it.color}, description: "${it.description}"}`) - .join(', ')}], ` - : '' - } - projectId: "${projectId}" - } - ) { - projectV2Field { - ${projectV2Field} - } - } - \n` - } -} diff --git a/services/github/pod-github/src/sync/pullrequests.ts b/services/github/pod-github/src/sync/pullrequests.ts index ad22fa6296e..e9ed6683863 100644 --- a/services/github/pod-github/src/sync/pullrequests.ts +++ b/services/github/pod-github/src/sync/pullrequests.ts @@ -56,8 +56,7 @@ import { toReviewDecision, toReviewState } from './githubTypes' -import { GithubIssueData, IssueSyncManagerBase, IssueSyncTarget, WithMarkup } from './issueBase' -import { syncConfig } from './syncConfig' +import { GithubIssueData, IssueSyncManagerBase, WithMarkup } from './issueBase' import { errorToObj, getSinceRaw, @@ -98,35 +97,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS const projectV2Event = (_event as any as ProjectsV2ItemEvent).projects_v2_item?.id !== undefined if (projectV2Event) { - const projectV2Event = _event as ProjectsV2ItemEvent - - const githubProjects = await this.provider.liveQuery.findAll(github.mixin.GithubProject, { - archived: false - }) - let prj = githubProjects.find((it) => it.projectNodeId === projectV2Event.projects_v2_item.project_node_id) - if (prj === undefined) { - // Checking for milestones - const m = await this.provider.liveQuery.findOne(github.mixin.GithubMilestone, { - projectNodeId: projectV2Event.projects_v2_item.project_node_id - }) - if (m !== undefined) { - prj = githubProjects.find((it) => it._id === m.space) - } - } - - if (prj === undefined) { - this.ctx.info('Event from unknown v2 project', { - nodeId: projectV2Event.projects_v2_item.project_node_id, - workspace: this.provider.getWorkspaceId().name - }) - return - } - - const urlId = projectV2Event.projects_v2_item.node_id - - await syncRunner.exec(urlId, async () => { - await this.processProjectV2Event(integration, projectV2Event, derivedClient, prj as GithubProject) - }) + // Ignore } else { const event = _event as PullRequestEvent const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id) @@ -378,7 +349,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS } async syncToTarget ( - target: IssueSyncTarget, container: ContainerFocus, existing: Doc | undefined, pullRequestExternal: PullRequestExternalData, @@ -390,66 +360,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS const accountGH = info.lastGithubUser ?? (await this.provider.getAccount(pullRequestExternal.author))?._id ?? core.account.System - // A target node id - const targetNodeId: string | undefined = info.targetNodeId as string - - const okit = (await this.provider.getOctokit(account as Ref)) ?? container.container.octokit - - const isProjectProjectTarget = target.target.projectNodeId === target.project.projectNodeId - const supportProjects = - (isProjectProjectTarget && syncConfig.MainProject) || (!isProjectProjectTarget && syncConfig.SupportMilestones) - const type = await this.provider.getTaskTypeOf(container.project.type, github.class.GithubPullRequest) const statuses = await this.provider.getStatuses(type?._id) - if ( - targetNodeId !== undefined && - target.target.projectNodeId !== undefined && - targetNodeId !== target.target.projectNodeId && - supportProjects - ) { - const itemNode = pullRequestExternal.projectItems.nodes.find((it) => it.project.id === targetNodeId) - if (itemNode !== undefined) { - await this.removeIssueFromProject(okit, target.target.projectNodeId, itemNode.id) - // remove data - pullRequestExternal.projectItems.nodes = pullRequestExternal.projectItems.nodes.filter( - (it) => it.id !== targetNodeId - ) - await derivedClient.update(info, { - external: pullRequestExternal, - externalVersion: githubExternalSyncVersion - }) - target.prjData = undefined - // We need to sync from platform as new to new project. - // We need to remove current sync - info.current = {} - } - } - - // Check if issue are added to project. - if (target.prjData === undefined && okit !== undefined && supportProjects) { - try { - target.prjData = await this.ctx.withLog( - 'add pull request to project}', - {}, - () => this.addIssueToProject(container, okit, pullRequestExternal, target.target.projectNodeId as string), - { url: pullRequestExternal.url } - ) - if (target.prjData !== undefined) { - pullRequestExternal.projectItems.nodes.push(target.prjData) - } - - await derivedClient.update(info, { - external: pullRequestExternal, - externalVersion: githubExternalSyncVersion - }) - } catch (err: any) { - this.ctx.error('Error', { err }) - Analytics.handleError(err) - return { needSync: githubSyncVersion, error: errorToObj(err) } - } - } - const assignees = await this.getAssignees(pullRequestExternal) const reviewers = await this.getReviewers(pullRequestExternal) @@ -496,7 +409,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS this.ctx.error('Missing required task type', { url: pullRequestExternal.url }) return { needSync: githubSyncVersion } } - await this.fillProjectV2Fields(target, container, pullRequestData, taskTypes[0]) const lastModified = new Date(pullRequestExternal.updatedAt).getTime() @@ -616,15 +528,13 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS {}, () => this.handleDiffUpdate( - target, + container, { ...(existing as any), description }, info, pullRequestData, - container, pullRequestExternal, account, - accountGH, - supportProjects + accountGH ), { url: pullRequestExternal.url } ) @@ -972,13 +882,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS return { needSync: githubSyncVersion } } const needCreateConnectedAtHuly = info.addHulyLink === true - if ( - (container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject - ) { - return { needSync: githubSyncVersion } - } if (info.repository == null) { return { needSync: githubSyncVersion } @@ -990,24 +893,13 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS return { needSync: '' } } - let target = await this.getMilestoneIssueTarget( - container.project, - container.container, - existing as Issue, - pullRequestExternal - ) - if (target === undefined) { - target = this.getProjectIssueTarget(container.project, pullRequestExternal) - } - - const syncResult = await this.syncToTarget(target, container, existing, pullRequestExternal, derivedClient, info) + const syncResult = await this.syncToTarget(container, existing, pullRequestExternal, derivedClient, info) if (existing !== undefined && pullRequestExternal !== undefined && needCreateConnectedAtHuly) { await this.addHulyLink(info, syncResult, existing, pullRequestExternal, container) } return { - ...syncResult, - targetNodeId: target.target.projectNodeId + ...syncResult } } @@ -1244,7 +1136,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS descriptionLocked: isDescriptionLocked } ) - await client.createMixin(prId, github.mixin.GithubIssue, prj._id, prj.mixinClass, {}) await this.addConnectToMessage( github.string.PullRequestConnectedActivityInfo, diff --git a/services/github/pod-github/src/sync/reviewComments.ts b/services/github/pod-github/src/sync/reviewComments.ts index a2bc597c520..597dc1c5202 100644 --- a/services/github/pod-github/src/sync/reviewComments.ts +++ b/services/github/pod-github/src/sync/reviewComments.ts @@ -12,13 +12,13 @@ import core, { Ref, TxOperations } from '@hcengineering/core' -import { LiveQuery } from '@hcengineering/query' import github, { DocSyncInfo, GithubIntegrationRepository, GithubProject, GithubReviewComment } from '@hcengineering/github' +import { LiveQuery } from '@hcengineering/query' import { ContainerFocus, DocSyncManager, @@ -34,7 +34,6 @@ import { collectUpdate, deleteObjects, errorToObj, isGHWriteAllowed } from './ut import { Analytics } from '@hcengineering/analytics' import { PullRequestReviewCommentCreatedEvent, PullRequestReviewCommentEvent } from '@octokit/webhooks-types' import config from '../config' -import { syncConfig } from './syncConfig' export type ReviewCommentData = DocData @@ -98,14 +97,6 @@ export class ReviewCommentSyncManager implements DocSyncManager { if (container === undefined) { return false } - if ( - container?.container === undefined || - ((container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject) - ) { - return false - } const commentExternal = info.external diff --git a/services/github/pod-github/src/sync/reviewThreads.ts b/services/github/pod-github/src/sync/reviewThreads.ts index 05db4229f6e..896f4aedbf2 100644 --- a/services/github/pod-github/src/sync/reviewThreads.ts +++ b/services/github/pod-github/src/sync/reviewThreads.ts @@ -40,7 +40,6 @@ import { collectUpdate, deleteObjects, errorToObj, isGHWriteAllowed, syncChilds, import { Analytics } from '@hcengineering/analytics' import { PullRequestReviewThreadEvent } from '@octokit/webhooks-types' import config from '../config' -import { syncConfig } from './syncConfig' import { githubConfiguration } from './configuration' export type ReviewThreadData = Pick< @@ -117,14 +116,6 @@ export class ReviewThreadSyncManager implements DocSyncManager { if (container === undefined) { return false } - if ( - container?.container === undefined || - ((container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject) - ) { - return false - } const commentExternal = info.external diff --git a/services/github/pod-github/src/sync/reviews.ts b/services/github/pod-github/src/sync/reviews.ts index f153375e558..98dd94e5ffc 100644 --- a/services/github/pod-github/src/sync/reviews.ts +++ b/services/github/pod-github/src/sync/reviews.ts @@ -34,7 +34,6 @@ import { collectUpdate, deleteObjects, errorToObj, isGHWriteAllowed, syncChilds import { Analytics } from '@hcengineering/analytics' import { PullRequestReviewEvent, PullRequestReviewSubmittedEvent } from '@octokit/webhooks-types' import config from '../config' -import { syncConfig } from './syncConfig' export type ReviewData = Pick @@ -95,14 +94,6 @@ export class ReviewSyncManager implements DocSyncManager { if (container === undefined) { return false } - if ( - container?.container === undefined || - ((container.project.projectNodeId === undefined || - !container.container.projectStructure.has(container.project._id)) && - syncConfig.MainProject) - ) { - return false - } const commentExternal = info.external diff --git a/services/github/pod-github/src/sync/syncConfig.ts b/services/github/pod-github/src/sync/syncConfig.ts deleted file mode 100644 index 9da406f2b4e..00000000000 --- a/services/github/pod-github/src/sync/syncConfig.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @public - */ -export const syncConfig = { - MainProject: false, - SupportMilestones: true, - IssuesInProject: true, - BacklogInProject: false, - PullRequestsInProject: false -} diff --git a/services/github/pod-github/src/sync/utils.ts b/services/github/pod-github/src/sync/utils.ts index 719be174f38..6786447b9ac 100644 --- a/services/github/pod-github/src/sync/utils.ts +++ b/services/github/pod-github/src/sync/utils.ts @@ -1,7 +1,6 @@ import { Analytics } from '@hcengineering/analytics' import core, { Account, - AnyAttribute, AttachedDoc, Class, Doc, @@ -12,9 +11,7 @@ import core, { SortingOrder, Status, Timestamp, - TxOperations, - Type, - toIdMap + TxOperations } from '@hcengineering/core' import github, { DocSyncInfo, @@ -23,11 +20,10 @@ import github, { GithubProject } from '@hcengineering/github' import { PlatformError, unknownStatus } from '@hcengineering/platform' -import task, { TaskType, calculateStatuses, createState, findStatusAttr } from '@hcengineering/task' -import tracker, { IssueStatus } from '@hcengineering/tracker' +import task from '@hcengineering/task' +import { IssueStatus } from '@hcengineering/tracker' import { deepEqual } from 'fast-equals' -import { IntegrationManager, githubExternalSyncVersion } from '../types' -import { GithubDataType } from './githubTypes' +import { githubExternalSyncVersion } from '../types' /** * Return if github write operations are allowed. @@ -148,58 +144,6 @@ export function gqlp (params: Record { - const color = hashCode(colorStr) - - const states = await provider.getStatuses(taskType._id) - - for (const s of states) { - if (s.name.toLowerCase().trim() === name.toLowerCase().trim()) { - return s._id - } - } - ctx.error('Create new project Status', { name, colorStr, category: 'Backlog' }) - // No status found, let's create one. - const id = await createState(client, taskType.statusClass, { - name, - description, - color, - ofAttribute: findStatusAttr(client.getHierarchy(), taskType.statusClass)._id, - category: task.statusCategory.UnStarted - }) - const type = await client.findOne(task.class.ProjectType, { _id: prj.type }) - if (type === undefined) { - return id - } - - if (!taskType.statuses.includes(id)) { - await client.update(taskType, { - $push: { statuses: id } - }) - const taskTypes = toIdMap(await client.findAll(task.class.TaskType, { parent: type._id })) - - const index = type.statuses.findIndex((it) => it._id === id) - if (index === -1) { - await client.update(type, { - statuses: calculateStatuses(type, taskTypes, [{ taskTypeId: taskType._id, statuses: taskType.statuses }]) - }) - } - } - return id -} - /** * @public */ @@ -207,39 +151,6 @@ export function hashCode (str: string): number { return str.split('').reduce((prevHash, currVal) => ((prevHash << 5) - prevHash + currVal.charCodeAt(0)) | 0, 0) } -export function getType (attr: AnyAttribute): GithubDataType | undefined { - if (attr.type._class === core.class.TypeString) { - return 'TEXT' - } - if ( - attr.type._class === core.class.TypeNumber || - attr.type._class === tracker.class.TypeReportedTime || - attr.type._class === tracker.class.TypeEstimation || - attr.type._class === tracker.class.TypeRemainingTime - ) { - return 'NUMBER' - } - if (attr.type._class === core.class.TypeDate) { - return 'DATE' - } - if (attr.type._class === core.class.EnumOf) { - return 'SINGLE_SELECT' - } -} - -export function getPlatformType (dataType: GithubDataType): Ref>> | undefined { - switch (dataType) { - case 'TEXT': - return core.class.TypeString - case 'NUMBER': - return core.class.TypeNumber - case 'DATE': - return core.class.TypeDate - case 'SINGLE_SELECT': - return core.class.EnumOf - } -} - export async function guessStatus ( pr: { state: 'OPEN' | 'CLOSED' | 'MERGED', stateReason?: GithubIssueStateReason | null }, statuses: Status[] diff --git a/services/github/pod-github/src/types.ts b/services/github/pod-github/src/types.ts index 7d77d1a7c05..23588f473ab 100644 --- a/services/github/pod-github/src/types.ts +++ b/services/github/pod-github/src/types.ts @@ -22,12 +22,10 @@ import { DocSyncInfo, GithubIntegration, GithubIntegrationRepository, - GithubMilestone, GithubProject, GithubUserInfo } from '@hcengineering/github' import { Octokit } from 'octokit' -import { GithubProjectV2 } from './sync/githubTypes' /** * @public @@ -61,8 +59,6 @@ export interface IntegrationContainer { installationName: string octokit: Octokit - projectStructure: Map, GithubProjectV2> - enabled: boolean synchronized: Set diff --git a/services/github/pod-github/src/worker.ts b/services/github/pod-github/src/worker.ts index 5372a2cbe1b..bf047191b74 100644 --- a/services/github/pod-github/src/worker.ts +++ b/services/github/pod-github/src/worker.ts @@ -64,13 +64,11 @@ import { createNotification } from './notifications' import { InstallationRecord, PlatformWorker } from './platform' import { CommentSyncManager } from './sync/comments' import { IssueSyncManager } from './sync/issues' -import { ProjectsSyncManager } from './sync/projects' import { PullRequestSyncManager } from './sync/pullrequests' import { RepositorySyncMapper } from './sync/repository' import { ReviewCommentSyncManager } from './sync/reviewComments' import { ReviewThreadSyncManager } from './sync/reviewThreads' import { ReviewSyncManager } from './sync/reviews' -import { syncConfig } from './sync/syncConfig' import { UsersSyncManager, fetchViewerDetails } from './sync/users' import { errorToObj } from './sync/utils' import { @@ -384,10 +382,6 @@ export class GithubWorker implements IntegrationManager { this.mappers = [ { _class: [github.mixin.GithubProject], mapper: this.repositoryManager }, - { - _class: [github.class.GithubIntegration, tracker.class.Milestone], - mapper: new ProjectsSyncManager(this.ctx.newChild('project', {}), this._client, this.liveQuery) - }, { _class: [tracker.class.Issue], mapper: new IssueSyncManager(this.ctx.newChild('issue', {}), this._client, this.liveQuery, this.collaborator) @@ -818,7 +812,6 @@ export class GithubWorker implements IntegrationManager { installationName: inst?.installationName ?? '', enabled: !inst.suspended, synchronized: new Set(), - projectStructure: new Map(), syncLock: new Map() } this.integrations.set(it.installationId, current) @@ -1246,7 +1239,7 @@ export class GithubWorker implements IntegrationManager { if (it.enabled) { const _projects = [] for (const p of allProjects) { - if (p.integration === it.integration._id && (!syncConfig.MainProject || it.projectStructure.has(p._id))) { + if (p.integration === it.integration._id) { _projects.push(p) } }