From d32beb8eade58322f95c8be46205b4e8a60b7707 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 1 Aug 2025 00:58:25 +0700 Subject: [PATCH] feat: Pass tracing info with websocket (#9627) Signed-off-by: Andrey Sobolev --- .vscode/launch.json | 5 +- packages/measurements-otlp/src/telemetry.ts | 40 +++-- packages/measurements/src/context.ts | 8 + packages/measurements/src/types.ts | 8 +- plugins/client-resources/src/connection.ts | 145 +++++++---------- plugins/client/src/index.ts | 3 - server/rpc/src/rpc.ts | 2 + server/server/src/sessionManager.ts | 5 +- services/github/pod-github/src/client.ts | 4 +- services/github/pod-github/src/platform.ts | 103 +++++++----- services/github/pod-github/src/server.ts | 2 +- .../github/pod-github/src/sync/comments.ts | 65 +++++--- .../github/pod-github/src/sync/issueBase.ts | 56 ++++--- services/github/pod-github/src/sync/issues.ts | 120 +++++++++----- .../pod-github/src/sync/pullrequests.ts | 154 +++++++++++------- .../github/pod-github/src/sync/repository.ts | 52 ++++-- .../pod-github/src/sync/reviewComments.ts | 64 ++++++-- .../pod-github/src/sync/reviewThreads.ts | 51 ++++-- .../github/pod-github/src/sync/reviews.ts | 55 +++++-- services/github/pod-github/src/sync/users.ts | 13 +- services/github/pod-github/src/types.ts | 25 ++- services/github/pod-github/src/worker.ts | 151 +++++++++-------- .../src/__tests__/attachments.test.ts | 1 + ws-tests/api-tests/src/__tests__/rest.test.ts | 2 +- 24 files changed, 694 insertions(+), 440 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 0d6ebd10b3..d3b6946089 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -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", diff --git a/packages/measurements-otlp/src/telemetry.ts b/packages/measurements-otlp/src/telemetry.ts index 1a9068474e..ec170c0162 100644 --- a/packages/measurements-otlp/src/telemetry.ts +++ b/packages/measurements-otlp/src/telemetry.ts @@ -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 } ): 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 { + const headers: Record = {} + if (this.context !== undefined) { + propagation.inject(this.context, headers) + } + return headers + } + with( 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 { diff --git a/packages/measurements/src/context.ts b/packages/measurements/src/context.ts index be7214c5f5..5080866518 100644 --- a/packages/measurements/src/context.ts +++ b/packages/measurements/src/context.ts @@ -157,6 +157,10 @@ export class MeasureMetricsContext implements MeasureContext { } } + extractMeta (): Record { + return {} + } + withSync( name: string, params: ParamsType, @@ -226,6 +230,10 @@ export class NoMetricsContext implements MeasureContext { return r instanceof Promise ? r : Promise.resolve(r) } + extractMeta (): Record { + return {} + } + withSync( name: string, params: ParamsType, diff --git a/packages/measurements/src/types.ts b/packages/measurements/src/types.ts index 496da3d331..18db172604 100644 --- a/packages/measurements/src/types.ts +++ b/packages/measurements/src/types.ts @@ -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 } /** @@ -87,6 +90,7 @@ export interface MeasureContext { fullParams?: FullParamsType logger?: MeasureLogger span?: WithOptions['span'] // By default true + meta?: Record } ) => MeasureContext @@ -108,6 +112,8 @@ export interface MeasureContext { opt?: WithOptions ) => T + extractMeta: () => Record + logger: MeasureLogger parent?: MeasureContext diff --git a/plugins/client-resources/src/connection.ts b/plugins/client-resources/src/connection.ts index ba72e49f24..456c881ed6 100644 --- a/plugins/client-resources/src/connection.ts +++ b/plugins/client-resources/src/connection.ts @@ -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 { - 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 { diff --git a/plugins/client/src/index.ts b/plugins/client/src/index.ts index 077284f21b..72d8e3d185 100644 --- a/plugins/client/src/index.ts +++ b/plugins/client/src/index.ts @@ -96,8 +96,5 @@ export default plugin(clientId, { }, function: { GetClient: '' as Resource - }, - event: { - NetworkRequests: '' as Metadata } }) diff --git a/server/rpc/src/rpc.ts b/server/rpc/src/rpc.ts index bbd4b356b8..d1aa70f22d 100644 --- a/server/rpc/src/rpc.ts +++ b/server/rpc/src/rpc.ts @@ -30,6 +30,8 @@ export interface Request

{ method: string params: P + meta?: Record + time?: number // Server time to perform operation } diff --git a/server/server/src/sessionManager.ts b/server/server/src/sessionManager.ts index 5ef5a66798..e988fa4a86 100644 --- a/server/server/src/sessionManager.ts +++ b/server/server/src/sessionManager.ts @@ -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) { diff --git a/services/github/pod-github/src/client.ts b/services/github/pod-github/src/client.ts index 878a92cf21..184ac89290 100644 --- a/services/github/pod-github/src/client.ts +++ b/services/github/pod-github/src/client.ts @@ -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 @@ -37,6 +38,7 @@ export async function createPlatformClient ( const connection = await ( await clientResources() ).function.GetClient(token, endpoint, { + ctx, onConnect: reconnect, useGlobalRPCHandler: true }) diff --git a/services/github/pod-github/src/platform.ts b/services/github/pod-github/src/platform.ts index 047174a02f..1dd77a19b3 100644 --- a/services/github/pod-github/src/platform.ts +++ b/services/github/pod-github/src/platform.ts @@ -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 { 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 { + async requestGithubAccessToken ( + ctx: MeasureContext, + payload: { + workspace: WorkspaceUuid + code: string + state: string + accountId: PersonId // Primary social Id + } + ): Promise { 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, 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 { + async checkRefreshToken (ctx: MeasureContext, auth: GithubUserRecord, force: boolean = false): Promise { 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 { - 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 { + async handleInstallationEventDelete (ctx: MeasureContext, installId: number): Promise { 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 { + public async revokeUserAuth (ctx: MeasureContext, record: GithubUserRecord): Promise { for (const [ws, acc] of Object.entries(record.accounts)) { await this.updateAccountAuthRecord( + ctx, { workspace: ws as WorkspaceUuid, accountId: acc }, { login: record._id }, undefined, diff --git a/services/github/pod-github/src/server.ts b/services/github/pod-github/src/server.ts index 1567a744a7..05a0b123bb 100644 --- a/services/github/pod-github/src/server.ts +++ b/services/github/pod-github/src/server.ts @@ -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, diff --git a/services/github/pod-github/src/sync/comments.ts b/services/github/pod-github/src/sync/comments.ts index 9db66c2883..baf8ebf439 100644 --- a/services/github/pod-github/src/sync/comments.ts +++ b/services/github/pod-github/src/sync/comments.ts @@ -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>() - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + + @withContext('comments-handle-event') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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 { - const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit + async deleteGithubDocument ( + ctx: MeasureContext, + container: ContainerFocus, + account: PersonId, + id: string + ): Promise { + 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 { 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) } } } diff --git a/services/github/pod-github/src/sync/issueBase.ts b/services/github/pod-github/src/sync/issueBase.ts index f11a1c3079..0ef7e42bcb 100644 --- a/services/github/pod-github/src/sync/issueBase.ts +++ b/services/github/pod-github/src/sync/issueBase.ts @@ -85,7 +85,6 @@ export type IssueUpdate = DocumentUpdate> 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, platformUpdate: DocumentUpdate, @@ -258,9 +259,16 @@ export abstract class IssueSyncManagerBase { account: PersonId ): Promise - abstract afterSync (existing: Issue, account: PersonId, issueExternal: any, info: DocSyncInfo): Promise + abstract afterSync ( + ctx: MeasureContext, + existing: Issue, + account: PersonId, + issueExternal: any, + info: DocSyncInfo + ): Promise async handleDiffUpdate ( + ctx: MeasureContext, container: ContainerFocus, existing: WithMarkup, info: DocSyncInfo, @@ -271,7 +279,7 @@ export abstract class IssueSyncManagerBase { ): Promise> { 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(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>, 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(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 + abstract deleteGithubDocument ( + ctx: MeasureContext, + container: ContainerFocus, + account: PersonId, + id: string + ): Promise 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 } diff --git a/services/github/pod-github/src/sync/issues.ts b/services/github/pod-github/src/sync/issues.ts index e918b511fd..19410d4249 100644 --- a/services/github/pod-github/src/sync/issues.ts +++ b/services/github/pod-github/src/sync/issues.ts @@ -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( + 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, account: PersonId): Promise {} + async afterSync ( + ctx: MeasureContext, + existing: Issue, + update: DocumentUpdate, + account: PersonId + ): Promise {} async performIssueFieldsUpdate ( + ctx: MeasureContext, info: DocSyncInfo, existing: WithMarkup, platformUpdate: DocumentUpdate, @@ -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, repository: GithubIntegrationRepository ): Promise { 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 { - const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit + async deleteGithubDocument ( + ctx: MeasureContext, + container: ContainerFocus, + account: PersonId, + id: string + ): Promise { + 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, existing: TGithubIssue, external: any): Promise {} + @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(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() diff --git a/services/github/pod-github/src/sync/pullrequests.ts b/services/github/pod-github/src/sync/pullrequests.ts index 039573b6b5..314a6e9750 100644 --- a/services/github/pod-github/src/sync/pullrequests.ts +++ b/services/github/pod-github/src/sync/pullrequests.ts @@ -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> export class PullRequestSyncManager extends IssueSyncManagerBase implements DocSyncManager { externalDerivedSync = true - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + + @withContext('pullrequests-handleEvent') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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 { + async afterSync ( + ctx: MeasureContext, + existing: Issue, + account: PersonId, + issueExternal: any, + info: DocSyncInfo + ): Promise { 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, platformUpdate: DocumentUpdate, @@ -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(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 { + async deleteGithubDocument ( + ctx: MeasureContext, + container: ContainerFocus, + account: PersonId, + id: string + ): Promise { // No delete is allowed for pull requests } } diff --git a/services/github/pod-github/src/sync/repository.ts b/services/github/pod-github/src/sync/repository.ts index 4b338cee1d..352ebbe071 100644 --- a/services/github/pod-github/src/sync/repository.ts +++ b/services/github/pod-github/src/sync/repository.ts @@ -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 | undefined> { + + @withContext('repository-sync') + async sync ( + ctx: MeasureContext, + existing: Doc | undefined, + info: DocSyncInfo + ): Promise | undefined> { return {} } async reloadRepositories ( + ctx: MeasureContext, integration: IntegrationContainer, repositories?: InstallationCreatedEvent['repositories'] | InstallationUnsuspendEvent['repositories'] ): Promise { @@ -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(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + @withContext('repository-handleEvent') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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 {} - 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 { 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 => { while (true) { const docs = await this.client.findAll( diff --git a/services/github/pod-github/src/sync/reviewComments.ts b/services/github/pod-github/src/sync/reviewComments.ts index d0e1c6b5aa..5cf98b349b 100644 --- a/services/github/pod-github/src/sync/reviewComments.ts +++ b/services/github/pod-github/src/sync/reviewComments.ts @@ -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>() - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + + @withContext('review-comments-handleEvent') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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 { - 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[], diff --git a/services/github/pod-github/src/sync/reviewThreads.ts b/services/github/pod-github/src/sync/reviewThreads.ts index 03c6293f20..b146b0dcb3 100644 --- a/services/github/pod-github/src/sync/reviewThreads.ts +++ b/services/github/pod-github/src/sync/reviewThreads.ts @@ -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>() - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + + @withContext('review-threads-handleEvent') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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[], diff --git a/services/github/pod-github/src/sync/reviews.ts b/services/github/pod-github/src/sync/reviews.ts index abf018cf4e..cee3e178ee 100644 --- a/services/github/pod-github/src/sync/reviews.ts +++ b/services/github/pod-github/src/sync/reviews.ts @@ -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>() - async handleEvent(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise { + + @withContext('reviews-handleEvent') + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise { 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 { - const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit + async deleteGithubDocument ( + ctx: MeasureContext, + container: ContainerFocus, + account: PersonId, + id: string + ): Promise { + 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[], diff --git a/services/github/pod-github/src/sync/users.ts b/services/github/pod-github/src/sync/users.ts index b018dce4fa..f49f18e0e3 100644 --- a/services/github/pod-github/src/sync/users.ts +++ b/services/github/pod-github/src/sync/users.ts @@ -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(integration: IntegrationContainer, derivedClient: TxOperations, evt: T): Promise {} + async handleEvent( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + evt: T + ): Promise {} async externalSync ( + ctx: MeasureContext, integration: IntegrationContainer, derivedClient: TxOperations, kind: ExternalSyncField, @@ -49,11 +57,12 @@ export class UsersSyncManager implements DocSyncManager { prj: GithubProject ): Promise {} - 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[], diff --git a/services/github/pod-github/src/types.ts b/services/github/pod-github/src/types.ts index 8341354c27..8957035d14 100644 --- a/services/github/pod-github/src/types.ts +++ b/services/github/pod-github/src/types.ts @@ -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) => Promise getAccount: (user?: UserInfo | null) => Promise getAccountU: (user: User) => Promise - getOctokit: (account: PersonId) => Promise + getOctokit: (ctx: MeasureContext, account: PersonId) => Promise getMarkupSafe: ( container: IntegrationContainer, text?: string | null, @@ -106,13 +107,14 @@ export interface IntegrationManager { getTaskTypeOf: (project: Ref, ofClass: Ref>) => Promise handleEvent: ( + ctx: MeasureContext, requestClass: Ref>, integrationId: number | undefined, repo: GithubIntegrationRepository, event: T ) => Promise - doSyncFor: (docs: DocSyncInfo[], project: GithubProject) => Promise + doSyncFor: (ctx: MeasureContext, docs: DocSyncInfo[], project: GithubProject) => Promise getWorkspaceId: () => WorkspaceUuid getWorkspaceUrl: () => string getBranding: () => Branding | null @@ -145,6 +147,7 @@ export interface DocSyncManager { init: (provider: IntegrationManager) => Promise // 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 | 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 - handleEvent: (integration: IntegrationContainer, derivedClient: TxOperations, event: T) => Promise + handleEvent: ( + ctx: MeasureContext, + integration: IntegrationContainer, + derivedClient: TxOperations, + event: T + ) => Promise externalDerivedSync: boolean - repositoryDisabled: (integration: IntegrationContainer, repo: GithubIntegrationRepository) => void + repositoryDisabled: ( + ctx: MeasureContext, + integration: IntegrationContainer, + repo: GithubIntegrationRepository + ) => void } /** diff --git a/services/github/pod-github/src/worker.ts b/services/github/pod-github/src/worker.ts index fe2f41d280..9780b2a976 100644 --- a/services/github/pod-github/src/worker.ts +++ b/services/github/pod-github/src/worker.ts @@ -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 { + async reloadRepositories (ctx: MeasureContext, installationId: number): Promise { 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 { + @withContext('get-octokit') + async getOctokit (ctx: MeasureContext, account: PersonId): Promise { 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 { + async updateIntegrations (ctx: MeasureContext): Promise { 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[] ): Promise { @@ -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) as GithubProject) + await this.doSyncFor(ctx, v, _projects.get(k as Ref) as GithubProject) } } return docs.length !== 0 } - async doSyncFor (docs: DocSyncInfo[], project: GithubProject): Promise { + async doSyncFor (ctx: MeasureContext, docs: DocSyncInfo[], project: GithubProject): Promise { 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>, syncInfo: DocSyncInfo[], project: GithubProject): Promise { + async syncClass ( + ctx: MeasureContext, + _class: Ref>, + syncInfo: DocSyncInfo[], + project: GithubProject + ): Promise { const externalDocs = await this._client.findAll(_class, { _id: { $in: syncInfo.map((it) => it._id as Ref) } }) @@ -1452,7 +1437,7 @@ export class GithubWorker implements IntegrationManager { _id: existing.space as Ref }) 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 { + async _performFullSync (ctx: MeasureContext): Promise { // 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(requestClass: Ref>, integrationId: number | undefined, event: T): Promise { + async handleEvent( + ctx: MeasureContext, + requestClass: Ref>, + integrationId: number | undefined, + event: T + ): Promise { 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 diff --git a/services/gmail/pod-gmail/src/__tests__/attachments.test.ts b/services/gmail/pod-gmail/src/__tests__/attachments.test.ts index 839223e5e7..d7b7d70484 100644 --- a/services/gmail/pod-gmail/src/__tests__/attachments.test.ts +++ b/services/gmail/pod-gmail/src/__tests__/attachments.test.ts @@ -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(), diff --git a/ws-tests/api-tests/src/__tests__/rest.test.ts b/ws-tests/api-tests/src/__tests__/rest.test.ts index 9116fb4c55..a07daf2af6 100644 --- a/ws-tests/api-tests/src/__tests__/rest.test.ts +++ b/ws-tests/api-tests/src/__tests__/rest.test.ts @@ -296,5 +296,5 @@ async function checkFindPerformance (conn: RestClient): Promise { const avg = total / ops // console.log('ops:', ops, 'total:', total, 'avg:', ) expect(ops).toEqual(attempts) - expect(avg).toBeLessThan(6) + expect(avg).toBeLessThan(10) }