feat: Pass tracing info with websocket (#9627)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-07-31 22:58:25 +05:00
committed by GitHub
parent fd5354a7d1
commit d32beb8ead
24 changed files with 694 additions and 440 deletions
+3 -2
View File
@@ -612,7 +612,8 @@
"PLATFORM_OPERATION_LOGGING": "true",
"FRONT_URL": "http://localhost:8080",
"PORT": "3500",
"STATS_URL": "http://huly.local:4900"
"STATS_URL": "http://huly.local:4900",
"OTEL_EXPORTER_OTLP_ENDPOINT": "http://huly.local:4318/v1/traces"
},
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"sourceMaps": true,
@@ -665,7 +666,7 @@
"SECRET": "secret",
"ACCOUNTS_URL": "http://localhost:3000",
"QUEUE_CONFIG": "localhost:19092",
"QUEUE_REGION": "cockroach",
"QUEUE_REGION": "cockroach"
},
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"runtimeVersion": "20",
+28 -12
View File
@@ -18,6 +18,7 @@ import {
import {
context,
metrics as otelMetrics,
propagation,
Span,
SpanStatusCode,
trace,
@@ -131,31 +132,37 @@ export class OpenTelemetryMetricsContext implements MeasureContext {
fullParams?: FullParamsType
logger?: MeasureLogger
span?: WithOptions['span'] // By default true
meta?: Record<string, string | number | boolean>
}
): MeasureContext {
let span: Span | undefined
let childContext: Context = context.active()
if (opt?.span === true) {
childContext =
this.span !== undefined
? trace.setSpan(this.context ?? context.active(), this.span)
: this.context ?? context.active()
span = this.tracer.startSpan(name, undefined, childContext)
let _span: Span | undefined
let childContext: Context | undefined
if (opt?.span === true || opt?.span === 'inherit') {
childContext = opt?.span === 'inherit' ? context.active() : this.context ?? context.active()
if (opt.meta !== undefined && Object.keys(opt.meta).length > 0) {
// We need to set meta params
childContext = propagation.extract(childContext ?? context.active(), opt.meta)
}
_span = this.tracer.startSpan(name, undefined, childContext)
const spanParams = [...Object.entries(params)]
for (const [k, v] of spanParams) {
span?.setAttribute(k, v as any)
_span?.setAttribute(k, v as any)
}
}
if (opt?.span === 'disable') {
childContext = suppressTracing(childContext)
childContext = suppressTracing(childContext ?? context.active())
}
if (childContext !== undefined && _span !== undefined) {
childContext = trace.setSpan(childContext, _span)
}
const result = new OpenTelemetryMetricsContext(
name,
this.tracer,
childContext,
span,
_span,
params,
opt?.fullParams ?? {},
childMetrics(this.metrics, [name]),
@@ -170,6 +177,14 @@ export class OpenTelemetryMetricsContext implements MeasureContext {
return result
}
extractMeta (): Record<string, string | number | boolean> {
const headers: Record<string, string> = {}
if (this.context !== undefined) {
propagation.inject(this.context, headers)
}
return headers
}
with<T>(
name: string,
params: ParamsType,
@@ -180,7 +195,8 @@ export class OpenTelemetryMetricsContext implements MeasureContext {
const c = this.newChild(name, opt?.inheritParams === true ? { ...this.params, ...params } : params, {
fullParams,
logger: this.logger,
span: opt?.span ?? true
span: opt?.span ?? true,
meta: opt?.meta
})
let needFinally = true
try {
+8
View File
@@ -157,6 +157,10 @@ export class MeasureMetricsContext implements MeasureContext {
}
}
extractMeta (): Record<string, string | number | boolean> {
return {}
}
withSync<T>(
name: string,
params: ParamsType,
@@ -226,6 +230,10 @@ export class NoMetricsContext implements MeasureContext {
return r instanceof Promise ? r : Promise.resolve(r)
}
extractMeta (): Record<string, string | number | boolean> {
return {}
}
withSync<T>(
name: string,
params: ParamsType,
+7 -1
View File
@@ -66,9 +66,12 @@ export interface MeasureLogger {
}
export interface WithOptions {
span?: true | false | 'disable' | 'skip' // 'none' means no span will be created, 'disable' means context will be tracing disabled
span?: true | false | 'disable' | 'skip' | 'inherit' // 'none' means no span will be created, 'disable' means context will be tracing disabled
log?: boolean
inheritParams?: boolean
// Passed context metadata
meta?: Record<string, string | number | boolean>
}
/**
@@ -87,6 +90,7 @@ export interface MeasureContext<Q = any> {
fullParams?: FullParamsType
logger?: MeasureLogger
span?: WithOptions['span'] // By default true
meta?: Record<string, string | number | boolean>
}
) => MeasureContext
@@ -108,6 +112,8 @@ export interface MeasureContext<Q = any> {
opt?: WithOptions
) => T
extractMeta: () => Record<string, string | number | boolean>
logger: MeasureLogger
parent?: MeasureContext
+57 -88
View File
@@ -55,14 +55,7 @@ import core, {
TxResult,
type WorkspaceUuid
} from '@hcengineering/core'
import platform, {
broadcastEvent,
getMetadata,
PlatformError,
Severity,
Status,
UNAUTHORIZED
} from '@hcengineering/platform'
import platform, { getMetadata, PlatformError, Severity, Status, UNAUTHORIZED } from '@hcengineering/platform'
import { HelloRequest, HelloResponse, type RateLimitInfo, ReqId, type Response, RPCHandler } from '@hcengineering/rpc'
import { uncompress } from 'snappyjs'
@@ -109,9 +102,6 @@ class Connection implements ClientConnection {
private dialTimer: any | undefined
private sockets = 0
private incomingTimer: any
private openAction: any
private readonly sessionId: string | undefined
@@ -492,9 +482,6 @@ class Connection implements ClientConnection {
promise.resolve(resp.result)
}
}
void broadcastEvent(client.event.NetworkRequests, this.requests.size).catch((err) => {
this.ctx.error('failed to broadcast', { err })
})
} else {
const txArr = Array.isArray(resp.result) ? (resp.result as Tx[]) : [resp.result as Tx]
@@ -508,17 +495,6 @@ class Connection implements ClientConnection {
this.handlers.forEach((handler) => {
handler(...txArr)
})
clearTimeout(this.incomingTimer)
void broadcastEvent(client.event.NetworkRequests, this.requests.size + 1).catch((err) => {
this.ctx.error('failed to broadcast', { err })
})
this.incomingTimer = setTimeout(() => {
void broadcastEvent(client.event.NetworkRequests, this.requests.size).catch((err) => {
this.ctx.error('failed to broadcast', { err })
})
}, 500)
}
}
@@ -657,10 +633,6 @@ class Connection implements ClientConnection {
wsocket.close()
return
}
// console.log('client websocket closed', socketId, ev?.reason)
void broadcastEvent(client.event.NetworkRequests, -1).catch((err) => {
this.ctx.error('failed broadcast', { err })
})
this.scheduleOpen(this.ctx, true)
}
wsocket.onopen = () => {
@@ -690,9 +662,6 @@ class Connection implements ClientConnection {
if (opened) {
console.error('client websocket error:', socketId, this.url, this.workspace, this.user)
}
void broadcastEvent(client.event.NetworkRequests, -1).catch((err) => {
this.ctx.error('failed to broadcast', { err })
})
}
}
@@ -707,83 +676,83 @@ class Connection implements ClientConnection {
allowReconnect?: boolean
overrideId?: number
}): Promise<any> {
return this.ctx.newChild('send-request', {}).with(data.method, {}, async (ctx) => {
if (this.closed) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.ConnectionClosed, {}))
}
return this.ctx.with(
'connection-' + data.method,
{},
async (ctx) => {
if (this.closed) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.ConnectionClosed, {}))
}
if (this.slowDownTimer > 0) {
// We need to wait a bit to avoid ban.
await new Promise((resolve) => setTimeout(resolve, this.slowDownTimer))
}
if (this.slowDownTimer > 0) {
// We need to wait a bit to avoid ban.
await new Promise((resolve) => setTimeout(resolve, this.slowDownTimer))
}
if (data.once === true) {
// Check if has same request already then skip
const dparams = JSON.stringify(data.params)
for (const [, v] of this.requests) {
if (v.method === data.method && JSON.stringify(v.params) === dparams) {
// We have same unanswered, do not add one more.
return
if (data.once === true) {
// Check if has same request already then skip
const dparams = JSON.stringify(data.params)
for (const [, v] of this.requests) {
if (v.method === data.method && JSON.stringify(v.params) === dparams) {
// We have same unanswered, do not add one more.
return
}
}
}
}
const id = data.overrideId ?? this.lastId++
const promise = new RequestPromise(data.method, data.params, data.handleResult)
promise.handleTime = data.measure
const id = data.overrideId ?? this.lastId++
const promise = new RequestPromise(data.method, data.params, data.handleResult)
promise.handleTime = data.measure
const w = this.waitOpenConnection(ctx)
if (w instanceof Promise) {
await w
}
if (data.method !== pingConst) {
this.requests.set(id, promise)
}
promise.sendData = (): void => {
if (this.websocket?.readyState === ClientSocketReadyState.OPEN) {
promise.startTime = Date.now()
const w = this.waitOpenConnection(ctx)
if (w instanceof Promise) {
await w
}
if (data.method !== pingConst) {
this.requests.set(id, promise)
}
promise.sendData = (): void => {
if (this.websocket?.readyState === ClientSocketReadyState.OPEN) {
promise.startTime = Date.now()
if (data.method !== pingConst) {
const dta = ctx.withSync('serialize', {}, () =>
this.rpcHandler.serialize(
if (data.method !== pingConst) {
const dta = this.rpcHandler.serialize(
{
method: data.method,
params: data.params,
meta: ctx.extractMeta(),
id,
time: Date.now()
},
this.binaryMode
)
)
ctx.withSync('send-data', {}, () => this.websocket?.send(dta))
} else {
this.websocket?.send(pingConst)
this.websocket?.send(dta)
} else {
this.websocket?.send(pingConst)
}
}
}
}
if (data.allowReconnect ?? true) {
promise.reconnect = () => {
setTimeout(async () => {
// In case we don't have response yet.
if (this.requests.has(id) && ((await data.retry?.()) ?? true)) {
promise.sendData()
}
}, 50)
if (data.allowReconnect ?? true) {
promise.reconnect = () => {
setTimeout(async () => {
// In case we don't have response yet.
if (this.requests.has(id) && ((await data.retry?.()) ?? true)) {
promise.sendData()
}
}, 50)
}
}
}
ctx.withSync('send-data', {}, () => {
promise.sendData()
})
void ctx
.with('broadcast-event', {}, () => broadcastEvent(client.event.NetworkRequests, this.requests.size))
.catch((err) => {
this.ctx.error('failed to broadcast', { err })
})
if (data.method !== pingConst) {
return await promise.promise
if (data.method !== pingConst) {
return await promise.promise
}
},
undefined,
{
span: 'inherit'
}
})
)
}
loadModel (last: Timestamp, hash?: string): Promise<Tx[] | LoadModelResponse> {
-3
View File
@@ -96,8 +96,5 @@ export default plugin(clientId, {
},
function: {
GetClient: '' as Resource<ClientFactory>
},
event: {
NetworkRequests: '' as Metadata<string>
}
})
+2
View File
@@ -30,6 +30,8 @@ export interface Request<P extends any[]> {
method: string
params: P
meta?: Record<string, string | number | boolean>
time?: number // Server time to perform operation
}
+4 -1
View File
@@ -1309,7 +1309,10 @@ export class TSessionManager implements SessionManager {
this.createOpContext(callTx, requestCtx, pipeline, request.id, service, ws, rateLimit),
...params
]),
{ ...request, user: service.getUser, socialId: service.getRawAccount().primarySocialId }
{ ...request, user: service.getUser, socialId: service.getRawAccount().primarySocialId },
{
meta: request.meta
}
)
})
} catch (err: any) {
+3 -1
View File
@@ -5,7 +5,7 @@
import client, { ClientSocket } from '@hcengineering/client'
import clientResources from '@hcengineering/client-resources'
import { Client, ClientConnectEvent, systemAccountUuid, WorkspaceUuid } from '@hcengineering/core'
import { Client, ClientConnectEvent, systemAccountUuid, WorkspaceUuid, type MeasureContext } from '@hcengineering/core'
import { setMetadata } from '@hcengineering/platform'
import { getTransactorEndpoint } from '@hcengineering/server-client'
import { generateToken } from '@hcengineering/server-token'
@@ -16,6 +16,7 @@ import config from './config'
* @public
*/
export async function createPlatformClient (
ctx: MeasureContext,
workspace: WorkspaceUuid,
timeout: number,
reconnect?: (event: ClientConnectEvent, data: any) => Promise<void>
@@ -37,6 +38,7 @@ export async function createPlatformClient (
const connection = await (
await clientResources()
).function.GetClient(token, endpoint, {
ctx,
onConnect: reconnect,
useGlobalRPCHandler: true
})
+66 -37
View File
@@ -308,11 +308,11 @@ export class PlatformWorker {
const oldWorker = this.clients.get(oldWorkspace) as GithubWorker
if (oldWorker !== undefined) {
await this.removeInstallationFromWorkspace(oldWorker.client, installationId)
await oldWorker.reloadRepositories(installationId)
await oldWorker.reloadRepositories(ctx, installationId)
} else {
let client: Client | undefined
try {
;({ client } = await createPlatformClient(oldWorkspace, 30000))
;({ client } = await createPlatformClient(ctx, oldWorkspace, 30000))
await this.removeInstallationFromWorkspace(oldWorker, installationId)
await client.close()
} catch (err: any) {
@@ -324,7 +324,7 @@ export class PlatformWorker {
await this.updateInstallation(installationId)
const worker = this.clients.get(workspace) as GithubWorker
await worker?.reloadRepositories(installationId)
await worker?.reloadRepositories(ctx, installationId)
worker?.triggerUpdate()
this.triggerCheckWorkspaces()
@@ -366,7 +366,7 @@ export class PlatformWorker {
await this.updateInstallation(installationId)
const worker = this.clients.get(workspace) as GithubWorker
await worker?.reloadRepositories(installationId)
await worker?.reloadRepositories(ctx, installationId)
worker?.triggerUpdate()
this.triggerCheckWorkspaces()
@@ -396,7 +396,7 @@ export class PlatformWorker {
}
})
await this.handleInstallationEventDelete(installationId)
await this.handleInstallationEventDelete(ctx, installationId)
} else {
await this.removeInstallationNoClient(workspace, ctx, installationId)
}
@@ -410,7 +410,7 @@ export class PlatformWorker {
): Promise<void> {
let client: Client | undefined
try {
const { client, endpoint } = await createPlatformClient(workspace, 30000)
const { client, endpoint } = await createPlatformClient(ctx, workspace, 30000)
ctx.info('connected to github', { workspace, endpoint })
const githubEnabled = (await client.findOne(core.class.PluginConfiguration, { pluginId: githubId }))?.enabled
@@ -426,12 +426,15 @@ export class PlatformWorker {
}
}
async requestGithubAccessToken (payload: {
workspace: WorkspaceUuid
code: string
state: string
accountId: PersonId // Primary social Id
}): Promise<void> {
async requestGithubAccessToken (
ctx: MeasureContext,
payload: {
workspace: WorkspaceUuid
code: string
state: string
accountId: PersonId // Primary social Id
}
): Promise<void> {
try {
const uri =
'https://github.com/login/oauth/access_token?' +
@@ -450,7 +453,7 @@ export class PlatformWorker {
const resultJson = await result.json()
if (resultJson.error !== undefined) {
await this.updateAccountAuthRecord(payload, { error: null }, undefined, false)
await this.updateAccountAuthRecord(ctx, payload, { error: null }, undefined, false)
} else {
const okit = new Octokit({
auth: resultJson.access_token,
@@ -484,6 +487,7 @@ export class PlatformWorker {
// Update workspace client login info.
await this.updateAccountAuthRecord(
ctx,
payload,
{
login: dta._id,
@@ -498,11 +502,12 @@ export class PlatformWorker {
}
} catch (err: any) {
Analytics.handleError(err)
await this.updateAccountAuthRecord(payload, { error: errorToObj(err) }, undefined, false)
await this.updateAccountAuthRecord(ctx, payload, { error: errorToObj(err) }, undefined, false)
}
}
private async updateAccountAuthRecord (
ctx: MeasureContext,
payload: { workspace: WorkspaceUuid, accountId: PersonId },
update: DocumentUpdate<GithubAuthentication>,
dta: GithubUserRecord | undefined,
@@ -515,7 +520,7 @@ export class PlatformWorker {
platformClient = this.clients.get(payload.workspace)?.client
if (platformClient === undefined) {
shouldClose = true
;({ client: platformClient } = await createPlatformClient(payload.workspace, 30000))
;({ client: platformClient } = await createPlatformClient(ctx, payload.workspace, 30000))
}
const client = new TxOperations(platformClient, payload.accountId)
@@ -702,7 +707,7 @@ export class PlatformWorker {
await syncUser(this.ctx, dta, personAuths[0], client, payload.accountId)
} catch (err: any) {
if (err.response?.data?.message === 'Bad credentials') {
await this.revokeUserAuth(dta)
await this.revokeUserAuth(ctx, dta)
} else {
this.ctx.error(`Failed to sync user ${dta._id}`, { error: errorToObj(err) })
}
@@ -721,7 +726,7 @@ export class PlatformWorker {
}
}
async checkRefreshToken (auth: GithubUserRecord, force: boolean = false): Promise<void> {
async checkRefreshToken (ctx: MeasureContext, auth: GithubUserRecord, force: boolean = false): Promise<void> {
if (auth.refreshToken != null && auth.expiresIn != null && auth.expiresIn < Date.now() / 1000) {
const uri =
'https://github.com/login/oauth/access_token?' +
@@ -742,7 +747,7 @@ export class PlatformWorker {
if (resultJson.error !== undefined) {
// We need to clear github integration info.
await this.revokeUserAuth(auth)
await this.revokeUserAuth(ctx, auth)
} else {
// Update okit
const nowTime = Date.now() / 1000
@@ -830,11 +835,12 @@ export class PlatformWorker {
}
async handleInstallationEvent (
ctx: MeasureContext,
install: Installation,
repositories: InstallationCreatedEvent['repositories'] | InstallationUnsuspendEvent['repositories'],
enabled: boolean
): Promise<void> {
this.ctx.info('handle integration add', { installId: install.id, name: install.html_url })
ctx.info('handle integration add', { installId: install.id, name: install.html_url })
const okit = await this.app.getInstallationOctokit(install.id)
const iName = `${install.account.html_url ?? ''}`
@@ -856,7 +862,7 @@ export class PlatformWorker {
}
await worker.syncUserData(this.ctx)
await worker.reloadRepositories(install.id)
await worker.reloadRepositories(ctx, install.id)
worker.triggerUpdate()
worker.triggerSync()
@@ -866,10 +872,10 @@ export class PlatformWorker {
this.triggerCheckWorkspaces()
}
async handleInstallationEventDelete (installId: number): Promise<void> {
async handleInstallationEventDelete (ctx: MeasureContext, installId: number): Promise<void> {
const existing = this.installations.get(installId)
this.installations.delete(installId)
this.ctx.info('handle integration delete', { installId, name: existing?.installationName })
ctx.info('handle integration delete', { installId, name: existing?.installationName })
const interg = this.integrations.filter((it) => it.installationId.includes(installId))
@@ -1189,7 +1195,7 @@ export class PlatformWorker {
const record = await this.getAccount(sender.login)
if (record !== undefined) {
await this.revokeUserAuth(record)
await this.revokeUserAuth(webhook, record)
await this.userManager.removeUser(sender.login)
}
}
@@ -1217,7 +1223,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(github.class.GithubPullRequest, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.class.GithubPullRequest, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1230,7 +1238,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(tracker.class.Issue, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, tracker.class.Issue, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1242,7 +1252,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(chunter.class.ChatMessage, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, chunter.class.ChatMessage, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1255,7 +1267,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(github.mixin.GithubProject, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.mixin.GithubProject, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1269,7 +1283,9 @@ export class PlatformWorker {
if (repoWorker !== undefined) {
if (payload.projects_v2_item.content_type === 'Issue') {
catchEventError(
repoWorker.handleEvent(tracker.class.Issue, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, tracker.class.Issue, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1277,7 +1293,9 @@ export class PlatformWorker {
)
} else if (payload.projects_v2_item.content_type === 'PullRequest') {
catchEventError(
repoWorker.handleEvent(github.class.GithubPullRequest, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.class.GithubPullRequest, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1292,7 +1310,9 @@ export class PlatformWorker {
case 'created':
case 'unsuspend': {
catchEventError(
this.handleInstallationEvent(payload.installation, payload.repositories, true),
this.ctx.with(name, {}, (ctx) =>
this.handleInstallationEvent(ctx, payload.installation, payload.repositories, true)
),
payload.action,
name,
id,
@@ -1302,7 +1322,9 @@ export class PlatformWorker {
}
case 'suspend': {
catchEventError(
this.handleInstallationEvent(payload.installation, payload.repositories, false),
this.ctx.with(name, {}, (ctx) =>
this.handleInstallationEvent(ctx, payload.installation, payload.repositories, false)
),
payload.action,
name,
id,
@@ -1312,7 +1334,7 @@ export class PlatformWorker {
}
case 'deleted': {
catchEventError(
this.handleInstallationEventDelete(payload.installation.id),
this.ctx.with(name, {}, (ctx) => this.handleInstallationEventDelete(ctx, payload.installation.id)),
payload.action,
name,
id,
@@ -1329,7 +1351,7 @@ export class PlatformWorker {
return
}
catchEventError(
worker.reloadRepositories(payload.installation.id),
this.ctx.with(name, {}, (ctx) => worker.reloadRepositories(ctx, payload.installation.id)),
payload.action,
name,
id,
@@ -1345,7 +1367,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(github.class.GithubReview, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.class.GithubReview, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1358,7 +1382,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(github.class.GithubReviewComment, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.class.GithubReviewComment, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1370,7 +1396,9 @@ export class PlatformWorker {
const repoWorker = this.getWorker(payload.installation?.id)
if (repoWorker !== undefined) {
catchEventError(
repoWorker.handleEvent(github.class.GithubReviewThread, payload.installation?.id, payload),
this.ctx.with(name, {}, (ctx) =>
repoWorker.handleEvent(ctx, github.class.GithubReviewThread, payload.installation?.id, payload)
),
payload.action,
name,
id,
@@ -1380,9 +1408,10 @@ export class PlatformWorker {
})
}
public async revokeUserAuth (record: GithubUserRecord): Promise<void> {
public async revokeUserAuth (ctx: MeasureContext, record: GithubUserRecord): Promise<void> {
for (const [ws, acc] of Object.entries(record.accounts)) {
await this.updateAccountAuthRecord(
ctx,
{ workspace: ws as WorkspaceUuid, accountId: acc },
{ login: record._id },
undefined,
+1 -1
View File
@@ -129,7 +129,7 @@ export async function start (ctx: MeasureContext, brandingMap: BrandingMap): Pro
state: payloadData.state
})
await ctx.with('request-github-access-token', {}, async (ctx) => {
await worker.requestGithubAccessToken({
await worker.requestGithubAccessToken(ctx, {
workspace: decodedToken.workspace,
accountId: payloadData.accountId,
code: payloadData.code,
+44 -21
View File
@@ -9,7 +9,8 @@ import core, {
DocumentUpdate,
MeasureContext,
Ref,
TxOperations
TxOperations,
withContext
} from '@hcengineering/core'
import github, { DocSyncInfo, GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
import { LiveQuery } from '@hcengineering/query'
@@ -43,7 +44,6 @@ export class CommentSyncManager implements DocSyncManager {
externalDerivedSync = false
constructor (
readonly ctx: MeasureContext,
readonly client: TxOperations,
readonly lq: LiveQuery
) {}
@@ -53,10 +53,17 @@ export class CommentSyncManager implements DocSyncManager {
}
eventSync = new Map<string, Promise<void>>()
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('comments-handle-event')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
await this.createCommentPromise
const event = evt as IssueCommentEvent
this.ctx.info('comments:handleEvent', {
ctx.info('comments:handleEvent', {
action: event.action,
login: event.sender.login,
workspace: this.provider.getWorkspaceId()
@@ -71,13 +78,14 @@ export class CommentSyncManager implements DocSyncManager {
}
await this.eventSync.get(event.issue.url)
const promise = this.processEvent(event, derivedClient, integration)
const promise = this.processEvent(ctx, event, derivedClient, integration)
this.eventSync.set(event.issue.url, promise)
await promise
this.eventSync.delete(event.issue.url)
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -99,7 +107,7 @@ export class CommentSyncManager implements DocSyncManager {
if (commentExternal !== undefined) {
try {
await this.deleteGithubDocument(container, account, commentExternal.node_id)
await this.deleteGithubDocument(ctx, container, account, commentExternal.node_id)
} catch (err: any) {
let cnt = false
if (Array.isArray(err.errors)) {
@@ -119,13 +127,18 @@ export class CommentSyncManager implements DocSyncManager {
}
if (existing !== undefined && deleteExisting) {
await deleteObjects(this.ctx, this.client, [existing], account)
await deleteObjects(ctx, this.client, [existing], account)
}
return true
}
async deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void> {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void> {
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation deleteComment($commentID: ID!) {
deleteIssueComment(
@@ -142,13 +155,14 @@ export class CommentSyncManager implements DocSyncManager {
}
private async processEvent (
ctx: MeasureContext,
event: IssueCommentEvent,
derivedClient: TxOperations,
integration: IntegrationContainer
): Promise<void> {
const { repository: repo } = await this.provider.getProjectAndRepository(event.repository.node_id)
if (repo === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
repository: event.repository,
workspace: this.provider.getWorkspaceId()
})
@@ -233,7 +247,9 @@ export class CommentSyncManager implements DocSyncManager {
}
}
@withContext('comments-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -252,7 +268,7 @@ export class CommentSyncManager implements DocSyncManager {
}
// If no external document, we need to create it.
this.createCommentPromise = this.createGithubComment(container, existing, info, parent, derivedClient)
this.createCommentPromise = this.createGithubComment(ctx, container, existing, info, parent, derivedClient)
return await this.createCommentPromise
}
const comment = info.external as CommentExternalData
@@ -279,16 +295,17 @@ export class CommentSyncManager implements DocSyncManager {
return { needSync: githubSyncVersion, current: messageData }
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
} else {
await this.handleDiffUpdate(existing, info, messageData, container, parent, comment, account)
await this.handleDiffUpdate(ctx, existing, info, messageData, container, parent, comment, account)
}
return { current: messageData, needSync: githubSyncVersion }
}
private async handleDiffUpdate (
ctx: MeasureContext,
existing: Doc,
info: DocSyncInfo,
messageData: MessageData,
@@ -327,7 +344,7 @@ export class CommentSyncManager implements DocSyncManager {
if (Object.keys(platformUpdate).length > 0) {
// Check and update body with external
const okit = (await this.provider.getOctokit(existing.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existing.modifiedBy)) ?? container.container.octokit
const mdown = await this.provider.getMarkdown(existingComment.message)
if (mdown.trim().length > 0) {
await okit?.rest.issues.updateComment({
@@ -382,6 +399,7 @@ export class CommentSyncManager implements DocSyncManager {
}
async createGithubComment (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
info: DocSyncInfo,
@@ -399,7 +417,7 @@ export class CommentSyncManager implements DocSyncManager {
return {}
}
const chatMessage = existing as ChatMessage
const okit = (await this.provider.getOctokit(chatMessage.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, chatMessage.modifiedBy)) ?? container.container.octokit
// No external version yet, create it.
try {
@@ -430,12 +448,14 @@ export class CommentSyncManager implements DocSyncManager {
return { needSync: githubSyncVersion }
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
}
@withContext('comments-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -452,11 +472,13 @@ export class CommentSyncManager implements DocSyncManager {
this.provider.sync()
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:comment`)
}
@withContext('comments-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -500,25 +522,26 @@ export class CommentSyncManager implements DocSyncManager {
break
}
const comments: CommentExternalData[] = data.data as any
this.ctx.info('retrieve comments for', {
ctx.info('retrieve comments for', {
repo: repo.name,
comments: comments.length,
used: data.headers['x-ratelimit-used'],
limit: data.headers['x-ratelimit-limit'],
workspace: this.provider.getWorkspaceId()
})
await this.syncComments(repo, comments, derivedClient)
await this.syncComments(ctx, repo, comments, derivedClient)
this.provider.sync()
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
}
integration.synchronized.add(syncKey)
}
}
async syncComments (
ctx: MeasureContext,
repo: GithubIntegrationRepository,
comments: CommentExternalData[],
derivedClient: TxOperations
@@ -571,7 +594,7 @@ export class CommentSyncManager implements DocSyncManager {
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
}
}
}
@@ -85,7 +85,6 @@ export type IssueUpdate = DocumentUpdate<WithMarkup<Issue>>
export abstract class IssueSyncManagerBase {
provider!: IntegrationManager
constructor (
readonly ctx: MeasureContext,
readonly client: TxOperations,
readonly lq: LiveQuery,
readonly collaborator: CollaboratorClient
@@ -114,6 +113,7 @@ export abstract class IssueSyncManagerBase {
}
async handleUpdate (
ctx: MeasureContext,
external: IssueExternalData,
derivedClient: TxOperations,
update: IssueUpdate,
@@ -156,7 +156,7 @@ export abstract class IssueSyncManagerBase {
await this.collaborator.updateMarkup(collabId, update.description)
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
}
} else {
delete update.description
@@ -248,6 +248,7 @@ export abstract class IssueSyncManagerBase {
}
abstract performIssueFieldsUpdate (
ctx: MeasureContext,
info: DocSyncInfo,
existing: WithMarkup<Issue>,
platformUpdate: DocumentUpdate<Issue>,
@@ -258,9 +259,16 @@ export abstract class IssueSyncManagerBase {
account: PersonId
): Promise<boolean>
abstract afterSync (existing: Issue, account: PersonId, issueExternal: any, info: DocSyncInfo): Promise<void>
abstract afterSync (
ctx: MeasureContext,
existing: Issue,
account: PersonId,
issueExternal: any,
info: DocSyncInfo
): Promise<void>
async handleDiffUpdate (
ctx: MeasureContext,
container: ContainerFocus,
existing: WithMarkup<Issue>,
info: DocSyncInfo,
@@ -271,7 +279,7 @@ export abstract class IssueSyncManagerBase {
): Promise<DocumentUpdate<DocSyncInfo>> {
let needUpdate = false
if (!this.client.getHierarchy().hasMixin(existing, github.mixin.GithubIssue)) {
await this.ctx.with(
await ctx.with(
'create mixin issue: GithubIssue',
{},
async () => {
@@ -318,7 +326,7 @@ export abstract class IssueSyncManagerBase {
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
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
// Remove current same values from update
for (const [k, v] of Object.entries(update)) {
@@ -343,7 +351,7 @@ export abstract class IssueSyncManagerBase {
}
if (pv != null && pv !== v) {
// We have conflict of values, assume platform is more proper one.
this.ctx.error('conflict', { id: existing.identifier, k })
ctx.error('conflict', { id: existing.identifier, k })
// Assume platform change is more important in case of conflict values.
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete (update as any)[k]
@@ -358,6 +366,7 @@ export abstract class IssueSyncManagerBase {
if (container !== undefined && okit !== undefined) {
// Check and update issue fields.
needExternalSync = await this.performIssueFieldsUpdate(
ctx,
info,
existing,
platformUpdate,
@@ -382,7 +391,7 @@ export abstract class IssueSyncManagerBase {
// Update collaborative description
if (update.description !== undefined) {
this.ctx.info(`<= perform ${issueExternal.url} update to collaborator`, {
ctx.info(`<= perform ${issueExternal.url} update to collaborator`, {
workspace: this.provider.getWorkspaceId()
})
try {
@@ -392,21 +401,21 @@ export abstract class IssueSyncManagerBase {
await this.collaborator.updateMarkup(collabId, description)
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('error during description update', err)
ctx.error('error during description update', err)
}
delete update.description
}
if (Object.keys(update).length > 0) {
// We have some fields to update of existing from external
this.ctx.info(`<= perform ${issueExternal.url} update to platform`, {
ctx.info(`<= perform ${issueExternal.url} update to platform`, {
...update,
workspace: this.provider.getWorkspaceId()
})
await this.client.update(existingIssue, update, false, new Date().getTime(), accountGH)
}
await this.afterSync(existingIssue, accountGH, issueExternal, info)
await this.afterSync(ctx, existingIssue, accountGH, issueExternal, info)
// 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,
@@ -564,11 +573,12 @@ export abstract class IssueSyncManagerBase {
update.assignee = assignees?.[0] ?? null
}
if (Object.keys(update).length > 0) {
await this.handleUpdate(issueExternal, derivedClient, update, account, container.project, false)
await this.handleUpdate(ctx, issueExternal, derivedClient, update, account, container.project, false)
}
}
async syncIssues (
ctx: MeasureContext,
_class: Ref<Class<Doc>>,
repo: GithubIntegrationRepository,
issues: IssueExternalData[],
@@ -592,14 +602,14 @@ export abstract class IssueSyncManagerBase {
for (const issue of issues) {
try {
if (issue.url === undefined && Object.keys(issue).length === 0) {
this.ctx.info('Retrieve empty document', { repo: repo.name, workspace: this.provider.getWorkspaceId() })
ctx.info('Retrieve empty document', { repo: repo.name, workspace: this.provider.getWorkspaceId() })
continue
}
const existing =
syncInfo.find((it) => it.url.toLowerCase() === issue.url.toLowerCase()) ??
syncInfo.find((it) => (it.external as IssueExternalData)?.id === issue.id)
if (existing === undefined && syncDocs === undefined) {
this.ctx.info('Create sync doc', { url: issue.url, workspace: this.provider.getWorkspaceId() })
ctx.info('Create sync doc', { url: issue.url, workspace: this.provider.getWorkspaceId() })
await ops.createDoc<DocSyncInfo>(github.class.DocSyncInfo, repo.githubProject, {
url: issue.url.toLowerCase(),
needSync: '',
@@ -618,7 +628,7 @@ export abstract class IssueSyncManagerBase {
}
const externalEqual = deepEqual(existing.external, issue) && existing.repository === repo._id
if (!externalEqual || existing.externalVersion !== githubExternalSyncVersion) {
this.ctx.info('Update sync doc(extarnal changes)', {
ctx.info('Update sync doc(extarnal changes)', {
url: issue.url,
workspace: this.provider.getWorkspaceId()
})
@@ -626,7 +636,7 @@ export abstract class IssueSyncManagerBase {
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)
await this.performDocumentExternalSync(ctx, existing, existing.external, issue, derivedClient)
}
await ops.diffUpdate(
@@ -646,7 +656,7 @@ export abstract class IssueSyncManagerBase {
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error(err)
ctx.error(err)
}
}
// if no sync doc, mark it as synchronized
@@ -661,9 +671,15 @@ export abstract class IssueSyncManagerBase {
this.provider.sync()
}
abstract deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void>
abstract deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void>
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -684,7 +700,7 @@ export abstract class IssueSyncManagerBase {
if (issueExternal !== undefined) {
try {
await this.deleteGithubDocument(container, account, issueExternal.id)
await this.deleteGithubDocument(ctx, container, account, issueExternal.id)
} catch (err: any) {
let cnt = false
if (Array.isArray(err.errors)) {
@@ -698,7 +714,7 @@ export abstract class IssueSyncManagerBase {
}
if (!cnt) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
await derivedClient.update(info, { error: errorToObj(err), needSync: githubSyncVersion })
return false
}
@@ -714,7 +730,7 @@ export abstract class IssueSyncManagerBase {
await derivedClient.remove(u)
}
await deleteObjects(this.ctx, this.client, [existing], account)
await deleteObjects(ctx, this.client, [existing], account)
}
return true
}
+76 -44
View File
@@ -8,10 +8,10 @@
*/
import { Analytics } from '@hcengineering/analytics'
import core, {
PersonId,
AttachedData,
Doc,
DocumentUpdate,
PersonId,
Ref,
SortingOrder,
Status,
@@ -20,7 +20,9 @@ import core, {
generateId,
makeCollabId,
makeCollabJsonId,
makeDocCollabId
makeDocCollabId,
withContext,
type MeasureContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -64,7 +66,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
return assignees
}
@withContext('issues-handleEvent')
async handleEvent<T = IssuesEvent | ProjectsV2ItemEvent>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
@@ -72,7 +76,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
await this.createPromise
const event = evt as IssuesEvent | ProjectsV2ItemEvent
this.ctx.info('issue:handleEvent', {
ctx.info('issue:handleEvent', {
nodeId: (event as IssuesEvent).issue?.html_url ?? (event as ProjectsV2ItemEvent)?.projects_v2_item.node_id,
action: event.action,
login: event.sender.login,
@@ -96,7 +100,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const issueEvent = event as IssuesEvent
const { project, repository } = await this.provider.getProjectAndRepository(issueEvent.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
repository: issueEvent.repository.name,
nodeId: issueEvent.repository.node_id,
workspace: this.provider.getWorkspaceId()
@@ -107,12 +111,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const urlId = issueEvent.issue.url
await syncRunner.exec(urlId, async () => {
await this.processEvent(issueEvent, derivedClient, repository, integration, project)
await this.processEvent(ctx, issueEvent, derivedClient, repository, integration, project)
})
}
}
private async processEvent (
ctx: MeasureContext,
event: IssuesEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -142,7 +147,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
externalData = response.repository.issue
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
// We need to check if we do not have sync data, we need to create by html_url
await this.createErrorSyncDataByUrl(
@@ -197,6 +202,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
await this.handleUpdate(
ctx,
externalData as IssueExternalData,
derivedClient,
update,
@@ -216,7 +222,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const update: IssueUpdate = {
assignee: persons?.[0] ?? null
}
await this.handleUpdate(externalData as IssueExternalData, derivedClient, update, account, prj, false)
await this.handleUpdate(ctx, externalData as IssueExternalData, derivedClient, update, account, prj, false)
break
}
case 'closed':
@@ -241,6 +247,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
)._id
}
await this.handleUpdate(
ctx,
externalData as IssueExternalData,
derivedClient,
update,
@@ -294,7 +301,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}
@withContext('issues-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -317,7 +326,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
if (info.repository == null) {
// No need to sync if component it not yet set
this.ctx.error('Not syncing repository === null', {
ctx.error('Not syncing repository === null', {
url: info.url,
identifier: (existing as Issue).identifier
})
@@ -332,14 +341,14 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
if (info.external === undefined && existing !== undefined) {
const repository = await this.provider.getRepositoryById(info.repository)
if (repository === undefined) {
this.ctx.error('Not syncing repository === undefined', {
ctx.error('Not syncing repository === undefined', {
url: info.url,
identifier: (existing as Issue).identifier
})
return { needSync: githubSyncVersion }
}
const description = await this.ctx.with(
const description = await ctx.with(
'query collaborative description',
{},
async () => {
@@ -350,23 +359,28 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
{ log: true }
)
this.ctx.info('create github issue', {
ctx.info('create github issue', {
title: (existing as Issue).title,
number: (existing as Issue).number,
workspace: this.provider.getWorkspaceId()
})
const createdIssueData = await this.ctx.with(
const createdIssueData = await ctx.with(
'create github issue',
{},
async () => {
this.createPromise = this.createGithubIssue(container, { ...(existing as Issue), description }, repository)
this.createPromise = this.createGithubIssue(
ctx,
container,
{ ...(existing as Issue), description },
repository
)
return await this.createPromise
},
{ id: (existing as Issue).identifier, workspace: this.provider.getWorkspaceId() },
{ log: true }
)
if (createdIssueData === undefined) {
this.ctx.error('Error create issue', { url: info.url })
ctx.error('Error create issue', { url: info.url })
return { needSync: githubSyncVersion, error: 'Unknown error on create issue' }
}
issueExternal = createdIssueData
@@ -397,7 +411,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
return { needSync: githubSyncVersion }
}
const syncResult = await this.syncToTarget(container, existing, issueExternal, derivedClient, info)
const syncResult = await this.syncToTarget(ctx, container, existing, issueExternal, derivedClient, info)
if (externalWasCreated && existing !== undefined) {
// Create child documents
@@ -412,7 +426,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
break
}
await this.provider.doSyncFor(attachedDocs, container.project)
await this.provider.doSyncFor(ctx, attachedDocs, container.project)
for (const child of attachedDocs) {
await derivedClient.update(child, { createId })
}
@@ -430,6 +444,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
async syncToTarget (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
issueExternal: IssueExternalData,
@@ -463,13 +478,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// 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', { identifier: (existing as Issue)?.identifier })
ctx.error('Missing required task type', { identifier: (existing as Issue)?.identifier })
return { needSync: githubSyncVersion }
}
if (existing === undefined) {
try {
this.ctx.info('create platform issue', {
ctx.info('create platform issue', {
url: issueExternal.url,
title: issueExternal.title,
workspace: this.provider.getWorkspaceId()
@@ -483,7 +498,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// No repository, it probable deleted
return { needSync: githubSyncVersion }
}
await this.ctx.with(
await ctx.with(
'create platform issue',
{},
async () => {
@@ -521,12 +536,12 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
return { needSync: githubSyncVersion, error: JSON.stringify(err) }
}
} else {
try {
const description = await this.ctx.with(
const description = await ctx.with(
'query collaborative description',
{},
async () => {
@@ -537,11 +552,12 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
{ log: true }
)
const updateResult = await this.ctx.with(
const updateResult = await ctx.with(
'diff update',
{},
async () =>
await this.handleDiffUpdate(
ctx,
container,
{ ...(existing as any), description },
info,
@@ -560,15 +576,21 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('error sync', { err })
ctx.error('error sync', { err })
return { needSync: githubSyncVersion, error: JSON.stringify(err), external: issueExternal }
}
}
}
async afterSync (existing: Issue, update: DocumentUpdate<Doc>, account: PersonId): Promise<void> {}
async afterSync (
ctx: MeasureContext,
existing: Issue,
update: DocumentUpdate<Doc>,
account: PersonId
): Promise<void> {}
async performIssueFieldsUpdate (
ctx: MeasureContext,
info: DocSyncInfo,
existing: WithMarkup<Issue>,
platformUpdate: DocumentUpdate<Issue>,
@@ -630,11 +652,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
if (hasFieldStateChanges || body !== undefined) {
if (body !== undefined && !isLocked) {
await this.ctx.with(
await ctx.with(
'==> updateIssue',
{},
async () => {
this.ctx.info('update fields', {
ctx.info('update fields', {
url: issueExternal.url,
...issueUpdate,
body,
@@ -671,11 +693,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
)
issueData.description = await this.provider.getMarkupSafe(container.container, body, this.stripGuestLink)
} else if (hasFieldStateChanges) {
await this.ctx.with(
await ctx.with(
'==> updateIssue',
{},
async () => {
this.ctx.info('update fields', { ...issueUpdate, workspace: this.provider.getWorkspaceId() })
ctx.info('update fields', { ...issueUpdate, workspace: this.provider.getWorkspaceId() })
if (isGHWriteAllowed()) {
const hasOtherChanges = Object.keys(issueUpdate).length > 0
if (state === 'OPEN') {
@@ -714,13 +736,14 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
async createGithubIssue (
ctx: MeasureContext,
container: ContainerFocus,
existing: WithMarkup<Issue>,
repository: GithubIntegrationRepository
): Promise<IssueExternalData | undefined> {
const existingIssue = existing
const okit = (await this.provider.getOctokit(existingIssue.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existingIssue.modifiedBy)) ?? container.container.octokit
const repoId = repository.nodeId
@@ -760,8 +783,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}
async deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void> {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void> {
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation deleteIssue($issueID: ID!) {
deleteIssue(
@@ -871,7 +899,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
async fillBackChanges (update: DocumentUpdate<Issue>, existing: TGithubIssue, external: any): Promise<void> {}
@withContext('issues-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -897,7 +927,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const idsp = idsPart.map((it) => `"${it}"`).join(', ')
try {
const response: any = await this.ctx.with(
const response: any = await ctx.with(
'graphql.listIssue',
{},
() =>
@@ -919,19 +949,19 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const issues: IssueExternalData[] = response.nodes
if (issues.some((issue) => issue.url === undefined && Object.keys(issue).length === 0)) {
this.ctx.error('empty document content', {
ctx.error('empty document content', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(response)
})
}
await this.syncIssues(tracker.class.Issue, repo, issues, derivedClient, docsPart)
await this.syncIssues(ctx, tracker.class.Issue, repo, issues, derivedClient, docsPart)
} catch (err: any) {
if (partsize > 1) {
partsize = 1
allSyncDocs.push(...docsPart)
this.ctx.warn('issue external retrieval switch to one by one mode', {
ctx.warn('issue external retrieval switch to one by one mode', {
errors: err.errors,
msg: err.message,
workspace: this.provider.getWorkspaceId()
@@ -940,7 +970,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// We need to update issue, since it is missing on external side.
const syncDoc = syncDocs.find((it) => it.external.id === idsPart[0])
if (syncDoc !== undefined) {
this.ctx.warn('mark missing external PR', {
ctx.warn('mark missing external PR', {
errors: err.errors,
msg: err.message,
url: syncDoc.url,
@@ -961,7 +991,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
for (const d of syncDocs) {
if ((d.external as IssueExternalData).id == null) {
this.ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
// no external data for doc
await derivedClient.update<DocSyncInfo>(d, {
externalVersion: githubExternalSyncVersion
@@ -971,15 +1001,17 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
this.provider.sync()
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
}
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:issues`)
}
@withContext('issues-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -1010,7 +1042,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const since = await getSince(this.client, tracker.class.Issue, repo)
this.ctx.info('sync external issues', { repo: repo.name, since, workspace: this.provider.getWorkspaceId() })
ctx.info('sync external issues', { repo: repo.name, since, workspace: this.provider.getWorkspaceId() })
const i = integration.octokit.graphql.paginate.iterator(
`query listIssue($name: String!, $owner: String!, $since: DateTime!, $cursor: String) {
@@ -1042,21 +1074,21 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const issues: IssueExternalData[] = data.repository.issues.nodes
if (issues.some((issue) => issue.url === undefined && Object.keys(issue).length === 0)) {
this.ctx.error('empty document content', {
ctx.error('empty document content', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(data)
})
}
await this.syncIssues(tracker.class.Issue, repo, issues, derivedClient)
await this.syncIssues(ctx, tracker.class.Issue, repo, issues, derivedClient)
this.provider.sync()
}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
this.ctx.info('sync external issues - done', {
ctx.info('sync external issues - done', {
repo: repo.name,
since,
workspace: this.provider.getWorkspaceId()
@@ -17,7 +17,9 @@ import core, {
cutObjectArray,
generateId,
makeCollabId,
makeDocCollabId
makeDocCollabId,
withContext,
type MeasureContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -76,7 +78,14 @@ type GithubPullRequestUpdate = DocumentUpdate<WithMarkup<GithubPullRequest>>
export class PullRequestSyncManager extends IssueSyncManagerBase implements DocSyncManager {
externalDerivedSync = true
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('pullrequests-handleEvent')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
const _event = evt as PullRequestEvent | ProjectsV2ItemEvent
if (_event.sender.type === 'Bot') {
@@ -86,7 +95,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
return
}
}
this.ctx.info('pull request:handleEvent', {
ctx.info('pull request:handleEvent', {
nodeId:
(_event as PullRequestEvent).pull_request?.html_url ??
(_event as ProjectsV2ItemEvent).projects_v2_item?.node_id,
@@ -104,7 +113,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
name: event.repository.name,
workspace: this.provider.getWorkspaceId()
})
@@ -113,12 +122,13 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const url = event.pull_request.issue_url
await syncRunner.exec(url, async () => {
await this.processEvent(event, derivedClient, repository, integration, project)
await this.processEvent(ctx, event, derivedClient, repository, integration, project)
})
}
}
private async processEvent (
ctx: MeasureContext,
event: PullRequestEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -146,7 +156,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
)
externalData = response.repository.pullRequest
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
await this.createErrorSyncDataByUrl(
event.pull_request.html_url,
@@ -192,22 +202,22 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (event.changes.base !== undefined) {
update.base = externalData.baseRef
}
await this.handleUpdate(externalData, derivedClient, update, account, prj, false, undefined, undefined, du)
await this.handleUpdate(ctx, externalData, derivedClient, update, account, prj, false, undefined, undefined, du)
break
}
case 'review_requested': {
const update: GithubPullRequestUpdate = {}
await this.handleUpdate(externalData, derivedClient, update, account, prj, true)
await this.handleUpdate(ctx, externalData, derivedClient, update, account, prj, true)
break
}
case 'review_request_removed': {
const update: GithubPullRequestUpdate = {}
await this.handleUpdate(externalData, derivedClient, update, account, prj, true)
await this.handleUpdate(ctx, externalData, derivedClient, update, account, prj, true)
break
}
case 'converted_to_draft':
case 'ready_for_review': {
await this.handleUpdate(externalData, derivedClient, {}, account, prj, true)
await this.handleUpdate(ctx, externalData, derivedClient, {}, account, prj, true)
break
}
case 'assigned':
@@ -216,7 +226,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const update: GithubPullRequestUpdate = {
assignee: assignees?.[0] ?? null
}
await this.handleUpdate(externalData, derivedClient, update, account, prj, true)
await this.handleUpdate(ctx, externalData, derivedClient, update, account, prj, true)
break
}
case 'closed':
@@ -259,6 +269,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
})
}
await this.handleUpdate(
ctx,
externalData,
derivedClient,
update,
@@ -350,6 +361,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
async syncToTarget (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
pullRequestExternal: PullRequestExternalData,
@@ -407,7 +419,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (taskTypes.length === 0) {
// Missing required task type
this.ctx.error('Missing required task type', { url: pullRequestExternal.url })
ctx.error('Missing required task type', { url: pullRequestExternal.url })
return { needSync: githubSyncVersion }
}
@@ -415,11 +427,12 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (existing === undefined) {
try {
await this.ctx.with(
await ctx.with(
'retrieve pull request patch',
{},
() =>
(ctx) =>
this.handlePatch(
ctx,
info,
container,
pullRequestExternal,
@@ -442,10 +455,10 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
let op = this.client.apply()
let createdPullRequest: GithubPullRequest | undefined
await this.ctx.with(
await ctx.with(
'create pull request in platform',
{},
async () => {
async (ctx) => {
createdPullRequest = await this.createPullRequest(
op,
info,
@@ -475,9 +488,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (pullRequestObj !== undefined) {
op = this.client.apply()
try {
await this.todoSync(op, pullRequestObj, pullRequestExternal, info, account)
await this.todoSync(ctx, op, pullRequestObj, pullRequestExternal, info, account)
} catch (err: any) {
this.ctx.error('failed to sync todos', { err, url: pullRequestExternal.url, id: pullRequestObj._id })
ctx.error('failed to sync todos', { err, url: pullRequestExternal.url, id: pullRequestObj._id })
}
await op.commit()
}
@@ -494,18 +507,19 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
markdown
}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
} else {
try {
if (info.updatePatch === true) {
await this.ctx.with(
await ctx.with(
'update pull request patch',
{},
() =>
(ctx) =>
this.handlePatch(
ctx,
info,
container,
pullRequestExternal,
@@ -522,10 +536,10 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
)
}
const description = await this.ctx.with(
const description = await ctx.with(
'query collaborative pull request description',
{},
async () => {
async (ctx) => {
const collabId = makeDocCollabId(existing, 'description')
return await this.collaborator.getMarkup(collabId, (existing as GithubPullRequest).description)
},
@@ -533,11 +547,12 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
{ log: true }
)
const update = await this.ctx.with(
const update = await ctx.with(
'perform pull request diff update',
{},
() =>
(ctx) =>
this.handleDiffUpdate(
ctx,
container,
{ ...(existing as any), description },
info,
@@ -556,23 +571,30 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
lastGithubAccount: null
}
} catch (err: any) {
this.ctx.error('Error update pr', { err })
ctx.error('Error update pr', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err), external: pullRequestExternal }
}
}
}
async afterSync (existing: Issue, account: PersonId, issueExternal: any, info: DocSyncInfo): Promise<void> {
async afterSync (
ctx: MeasureContext,
existing: Issue,
account: PersonId,
issueExternal: any,
info: DocSyncInfo
): Promise<void> {
const pullRequest = existing as GithubPullRequest
try {
await this.todoSync(this.client, pullRequest, issueExternal as PullRequestExternalData, info, account)
await this.todoSync(ctx, this.client, pullRequest, issueExternal as PullRequestExternalData, info, account)
} catch (err: any) {
this.ctx.error('failed to sync todos', { err, url: issueExternal.url, id: pullRequest._id })
ctx.error('failed to sync todos', { err, url: issueExternal.url, id: pullRequest._id })
}
}
async todoSync (
ctx: MeasureContext,
client: TxOperations,
pullRequest: Pick<
GithubPullRequest,
@@ -893,7 +915,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
}
@withContext('pullrequests-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -915,7 +939,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
return { needSync: '' }
}
const syncResult = await this.syncToTarget(container, existing, pullRequestExternal, derivedClient, info)
const syncResult = await this.syncToTarget(ctx, container, existing, pullRequestExternal, derivedClient, info)
if (existing !== undefined && pullRequestExternal !== undefined && needCreateConnectedAtHuly) {
await this.addHulyLink(info, syncResult, existing, pullRequestExternal, container)
@@ -926,6 +950,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
async performIssueFieldsUpdate (
ctx: MeasureContext,
info: DocSyncInfo,
existing: WithMarkup<Issue>,
platformUpdate: DocumentUpdate<Issue>,
@@ -955,11 +980,11 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (hasFieldsUpdate || body !== undefined) {
if (body !== undefined && !isLocked) {
await this.ctx.with(
await ctx.with(
'==> updatePullRequest',
{},
async () => {
this.ctx.info('update-pr-fields', {
async (ctx) => {
ctx.info('update-pr-fields', {
url: issueExternal.url,
...issueUpdate,
body,
@@ -990,11 +1015,11 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
)
issueData.description = await this.provider.getMarkupSafe(container.container, body, this.stripGuestLink)
} else if (hasFieldsUpdate) {
await this.ctx.with(
await ctx.with(
'==> updatePullRequest:',
{},
async () => {
this.ctx.info('update-fields', {
async (ctx) => {
ctx.info('update-fields', {
url: issueExternal.url,
...issueUpdate,
workspace: this.provider.getWorkspaceId()
@@ -1028,6 +1053,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
private async handlePatch (
ctx: MeasureContext,
info: DocSyncInfo,
container: ContainerFocus,
pullRequestExternal: PullRequestExternalData,
@@ -1040,7 +1066,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
return
}
if (info.external?.patch !== true) {
const { patch, contentType } = await this.fetchPatch(pullRequestExternal, container.container.octokit, repo)
const { patch, contentType } = await this.fetchPatch(ctx, pullRequestExternal, container.container.octokit, repo)
// Update attached patch data.
const patchAttachment = await this.client.findOne(github.class.GithubPatch, { attachedTo: existingPR._id })
@@ -1189,7 +1215,9 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
}
@withContext('pullrequests-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -1200,7 +1228,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
if (kind === 'externalVersion') {
// Bulk update of selected PR's
// Wait global project sync
await this.performExternalSync(integration, prj, syncDocs, repo, derivedClient)
await this.performExternalSync(ctx, integration, prj, syncDocs, repo, derivedClient)
}
if (kind === 'derivedVersion') {
@@ -1258,6 +1286,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
private async performExternalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
prj: GithubProject,
syncDocs: DocSyncInfo[],
@@ -1278,10 +1307,10 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
const idsp = idsPart.map((it) => `"${it}"`).join(', ')
try {
const response: any = await this.ctx.with(
const response: any = await ctx.with(
'fetch pull request updates',
{},
async () =>
async (ctx) =>
await integration.octokit.graphql(
`query listIssues {
nodes(ids: [${idsp}] ) {
@@ -1301,18 +1330,18 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const issues: PullRequestExternalData[] = response.nodes
if (issues.some((issue) => issue.url === undefined && Object.keys(issue).length === 0)) {
this.ctx.error('empty document content updates', {
ctx.error('empty document content updates', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(response)
})
}
await this.syncIssues(github.class.GithubPullRequest, repo, issues, derivedClient, docsPart)
await this.syncIssues(ctx, github.class.GithubPullRequest, repo, issues, derivedClient, docsPart)
} catch (err: any) {
if (partsize > 1) {
partsize = 1
allSyncDocs.push(...docsPart)
this.ctx.warn('pull request external retrieval switch to one by one mode', {
ctx.warn('pull request external retrieval switch to one by one mode', {
errors: err.errors,
msg: err.message,
workspace: this.provider.getWorkspaceId()
@@ -1321,7 +1350,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
// We need to update issue, since it is missing on external side.
const syncDoc = syncDocs.find((it) => it.external.id === idsPart[0])
if (syncDoc !== undefined) {
this.ctx.warn('mark missing external PR', {
ctx.warn('mark missing external PR', {
errors: err.errors,
msg: err.message,
url: syncDoc.url,
@@ -1342,7 +1371,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
for (const d of syncDocs) {
if ((d.external as IssueExternalData).id == null) {
this.ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
// no external data for doc
await derivedClient.update<DocSyncInfo>(d, {
externalVersion: githubExternalSyncVersion
@@ -1350,17 +1379,19 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
this.provider.sync()
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:pullRequests`)
}
@withContext('pullrequests-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -1392,23 +1423,23 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
const since = await getSinceRaw(this.client, github.class.GithubPullRequest, repo)
// We need always sync open PRs, since review changes are not included into PR updated state.
this.ctx.info('sync external pull requests', {
ctx.info('sync external pull requests', {
repo: repo.name,
since,
workspace: this.provider.getWorkspaceId(),
state: 'OPEN'
})
await this.performPRSync(integration, repo, 'OPEN', undefined, derivedClient, prj)
await this.performPRSync(ctx, integration, repo, 'OPEN', undefined, derivedClient, prj)
this.ctx.info('sync external pull requests', {
ctx.info('sync external pull requests', {
repo: repo.name,
since,
workspace: this.provider.getWorkspaceId(),
state: 'CLOSED, MERGED'
})
await this.performPRSync(integration, repo, 'CLOSED, MERGED', since, derivedClient, prj)
await this.performPRSync(ctx, integration, repo, 'CLOSED, MERGED', since, derivedClient, prj)
this.ctx.info('sync external pull requests - done', {
ctx.info('sync external pull requests - done', {
repo: repo.name,
since,
workspace: this.provider.getWorkspaceId()
@@ -1420,6 +1451,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
}
private async performPRSync (
ctx: MeasureContext,
integration: IntegrationContainer,
repo: GithubIntegrationRepository,
states: string,
@@ -1459,7 +1491,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
break
}
const issues: PullRequestExternalData[] = data.repository.pullRequests.nodes
this.ctx.info('retrieve pull requests for', {
ctx.info('retrieve pull requests for', {
repo: repo.name,
since,
len: issues.length,
@@ -1478,7 +1510,7 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
let emptyIndex = -1
emptyIndex = issues.findIndex((issue) => issue.url === undefined && Object.keys(issue).length === 0)
if (emptyIndex !== -1) {
this.ctx.error('empty document content', {
ctx.error('empty document content', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(data),
@@ -1487,15 +1519,16 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
})
}
await this.syncIssues(github.class.GithubPullRequest, repo, issues, derivedClient)
await this.syncIssues(ctx, github.class.GithubPullRequest, repo, issues, derivedClient)
}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
}
async fetchPatch (
ctx: MeasureContext,
pullRequest: PullRequestExternalData,
octokit: Octokit,
repository: GithubIntegrationRepository
@@ -1515,13 +1548,18 @@ export class PullRequestSyncManager extends IssueSyncManagerBase implements DocS
patch = (patchContent.data as unknown as string) ?? ''
contentType = patchContent.headers['content-type'] ?? 'application/vnd.github.VERSION.diff'
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
return { patch, contentType }
}
async deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void> {
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void> {
// No delete is allowed for pull requests
}
}
@@ -3,7 +3,15 @@
//
//
import core, { Doc, DocData, DocumentUpdate, MeasureContext, TxOperations, generateId } from '@hcengineering/core'
import core, {
Doc,
DocData,
DocumentUpdate,
MeasureContext,
TxOperations,
generateId,
withContext
} from '@hcengineering/core'
import github, { DocSyncInfo, GithubIntegrationRepository, GithubProject } from '@hcengineering/github'
import { Endpoints } from '@octokit/types'
import {
@@ -20,7 +28,6 @@ const syncReposKey = 'repo_sync'
export class RepositorySyncMapper implements DocSyncManager {
constructor (
private readonly ctx: MeasureContext,
private readonly client: TxOperations,
private readonly app: App
) {}
@@ -35,11 +42,18 @@ export class RepositorySyncMapper implements DocSyncManager {
}
// Perform synchronization of document with external source.
async sync (existing: Doc | undefined, info: DocSyncInfo): Promise<DocumentUpdate<DocSyncInfo> | undefined> {
@withContext('repository-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo
): Promise<DocumentUpdate<DocSyncInfo> | undefined> {
return {}
}
async reloadRepositories (
ctx: MeasureContext,
integration: IntegrationContainer,
repositories?: InstallationCreatedEvent['repositories'] | InstallationUnsuspendEvent['repositories']
): Promise<void> {
@@ -93,7 +107,7 @@ export class RepositorySyncMapper implements DocSyncManager {
Date.now(),
integration.integration.createdBy
)
this.ctx.info('Creating repository info document...', {
ctx.info('Creating repository info document...', {
url: repository.full_name,
workspace: this.provider.getWorkspaceId()
})
@@ -102,7 +116,13 @@ export class RepositorySyncMapper implements DocSyncManager {
}
}
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('repository-handleEvent')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
const event = evt as RepositoryEvent
const account = (await this.provider.getAccountU(event.sender)) ?? core.account.System
@@ -124,7 +144,7 @@ export class RepositorySyncMapper implements DocSyncManager {
Date.now(),
account
)
this.ctx.info('Creating repository info document...', {
ctx.info('Creating repository info document...', {
url: event.repository.url,
workspace: this.provider.getWorkspaceId()
})
@@ -148,7 +168,7 @@ export class RepositorySyncMapper implements DocSyncManager {
const allProjects = await this.client.findAll(github.mixin.GithubProject, { repositories: githubRepo?._id })
for (const prj of allProjects) {
// We need to force sync
await this.handleRepoRename(integration, prj, githubRepo)
await this.handleRepoRename(ctx, integration, prj, githubRepo)
}
}
@@ -178,6 +198,7 @@ export class RepositorySyncMapper implements DocSyncManager {
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -225,7 +246,9 @@ export class RepositorySyncMapper implements DocSyncManager {
}
}
@withContext('repository-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -234,9 +257,11 @@ export class RepositorySyncMapper implements DocSyncManager {
prj: GithubProject
): Promise<void> {}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
@withContext('repository-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -244,14 +269,14 @@ export class RepositorySyncMapper implements DocSyncManager {
): Promise<void> {
const inst = integration.octokit
if (inst === undefined || integration.octokit === undefined) {
this.ctx.info('no installation found', { workspace: this.provider.getWorkspaceId() })
ctx.info('no installation found', { workspace: this.provider.getWorkspaceId() })
return
}
if (integration.synchronized.has(syncReposKey)) {
return
}
this.ctx.info('Checking github installation repositories...', {
ctx.info('Checking github installation repositories...', {
installationId: integration.installationId,
workspace: this.provider.getWorkspaceId()
})
@@ -297,7 +322,7 @@ export class RepositorySyncMapper implements DocSyncManager {
Date.now(),
integration.integration.createdBy
)
this.ctx.info('Creating repository info document...', {
ctx.info('Creating repository info document...', {
url: repository.url,
workspace: this.provider.getWorkspaceId()
})
@@ -312,7 +337,7 @@ export class RepositorySyncMapper implements DocSyncManager {
['name', ...Object.keys(rdata)]
)
if (Object.keys(diff).length > 0) {
this.ctx.info('processing repository diff update...', {
ctx.info('processing repository diff update...', {
repository: repository.name,
...diff,
workspace: this.provider.getWorkspaceId()
@@ -343,6 +368,7 @@ export class RepositorySyncMapper implements DocSyncManager {
// Perform a synchronization of a single repository.
async handleRepoRename (
ctx: MeasureContext,
integration: IntegrationContainer,
prj: GithubProject,
repo: GithubIntegrationRepository
@@ -360,7 +386,7 @@ export class RepositorySyncMapper implements DocSyncManager {
"https://api.github.com/repos/hcengineering/anticrm/issues/comments/1679316918"
"https://github.com/hcengineering/uberflow/pull/195"
* */
this.ctx.info('handle repository rename', { repo, workspace: this.provider.getWorkspaceId() })
ctx.info('handle repository rename', { repo, workspace: this.provider.getWorkspaceId() })
const update = async (): Promise<void> => {
while (true) {
const docs = await this.client.findAll(
@@ -9,7 +9,8 @@ import core, {
DocumentUpdate,
MeasureContext,
Ref,
TxOperations
TxOperations,
withContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -44,7 +45,6 @@ export class ReviewCommentSyncManager implements DocSyncManager {
externalDerivedSync = false
constructor (
readonly ctx: MeasureContext,
readonly client: TxOperations,
readonly lq: LiveQuery
) {}
@@ -54,7 +54,14 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
eventSync = new Map<string, Promise<void>>()
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('review-comments-handleEvent')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
await this.createCommentPromise
const event = evt as PullRequestReviewCommentEvent
@@ -65,27 +72,28 @@ export class ReviewCommentSyncManager implements DocSyncManager {
return
}
}
this.ctx.info('reviewComments:handleEvent', {
ctx.info('reviewComments:handleEvent', {
action: event.action,
login: event.sender.login,
workspace: this.provider.getWorkspaceId()
})
const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
name: event.repository.name,
workspace: this.provider.getWorkspaceId()
})
return
}
await this.eventSync.get(event.comment.html_url)
const promise = this.processEvent(event, derivedClient, repository, integration)
const promise = this.processEvent(ctx, event, derivedClient, repository, integration)
this.eventSync.set(event.comment.html_url, promise)
await promise
this.eventSync.delete(event.comment.html_url)
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -108,7 +116,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
if (commentExternal !== undefined) {
try {
await this.deleteGithubDocument(container, account, commentExternal.node_id, derivedClient, parent)
await this.deleteGithubDocument(ctx, container, account, commentExternal.node_id, derivedClient, parent)
} catch (err: any) {
let cnt = false
if (Array.isArray(err.errors)) {
@@ -121,7 +129,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
}
if (!cnt) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
await derivedClient.update(info, { error: errorToObj(err) })
}
@@ -129,19 +137,20 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
if (existing !== undefined && deleteExisting) {
await deleteObjects(this.ctx, this.client, [existing], account)
await deleteObjects(ctx, this.client, [existing], account)
}
return true
}
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string,
derivedClient: TxOperations,
parent?: DocSyncInfo
): Promise<void> {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation deleteReviewComment($reviewID: ID!) {
deletePullRequestReviewComment(input: {
id: $reviewID
@@ -163,6 +172,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
private async processEvent (
ctx: MeasureContext,
event: PullRequestReviewCommentEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -188,7 +198,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
)
externalData = response.node
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return
}
@@ -292,7 +302,9 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
}
@withContext('review-comments-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -314,7 +326,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
// If no external document, we need to create it.
this.createCommentPromise = this.createGithubReviewComment(container, existing, info, parent, derivedClient)
this.createCommentPromise = this.createGithubReviewComment(ctx, container, existing, info, parent, derivedClient)
return await this.createCommentPromise
}
const reviewComment = info.external as ReviewCommentExternalData
@@ -355,17 +367,28 @@ export class ReviewCommentSyncManager implements DocSyncManager {
await this.createReviewComment(info, messageData, parent, reviewComment, account)
return { needSync: githubSyncVersion, current: messageData }
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
} else {
await this.handleDiffUpdate(existing, info, messageData, container, parent, reviewComment, account, derivedClient)
await this.handleDiffUpdate(
ctx,
existing,
info,
messageData,
container,
parent,
reviewComment,
account,
derivedClient
)
}
return { current: messageData, needSync: githubSyncVersion }
}
private async handleDiffUpdate (
ctx: MeasureContext,
existing: Doc,
info: DocSyncInfo,
reviewCommentData: ReviewCommentData,
@@ -406,7 +429,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
if (Object.keys(platformUpdate).length > 0) {
if (platformUpdate.body !== undefined) {
const body = await this.provider.getMarkupSafe(container.container, platformUpdate.body)
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation updateReviewComment($commentID: ID!, $body: String!) {
updatePullRequestReviewComment(input: {
threadId: $threadID
@@ -460,6 +483,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
async createGithubReviewComment (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
info: DocSyncInfo,
@@ -477,7 +501,7 @@ export class ReviewCommentSyncManager implements DocSyncManager {
return {}
}
const existingReview = existing as GithubReviewComment
const okit = (await this.provider.getOctokit(existingReview.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existingReview.modifiedBy)) ?? container.container.octokit
// No external version yet, create it.
try {
@@ -540,13 +564,15 @@ export class ReviewCommentSyncManager implements DocSyncManager {
}
return {}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
}
@withContext('review-comments-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -563,9 +589,11 @@ export class ReviewCommentSyncManager implements DocSyncManager {
this.provider.sync()
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
@withContext('review-comments-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -8,7 +8,8 @@ import core, {
DocumentUpdate,
MeasureContext,
Ref,
TxOperations
TxOperations,
withContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -66,7 +67,6 @@ export class ReviewThreadSyncManager implements DocSyncManager {
externalDerivedSync = true
constructor (
readonly ctx: MeasureContext,
readonly client: TxOperations,
readonly lq: LiveQuery
) {}
@@ -76,7 +76,14 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
eventSync = new Map<string, Promise<void>>()
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('review-threads-handleEvent')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
await this.createCommentPromise
const event = evt as PullRequestReviewThreadEvent
@@ -87,11 +94,11 @@ export class ReviewThreadSyncManager implements DocSyncManager {
return
}
}
this.ctx.info('reviewThreads:handleEvent', { event, workspace: this.provider.getWorkspaceId() })
ctx.info('reviewThreads:handleEvent', { event, workspace: this.provider.getWorkspaceId() })
const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
name: event.repository.name,
workspace: this.provider.getWorkspaceId()
})
@@ -99,13 +106,14 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
await this.eventSync.get(event.thread.node_id)
const promise = this.processEvent(event, derivedClient, repository, integration)
const promise = this.processEvent(ctx, event, derivedClient, repository, integration)
this.eventSync.set(event.thread.node_id, promise)
await promise
this.eventSync.delete(event.thread.node_id)
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -140,7 +148,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
}
if (!cnt) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
await derivedClient.update(info, { error: errorToObj(err) })
}
@@ -148,7 +156,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
if (existing !== undefined && deleteExisting) {
await deleteObjects(this.ctx, this.client, [existing], account)
await deleteObjects(ctx, this.client, [existing], account)
}
return true
}
@@ -158,6 +166,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
private async processEvent (
ctx: MeasureContext,
event: PullRequestReviewThreadEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -183,7 +192,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
)
externalData = response.node
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return
}
@@ -248,7 +257,9 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
}
@withContext('review-threads-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -270,7 +281,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
// If no external document, we need to create it.
this.createCommentPromise = this.createGithubReviewThread(container, existing, info, parent, derivedClient)
this.createCommentPromise = this.createGithubReviewThread(ctx, container, existing, info, parent, derivedClient)
return await this.createCommentPromise
}
const review = info.external as ReviewThreadExternalData
@@ -303,17 +314,18 @@ export class ReviewThreadSyncManager implements DocSyncManager {
await syncChilds(info, this.client, derivedClient)
return { needSync: githubSyncVersion, current: messageData }
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
} else {
await this.handleDiffUpdate(existing, info, messageData, container, parent, review, account, derivedClient)
await this.handleDiffUpdate(ctx, existing, info, messageData, container, parent, review, account, derivedClient)
}
return { current: messageData, needSync: githubSyncVersion }
}
private async handleDiffUpdate (
ctx: MeasureContext,
existing: Doc,
info: DocSyncInfo,
reviewData: ReviewThreadData,
@@ -354,7 +366,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
if (Object.keys(platformUpdate).length > 0) {
// Check and update external
if (platformUpdate.isResolved !== undefined && githubConfiguration.ResolveThreadSupported) {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation updateReviewThread($threadID: ID!) {
${platformUpdate.isResolved ? 'resolveReviewThread' : 'unresolveReviewThread'} (
input: {
@@ -375,7 +387,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
} catch (err: any) {
update.isResolved = !platformUpdate.isResolved
platformUpdate.isResolved = !platformUpdate.isResolved
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
await derivedClient.update(info, { external: { ...info.external, isResolved: platformUpdate.isResolved } })
@@ -411,6 +423,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
async createGithubReviewThread (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
info: DocSyncInfo,
@@ -428,7 +441,7 @@ export class ReviewThreadSyncManager implements DocSyncManager {
return {}
}
const existingReview = existing as GithubReviewThread
const okit = (await this.provider.getOctokit(existingReview.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existingReview.modifiedBy)) ?? container.container.octokit
// No external version yet, create it.
// Will be added into pending state.
@@ -478,13 +491,15 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
return {}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
}
@withContext('review-threads-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -550,9 +565,11 @@ export class ReviewThreadSyncManager implements DocSyncManager {
}
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
@withContext('review-threads-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
+38 -17
View File
@@ -8,7 +8,8 @@ import core, {
DocumentUpdate,
MeasureContext,
Ref,
TxOperations
TxOperations,
withContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -44,7 +45,6 @@ export class ReviewSyncManager implements DocSyncManager {
externalDerivedSync = false
constructor (
readonly ctx: MeasureContext,
readonly client: TxOperations,
readonly lq: LiveQuery
) {}
@@ -54,7 +54,14 @@ export class ReviewSyncManager implements DocSyncManager {
}
eventSync = new Map<string, Promise<void>>()
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {
@withContext('reviews-handleEvent')
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {
await this.createCommentPromise
const event = evt as PullRequestReviewEvent
@@ -65,11 +72,11 @@ export class ReviewSyncManager implements DocSyncManager {
return
}
}
this.ctx.info('reviews:handleEvent', { event, workspace: this.provider.getWorkspaceId() })
ctx.info('reviews:handleEvent', { event, workspace: this.provider.getWorkspaceId() })
const { project, repository } = await this.provider.getProjectAndRepository(event.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
name: event.repository.name,
workspace: this.provider.getWorkspaceId()
})
@@ -77,13 +84,14 @@ export class ReviewSyncManager implements DocSyncManager {
}
await this.eventSync.get(event.review.html_url)
const promise = this.processEvent(event, derivedClient, repository, integration)
const promise = this.processEvent(ctx, event, derivedClient, repository, integration)
this.eventSync.set(event.review.html_url, promise)
await promise
this.eventSync.delete(event.review.html_url)
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -105,7 +113,7 @@ export class ReviewSyncManager implements DocSyncManager {
if (commentExternal !== undefined) {
try {
await this.deleteGithubDocument(container, account, commentExternal.node_id)
await this.deleteGithubDocument(ctx, container, account, commentExternal.node_id)
} catch (err: any) {
let cnt = false
if (Array.isArray(err.errors)) {
@@ -118,7 +126,7 @@ export class ReviewSyncManager implements DocSyncManager {
}
}
if (!cnt) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
await derivedClient.update(info, { error: errorToObj(err) })
}
@@ -126,13 +134,18 @@ export class ReviewSyncManager implements DocSyncManager {
}
if (existing !== undefined && deleteExisting) {
await deleteObjects(this.ctx, this.client, [existing], account)
await deleteObjects(ctx, this.client, [existing], account)
}
return true
}
async deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void> {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void> {
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation deleteReview($reviewID: ID!) {
deletePullRequestReview(input: {
pullRequestReviewId: $reviewID
@@ -150,6 +163,7 @@ export class ReviewSyncManager implements DocSyncManager {
}
private async processEvent (
ctx: MeasureContext,
event: PullRequestReviewEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -175,7 +189,7 @@ export class ReviewSyncManager implements DocSyncManager {
)
externalData = response.node
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return
}
@@ -266,7 +280,9 @@ export class ReviewSyncManager implements DocSyncManager {
}
}
@withContext('reviews-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -288,7 +304,7 @@ export class ReviewSyncManager implements DocSyncManager {
}
// If no external document, we need to create it.
this.createCommentPromise = this.createGithubReview(container, existing, info, parent, derivedClient)
this.createCommentPromise = this.createGithubReview(ctx, container, existing, info, parent, derivedClient)
return await this.createCommentPromise
}
const review = info.external as ReviewExternalData
@@ -307,7 +323,7 @@ export class ReviewSyncManager implements DocSyncManager {
await syncChilds(info, this.client, derivedClient)
return { needSync: githubSyncVersion, current: messageData }
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
@@ -388,6 +404,7 @@ export class ReviewSyncManager implements DocSyncManager {
}
async createGithubReview (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
info: DocSyncInfo,
@@ -405,7 +422,7 @@ export class ReviewSyncManager implements DocSyncManager {
return {}
}
const existingReview = existing as GithubReview
const okit = (await this.provider.getOctokit(existingReview.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existingReview.modifiedBy)) ?? container.container.octokit
// No external version yet, create it.
try {
@@ -452,13 +469,15 @@ export class ReviewSyncManager implements DocSyncManager {
}
return {}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
return { needSync: githubSyncVersion, error: errorToObj(err) }
}
}
@withContext('reviews-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -475,9 +494,11 @@ export class ReviewSyncManager implements DocSyncManager {
this.provider.sync()
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {}
@withContext('reviews-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
+11 -2
View File
@@ -22,6 +22,7 @@ export class UsersSyncManager implements DocSyncManager {
}
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent?: DocSyncInfo
@@ -30,6 +31,7 @@ export class UsersSyncManager implements DocSyncManager {
}
async handleDelete (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -38,9 +40,15 @@ export class UsersSyncManager implements DocSyncManager {
return false
}
async handleEvent<T>(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise<void> {}
async handleEvent<T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
): Promise<void> {}
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -49,11 +57,12 @@ export class UsersSyncManager implements DocSyncManager {
prj: GithubProject
): Promise<void> {}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:users`)
}
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
+20 -5
View File
@@ -12,7 +12,8 @@ import {
TxOperations,
WithLookup,
WorkspaceUuid,
type Blob
type Blob,
type MeasureContext
} from '@hcengineering/core'
import {
DocSyncInfo,
@@ -81,7 +82,7 @@ export interface IntegrationManager {
getContainer: (space: Ref<Space>) => Promise<ContainerFocus | undefined>
getAccount: (user?: UserInfo | null) => Promise<PersonId | undefined>
getAccountU: (user: User) => Promise<PersonId | undefined>
getOctokit: (account: PersonId) => Promise<Octokit | undefined>
getOctokit: (ctx: MeasureContext, account: PersonId) => Promise<Octokit | undefined>
getMarkupSafe: (
container: IntegrationContainer,
text?: string | null,
@@ -106,13 +107,14 @@ export interface IntegrationManager {
getTaskTypeOf: (project: Ref<ProjectType>, ofClass: Ref<Class<Doc>>) => Promise<TaskType | undefined>
handleEvent: <T>(
ctx: MeasureContext,
requestClass: Ref<Class<Doc>>,
integrationId: number | undefined,
repo: GithubIntegrationRepository,
event: T
) => Promise<void>
doSyncFor: (docs: DocSyncInfo[], project: GithubProject) => Promise<void>
doSyncFor: (ctx: MeasureContext, docs: DocSyncInfo[], project: GithubProject) => Promise<void>
getWorkspaceId: () => WorkspaceUuid
getWorkspaceUrl: () => string
getBranding: () => Branding | null
@@ -145,6 +147,7 @@ export interface DocSyncManager {
init: (provider: IntegrationManager) => Promise<void>
// Perform synchronization of document with external source.
sync: (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -152,6 +155,7 @@ export interface DocSyncManager {
) => Promise<DocumentUpdate<DocSyncInfo> | undefined>
handleDelete: (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
derivedClient: TxOperations,
@@ -161,6 +165,7 @@ export interface DocSyncManager {
// Perform synchronization with external source.
externalFullSync: (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -169,6 +174,7 @@ export interface DocSyncManager {
// Perform synchronization with external source.
externalSync: (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -177,11 +183,20 @@ export interface DocSyncManager {
project: GithubProject
) => Promise<void>
handleEvent: <T>(integration: IntegrationContainer, derivedClient: TxOperations, event: T) => Promise<void>
handleEvent: <T>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
event: T
) => Promise<void>
externalDerivedSync: boolean
repositoryDisabled: (integration: IntegrationContainer, repo: GithubIntegrationRepository) => void
repositoryDisabled: (
ctx: MeasureContext,
integration: IntegrationContainer,
repo: GithubIntegrationRepository
) => void
}
/**
+73 -78
View File
@@ -43,6 +43,7 @@ import core, {
reduceCalls,
systemAccountUuid,
toIdMap,
withContext,
type Blob,
type Data,
type MigrationState,
@@ -166,10 +167,10 @@ export class GithubWorker implements IntegrationManager {
return this.branding
}
async reloadRepositories (installationId: number): Promise<void> {
async reloadRepositories (ctx: MeasureContext, installationId: number): Promise<void> {
const current = this.integrations.get(installationId)
if (current !== undefined) {
await this.repositoryManager.reloadRepositories(current)
await this.repositoryManager.reloadRepositories(ctx, current)
this.triggerUpdate()
}
}
@@ -398,11 +399,7 @@ export class GithubWorker implements IntegrationManager {
this._client = new TxOperations(this.client, core.account.System)
this.liveQuery = new LiveQuery(client)
this.repositoryManager = new RepositorySyncMapper(
this.ctx.newChild('repository', {}, { span: false }),
this._client,
this.app
)
this.repositoryManager = new RepositorySyncMapper(this._client, this.app)
this.collaborator = createCollaboratorClient(this.workspace.uuid)
@@ -416,25 +413,15 @@ export class GithubWorker implements IntegrationManager {
{ _class: [github.mixin.GithubProject], mapper: this.repositoryManager },
{
_class: [tracker.class.Issue],
mapper: new IssueSyncManager(
this.ctx.newChild('issue', {}, { span: false }),
this._client,
this.liveQuery,
this.collaborator
)
mapper: new IssueSyncManager(this._client, this.liveQuery, this.collaborator)
},
{
_class: [github.class.GithubPullRequest],
mapper: new PullRequestSyncManager(
this.ctx.newChild('pullRequest', {}, { span: false }),
this._client,
this.liveQuery,
this.collaborator
)
mapper: new PullRequestSyncManager(this._client, this.liveQuery, this.collaborator)
},
{
_class: [chunter.class.ChatMessage],
mapper: new CommentSyncManager(this.ctx.newChild('comment', {}, { span: false }), this._client, this.liveQuery)
mapper: new CommentSyncManager(this._client, this.liveQuery)
},
// {
// _class: [contact.class.PersonAccount],
@@ -442,23 +429,15 @@ export class GithubWorker implements IntegrationManager {
// },
{
_class: [github.class.GithubReview],
mapper: new ReviewSyncManager(this.ctx.newChild('review', {}, { span: false }), this._client, this.liveQuery)
mapper: new ReviewSyncManager(this._client, this.liveQuery)
},
{
_class: [github.class.GithubReviewThread],
mapper: new ReviewThreadSyncManager(
this.ctx.newChild('review-thread', {}, { span: false }),
this._client,
this.liveQuery
)
mapper: new ReviewThreadSyncManager(this._client, this.liveQuery)
},
{
_class: [github.class.GithubReviewComment],
mapper: new ReviewCommentSyncManager(
this.ctx.newChild('review-comment', {}, { span: false }),
this._client,
this.liveQuery
)
mapper: new ReviewCommentSyncManager(this._client, this.liveQuery)
}
]
@@ -599,13 +578,13 @@ export class GithubWorker implements IntegrationManager {
continue
}
try {
await this.platform.checkRefreshToken(record, true)
await this.platform.checkRefreshToken(ctx, record, true)
const ops = new TxOperations(this.client, account._id)
await syncUser(ctx, record, userAuth, ops, account._id)
} catch (err: any) {
try {
await this.platform.revokeUserAuth(record)
await this.platform.revokeUserAuth(ctx, record)
} catch (err: any) {
ctx.error(`Failed to revoke user ${record._id}`, err)
}
@@ -628,7 +607,8 @@ export class GithubWorker implements IntegrationManager {
}
}
async getOctokit (account: PersonId): Promise<Octokit | undefined> {
@withContext('get-octokit')
async getOctokit (ctx: MeasureContext, account: PersonId): Promise<Octokit | undefined> {
let record = await this.platform.getAccountByRef(this.workspace.uuid, account)
const accountRef = await this._client.findOne(contact.class.SocialIdentity, { _id: account as any })
@@ -646,7 +626,7 @@ export class GithubWorker implements IntegrationManager {
// Check and refresh token if required.
if (record !== undefined) {
this.ctx.info('get octokit', { account, recordId: record._id, workspace: this.workspace.uuid })
await this.platform.checkRefreshToken(record)
await this.platform.checkRefreshToken(ctx, record)
return new Octokit({
auth: record.token,
client_id: config.ClientID,
@@ -800,7 +780,7 @@ export class GithubWorker implements IntegrationManager {
const i = Array.from(this.integrations.values()).find((it) => it.integration._id === r.attachedTo)
if (i !== undefined) {
for (const m of this.mappers) {
m.mapper.repositoryDisabled(i, r)
m.mapper.repositoryDisabled(this.ctx, i, r)
}
}
}
@@ -856,7 +836,7 @@ export class GithubWorker implements IntegrationManager {
})
}
async updateIntegrations (): Promise<void> {
async updateIntegrations (ctx: MeasureContext): Promise<void> {
await this.checkMapping()
for (const it of this.integrationsRaw) {
let current = this.integrations.get(it.installationId)
@@ -880,7 +860,7 @@ export class GithubWorker implements IntegrationManager {
syncLock: new Map()
}
this.integrations.set(it.installationId, current)
await this.repositoryManager.reloadRepositories(current, inst.repositories)
await this.repositoryManager.reloadRepositories(ctx, current, inst.repositories)
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
@@ -892,7 +872,7 @@ export class GithubWorker implements IntegrationManager {
continue
}
current.integration = it
await this.repositoryManager.reloadRepositories(current, inst.repositories)
await this.repositoryManager.reloadRepositories(ctx, current, inst.repositories)
}
}
}
@@ -995,6 +975,7 @@ export class GithubWorker implements IntegrationManager {
}
async performExternalSync (
ctx: MeasureContext,
projects: GithubProject[],
repositories: GithubIntegrationRepository[],
field: ExternalSyncField,
@@ -1058,7 +1039,7 @@ export class GithubWorker implements IntegrationManager {
for (const [_class, _docs] of byClass.entries()) {
const mapper = this.mappers.find((it) => it._class.includes(_class))?.mapper
try {
await mapper?.externalSync(integration, derivedClient, field, _docs, repo, prj)
await mapper?.externalSync(ctx, integration, derivedClient, field, _docs, repo, prj)
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('failed to perform external sync', err)
@@ -1211,10 +1192,12 @@ export class GithubWorker implements IntegrationManager {
while (!this.closing) {
if (this.updateRequests > 0) {
this.updateRequests = 0 // Just in case
await this.updateIntegrations()
void this.performFullSync().catch((err) => {
this.ctx.error('Failed to perform full sync', { error: err })
})
await this.ctx.with('update-integrations', {}, (ctx) => this.updateIntegrations(ctx))
void this.ctx.with('performFullSync', {}, (ctx) =>
this.performFullSync(ctx).catch((err) => {
this.ctx.error('Failed to perform full sync', { error: err })
})
)
}
try {
const { projects, repositories } = await this.collectActiveProjects()
@@ -1224,21 +1207,17 @@ export class GithubWorker implements IntegrationManager {
}
// Check if we have documents with external sync request's pending.
const hadExternalChanges = await this.performExternalSync(
projects,
repositories,
'externalVersion',
githubExternalSyncVersion
const hadExternalChanges = await this.ctx.with('performExternalSync', {}, (ctx) =>
this.performExternalSync(ctx, projects, repositories, 'externalVersion', githubExternalSyncVersion)
)
const hadSyncChanges = await this.ctx.with('performSync', {}, (ctx) =>
this.performSync(ctx, projects, repositories)
)
const hadSyncChanges = await this.performSync(projects, repositories)
// Perform derived operations
// Sync derived external data, like pull request reviews, files etc.
const hadDerivedChanges = await this.performExternalSync(
projects,
repositories,
'derivedVersion',
githubDerivedSyncVersion
const hadDerivedChanges = await this.ctx.with('performDerivedSync', {}, (ctx) =>
this.performExternalSync(ctx, projects, repositories, 'derivedVersion', githubDerivedSyncVersion)
)
if (!hadExternalChanges && !hadSyncChanges && !hadDerivedChanges) {
@@ -1256,6 +1235,7 @@ export class GithubWorker implements IntegrationManager {
}
private async performSync (
ctx: MeasureContext,
projects: GithubProject[],
repositories: Pick<GithubIntegrationRepository, '_id'>[]
): Promise<boolean> {
@@ -1264,7 +1244,7 @@ export class GithubWorker implements IntegrationManager {
const docs = await this.limiter.exec(
async () =>
await this.ctx.with(
await ctx.with(
'find-doc-sync-info',
{},
(ctx) =>
@@ -1288,23 +1268,23 @@ export class GithubWorker implements IntegrationManager {
if (docs.length > 0) {
this.previousWait += docs.length
this.ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.uuid })
ctx.info('Syncing', { docs: docs.length, workspace: this.workspace.uuid })
const bySpace = groupByArray(docs, (it) => it.space)
for (const [k, v] of bySpace.entries()) {
await this.doSyncFor(v, _projects.get(k as Ref<GithubProject>) as GithubProject)
await this.doSyncFor(ctx, v, _projects.get(k as Ref<GithubProject>) as GithubProject)
}
}
return docs.length !== 0
}
async doSyncFor (docs: DocSyncInfo[], project: GithubProject): Promise<void> {
async doSyncFor (ctx: MeasureContext, docs: DocSyncInfo[], project: GithubProject): Promise<void> {
const byClass = this.groupByClass(docs)
// We need to reorder based on our sync mappers
for (const [_class, clDocs] of byClass.entries()) {
await this.syncClass(_class, clDocs, project)
await this.syncClass(ctx, _class, clDocs, project)
}
}
@@ -1386,7 +1366,12 @@ export class GithubWorker implements IntegrationManager {
}
}
async syncClass (_class: Ref<Class<Doc>>, syncInfo: DocSyncInfo[], project: GithubProject): Promise<void> {
async syncClass (
ctx: MeasureContext,
_class: Ref<Class<Doc>>,
syncInfo: DocSyncInfo[],
project: GithubProject
): Promise<void> {
const externalDocs = await this._client.findAll<Doc>(_class, {
_id: { $in: syncInfo.map((it) => it._id as Ref<Doc>) }
})
@@ -1452,7 +1437,7 @@ export class GithubWorker implements IntegrationManager {
_id: existing.space as Ref<GithubProject>
})
try {
if (await mapper.handleDelete(existing, info, derivedClient, false, parent)) {
if (await mapper.handleDelete(ctx, existing, info, derivedClient, false, parent)) {
const h = this._client.getHierarchy()
await derivedClient.remove(info)
if (h.hasMixin(existing, github.mixin.GithubIssue)) {
@@ -1491,7 +1476,7 @@ export class GithubWorker implements IntegrationManager {
if (info.deleted === true) {
try {
if (await mapper.handleDelete(existing, info, derivedClient, true)) {
if (await mapper.handleDelete(ctx, existing, info, derivedClient, true)) {
await derivedClient.remove(info)
}
} catch (err: any) {
@@ -1500,10 +1485,10 @@ export class GithubWorker implements IntegrationManager {
return
}
const docUpdate = await this.ctx.with(
const docUpdate = await ctx.with(
'sync doc',
{},
(ctx) => mapper.sync(existing, info, parent, derivedClient),
(ctx) => mapper.sync(ctx, existing, info, parent, derivedClient),
{
url: info.url.toLowerCase(),
workspace: this.workspace.uuid,
@@ -1579,15 +1564,15 @@ export class GithubWorker implements IntegrationManager {
this.triggerSync()
}
performFullSync = reduceCalls(async () => {
performFullSync = reduceCalls(async (ctx: MeasureContext) => {
try {
await this._performFullSync()
await this._performFullSync(ctx)
} catch (err: any) {
this.ctx.error('Failed to perform full sync', { error: err })
}
})
async _performFullSync (): Promise<void> {
async _performFullSync (ctx: MeasureContext): Promise<void> {
// Wait previous active sync
for (const integration of this.integrations.values()) {
if (this.closing) {
@@ -1692,7 +1677,7 @@ export class GithubWorker implements IntegrationManager {
'external sync',
{ _class: _class.join(', ') },
async () => {
await mapper.externalFullSync(integration, derivedClient, _projects, _repositories)
await mapper.externalFullSync(ctx, integration, derivedClient, _projects, _repositories)
},
{ installation: integration.installationName, workspace: this.workspace.uuid },
{ log: true }
@@ -1705,7 +1690,12 @@ export class GithubWorker implements IntegrationManager {
}
}
async handleEvent<T>(requestClass: Ref<Class<Doc>>, integrationId: number | undefined, event: T): Promise<void> {
async handleEvent<T>(
ctx: MeasureContext,
requestClass: Ref<Class<Doc>>,
integrationId: number | undefined,
event: T
): Promise<void> {
if (integrationId === undefined) {
return
}
@@ -1717,7 +1707,7 @@ export class GithubWorker implements IntegrationManager {
for (const { _class, mapper } of this.mappers) {
if (_class.includes(requestClass)) {
try {
await mapper.handleEvent(integration, derivedClient, event)
await mapper.handleEvent(ctx, integration, derivedClient, event)
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('exception during processing of event:', { event, err })
@@ -1755,14 +1745,19 @@ export class GithubWorker implements IntegrationManager {
let endpoint: string | undefined
let maitenanceState = false
try {
;({ client, endpoint } = await createPlatformClient(workspace.uuid, 30000, async (event: ClientConnectEvent) => {
if (event === ClientConnectEvent.Maintenance) {
await client?.close()
maitenanceState = true
throw new Error('Workspace in maintenance')
;({ client, endpoint } = await createPlatformClient(
ctx,
workspace.uuid,
30000,
async (event: ClientConnectEvent) => {
if (event === ClientConnectEvent.Maintenance) {
await client?.close()
maitenanceState = true
throw new Error('Workspace in maintenance')
}
reconnect(workspace.uuid, event)
}
reconnect(workspace.uuid, event)
}))
))
ctx.info('connected to github', { workspace: workspace.uuid, endpoint })
const githubEnabled = (await client.findOne(core.class.PluginConfiguration, { pluginId: githubId }))?.enabled
@@ -27,6 +27,7 @@ describe('AttachmentHandler', () => {
newChild: jest.fn(),
with: jest.fn(),
withSync: jest.fn(),
extractMeta: jest.fn(),
logger: {
info: jest.fn(),
error: jest.fn(),
@@ -296,5 +296,5 @@ async function checkFindPerformance (conn: RestClient): Promise<void> {
const avg = total / ops
// console.log('ops:', ops, 'total:', total, 'avg:', )
expect(ops).toEqual(attempts)
expect(avg).toBeLessThan(6)
expect(avg).toBeLessThan(10)
}