feat: Pass tracing info with websocket (#9627)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2025-07-31 22:58:25 +05:00
committed by GitHub
parent fd5354a7d1
commit d32beb8ead
24 changed files with 694 additions and 440 deletions
+76 -44
View File
@@ -8,10 +8,10 @@
*/
import { Analytics } from '@hcengineering/analytics'
import core, {
PersonId,
AttachedData,
Doc,
DocumentUpdate,
PersonId,
Ref,
SortingOrder,
Status,
@@ -20,7 +20,9 @@ import core, {
generateId,
makeCollabId,
makeCollabJsonId,
makeDocCollabId
makeDocCollabId,
withContext,
type MeasureContext
} from '@hcengineering/core'
import github, {
DocSyncInfo,
@@ -64,7 +66,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
return assignees
}
@withContext('issues-handleEvent')
async handleEvent<T = IssuesEvent | ProjectsV2ItemEvent>(
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
evt: T
@@ -72,7 +76,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
await this.createPromise
const event = evt as IssuesEvent | ProjectsV2ItemEvent
this.ctx.info('issue:handleEvent', {
ctx.info('issue:handleEvent', {
nodeId: (event as IssuesEvent).issue?.html_url ?? (event as ProjectsV2ItemEvent)?.projects_v2_item.node_id,
action: event.action,
login: event.sender.login,
@@ -96,7 +100,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const issueEvent = event as IssuesEvent
const { project, repository } = await this.provider.getProjectAndRepository(issueEvent.repository.node_id)
if (project === undefined || repository === undefined) {
this.ctx.info('No project for repository', {
ctx.info('No project for repository', {
repository: issueEvent.repository.name,
nodeId: issueEvent.repository.node_id,
workspace: this.provider.getWorkspaceId()
@@ -107,12 +111,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const urlId = issueEvent.issue.url
await syncRunner.exec(urlId, async () => {
await this.processEvent(issueEvent, derivedClient, repository, integration, project)
await this.processEvent(ctx, issueEvent, derivedClient, repository, integration, project)
})
}
}
private async processEvent (
ctx: MeasureContext,
event: IssuesEvent,
derivedClient: TxOperations,
repo: GithubIntegrationRepository,
@@ -142,7 +147,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
externalData = response.repository.issue
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
// We need to check if we do not have sync data, we need to create by html_url
await this.createErrorSyncDataByUrl(
@@ -197,6 +202,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
await this.handleUpdate(
ctx,
externalData as IssueExternalData,
derivedClient,
update,
@@ -216,7 +222,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const update: IssueUpdate = {
assignee: persons?.[0] ?? null
}
await this.handleUpdate(externalData as IssueExternalData, derivedClient, update, account, prj, false)
await this.handleUpdate(ctx, externalData as IssueExternalData, derivedClient, update, account, prj, false)
break
}
case 'closed':
@@ -241,6 +247,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
)._id
}
await this.handleUpdate(
ctx,
externalData as IssueExternalData,
derivedClient,
update,
@@ -294,7 +301,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}
@withContext('issues-sync')
async sync (
ctx: MeasureContext,
existing: Doc | undefined,
info: DocSyncInfo,
parent: DocSyncInfo | undefined,
@@ -317,7 +326,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
if (info.repository == null) {
// No need to sync if component it not yet set
this.ctx.error('Not syncing repository === null', {
ctx.error('Not syncing repository === null', {
url: info.url,
identifier: (existing as Issue).identifier
})
@@ -332,14 +341,14 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
if (info.external === undefined && existing !== undefined) {
const repository = await this.provider.getRepositoryById(info.repository)
if (repository === undefined) {
this.ctx.error('Not syncing repository === undefined', {
ctx.error('Not syncing repository === undefined', {
url: info.url,
identifier: (existing as Issue).identifier
})
return { needSync: githubSyncVersion }
}
const description = await this.ctx.with(
const description = await ctx.with(
'query collaborative description',
{},
async () => {
@@ -350,23 +359,28 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
{ log: true }
)
this.ctx.info('create github issue', {
ctx.info('create github issue', {
title: (existing as Issue).title,
number: (existing as Issue).number,
workspace: this.provider.getWorkspaceId()
})
const createdIssueData = await this.ctx.with(
const createdIssueData = await ctx.with(
'create github issue',
{},
async () => {
this.createPromise = this.createGithubIssue(container, { ...(existing as Issue), description }, repository)
this.createPromise = this.createGithubIssue(
ctx,
container,
{ ...(existing as Issue), description },
repository
)
return await this.createPromise
},
{ id: (existing as Issue).identifier, workspace: this.provider.getWorkspaceId() },
{ log: true }
)
if (createdIssueData === undefined) {
this.ctx.error('Error create issue', { url: info.url })
ctx.error('Error create issue', { url: info.url })
return { needSync: githubSyncVersion, error: 'Unknown error on create issue' }
}
issueExternal = createdIssueData
@@ -397,7 +411,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
return { needSync: githubSyncVersion }
}
const syncResult = await this.syncToTarget(container, existing, issueExternal, derivedClient, info)
const syncResult = await this.syncToTarget(ctx, container, existing, issueExternal, derivedClient, info)
if (externalWasCreated && existing !== undefined) {
// Create child documents
@@ -412,7 +426,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
break
}
await this.provider.doSyncFor(attachedDocs, container.project)
await this.provider.doSyncFor(ctx, attachedDocs, container.project)
for (const child of attachedDocs) {
await derivedClient.update(child, { createId })
}
@@ -430,6 +444,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
async syncToTarget (
ctx: MeasureContext,
container: ContainerFocus,
existing: Doc | undefined,
issueExternal: IssueExternalData,
@@ -463,13 +478,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// TODO: Use GithubProject configuration to specify target type for issues
if (taskTypes.length === 0) {
// Missing required task type
this.ctx.error('Missing required task type', { identifier: (existing as Issue)?.identifier })
ctx.error('Missing required task type', { identifier: (existing as Issue)?.identifier })
return { needSync: githubSyncVersion }
}
if (existing === undefined) {
try {
this.ctx.info('create platform issue', {
ctx.info('create platform issue', {
url: issueExternal.url,
title: issueExternal.title,
workspace: this.provider.getWorkspaceId()
@@ -483,7 +498,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// No repository, it probable deleted
return { needSync: githubSyncVersion }
}
await this.ctx.with(
await ctx.with(
'create platform issue',
{},
async () => {
@@ -521,12 +536,12 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
return { needSync: githubSyncVersion, error: JSON.stringify(err) }
}
} else {
try {
const description = await this.ctx.with(
const description = await ctx.with(
'query collaborative description',
{},
async () => {
@@ -537,11 +552,12 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
{ log: true }
)
const updateResult = await this.ctx.with(
const updateResult = await ctx.with(
'diff update',
{},
async () =>
await this.handleDiffUpdate(
ctx,
container,
{ ...(existing as any), description },
info,
@@ -560,15 +576,21 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('error sync', { err })
ctx.error('error sync', { err })
return { needSync: githubSyncVersion, error: JSON.stringify(err), external: issueExternal }
}
}
}
async afterSync (existing: Issue, update: DocumentUpdate<Doc>, account: PersonId): Promise<void> {}
async afterSync (
ctx: MeasureContext,
existing: Issue,
update: DocumentUpdate<Doc>,
account: PersonId
): Promise<void> {}
async performIssueFieldsUpdate (
ctx: MeasureContext,
info: DocSyncInfo,
existing: WithMarkup<Issue>,
platformUpdate: DocumentUpdate<Issue>,
@@ -630,11 +652,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
if (hasFieldStateChanges || body !== undefined) {
if (body !== undefined && !isLocked) {
await this.ctx.with(
await ctx.with(
'==> updateIssue',
{},
async () => {
this.ctx.info('update fields', {
ctx.info('update fields', {
url: issueExternal.url,
...issueUpdate,
body,
@@ -671,11 +693,11 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
)
issueData.description = await this.provider.getMarkupSafe(container.container, body, this.stripGuestLink)
} else if (hasFieldStateChanges) {
await this.ctx.with(
await ctx.with(
'==> updateIssue',
{},
async () => {
this.ctx.info('update fields', { ...issueUpdate, workspace: this.provider.getWorkspaceId() })
ctx.info('update fields', { ...issueUpdate, workspace: this.provider.getWorkspaceId() })
if (isGHWriteAllowed()) {
const hasOtherChanges = Object.keys(issueUpdate).length > 0
if (state === 'OPEN') {
@@ -714,13 +736,14 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
async createGithubIssue (
ctx: MeasureContext,
container: ContainerFocus,
existing: WithMarkup<Issue>,
repository: GithubIntegrationRepository
): Promise<IssueExternalData | undefined> {
const existingIssue = existing
const okit = (await this.provider.getOctokit(existingIssue.modifiedBy)) ?? container.container.octokit
const okit = (await this.provider.getOctokit(ctx, existingIssue.modifiedBy)) ?? container.container.octokit
const repoId = repository.nodeId
@@ -760,8 +783,13 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
}
async deleteGithubDocument (container: ContainerFocus, account: PersonId, id: string): Promise<void> {
const okit = (await this.provider.getOctokit(account)) ?? container.container.octokit
async deleteGithubDocument (
ctx: MeasureContext,
container: ContainerFocus,
account: PersonId,
id: string
): Promise<void> {
const okit = (await this.provider.getOctokit(ctx, account)) ?? container.container.octokit
const q = `mutation deleteIssue($issueID: ID!) {
deleteIssue(
@@ -871,7 +899,9 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
async fillBackChanges (update: DocumentUpdate<Issue>, existing: TGithubIssue, external: any): Promise<void> {}
@withContext('issues-externalSync')
async externalSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
kind: ExternalSyncField,
@@ -897,7 +927,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const idsp = idsPart.map((it) => `"${it}"`).join(', ')
try {
const response: any = await this.ctx.with(
const response: any = await ctx.with(
'graphql.listIssue',
{},
() =>
@@ -919,19 +949,19 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
const issues: IssueExternalData[] = response.nodes
if (issues.some((issue) => issue.url === undefined && Object.keys(issue).length === 0)) {
this.ctx.error('empty document content', {
ctx.error('empty document content', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(response)
})
}
await this.syncIssues(tracker.class.Issue, repo, issues, derivedClient, docsPart)
await this.syncIssues(ctx, tracker.class.Issue, repo, issues, derivedClient, docsPart)
} catch (err: any) {
if (partsize > 1) {
partsize = 1
allSyncDocs.push(...docsPart)
this.ctx.warn('issue external retrieval switch to one by one mode', {
ctx.warn('issue external retrieval switch to one by one mode', {
errors: err.errors,
msg: err.message,
workspace: this.provider.getWorkspaceId()
@@ -940,7 +970,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
// We need to update issue, since it is missing on external side.
const syncDoc = syncDocs.find((it) => it.external.id === idsPart[0])
if (syncDoc !== undefined) {
this.ctx.warn('mark missing external PR', {
ctx.warn('mark missing external PR', {
errors: err.errors,
msg: err.message,
url: syncDoc.url,
@@ -961,7 +991,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
for (const d of syncDocs) {
if ((d.external as IssueExternalData).id == null) {
this.ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
ctx.error('failed to do external sync for', { objectClass: d.objectClass, _id: d._id })
// no external data for doc
await derivedClient.update<DocSyncInfo>(d, {
externalVersion: githubExternalSyncVersion
@@ -971,15 +1001,17 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
this.provider.sync()
} catch (err: any) {
Analytics.handleError(err)
this.ctx.error('Error', { err })
ctx.error('Error', { err })
}
}
repositoryDisabled (integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
repositoryDisabled (ctx: MeasureContext, integration: IntegrationContainer, repo: GithubIntegrationRepository): void {
integration.synchronized.delete(`${repo._id}:issues`)
}
@withContext('issues-externalFullSync')
async externalFullSync (
ctx: MeasureContext,
integration: IntegrationContainer,
derivedClient: TxOperations,
projects: GithubProject[],
@@ -1010,7 +1042,7 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const since = await getSince(this.client, tracker.class.Issue, repo)
this.ctx.info('sync external issues', { repo: repo.name, since, workspace: this.provider.getWorkspaceId() })
ctx.info('sync external issues', { repo: repo.name, since, workspace: this.provider.getWorkspaceId() })
const i = integration.octokit.graphql.paginate.iterator(
`query listIssue($name: String!, $owner: String!, $since: DateTime!, $cursor: String) {
@@ -1042,21 +1074,21 @@ export class IssueSyncManager extends IssueSyncManagerBase implements DocSyncMan
}
const issues: IssueExternalData[] = data.repository.issues.nodes
if (issues.some((issue) => issue.url === undefined && Object.keys(issue).length === 0)) {
this.ctx.error('empty document content', {
ctx.error('empty document content', {
repo: repo.name,
workspace: this.provider.getWorkspaceId(),
data: cutObjectArray(data)
})
}
await this.syncIssues(tracker.class.Issue, repo, issues, derivedClient)
await this.syncIssues(ctx, tracker.class.Issue, repo, issues, derivedClient)
this.provider.sync()
}
} catch (err: any) {
this.ctx.error('Error', { err })
ctx.error('Error', { err })
Analytics.handleError(err)
}
this.ctx.info('sync external issues - done', {
ctx.info('sync external issues - done', {
repo: repo.name,
since,
workspace: this.provider.getWorkspaceId()