mirror of
https://github.com/hcengineering/platform.git
synced 2026-08-24 05:12:24 +02:00
Remove unstable/unused Github project support (#8891)
* Remove unstable/unused Github project support Signed-off-by: Andrey Sobolev <haiodo@gmail.com> * Fix unused file Signed-off-by: Andrey Sobolev <haiodo@gmail.com> --------- Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -28,7 +28,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
|
||||
@@ -88,14 +87,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
|
||||
|
||||
|
||||
@@ -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<string, any>
|
||||
}
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
@@ -21,57 +21,23 @@ import core, {
|
||||
PersonId,
|
||||
Ref,
|
||||
Space,
|
||||
Status,
|
||||
TxOperations,
|
||||
makeDocCollabId
|
||||
} from '@hcengineering/core'
|
||||
import github, {
|
||||
DocSyncInfo,
|
||||
GithubFieldMapping,
|
||||
GithubIntegrationRepository,
|
||||
GithubIssue,
|
||||
GithubMilestone,
|
||||
GithubProject
|
||||
} from '@hcengineering/github'
|
||||
import github, { DocSyncInfo, GithubIntegrationRepository, GithubIssue, 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 +82,6 @@ Record<string, any>
|
||||
*/
|
||||
export type IssueUpdate = DocumentUpdate<WithMarkup<Issue>>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface IssueSyncTarget {
|
||||
project: GithubProject
|
||||
mappings: GithubFieldMapping[]
|
||||
target: GithubProject | GithubMilestone
|
||||
prjData?: GithubProjectV2Item
|
||||
}
|
||||
|
||||
export abstract class IssueSyncManagerBase {
|
||||
provider!: IntegrationManager
|
||||
constructor (
|
||||
@@ -157,189 +113,6 @@ export abstract class IssueSyncManagerBase {
|
||||
return socialIds.map((it) => it.attachedTo)
|
||||
}
|
||||
|
||||
async processProjectV2Event (
|
||||
integration: IntegrationContainer,
|
||||
event: ProjectsV2ItemEvent,
|
||||
derivedClient: TxOperations,
|
||||
prj: GithubProject
|
||||
): Promise<void> {
|
||||
const account = (await this.provider.getAccountU(event.sender)) ?? 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<GithubMilestone>(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() })
|
||||
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() })
|
||||
return
|
||||
}
|
||||
|
||||
let needProjectRefresh = false
|
||||
const update: DocumentUpdate<WithMarkup<Issue>> & Record<string, any> = {}
|
||||
|
||||
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<GithubIntegrationRepository>(
|
||||
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,
|
||||
@@ -425,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()
|
||||
}
|
||||
}
|
||||
@@ -439,229 +207,6 @@ export abstract class IssueSyncManagerBase {
|
||||
}
|
||||
}
|
||||
|
||||
async addIssueToProject (
|
||||
container: ContainerFocus,
|
||||
okit: Octokit,
|
||||
issue: IssueExternalData,
|
||||
projectTarget: string
|
||||
): Promise<GithubProjectV2Item | undefined> {
|
||||
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<void> {
|
||||
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<string, IssuePriority> = {
|
||||
'': 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<string, any>,
|
||||
taskType: TaskType
|
||||
): Promise<void> {
|
||||
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()
|
||||
})
|
||||
errors.push({ error: err, response })
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
abstract fillBackChanges (update: DocumentUpdate<Issue>, existing: GithubIssue, external: any): Promise<void>
|
||||
|
||||
async addConnectToMessage (
|
||||
@@ -702,15 +247,13 @@ export abstract class IssueSyncManagerBase {
|
||||
abstract afterSync (existing: Issue, account: PersonId, issueExternal: any, info: DocSyncInfo): Promise<void>
|
||||
|
||||
async handleDiffUpdate (
|
||||
target: IssueSyncTarget,
|
||||
container: ContainerFocus,
|
||||
existing: WithMarkup<Issue>,
|
||||
info: DocSyncInfo,
|
||||
issueData: GithubIssueData,
|
||||
container: ContainerFocus,
|
||||
issueExternal: IssueExternalData,
|
||||
account: PersonId,
|
||||
accountGH: PersonId,
|
||||
syncToProject: boolean
|
||||
accountGH: PersonId
|
||||
): Promise<DocumentUpdate<DocSyncInfo>> {
|
||||
let needUpdate = false
|
||||
if (!this.client.getHierarchy().hasMixin(existing, github.mixin.GithubIssue)) {
|
||||
@@ -746,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<Issue, Issue>(
|
||||
existing._id as Ref<GithubIssue>,
|
||||
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<Issue>(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<Issue>(previousData, existingIssue, Array.from(allAttributes.keys()))
|
||||
|
||||
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
|
||||
@@ -826,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()
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (attr.name === 'priority') {
|
||||
const values: Record<IssuePriority, string> = {
|
||||
[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()
|
||||
})
|
||||
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()
|
||||
})
|
||||
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()
|
||||
})
|
||||
}
|
||||
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]
|
||||
@@ -1214,40 +645,6 @@ export abstract class IssueSyncManagerBase {
|
||||
this.provider.sync()
|
||||
}
|
||||
|
||||
async getMilestoneIssueTarget (
|
||||
project: GithubProject,
|
||||
container: IntegrationContainer,
|
||||
existingIssue: Issue | undefined,
|
||||
external: IssueExternalData
|
||||
): Promise<IssueSyncTarget | undefined> {
|
||||
if (existingIssue !== undefined) {
|
||||
// Select a milestone project
|
||||
if (existingIssue.milestone != null) {
|
||||
const milestone = await this.provider.liveQuery.findOne<GithubMilestone>(github.mixin.GithubMilestone, {
|
||||
_id: existingIssue.milestone as Ref<GithubMilestone>
|
||||
})
|
||||
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: PersonId, id: string): Promise<void>
|
||||
|
||||
async handleDelete (
|
||||
@@ -1260,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
|
||||
|
||||
@@ -1276,38 +665,6 @@ export abstract class IssueSyncManagerBase {
|
||||
return true
|
||||
}
|
||||
const account = existing?.createdBy ?? (await this.provider.getAccount(issueExternal.author)) ?? core.account.System
|
||||
const okit = (await this.provider.getOctokit(account)) ?? 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 {
|
||||
|
||||
@@ -45,8 +45,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 {
|
||||
@@ -92,35 +91,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()
|
||||
})
|
||||
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)) ?? 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)) ?? 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()
|
||||
})
|
||||
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 }
|
||||
)
|
||||
@@ -954,7 +846,6 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
|
||||
repository: repo,
|
||||
descriptionLocked: isDescriptionLocked
|
||||
})
|
||||
await this.client.createMixin<Issue, Issue>(issueId, github.mixin.GithubIssue, prj._id, prj.mixinClass, {})
|
||||
|
||||
await this.addConnectToMessage(
|
||||
github.string.IssueConnectedActivityInfo,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -57,8 +57,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,
|
||||
@@ -99,35 +98,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()
|
||||
})
|
||||
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)
|
||||
@@ -379,7 +350,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
|
||||
}
|
||||
|
||||
async syncToTarget (
|
||||
target: IssueSyncTarget,
|
||||
container: ContainerFocus,
|
||||
existing: Doc | undefined,
|
||||
pullRequestExternal: PullRequestExternalData,
|
||||
@@ -391,66 +361,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
|
||||
const accountGH =
|
||||
info.lastGithubUser ?? (await this.provider.getAccount(pullRequestExternal.author)) ?? core.account.System
|
||||
|
||||
// A target node id
|
||||
const targetNodeId: string | undefined = info.targetNodeId as string
|
||||
|
||||
const okit = (await this.provider.getOctokit(account)) ?? 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: PersonId[] = await this.getReviewers(pullRequestExternal)
|
||||
|
||||
@@ -497,7 +410,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()
|
||||
|
||||
@@ -617,15 +529,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 }
|
||||
)
|
||||
@@ -980,13 +890,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 }
|
||||
@@ -998,24 +901,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1251,7 +1143,6 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
|
||||
descriptionLocked: isDescriptionLocked
|
||||
}
|
||||
)
|
||||
await client.createMixin<Issue, Issue>(prId, github.mixin.GithubIssue, prj._id, prj.mixinClass, {})
|
||||
|
||||
await this.addConnectToMessage(
|
||||
github.string.PullRequestConnectedActivityInfo,
|
||||
|
||||
@@ -11,13 +11,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,
|
||||
@@ -33,7 +33,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<GithubReviewComment>
|
||||
|
||||
@@ -97,14 +96,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
|
||||
|
||||
|
||||
@@ -39,7 +39,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<
|
||||
@@ -116,14 +115,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
|
||||
|
||||
|
||||
@@ -33,7 +33,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<GithubReview, 'body' | 'state' | 'comments'>
|
||||
|
||||
@@ -94,14 +93,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
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const syncConfig = {
|
||||
MainProject: false,
|
||||
SupportMilestones: true,
|
||||
IssuesInProject: true,
|
||||
BacklogInProject: false,
|
||||
PullRequestsInProject: false
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
import { Analytics } from '@hcengineering/analytics'
|
||||
import core, {
|
||||
PersonId,
|
||||
AnyAttribute,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Doc,
|
||||
DocumentQuery,
|
||||
DocumentUpdate,
|
||||
MeasureContext,
|
||||
PersonId,
|
||||
Ref,
|
||||
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<string, string | number | string[] | undefi
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function getCreateStatus (
|
||||
ctx: MeasureContext,
|
||||
provider: IntegrationManager,
|
||||
client: TxOperations,
|
||||
prj: GithubProject,
|
||||
name: string,
|
||||
description: string,
|
||||
colorStr: string,
|
||||
taskType: TaskType
|
||||
): Promise<string> {
|
||||
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<Class<Type<any>>> | 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[]
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
DocSyncInfo,
|
||||
GithubIntegration,
|
||||
GithubIntegrationRepository,
|
||||
GithubMilestone,
|
||||
GithubProject,
|
||||
GithubUserInfo
|
||||
} from '@hcengineering/github'
|
||||
@@ -27,7 +26,6 @@ import { ProjectType, TaskType } from '@hcengineering/task'
|
||||
import { MarkupNode } from '@hcengineering/text'
|
||||
import { User } from '@octokit/webhooks-types'
|
||||
import { Octokit } from 'octokit'
|
||||
import { GithubProjectV2 } from './sync/githubTypes'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -61,8 +59,6 @@ export interface IntegrationContainer {
|
||||
installationName: string
|
||||
octokit: Octokit
|
||||
|
||||
projectStructure: Map<Ref<GithubProject | GithubMilestone>, GithubProjectV2>
|
||||
|
||||
enabled: boolean
|
||||
synchronized: Set<string>
|
||||
|
||||
|
||||
@@ -79,13 +79,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 {
|
||||
@@ -407,10 +405,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)
|
||||
@@ -861,7 +855,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)
|
||||
@@ -1287,7 +1280,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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user