UBER-1233: Milestone related fixes (#7614)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-01-09 14:53:15 +07:00
committed by GitHub
parent 12e0aaa5f7
commit c9f5f4656c
15 changed files with 132 additions and 45 deletions
+7 -2
View File
@@ -27,6 +27,8 @@ interface Config {
SentryDSN: string
BrandingPath: string
WorkspaceInactivityInterval: number // Interval in days to stop workspace synchronization if not visited
}
const envMap: { [key in keyof Config]: string } = {
@@ -51,7 +53,9 @@ const envMap: { [key in keyof Config]: string } = {
CollaboratorURL: 'COLLABORATOR_URL',
SentryDSN: 'SENTRY_DSN',
BrandingPath: 'BRANDING_PATH'
BrandingPath: 'BRANDING_PATH',
WorkspaceInactivityInterval: 'WORKSPACE_INACTIVITY_INTERVAL'
}
const required: Array<keyof Config> = [
@@ -96,7 +100,8 @@ const config: Config = (() => {
CollaboratorURL: process.env[envMap.CollaboratorURL],
SentryDSN: process.env[envMap.SentryDSN],
BrandingPath: process.env[envMap.BrandingPath] ?? ''
BrandingPath: process.env[envMap.BrandingPath] ?? '',
WorkspaceInactivityInterval: parseInt(process.env[envMap.WorkspaceInactivityInterval] ?? '5') // In days
}
const missingEnv = required.filter((key) => params[key] === undefined).map((key) => envMap[key])
+39 -18
View File
@@ -697,6 +697,35 @@ export class PlatformWorker {
return Array.from(workspaces)
}
async checkWorkspaceIsActive (token: string, workspace: string): Promise<ClientWorkspaceInfo | undefined> {
let workspaceInfo: ClientWorkspaceInfo | undefined
try {
workspaceInfo = await getWorkspaceInfo(token)
} catch (err: any) {
this.ctx.error('Workspace not found:', { workspace })
return
}
if (workspaceInfo?.workspace === undefined) {
this.ctx.error('No workspace exists for workspaceId', { workspace })
return
}
if (!isActiveMode(workspaceInfo?.mode)) {
this.ctx.warn('Workspace is in maitenance, skipping for now.', { workspace })
return
}
if (workspaceInfo?.disabled === true) {
this.ctx.warn('Workspace is disabled', { workspace })
return
}
const lastVisit = (Date.now() - workspaceInfo.lastVisit) / (3600 * 24 * 1000) // In days
if (config.WorkspaceInactivityInterval > 0 && lastVisit > config.WorkspaceInactivityInterval) {
this.ctx.warn('Workspace is inactive for too long, skipping for now.', { workspace })
return
}
return workspaceInfo
}
private async checkWorkspaces (): Promise<boolean> {
this.ctx.info('************************* Check workspaces ************************* ', {
workspaces: this.clients.size
@@ -737,27 +766,11 @@ export class PlatformWorker {
},
{ mode: 'github' }
)
let workspaceInfo: ClientWorkspaceInfo | undefined
try {
workspaceInfo = await getWorkspaceInfo(token, true)
} catch (err: any) {
this.ctx.error('Workspace not found:', { workspace })
const workspaceInfo = await this.checkWorkspaceIsActive(token, workspace)
if (workspaceInfo === undefined) {
errors++
return
}
if (workspaceInfo?.workspace === undefined) {
this.ctx.error('No workspace exists for workspaceId', { workspace })
errors++
return
}
if (!isActiveMode(workspaceInfo?.mode)) {
this.ctx.warn('Workspace is in maitenance, skipping for now.', { workspace })
return
}
if (workspaceInfo?.disabled === true) {
this.ctx.warn('Workspace is disabled', { workspace })
return
}
try {
const branding = Object.values(this.brandingMap).find((b) => b.key === workspaceInfo?.branding) ?? null
const workerCtx = this.ctx.newChild('worker', { workspace: workspaceInfo.workspace }, {})
@@ -786,6 +799,14 @@ export class PlatformWorker {
if (event === ClientConnectEvent.Refresh || event === ClientConnectEvent.Upgraded) {
void this.clients.get(workspace)?.refreshClient(event === ClientConnectEvent.Upgraded)
}
// We need to check if workspace is inactive
void this.checkWorkspaceIsActive(token, workspace).then((res) => {
if (res === undefined) {
this.ctx.warn('Workspace is inactive, removing from clients list.', { workspace })
this.clients.delete(workspace)
void worker?.close()
}
})
}
)
if (worker !== undefined) {
@@ -35,7 +35,8 @@ import github, {
} from '@hcengineering/github'
import { IntlString } from '@hcengineering/platform'
import { LiveQuery } from '@hcengineering/query'
import task, { TaskType } from '@hcengineering/task'
import { getPublicLink } from '@hcengineering/server-guest-resources'
import task, { TaskType, 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'
@@ -59,7 +60,7 @@ import {
projectValue,
supportedGithubTypes
} from './githubTypes'
import { appendGuestLink, stripGuestLink } from './guest'
import { stripGuestLink } from './guest'
import { syncConfig } from './syncConfig'
import {
collectUpdate,
@@ -998,13 +999,7 @@ export abstract class IssueSyncManagerBase {
}
if (platformUpdate.description != null) {
// Need to convert to markdown
const pp = async (nodes: MarkupNode): Promise<void> => {
await appendGuestLink(this.client, doc, nodes, this.provider.getWorkspaceId(), this.provider.getBranding())
}
issueUpdate.body = await this.provider.getMarkdown(
platformUpdate.description ?? '',
info.allowOpenInHuly === true ? pp : undefined
)
issueUpdate.body = await this.provider.getMarkdown(platformUpdate.description ?? '')
issueData.description = await this.provider.getMarkup(
container.container,
issueUpdate.body ?? '',
@@ -1270,7 +1265,7 @@ export abstract class IssueSyncManagerBase {
if (existing !== undefined && deleteExisting) {
const childItems = await derivedClient.findAll(github.class.DocSyncInfo, {
parentUrl: (issueExternal.url ?? '').toLowerCase()
parent: (issueExternal.url ?? '').toLowerCase()
})
for (const u of childItems) {
// We need just to clean all of them, since child's for issue are comments for now.
@@ -1315,4 +1310,35 @@ export abstract class IssueSyncManagerBase {
this.provider.sync()
}
}
async addHulyLink (
info: DocSyncInfo,
syncResult: DocumentUpdate<DocSyncInfo>,
object: Doc,
external: IssueExternalData,
container: ContainerFocus
): Promise<void> {
const repository = await this.provider.getRepositoryById(info.repository)
if (repository !== undefined) {
syncResult.addHulyLink = false
const publicLink = await getPublicLink(
object,
this.client,
this.provider.getWorkspaceId(),
false,
this.provider.getBranding()
)
// We need to create comment on Github about issue is connected.
await container.container.octokit.rest.issues.createComment({
owner: repository.owner?.login as string,
repo: repository.name,
issue_number: external.number,
body: `<p>Connected to <b><a href="${publicLink}">Huly&reg;: ${(object as Task).identifier}</a></b></p>`,
headers: {
'X-GitHub-Api-Version': '2022-11-28'
}
})
}
}
}
+14 -9
View File
@@ -19,9 +19,9 @@ import core, {
TxOperations,
cutObjectArray,
generateId,
makeDocCollabId,
makeCollabId,
makeCollabJsonId,
makeCollabId
makeDocCollabId
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -46,7 +46,6 @@ import {
githubSyncVersion
} from '../types'
import { IssueExternalData, issueDetails } from './githubTypes'
import { appendGuestLink } from './guest'
import { GithubIssueData, IssueSyncManagerBase, IssueSyncTarget, IssueUpdate, WithMarkup } from './issueBase'
import { syncConfig } from './syncConfig'
import { getSince, gqlp, guessStatus, isGHWriteAllowed, syncRunner } from './utils'
@@ -307,7 +306,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
external: issueExternal,
externalVersion: githubExternalSyncVersion,
lastModified: new Date(issueExternal.updatedAt).getTime(),
allowOpenInHuly: true
addHulyLink: true
})
// We need trigger comments, if their sync data created before
@@ -332,6 +331,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
if (container?.container === undefined) {
return { needSync: githubSyncVersion }
}
let needCreateConnectedAtHuly = info.addHulyLink === true
if (
(container.project.projectNodeId === undefined ||
!container.container.projectStructure.has(container.project._id)) &&
@@ -413,12 +415,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
url: issueExternal.url.toLowerCase(),
githubNumber: issueExternal.number,
lastModified: new Date(issueExternal.updatedAt).getTime(),
allowOpenInHuly: true,
addHulyLink: false, // Do not need, since we create comment on Github about issue is connected.
current: {
title: issueExternal.title,
description: await this.provider.getMarkup(container.container, issueExternal.body, this.stripGuestLink)
}
}
needCreateConnectedAtHuly = true
await derivedClient.update(info, update)
info.external = update.external
info.externalVersion = update.externalVersion
@@ -462,6 +465,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}
}
if (existing !== undefined && issueExternal !== undefined && needCreateConnectedAtHuly) {
await this.addHulyLink(info, syncResult, existing, issueExternal, container)
}
return {
...syncResult,
issueExternal,
@@ -837,10 +845,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}`
const body =
(await this.provider.getMarkdown(existingIssue.description, async (nodes) => {
await appendGuestLink(this.client, existing, nodes, this.provider.getWorkspaceId(), this.provider.getBranding())
})) ?? ''
const body = (await this.provider.getMarkdown(existingIssue.description)) ?? ''
if (isGHWriteAllowed()) {
const response:
| {
@@ -33,6 +33,7 @@ import {
ExternalSyncField,
IntegrationContainer,
IntegrationManager,
githubExternalSyncVersion,
githubSyncVersion
} from '../types'
import {
@@ -353,7 +354,16 @@ export class ProjectsSyncManager implements DocSyncManager {
syncDocs: DocSyncInfo[],
repository: GithubIntegrationRepository,
project: GithubProject
): Promise<void> {}
): Promise<void> {
for (const d of syncDocs) {
if (d.objectClass === tracker.class.Milestone) {
// no external data for doc
await derivedClient.update<DocSyncInfo>(d, {
externalVersion: githubExternalSyncVersion
})
}
}
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:issues`)
@@ -357,7 +357,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
external: pullRequestExternal,
externalVersion: githubExternalSyncVersion,
derivedVersion: '',
allowOpenInHuly: true,
addHulyLink: true,
lastModified,
lastGithubUser: account
})
@@ -961,6 +961,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (container?.container === undefined) {
return { needSync: githubSyncVersion }
}
const needCreateConnectedAtHuly = info.addHulyLink === true
if (
(container.project.projectNodeId === undefined ||
!container.container.projectStructure.has(container.project._id)) &&
@@ -991,6 +992,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const syncResult = await this.syncToTarget(target, 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
+1 -1
View File
@@ -1202,7 +1202,7 @@ export class GithubWorker implements IntegrationManager {
const projects: GithubProject[] = []
const repositories: GithubIntegrationRepository[] = []
const allProjects = await this.liveQuery.queryFind<GithubProject>(github.mixin.GithubProject, {})
const allProjects = await this.liveQuery.queryFind<GithubProject>(github.mixin.GithubProject, { archived: false })
const allRepositories = await this.liveQuery.queryFind(github.class.GithubIntegrationRepository, { enabled: true })
for (const it of Array.from(this.integrations.values())) {