Skip to main content
Glama
feature.ts11.7 kB
import { WebApi } from 'azure-devops-node-api'; import { GitPullRequestStatus, GitStatusState, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { PolicyEvaluationRecord, PolicyEvaluationStatus, } from 'azure-devops-node-api/interfaces/PolicyInterfaces'; import { AzureDevOpsError, AzureDevOpsResourceNotFoundError, } from '../../../shared/errors'; export interface PullRequestChecksOptions { projectId: string; repositoryId: string; pullRequestId: number; } export interface PipelineReference { pipelineId?: number; definitionId?: number; runId?: number; buildId?: number; displayName?: string; targetUrl?: string; } export interface PullRequestStatusCheck { id?: number; state: string; description?: string; context?: { name?: string; genre?: string; }; createdDate?: string; updatedDate?: string; targetUrl?: string; pipeline?: PipelineReference; } export interface PullRequestPolicyCheck { evaluationId?: string; status: string; isBlocking?: boolean; isEnabled?: boolean; configurationId?: number; configurationRevision?: number; configurationTypeId?: string; configurationTypeDisplayName?: string; displayName?: string; startedDate?: string; completedDate?: string; message?: string; targetUrl?: string; pipeline?: PipelineReference; } export interface PullRequestChecksResult { statuses: PullRequestStatusCheck[]; policyEvaluations: PullRequestPolicyCheck[]; } /** * Retrieve status checks and policy evaluations for a pull request. */ export async function getPullRequestChecks( connection: WebApi, options: PullRequestChecksOptions, ): Promise<PullRequestChecksResult> { try { const [gitApi, policyApi, projectId] = await Promise.all([ connection.getGitApi(), connection.getPolicyApi(), resolveProjectId(connection, options.projectId), ]); const [statusRecords, evaluationRecords] = await Promise.all([ gitApi.getPullRequestStatuses( options.repositoryId, options.pullRequestId, projectId, ), policyApi.getPolicyEvaluations( projectId, buildPolicyArtifactId(projectId, options.pullRequestId), ), ]); return { statuses: (statusRecords ?? []).map(mapStatusRecord), policyEvaluations: (evaluationRecords ?? []).map(mapEvaluationRecord), }; } catch (error) { if (error instanceof AzureDevOpsError) { throw error; } throw new Error( `Failed to get pull request checks: ${ error instanceof Error ? error.message : String(error) }`, ); } } const buildPolicyArtifactId = (projectId: string, pullRequestId: number) => `vstfs:///CodeReview/CodeReviewId/${projectId}/${pullRequestId}`; const projectIdGuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const resolveProjectId = async ( connection: WebApi, projectIdOrName: string, ): Promise<string> => { if (projectIdGuidPattern.test(projectIdOrName)) { return projectIdOrName; } const coreApi = await connection.getCoreApi(); const project = await coreApi.getProject(projectIdOrName); if (!project?.id) { throw new AzureDevOpsResourceNotFoundError( `Project '${projectIdOrName}' not found`, ); } return project.id; }; const gitStatusStateMap = GitStatusState as unknown as Record<number, string>; const policyStatusMap = PolicyEvaluationStatus as unknown as Record< number, string >; const mapStatusRecord = ( status: GitPullRequestStatus, ): PullRequestStatusCheck => { const pipeline = mergePipelineReferences( parsePipelineReferenceFromUrl(status.targetUrl), extractPipelineReferenceFromObject(status.context), extractPipelineReferenceFromObject(status.properties), ); return { id: status.id, state: formatEnumValue(status.state, gitStatusStateMap), description: status.description, context: { name: status.context?.name, genre: status.context?.genre, }, createdDate: toIsoString(status.creationDate), updatedDate: toIsoString(status.updatedDate), targetUrl: status.targetUrl ?? pipeline?.targetUrl, pipeline, }; }; const mapEvaluationRecord = ( evaluation: PolicyEvaluationRecord, ): PullRequestPolicyCheck => { const settings = (evaluation.configuration?.settings as Record<string, unknown>) || {}; const context = (evaluation.context as Record<string, unknown> | undefined) ?? {}; const pipeline = mergePipelineReferences( extractPipelineReferenceFromObject(settings), extractPipelineReferenceFromObject(context), parsePipelineReferenceFromUrl( extractString(settings.targetUrl) ?? extractString(context.targetUrl), ), ); const displayName = extractString(settings.displayName) ?? extractString(context.displayName) ?? evaluation.configuration?.type?.displayName; const targetUrl = pipeline?.targetUrl ?? extractString(context.targetUrl) ?? extractString(settings.targetUrl); return { evaluationId: evaluation.evaluationId, status: formatEnumValue(evaluation.status, policyStatusMap), isBlocking: evaluation.configuration?.isBlocking, isEnabled: evaluation.configuration?.isEnabled, configurationId: evaluation.configuration?.id, configurationRevision: evaluation.configuration?.revision, configurationTypeId: evaluation.configuration?.type?.id, configurationTypeDisplayName: evaluation.configuration?.type?.displayName, displayName, startedDate: toIsoString(evaluation.startedDate), completedDate: toIsoString(evaluation.completedDate), message: extractString(context.message) ?? extractString(settings.message), targetUrl, pipeline, }; }; const formatEnumValue = ( value: number | undefined, map: Record<number, string>, ): string => { if (typeof value === 'number' && map[value]) { const name = map[value]; return name.charAt(0).toLowerCase() + name.slice(1); } return 'unknown'; }; const toIsoString = (date: Date | undefined): string | undefined => date ? date.toISOString() : undefined; const extractString = (value: unknown): string | undefined => typeof value === 'string' ? value : undefined; const parseNumeric = (value: unknown): number | undefined => { if (value === null || value === undefined) { return undefined; } const numeric = Number(value); return Number.isFinite(numeric) ? numeric : undefined; }; const parseIdFromUri = (uri?: string): number | undefined => { if (!uri) { return undefined; } const match = uri.match(/(\d+)(?!.*\d)/); if (!match) { return undefined; } const id = Number(match[1]); return Number.isFinite(id) ? id : undefined; }; const parsePipelineReferenceFromUrl = ( targetUrl?: string, ): PipelineReference | undefined => { if (!targetUrl) { return undefined; } try { const url = new URL(targetUrl); const result: PipelineReference = { targetUrl }; const setParam = (param: string, setter: (value: number) => void) => { const raw = url.searchParams.get(param); const numeric = parseNumeric(raw); if (numeric !== undefined) { setter(numeric); } }; setParam('pipelineId', (value) => { result.pipelineId = value; }); setParam('definitionId', (value) => { result.definitionId = value; }); setParam('buildDefinitionId', (value) => { result.definitionId = result.definitionId ?? value; }); setParam('runId', (value) => { result.runId = value; }); setParam('buildId', (value) => { result.buildId = value; result.runId = result.runId ?? value; }); const segments = url.pathname.split('/').filter(Boolean); const pipelinesIndex = segments.lastIndexOf('pipelines'); if (pipelinesIndex !== -1 && pipelinesIndex + 1 < segments.length) { const pipelineCandidate = parseNumeric(segments[pipelinesIndex + 1]); if (pipelineCandidate !== undefined) { result.pipelineId = result.pipelineId ?? pipelineCandidate; } } const runsIndex = segments.lastIndexOf('runs'); if (runsIndex !== -1 && runsIndex + 1 < segments.length) { const runCandidate = parseNumeric(segments[runsIndex + 1]); if (runCandidate !== undefined) { result.runId = result.runId ?? runCandidate; } if (runsIndex > 0) { const preceding = segments[runsIndex - 1]; const pipelineCandidate = parseNumeric(preceding); if (pipelineCandidate !== undefined) { result.pipelineId = result.pipelineId ?? pipelineCandidate; } } } const buildMatch = url.pathname.match(/\/build\/(?:definition\/)?(\d+)/i); if (!result.definitionId && buildMatch) { const id = parseNumeric(buildMatch[1]); if (id !== undefined) { result.definitionId = id; } } const buildUriMatch = url.pathname.match(/\/Build\/Build\/(\d+)/i); if (buildUriMatch) { const buildId = parseNumeric(buildUriMatch[1]); if (buildId !== undefined) { result.buildId = result.buildId ?? buildId; result.runId = result.runId ?? buildId; } } return result; } catch { return { targetUrl }; } }; const extractPipelineReferenceFromObject = ( value: unknown, ): PipelineReference | undefined => { if (!value || typeof value !== 'object') { return undefined; } const object = value as Record<string, unknown>; const candidate: PipelineReference = {}; const pipelineId = parseNumeric( object.pipelineId ?? (object.pipeline as Record<string, unknown> | undefined)?.id, ); if (pipelineId !== undefined) { candidate.pipelineId = pipelineId; } const definitionId = parseNumeric( object.definitionId ?? object.buildDefinitionId ?? object.pipelineDefinitionId ?? (object.definition as Record<string, unknown> | undefined)?.id, ); if (definitionId !== undefined) { candidate.definitionId = definitionId; } const runId = parseNumeric( object.runId ?? object.buildId ?? object.stageRunId ?? object.jobRunId ?? object.planId, ); if (runId !== undefined) { candidate.runId = runId; } const buildId = parseNumeric( object.buildId ?? (object.build as Record<string, unknown> | undefined)?.id ?? parseIdFromUri(extractString(object.buildUri)) ?? parseIdFromUri(extractString(object.uri)), ); if (buildId !== undefined) { candidate.buildId = candidate.buildId ?? buildId; if (candidate.runId === undefined) { candidate.runId = buildId; } } const displayName = extractString(object.displayName) ?? extractString(object.name); if (displayName) { candidate.displayName = displayName; } const targetUrl = extractString(object.targetUrl) ?? extractString(object.url) ?? extractString(object.href); return mergePipelineReferences( candidate, parsePipelineReferenceFromUrl(targetUrl), ); }; const mergePipelineReferences = ( ...refs: Array<PipelineReference | undefined> ): PipelineReference | undefined => { const merged: PipelineReference = {}; let hasValue = false; for (const ref of refs) { if (!ref) { continue; } const apply = <K extends keyof PipelineReference>(key: K) => { const value = ref[key]; if (value !== undefined && merged[key] === undefined) { merged[key] = value; hasValue = true; } }; apply('pipelineId'); apply('definitionId'); apply('runId'); apply('buildId'); apply('displayName'); apply('targetUrl'); } return hasValue ? merged : undefined; };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Tiberriver256/mcp-server-azure-devops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server