From cd3fdb4a5ac4cb27dc260721e500826dc75eb1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B9=9B=E5=85=AE?= Date: Fri, 12 Jun 2026 04:12:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B1=95=E7=A4=BA=E9=A1=B9=E7=9B=AE=20?= =?UTF-8?q?Jenkins=20=E8=AF=8A=E6=96=AD=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/devopsApi.ts | 65 +++++++++++++++++++++++++++++++++ src/stores/platform.ts | 73 ++++++++++++++++++++++++++++++++++++++ src/types/devops.ts | 30 ++++++++++++++++ src/views/ProjectsView.vue | 64 ++++++++++++++++++++++++++++++++- 4 files changed, 231 insertions(+), 1 deletion(-) diff --git a/src/services/devopsApi.ts b/src/services/devopsApi.ts index c43dffa..9dd9c26 100644 --- a/src/services/devopsApi.ts +++ b/src/services/devopsApi.ts @@ -29,6 +29,8 @@ import type { PipelineStep, PlatformMessageSummary, ProjectConfig, + ProjectJenkinsDiagnosticsSummary, + ProjectJenkinsJobDiagnostic, ProjectRefs, ReleaseRun, ReleaseStatus, @@ -349,6 +351,14 @@ export const devopsApi = { return toProjectConfigs(await fetchProjects()) }, + async getProjectJenkinsDiagnostics(): Promise { + const { data } = await http.get( + '/projects/jenkins-diagnostics', + ) + lastDataSource = 'backend' + return sanitizeProjectJenkinsDiagnostics(data) + }, + async getProjectRefs(projectKey: string): Promise { const { data } = await http.get(`/projects/${projectKey}/refs`) lastDataSource = 'backend' @@ -787,6 +797,8 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] { lastReleasedAt: '-', lastActor: '-', jenkinsJob: environment.jenkinsJobPath, + jenkinsStatus: 'warning' as const, + jenkinsMessage: '尚未执行 Jenkins 诊断', wecomRobot: '后端通知配置', } }), @@ -800,6 +812,59 @@ function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] { }) } +function sanitizeProjectJenkinsDiagnostics( + value: ProjectJenkinsDiagnosticsSummary, +): ProjectJenkinsDiagnosticsSummary { + return { + checkedAt: safeText(value.checkedAt, new Date().toISOString()), + jobs: Array.isArray(value.jobs) + ? value.jobs.map(sanitizeProjectJenkinsJobDiagnostic) + : [], + } +} + +function sanitizeProjectJenkinsJobDiagnostic( + value: ProjectJenkinsJobDiagnostic, +): ProjectJenkinsJobDiagnostic { + return { + projectKey: safeText(value.projectKey, 'unknown-project'), + environment: toEnvironmentName(value.environment), + jenkinsJobPath: safeText(value.jenkinsJobPath, ''), + status: toProjectJenkinsDiagnosticStatus(value.status), + message: optionalSafeText(value.message), + buildable: typeof value.buildable === 'boolean' ? value.buildable : undefined, + url: optionalSafeText(value.url), + lastBuild: value.lastBuild + ? { + number: Number.isFinite(value.lastBuild.number) + ? value.lastBuild.number + : undefined, + result: value.lastBuild.result, + building: + typeof value.lastBuild.building === 'boolean' + ? value.lastBuild.building + : undefined, + url: optionalSafeText(value.lastBuild.url), + } + : undefined, + } +} + +function toProjectJenkinsDiagnosticStatus( + value: ProjectJenkinsJobDiagnostic['status'], +): ProjectJenkinsJobDiagnostic['status'] { + if ( + value === 'ok' || + value === 'missing' || + value === 'not_configured' || + value === 'unavailable' + ) { + return value + } + + return 'unavailable' +} + function toReleaseRun(run: BackendDeployRun, projectList: ProjectConfig[]): ReleaseRun { const projectKey = run.projectKey || 'unknown-project' const project = projectList.find((item) => item.key === projectKey) diff --git a/src/stores/platform.ts b/src/stores/platform.ts index c5521d7..f3b06da 100644 --- a/src/stores/platform.ts +++ b/src/stores/platform.ts @@ -12,6 +12,7 @@ import type { IntegrationStatus, IntegrationConfigStatus, ProjectConfig, + ProjectJenkinsDiagnosticsSummary, ProjectRefs, JenkinsSyncSummary, NotificationOutboxMessage, @@ -123,6 +124,45 @@ export const usePlatformStore = defineStore('platform', () => { return refs } + async function loadProjectJenkinsDiagnostics(): Promise { + const diagnostics = await devopsApi.getProjectJenkinsDiagnostics() + const jobByEnvironment = new Map( + diagnostics.jobs.map((job) => [`${job.projectKey}:${job.environment}`, job]), + ) + + projects.value = projects.value.map((project) => { + const environments = project.environments.map((environment) => { + const diagnostic = jobByEnvironment.get(`${project.key}:${environment.name}`) + + if (!diagnostic) { + return environment + } + + return { + ...environment, + jenkinsJob: diagnostic.jenkinsJobPath || environment.jenkinsJob, + jenkinsStatus: jenkinsDiagnosticState(diagnostic.status), + jenkinsMessage: diagnostic.message || jenkinsDiagnosticMessage(diagnostic.status), + jenkinsUrl: diagnostic.url, + jenkinsLastBuild: diagnostic.lastBuild, + } + }) + const hasError = environments.some((environment) => environment.jenkinsStatus === 'error') + const hasWarning = environments.some((environment) => environment.jenkinsStatus === 'warning') + + return { + ...project, + environments, + integrations: { + ...project.integrations, + jenkins: hasError ? 'error' : hasWarning ? 'warning' : 'healthy', + }, + } + }) + dataSource.value = devopsApi.getLastDataSource() + return diagnostics + } + async function loadAuditLogs() { auditLogs.value = await devopsApi.getAuditLogs() dataSource.value = devopsApi.getLastDataSource() @@ -171,8 +211,41 @@ export const usePlatformStore = defineStore('platform', () => { syncJenkinsRun, syncJenkinsRuns, loadProjectRefs, + loadProjectJenkinsDiagnostics, loadAuditLogs, loadNotificationOutbox, retryNotificationOutboxMessage, } }) + +function jenkinsDiagnosticState( + status: ProjectJenkinsDiagnosticsSummary['jobs'][number]['status'], +) { + if (status === 'ok') { + return 'healthy' as const + } + + if (status === 'not_configured') { + return 'warning' as const + } + + return 'error' as const +} + +function jenkinsDiagnosticMessage( + status: ProjectJenkinsDiagnosticsSummary['jobs'][number]['status'], +): string { + if (status === 'ok') { + return 'Jenkins job 可达' + } + + if (status === 'missing') { + return 'Jenkins job 不存在' + } + + if (status === 'not_configured') { + return 'Jenkins 集成未配置' + } + + return 'Jenkins job 不可用' +} diff --git a/src/types/devops.ts b/src/types/devops.ts index 805cc19..77b076f 100644 --- a/src/types/devops.ts +++ b/src/types/devops.ts @@ -187,6 +187,15 @@ export interface EnvironmentStatus { lastReleasedAt: string lastActor: string jenkinsJob: string + jenkinsStatus: HealthState + jenkinsMessage?: string + jenkinsUrl?: string + jenkinsLastBuild?: { + number?: number + result?: 'SUCCESS' | 'FAILURE' | 'ABORTED' | 'UNSTABLE' + building?: boolean + url?: string + } wecomRobot: string } @@ -214,6 +223,27 @@ export interface ProjectRefs { tags: ProjectRefItem[] } +export interface ProjectJenkinsJobDiagnostic { + projectKey: string + environment: EnvironmentName + jenkinsJobPath: string + status: 'ok' | 'missing' | 'not_configured' | 'unavailable' + message?: string + buildable?: boolean + url?: string + lastBuild?: { + number?: number + result?: 'SUCCESS' | 'FAILURE' | 'ABORTED' | 'UNSTABLE' + building?: boolean + url?: string + } +} + +export interface ProjectJenkinsDiagnosticsSummary { + checkedAt: string + jobs: ProjectJenkinsJobDiagnostic[] +} + export interface ProjectRefCommit { sha: string message: string diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue index 8f14138..01808a2 100644 --- a/src/views/ProjectsView.vue +++ b/src/views/ProjectsView.vue @@ -1,8 +1,40 @@