Skip to content

Commit a34b916

Browse files
committed
feat(core): add GitHub Actions pipeline monitoring
Add automatic monitoring of GitHub Actions after successful git push with real-time status notifications and timeout handling.
1 parent 13b379f commit a34b916

File tree

5 files changed

+212
-149
lines changed

5 files changed

+212
-149
lines changed

core/src/233/main/resources/META-INF/autodev-core.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@
333333
<projectListeners>
334334
<listener class="cc.unitmesh.devti.actions.rename.RenameLookupManagerListener"
335335
topic="com.intellij.codeInsight.lookup.LookupManagerListener"/>
336+
<listener class="cc.unitmesh.devti.observer.agent.PipelineStatusProcessor"
337+
topic="git4idea.push.GitPushListener"/>
336338
</projectListeners>
337339

338340
<extensions defaultExtensionNs="cc.unitmesh">

core/src/main/kotlin/cc/unitmesh/devti/AutoDevNotifications.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ object AutoDevNotifications {
1010
return NotificationGroupManager.getInstance().getNotificationGroup("AutoDev.notification.group")
1111
}
1212

13-
fun notify(project: Project, msg: String) {
14-
group()?.createNotification(msg, NotificationType.INFORMATION)?.notify(project)
13+
fun notify(project: Project, msg: String, type: NotificationType?) {
14+
if (type == null) {
15+
info(project, msg)
16+
} else {
17+
group()?.createNotification(msg, type)?.notify(project)
18+
}
1519
}
1620

1721
fun error(project: Project, msg: String) {

core/src/main/kotlin/cc/unitmesh/devti/observer/RemoteHookObserverFixed.kt

Lines changed: 0 additions & 144 deletions
This file was deleted.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
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

Comments
 (0)