1+ package cc.unitmesh.devti.observer.agent
2+
3+ import cc.unitmesh.devti.AutoDevNotifications
4+ import cc.unitmesh.devti.settings.devops.devopsPromptsSettings
5+ import com.intellij.notification.NotificationType
6+ import com.intellij.openapi.diagnostic.Logger
7+ import com.intellij.openapi.project.Project
8+ import com.intellij.util.concurrency.AppExecutorUtil
9+ import git4idea.push.GitPushListener
10+ import git4idea.push.GitPushRepoResult
11+ import git4idea.repo.GitRepository
12+ import org.kohsuke.github.*
13+ import java.util.concurrent.ScheduledFuture
14+ import java.util.concurrent.TimeUnit
15+
16+ class PipelineStatusProcessor (private val project : Project ) : AgentProcessor, GitPushListener {
17+ private val log= Logger .getInstance(PipelineStatusProcessor ::class .java)
18+ private var monitoringJob: ScheduledFuture <* >? = null
19+ private val timeoutMinutes= 30
20+
21+ override fun onCompleted (repository : GitRepository ,pushResult : GitPushRepoResult ) {
22+ // 检查 push 是否成功
23+ if (pushResult.type!= GitPushRepoResult .Type .SUCCESS ) {
24+ log.info(" Push failed, skipping pipeline monitoring" )
25+ return
26+ }
27+
28+ // 获取最新的 commit SHA
29+ val latestCommit= repository.currentRevision
30+ if (latestCommit== null ) {
31+ log.warn(" Could not determine latest commit SHA" )
32+ return
33+ }
34+
35+ log.info(" Push successful, starting pipeline monitoring for commit:$latestCommit " )
36+
37+ // 获取远程仓库信息
38+ val remoteUrl= getGitHubRemoteUrl(repository)
39+ if (remoteUrl== null ) {
40+ log.warn(" No GitHub remote URL found" )
41+ return
42+ }
43+
44+ // 开始监听流水线
45+ startMonitoring(repository, latestCommit, remoteUrl)
46+ }
47+
48+ override fun process () {
49+ // AgentProcessor 接口要求的方法,这里可以为空
50+ }
51+
52+ private fun getGitHubRemoteUrl (repository : GitRepository ):String? {
53+ return repository.remotes.firstOrNull { remote->
54+ remote.urls.any { url->
55+ url.contains(" github.com" )
56+ }
57+ }?.urls?.firstOrNull { it.contains(" github.com" ) }
58+ }
59+
60+ private fun startMonitoring (repository : GitRepository ,commitSha : String ,remoteUrl : String ) {
61+ log.info(" Starting pipeline monitoring for commit:$commitSha " )
62+
63+ val startTime= System .currentTimeMillis()
64+
65+ monitoringJob= AppExecutorUtil .getAppScheduledExecutorService().scheduleWithFixedDelay({
66+ try {
67+ val elapsedMinutes= (System .currentTimeMillis()- startTime)/ (1000 * 60 )
68+
69+ if (elapsedMinutes>= timeoutMinutes) {
70+ log.info(" Pipeline monitoring timeout reached for commit:$commitSha " )
71+ AutoDevNotifications .notify(
72+ project,
73+ " GitHub Action monitoring timeout (30 minutes) for commit:${commitSha.take(7 )} " ,
74+ NotificationType .WARNING
75+ )
76+ stopMonitoring()
77+ return @scheduleWithFixedDelay
78+ }
79+
80+ val workflowRun= findWorkflowRunForCommit(remoteUrl, commitSha)
81+ if (workflowRun!= null ) {
82+ val isComplete= checkWorkflowStatus(workflowRun, commitSha)
83+ if (isComplete) {
84+ stopMonitoring()
85+ }
86+ }
87+ }catch (e: Exception ) {
88+ log.error(" Error monitoring pipeline for commit:$commitSha " , e)
89+ AutoDevNotifications .notify(
90+ project,
91+ " Error monitoring GitHub Action:${e.message} " ,
92+ NotificationType .ERROR
93+ )
94+ stopMonitoring()
95+ }
96+ },30 ,30 ,TimeUnit .SECONDS )// Check every 30 seconds
97+ }
98+
99+ private fun findWorkflowRunForCommit (remoteUrl : String ,commitSha : String ):GHWorkflowRun ? {
100+ try {
101+ val github= createGitHubConnection()
102+ val ghRepository= getGitHubRepository(github, remoteUrl)? : return null
103+
104+ // 获取所有 workflows
105+ val workflows= ghRepository.listWorkflows().toList()
106+
107+ // 查找与指定 commit 相关的 workflow run
108+ for (workflowin workflows) {
109+ val runs= workflow.listRuns()
110+ .iterator()
111+ .asSequence()
112+ .take(10 )// 限制检查最近的 10 个运行
113+ .find { it.headSha== commitSha }
114+
115+ if (runs!= null ) {
116+ return runs
117+ }
118+ }
119+
120+ return null
121+ }catch (e: Exception ) {
122+ log.error(" Error finding workflow run for commit:$commitSha " , e)
123+ return null
124+ }
125+ }
126+
127+ private fun checkWorkflowStatus (workflowRun : GHWorkflowRun ,commitSha : String ):Boolean {
128+ return when (workflowRun.status) {
129+ GHWorkflowRun .Status .COMPLETED -> {
130+ when (workflowRun.conclusion) {
131+ GHWorkflowRun .Conclusion .SUCCESS -> {
132+ AutoDevNotifications .notify(
133+ project,
134+ " ✅ GitHub Action completed successfully for commit:${commitSha.take(7 )} " ,
135+ NotificationType .INFORMATION
136+ )
137+ true
138+ }
139+ GHWorkflowRun .Conclusion .FAILURE ,
140+ GHWorkflowRun .Conclusion .CANCELLED ,
141+ GHWorkflowRun .Conclusion .TIMED_OUT -> {
142+ AutoDevNotifications .notify(
143+ project,
144+ " ❌ GitHub Action failed for commit:${commitSha.take(7 )} -${workflowRun.conclusion} " ,
145+ NotificationType .ERROR
146+ )
147+ true
148+ }
149+ else -> {
150+ log.info(" Workflow completed with conclusion:${workflowRun.conclusion} " )
151+ false
152+ }
153+ }
154+ }
155+ GHWorkflowRun .Status .IN_PROGRESS ,GHWorkflowRun .Status .QUEUED -> {
156+ log.info(" Workflow still running:${workflowRun.status} " )
157+ false
158+ }
159+ else -> {
160+ log.info(" Unknown workflow status:${workflowRun.status} " )
161+ false
162+ }
163+ }
164+ }
165+
166+ private fun createGitHubConnection ():GitHub {
167+ val token= project.devopsPromptsSettings?.githubToken
168+ return if (token.isNullOrBlank()) {
169+ GitHub .connectAnonymously()
170+ }else {
171+ GitHub .connectUsingOAuth(token)
172+ }
173+ }
174+
175+ private fun getGitHubRepository (github : GitHub ,remoteUrl : String ):GHRepository ? {
176+ try {
177+ val repoPath= extractRepositoryPath(remoteUrl)? : return null
178+ return github.getRepository(repoPath)
179+ }catch (e: Exception ) {
180+ log.error(" Error getting GitHub repository from URL:$remoteUrl " , e)
181+ return null
182+ }
183+ }
184+
185+ private fun extractRepositoryPath (remoteUrl : String ):String? {
186+ // Handle both HTTPS and SSH URLs
187+ val httpsPattern= Regex (" https://github\\ .com/([^/]+/[^/]+)(?:\\ .git)?/?" )
188+ val sshPattern= Regex (" git@github\\ .com:([^/]+/[^/]+)(?:\\ .git)?/?" )
189+
190+ return httpsPattern.find(remoteUrl)?.groupValues?.get(1 )
191+ ? : sshPattern.find(remoteUrl)?.groupValues?.get(1 )
192+ }
193+
194+ private fun stopMonitoring () {
195+ monitoringJob?.cancel(false )
196+ monitoringJob= null
197+ log.info(" Pipeline monitoring stopped" )
198+ }
199+ }