Skip to content

Commit 61b7bf1

Browse files
committed
chore: Trace Azure DevOps workItems queries
1 parent 7134bbb commit 61b7bf1

File tree

1 file changed

+160
-145
lines changed

1 file changed

+160
-145
lines changed

Diff for: packages/server/utils/AzureDevOpsServerManager.ts

+160-145
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {JSONContent} from '@tiptap/core'
2+
import tracer from 'dd-trace'
23
import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId'
34
import IntegrationHash from 'parabol-client/shared/gqlIds/IntegrationHash'
45
import {splitTipTapContent} from 'parabol-client/shared/tiptap/splitTipTapContent'
@@ -422,55 +423,59 @@ class AzureDevOpsServerManager implements TaskIntegrationManager {
422423
}
423424

424425
async getWorkItemData(instanceId: string, workItemIds: number[], fields?: string[]) {
425-
const workItems = [] as WorkItem[]
426-
let firstError: Error | undefined
427-
const uri = `https://${instanceId}/_apis/wit/workitemsbatch?api-version=7.1-preview.1`
428-
// we can fetch at most 200 items at once VS403474
429-
for (let i = 0; i < workItemIds.length; i += 200) {
430-
const ids = workItemIds.slice(i, i + 200)
431-
const payload = !!fields ? {ids, fields: fields} : {ids, $expand: 'Links'}
432-
const res = await this.post<WorkItemBatchResponse>(uri, payload)
426+
return tracer.trace('AzureDevOpsServerManager.getWorkItemData', async () => {
427+
const workItems = [] as WorkItem[]
428+
let firstError: Error | undefined
429+
const uri = `https://${instanceId}/_apis/wit/workitemsbatch?api-version=7.1-preview.1`
430+
// we can fetch at most 200 items at once VS403474
431+
for (let i = 0; i < workItemIds.length; i += 200) {
432+
const ids = workItemIds.slice(i, i + 200)
433+
const payload = !!fields ? {ids, fields: fields} : {ids, $expand: 'Links'}
434+
const res = await this.post<WorkItemBatchResponse>(uri, payload)
435+
if (res instanceof Error) {
436+
if (!firstError) {
437+
firstError = res
438+
}
439+
} else {
440+
const mappedWorkItems = (res.value as WorkItem[]).map((workItem) => {
441+
return {
442+
...workItem
443+
}
444+
})
445+
workItems.push(...mappedWorkItems)
446+
}
447+
}
448+
return {error: firstError, workItems: workItems}
449+
})
450+
}
451+
452+
async executeWiqlQuery(instanceId: string, query: string) {
453+
return tracer.trace('AzureDevOpsServerManager.executeWiqlQuery', async () => {
454+
const workItemReferences = [] as WorkItemReference[]
455+
let firstError: Error | undefined
456+
const payload = {
457+
query: query
458+
}
459+
const res = await this.post<WorkItemQueryResult>(
460+
`https://${instanceId}/_apis/wit/wiql?api-version=6.0`,
461+
payload
462+
)
433463
if (res instanceof Error) {
434464
if (!firstError) {
435465
firstError = res
436466
}
437467
} else {
438-
const mappedWorkItems = (res.value as WorkItem[]).map((workItem) => {
468+
const workItems = res.workItems.map((workItem) => {
469+
const {id, url} = workItem
439470
return {
440-
...workItem
471+
id,
472+
url
441473
}
442474
})
443-
workItems.push(...mappedWorkItems)
444-
}
445-
}
446-
return {error: firstError, workItems: workItems}
447-
}
448-
449-
async executeWiqlQuery(instanceId: string, query: string) {
450-
const workItemReferences = [] as WorkItemReference[]
451-
let firstError: Error | undefined
452-
const payload = {
453-
query: query
454-
}
455-
const res = await this.post<WorkItemQueryResult>(
456-
`https://${instanceId}/_apis/wit/wiql?api-version=6.0`,
457-
payload
458-
)
459-
if (res instanceof Error) {
460-
if (!firstError) {
461-
firstError = res
475+
workItemReferences.push(...workItems)
462476
}
463-
} else {
464-
const workItems = res.workItems.map((workItem) => {
465-
const {id, url} = workItem
466-
return {
467-
id,
468-
url
469-
}
470-
})
471-
workItemReferences.push(...workItems)
472-
}
473-
return {error: firstError, workItems: workItemReferences}
477+
return {error: firstError, workItems: workItemReferences}
478+
})
474479
}
475480

476481
async getWorkItems(
@@ -479,74 +484,78 @@ class AzureDevOpsServerManager implements TaskIntegrationManager {
479484
projectKeyFilters: string[] | null,
480485
isWIQL: boolean
481486
) {
482-
let projectFilter = ''
483-
if (projectKeyFilters && projectKeyFilters.length > 0) {
484-
projectKeyFilters.forEach((projectKey, idx) => {
485-
if (idx === 0) projectFilter = `AND ( [System.TeamProject] = '${projectKey}'`
486-
else projectFilter += ` OR [System.TeamProject] = '${projectKey}'`
487-
})
488-
projectFilter += ` )`
489-
}
490-
let customQueryString = ''
491-
if (isWIQL)
492-
customQueryString = queryString
493-
? `Select [System.Id], [System.Title], [System.State] From WorkItems Where ${queryString} ${projectFilter}`
494-
: `Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] IN('User Story', 'Task', 'Issue', 'Bug', 'Feature', 'Epic') AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc`
495-
else {
496-
const queryFilter = queryString ? `AND [System.Title] contains '${queryString}'` : ''
497-
customQueryString = `Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] IN('User Story', 'Task', 'Issue', 'Bug', 'Feature', 'Epic') AND [State] <> 'Closed' ${queryFilter} ${projectFilter} AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc`
498-
}
499-
return await this.executeWiqlQuery(instanceId, customQueryString)
487+
return tracer.trace('AzureDevOpsServerManager.getWorkItems', async () => {
488+
let projectFilter = ''
489+
if (projectKeyFilters && projectKeyFilters.length > 0) {
490+
projectKeyFilters.forEach((projectKey, idx) => {
491+
if (idx === 0) projectFilter = `AND ( [System.TeamProject] = '${projectKey}'`
492+
else projectFilter += ` OR [System.TeamProject] = '${projectKey}'`
493+
})
494+
projectFilter += ` )`
495+
}
496+
let customQueryString = ''
497+
if (isWIQL)
498+
customQueryString = queryString
499+
? `Select [System.Id], [System.Title], [System.State] From WorkItems Where ${queryString} ${projectFilter}`
500+
: `Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] IN('User Story', 'Task', 'Issue', 'Bug', 'Feature', 'Epic') AND [State] <> 'Closed' AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc`
501+
else {
502+
const queryFilter = queryString ? `AND [System.Title] contains '${queryString}'` : ''
503+
customQueryString = `Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.WorkItemType] IN('User Story', 'Task', 'Issue', 'Bug', 'Feature', 'Epic') AND [State] <> 'Closed' ${queryFilter} ${projectFilter} AND [State] <> 'Removed' order by [Microsoft.VSTS.Common.Priority] asc, [System.CreatedDate] desc`
504+
}
505+
return await this.executeWiqlQuery(instanceId, customQueryString)
506+
})
500507
}
501508

502509
async getAllUserWorkItems(
503510
queryString: string | null,
504511
projectKeyFilters: string[] | null,
505512
isWIQL: boolean
506513
) {
507-
const allWorkItems = [] as WorkItem[]
508-
let firstError: Error | undefined
509-
510-
const meResult = await this.getMe()
511-
const {error: meError, azureDevOpsUser} = meResult
512-
if (!!meError || !azureDevOpsUser) return {error: meError, projects: null}
513-
514-
const {id} = azureDevOpsUser
515-
const {error: accessibleError, accessibleOrgs} = await this.getAccessibleOrgs(id)
516-
if (!!accessibleError) return {error: accessibleError, projects: null}
517-
518-
for (const resource of accessibleOrgs) {
519-
const {accountName} = resource
520-
const instanceId = `dev.azure.com/${accountName}`
521-
const {error: workItemsError, workItems} = await this.getWorkItems(
522-
instanceId,
523-
queryString,
524-
projectKeyFilters,
525-
isWIQL
526-
)
527-
if (!!workItemsError) {
528-
if (!firstError) {
529-
firstError = workItemsError
530-
}
531-
}
532-
if (!!workItems) {
533-
const resturnedIds = workItems.map((workItem) => workItem.id)
534-
if (resturnedIds.length > 0) {
535-
const {error: fullWorkItemsError, workItems: fullWorkItems} = await this.getWorkItemData(
514+
return tracer.trace('AzureDevOpsServerManager.getAllUserWorkItems', async () => {
515+
const allWorkItems = [] as WorkItem[]
516+
let firstError: Error | undefined
517+
518+
const meResult = await this.getMe()
519+
const {error: meError, azureDevOpsUser} = meResult
520+
if (!!meError || !azureDevOpsUser) return {error: meError, projects: null}
521+
522+
const {id} = azureDevOpsUser
523+
const {error: accessibleError, accessibleOrgs} = await this.getAccessibleOrgs(id)
524+
if (!!accessibleError) return {error: accessibleError, projects: null}
525+
526+
await Promise.allSettled(
527+
accessibleOrgs.map(async (resource) => {
528+
const {accountName} = resource
529+
const instanceId = `dev.azure.com/${accountName}`
530+
const {error: workItemsError, workItems} = await this.getWorkItems(
536531
instanceId,
537-
resturnedIds
532+
queryString,
533+
projectKeyFilters,
534+
isWIQL
538535
)
539-
if (!!fullWorkItemsError) {
536+
if (!!workItemsError) {
540537
if (!firstError) {
541-
firstError = fullWorkItemsError
538+
firstError = workItemsError
542539
}
543-
} else {
544-
allWorkItems.push(...fullWorkItems)
545540
}
546-
}
547-
}
548-
}
549-
return {error: firstError, workItems: allWorkItems}
541+
if (!!workItems) {
542+
const resturnedIds = workItems.map((workItem) => workItem.id)
543+
if (resturnedIds.length > 0) {
544+
const {error: fullWorkItemsError, workItems: fullWorkItems} =
545+
await this.getWorkItemData(instanceId, resturnedIds)
546+
if (!!fullWorkItemsError) {
547+
if (!firstError) {
548+
firstError = fullWorkItemsError
549+
}
550+
} else {
551+
allWorkItems.push(...fullWorkItems)
552+
}
553+
}
554+
}
555+
})
556+
)
557+
return {error: firstError, workItems: allWorkItems}
558+
})
550559
}
551560

552561
async getMe() {
@@ -567,28 +576,30 @@ class AzureDevOpsServerManager implements TaskIntegrationManager {
567576
}
568577

569578
async getAllUserProjects() {
570-
const teamProjectReferences = [] as TeamProjectReference[]
571-
let firstError: Error | undefined
572-
const meResult = await this.getMe()
573-
const {error: meError, azureDevOpsUser} = meResult
574-
if (!!meError || !azureDevOpsUser) return {error: meError, projects: null}
575-
576-
const {id} = azureDevOpsUser
577-
const {error: accessibleError, accessibleOrgs} = await this.getAccessibleOrgs(id)
578-
if (!!accessibleError) return {error: accessibleError, projects: null}
579-
580-
for (const resource of accessibleOrgs) {
581-
const {error: accountProjectsError, accountProjects} = await this.getAccountProjects(
582-
resource.accountName
583-
)
584-
if (!!accountProjectsError && !firstError) {
585-
firstError = accountProjectsError
586-
break
587-
} else {
588-
teamProjectReferences.push(...accountProjects)
579+
return tracer.trace('AzureDevOpsServerManager.getAllUserProjects', async () => {
580+
const teamProjectReferences = [] as TeamProjectReference[]
581+
let firstError: Error | undefined
582+
const meResult = await this.getMe()
583+
const {error: meError, azureDevOpsUser} = meResult
584+
if (!!meError || !azureDevOpsUser) return {error: meError, projects: null}
585+
586+
const {id} = azureDevOpsUser
587+
const {error: accessibleError, accessibleOrgs} = await this.getAccessibleOrgs(id)
588+
if (!!accessibleError) return {error: accessibleError, projects: null}
589+
590+
for (const resource of accessibleOrgs) {
591+
const {error: accountProjectsError, accountProjects} = await this.getAccountProjects(
592+
resource.accountName
593+
)
594+
if (!!accountProjectsError && !firstError) {
595+
firstError = accountProjectsError
596+
break
597+
} else {
598+
teamProjectReferences.push(...accountProjects)
599+
}
589600
}
590-
}
591-
return {error: undefined, projects: teamProjectReferences}
601+
return {error: undefined, projects: teamProjectReferences}
602+
})
592603
}
593604

594605
private async getProjectProperties(instanceId: string, projectId: string) {
@@ -603,39 +614,43 @@ class AzureDevOpsServerManager implements TaskIntegrationManager {
603614
}
604615

605616
async getProjectProcessTemplate(instanceId: string, projectId: string) {
606-
let firstError: Error | undefined
607-
const result = await this.getProjectProperties(instanceId, projectId)
608-
if (result.error) {
609-
firstError = result.error
610-
}
611-
const processTemplateProperty = result.projectProperties.value[0]
612-
if (processTemplateProperty?.name !== 'System.CurrentProcessTemplateId') {
613-
return {error: firstError, projectTemplate: ''}
614-
}
615-
const processTemplateDetailsResult = await this.getProcessTemplate(
616-
instanceId,
617-
processTemplateProperty?.value
618-
)
619-
if (processTemplateDetailsResult.error) {
620-
if (!firstError) {
621-
firstError = processTemplateDetailsResult.error
617+
return tracer.trace('AzureDevOpsServerManager.getProjectProcessTemplate', async () => {
618+
let firstError: Error | undefined
619+
const result = await this.getProjectProperties(instanceId, projectId)
620+
if (result.error) {
621+
firstError = result.error
622622
}
623-
}
624-
return {error: firstError, projectTemplate: processTemplateDetailsResult.process}
623+
const processTemplateProperty = result.projectProperties.value[0]
624+
if (processTemplateProperty?.name !== 'System.CurrentProcessTemplateId') {
625+
return {error: firstError, projectTemplate: ''}
626+
}
627+
const processTemplateDetailsResult = await this.getProcessTemplate(
628+
instanceId,
629+
processTemplateProperty?.value
630+
)
631+
if (processTemplateDetailsResult.error) {
632+
if (!firstError) {
633+
firstError = processTemplateDetailsResult.error
634+
}
635+
}
636+
return {error: firstError, projectTemplate: processTemplateDetailsResult.process}
637+
})
625638
}
626639

627640
async getProcessTemplate(instanceId: string, processId: string) {
628-
let firstError: Error | undefined
629-
const uri = `https://${instanceId}/_apis/process/processes/${processId}?api-version=6.0`
630-
const result = await this.get<Process>(uri)
631-
const unknownProcessErrorCode = 'VS402362'
632-
if (result instanceof Error) {
633-
if (result.message.includes(unknownProcessErrorCode, 0)) {
634-
return {error: firstError, process: 'Basic'}
641+
return tracer.trace('AzureDevOpsServerManager.getProcessTemplate', async () => {
642+
let firstError: Error | undefined
643+
const uri = `https://${instanceId}/_apis/process/processes/${processId}?api-version=6.0`
644+
const result = await this.get<Process>(uri)
645+
const unknownProcessErrorCode = 'VS402362'
646+
if (result instanceof Error) {
647+
if (result.message.includes(unknownProcessErrorCode, 0)) {
648+
return {error: firstError, process: 'Basic'}
649+
}
650+
firstError = result
635651
}
636-
firstError = result
637-
}
638-
return {error: firstError, process: result.name}
652+
return {error: firstError, process: result.name}
653+
})
639654
}
640655

641656
async getProject(instanceId: string, projectId: string) {

0 commit comments

Comments
 (0)