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