Skip to content

Commit 123a7a7

Browse files
authored
[CI] In-progress PR comments (#72211) (#74038)
1 parent 6ae11fc commit 123a7a7

File tree

4 files changed

+97
-29
lines changed

4 files changed

+97
-29
lines changed

vars/githubPr.groovy

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,63 @@
1515
*/
1616
def withDefaultPrComments(closure) {
1717
catchErrors {
18+
// sendCommentOnError() needs to know if comments are enabled, so lets track it with a global
19+
// isPr() just ensures this functionality is skipped for non-PR builds
20+
buildState.set('PR_COMMENTS_ENABLED', isPr())
1821
catchErrors {
1922
closure()
2023
}
24+
sendComment(true)
25+
}
26+
}
2127

22-
if (!params.ENABLE_GITHUB_PR_COMMENTS || !isPr()) {
23-
return
24-
}
28+
def sendComment(isFinal = false) {
29+
if (!buildState.get('PR_COMMENTS_ENABLED')) {
30+
return
31+
}
2532

26-
def status = buildUtils.getBuildStatus()
27-
if (status == "ABORTED") {
28-
return;
29-
}
33+
def status = buildUtils.getBuildStatus()
34+
if (status == "ABORTED") {
35+
return
36+
}
3037

31-
def lastComment = getLatestBuildComment()
32-
def info = getLatestBuildInfo(lastComment) ?: [:]
33-
info.builds = (info.builds ?: []).takeRight(5) // Rotate out old builds
38+
def lastComment = getLatestBuildComment()
39+
def info = getLatestBuildInfo(lastComment) ?: [:]
40+
info.builds = (info.builds ?: []).takeRight(5) // Rotate out old builds
41+
42+
// If two builds are running at the same time, the first one should not post a comment after the second one
43+
if (info.number && info.number.toInteger() > env.BUILD_NUMBER.toInteger()) {
44+
return
45+
}
3446

35-
def message = getNextCommentMessage(info)
36-
postComment(message)
47+
def shouldUpdateComment = !!info.builds.find { it.number == env.BUILD_NUMBER }
48+
49+
def message = getNextCommentMessage(info, isFinal)
50+
51+
if (shouldUpdateComment) {
52+
updateComment(lastComment.id, message)
53+
} else {
54+
createComment(message)
3755

3856
if (lastComment && lastComment.user.login == 'kibanamachine') {
3957
deleteComment(lastComment.id)
4058
}
4159
}
4260
}
4361

62+
def sendCommentOnError(Closure closure) {
63+
try {
64+
closure()
65+
} catch (ex) {
66+
// If this is the first failed step, it's likely that the error hasn't propagated up far enough to mark the build as a failure
67+
currentBuild.result = 'FAILURE'
68+
catchErrors {
69+
sendComment(false)
70+
}
71+
throw ex
72+
}
73+
}
74+
4475
// Checks whether or not this currently executing build was triggered via a PR in the elastic/kibana repo
4576
def isPr() {
4677
return !!(env.ghprbPullId && env.ghprbPullLink && env.ghprbPullLink =~ /\/elastic\/kibana\//)
@@ -66,7 +97,7 @@ def getLatestBuildInfo() {
6697
}
6798

6899
def getLatestBuildInfo(comment) {
69-
return comment ? getBuildInfoFromComment(comment) : null
100+
return comment ? getBuildInfoFromComment(comment.body) : null
70101
}
71102

72103
def createBuildInfo() {
@@ -137,14 +168,25 @@ def getTestFailuresMessage() {
137168
return messages.join("\n")
138169
}
139170

140-
def getNextCommentMessage(previousCommentInfo = [:]) {
171+
def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) {
141172
def info = previousCommentInfo ?: [:]
142173
info.builds = previousCommentInfo.builds ?: []
143174

175+
// When we update an in-progress comment, we need to remove the old version from the history
176+
info.builds = info.builds.findAll { it.number != env.BUILD_NUMBER }
177+
144178
def messages = []
145179
def status = buildUtils.getBuildStatus()
146180

147-
if (status == 'SUCCESS') {
181+
if (!isFinal) {
182+
def failuresPart = status != 'SUCCESS' ? ', with failures' : ''
183+
messages << """
184+
## :hourglass_flowing_sand: Build in-progress${failuresPart}
185+
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
186+
* Commit: ${getCommitHash()}
187+
* This comment will update when the build is complete
188+
"""
189+
} else if (status == 'SUCCESS') {
148190
messages << """
149191
## :green_heart: Build Succeeded
150192
* [continuous-integration/kibana-ci/pull-request](${env.BUILD_URL})
@@ -172,7 +214,9 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
172214
* [Pipeline Steps](${env.BUILD_URL}flowGraphTable) (look for red circles / failed steps)
173215
* [Interpreting CI Failures](https://www.elastic.co/guide/en/kibana/current/interpreting-ci-failures.html)
174216
"""
217+
}
175218

219+
if (status != 'SUCCESS' && status != 'UNSTABLE') {
176220
try {
177221
def steps = getFailedSteps()
178222
if (steps?.size() > 0) {
@@ -207,7 +251,7 @@ def getNextCommentMessage(previousCommentInfo = [:]) {
207251
.join("\n\n")
208252
}
209253

210-
def postComment(message) {
254+
def createComment(message) {
211255
if (!isPr()) {
212256
error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build"
213257
}
@@ -223,6 +267,20 @@ def getComments() {
223267
}
224268
}
225269

270+
def updateComment(commentId, message) {
271+
if (!isPr()) {
272+
error "Trying to post a GitHub PR comment on a non-PR or non-elastic PR build"
273+
}
274+
275+
withGithubCredentials {
276+
def path = "repos/elastic/kibana/issues/comments/${commentId}"
277+
def json = toJSON([ body: message ]).toString()
278+
279+
def resp = githubApi([ path: path ], [ method: "POST", data: json, headers: [ "X-HTTP-Method-Override": "PATCH" ] ])
280+
return toJSON(resp)
281+
}
282+
}
283+
226284
def deleteComment(commentId) {
227285
withGithubCredentials {
228286
def path = "repos/elastic/kibana/issues/comments/${commentId}"

vars/jenkinsApi.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def getSteps() {
1010

1111
def getFailedSteps() {
1212
def steps = getSteps()
13-
def failedSteps = steps?.findAll { it.iconColor == "red" && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" }
13+
def failedSteps = steps?.findAll { (it.iconColor == "red" || it.iconColor == "red_anime") && it._class == "org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode" }
1414
failedSteps.each { step ->
1515
step.logs = "${env.BUILD_URL}execution/node/${step.id}/log".toString()
1616
}

vars/kibanaPipeline.groovy

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ def functionalTestProcess(String name, Closure closure) {
3737
"JOB=${name}",
3838
"KBN_NP_PLUGINS_BUILT=true",
3939
]) {
40-
closure()
40+
githubPr.sendCommentOnError {
41+
closure()
42+
}
4143
}
4244
}
4345
}
@@ -182,26 +184,32 @@ def bash(script, label) {
182184
}
183185

184186
def doSetup() {
185-
retryWithDelay(2, 15) {
186-
try {
187-
runbld("./test/scripts/jenkins_setup.sh", "Setup Build Environment and Dependencies")
188-
} catch (ex) {
187+
githubPr.sendCommentOnError {
188+
retryWithDelay(2, 15) {
189189
try {
190-
// Setup expects this directory to be missing, so we need to remove it before we do a retry
191-
bash("rm -rf ../elasticsearch", "Remove elasticsearch sibling directory, if it exists")
192-
} finally {
193-
throw ex
190+
runbld("./test/scripts/jenkins_setup.sh", "Setup Build Environment and Dependencies")
191+
} catch (ex) {
192+
try {
193+
// Setup expects this directory to be missing, so we need to remove it before we do a retry
194+
bash("rm -rf ../elasticsearch", "Remove elasticsearch sibling directory, if it exists")
195+
} finally {
196+
throw ex
197+
}
194198
}
195199
}
196200
}
197201
}
198202

199203
def buildOss() {
200-
runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana")
204+
githubPr.sendCommentOnError {
205+
runbld("./test/scripts/jenkins_build_kibana.sh", "Build OSS/Default Kibana")
206+
}
201207
}
202208

203209
def buildXpack() {
204-
runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana")
210+
githubPr.sendCommentOnError {
211+
runbld("./test/scripts/jenkins_xpack_build_kibana.sh", "Build X-Pack Kibana")
212+
}
205213
}
206214

207215
def runErrorReporter() {

vars/workers.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ def intake(jobName, String script) {
126126
return {
127127
ci(name: jobName, size: 's-highmem', ramDisk: true) {
128128
withEnv(["JOB=${jobName}"]) {
129-
runbld(script, "Execute ${jobName}")
129+
githubPr.sendCommentOnError {
130+
runbld(script, "Execute ${jobName}")
131+
}
130132
}
131133
}
132134
}

0 commit comments

Comments
 (0)