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 (workflow in 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
+ }
0 commit comments