190 lines
4.5 KiB
Groovy
190 lines
4.5 KiB
Groovy
def getFailedBuildBlocks() {
|
|
def messages = [
|
|
getFailedSteps(),
|
|
getTestFailures(),
|
|
]
|
|
|
|
return messages
|
|
.findAll { !!it } // No blank strings
|
|
.collect { markdownBlock(it) }
|
|
}
|
|
|
|
def dividerBlock() {
|
|
return [ type: "divider" ]
|
|
}
|
|
|
|
// If a message is longer than the limit, split it up by '\n' into parts, and return as many parts as will fit within the limit
|
|
def shortenMessage(message, sizeLimit = 3000) {
|
|
if (message.size() <= sizeLimit) {
|
|
return message
|
|
}
|
|
|
|
def truncatedMessage = "[...truncated...]"
|
|
|
|
def parts = message.split("\n")
|
|
message = ""
|
|
|
|
for(def part in parts) {
|
|
if ((message.size() + part.size() + truncatedMessage.size() + 1) > sizeLimit) {
|
|
break;
|
|
}
|
|
message += part+"\n"
|
|
}
|
|
|
|
message += truncatedMessage
|
|
|
|
return message.size() <= sizeLimit ? message : truncatedMessage
|
|
}
|
|
|
|
def markdownBlock(message) {
|
|
return [
|
|
type: "section",
|
|
text: [
|
|
type: "mrkdwn",
|
|
text: shortenMessage(message, 3000), // 3000 is max text length for `section`s only
|
|
],
|
|
]
|
|
}
|
|
|
|
def contextBlock(message) {
|
|
return [
|
|
type: "context",
|
|
elements: [
|
|
[
|
|
type: 'mrkdwn',
|
|
text: message, // Not sure what the size limit is here, I tried 10000s of characters and it still worked
|
|
]
|
|
]
|
|
]
|
|
}
|
|
|
|
def getFailedSteps() {
|
|
try {
|
|
def steps = jenkinsApi.getFailedSteps()?.findAll { step ->
|
|
step.displayName != 'Check out from version control'
|
|
}
|
|
|
|
if (steps?.size() > 0) {
|
|
def list = steps.collect { "• <${it.logs}|${it.displayName}>" }.join("\n")
|
|
return "*Failed Steps*\n${list}"
|
|
}
|
|
} catch (ex) {
|
|
buildUtils.printStacktrace(ex)
|
|
print "Error retrieving failed pipeline steps for PR comment, will skip this section"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
def getTestFailures() {
|
|
def failures = testUtils.getFailures()
|
|
if (!failures) {
|
|
return ""
|
|
}
|
|
|
|
def messages = []
|
|
messages << "*Test Failures*"
|
|
|
|
def list = failures.take(10).collect {
|
|
def name = it
|
|
.fullDisplayName
|
|
.split(/\./, 2)[-1]
|
|
// Only the following three characters need to be escaped for link text, per Slack's docs
|
|
.replaceAll('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
|
|
return "• <${it.url}|${name}>"
|
|
}.join("\n")
|
|
|
|
def moreText = failures.size() > 10 ? "\n• ...and ${failures.size()-10} more" : ""
|
|
return "*Test Failures*\n${list}${moreText}"
|
|
}
|
|
|
|
def getDefaultDisplayName() {
|
|
return "${env.JOB_NAME} ${env.BUILD_DISPLAY_NAME}"
|
|
}
|
|
|
|
def getDefaultContext() {
|
|
def duration = currentBuild.durationString.replace(' and counting', '')
|
|
|
|
return contextBlock([
|
|
"${buildUtils.getBuildStatus().toLowerCase().capitalize()} after ${duration}",
|
|
"<https://ci.kibana.dev/${env.JOB_BASE_NAME}/${env.BUILD_NUMBER}|ci.kibana.dev>",
|
|
].join(' · '))
|
|
}
|
|
|
|
def getStatusIcon() {
|
|
def status = buildUtils.getBuildStatus()
|
|
if (status == 'UNSTABLE') {
|
|
return ':yellow_heart:'
|
|
}
|
|
|
|
return ':broken_heart:'
|
|
}
|
|
|
|
def getBackupMessage(config) {
|
|
return "${getStatusIcon()} ${config.title}\n\nFirst attempt at sending this notification failed. Please check the build."
|
|
}
|
|
|
|
def sendFailedBuild(Map params = [:]) {
|
|
def config = [
|
|
channel: '#kibana-operations-alerts',
|
|
title: "*<${env.BUILD_URL}|${getDefaultDisplayName()}>*",
|
|
message: getDefaultDisplayName(),
|
|
color: 'danger',
|
|
icon: ':jenkins:',
|
|
username: 'Kibana Operations',
|
|
context: getDefaultContext(),
|
|
] + params
|
|
|
|
def title = "${getStatusIcon()} ${config.title}"
|
|
def message = "${getStatusIcon()} ${config.message}"
|
|
|
|
def blocks = [markdownBlock(title)]
|
|
getFailedBuildBlocks().each { blocks << it }
|
|
blocks << dividerBlock()
|
|
blocks << config.context
|
|
|
|
def resp = slackSend(
|
|
channel: config.channel,
|
|
username: config.username,
|
|
iconEmoji: config.icon,
|
|
color: config.color,
|
|
message: message,
|
|
blocks: blocks
|
|
)
|
|
|
|
if (!resp) {
|
|
slackSend(
|
|
channel: config.channel,
|
|
username: config.username,
|
|
iconEmoji: config.icon,
|
|
color: config.color,
|
|
message: message,
|
|
blocks: [markdownBlock(getBackupMessage(config))]
|
|
)
|
|
}
|
|
}
|
|
|
|
def onFailure(Map options = [:]) {
|
|
catchError {
|
|
def status = buildUtils.getBuildStatus()
|
|
if (status != "SUCCESS") {
|
|
catchErrors {
|
|
sendFailedBuild(options)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def onFailure(Map options = [:], Closure closure) {
|
|
// try/finally will NOT work here, because the build status will not have been changed to ERROR when the finally{} block executes
|
|
catchError {
|
|
closure()
|
|
}
|
|
|
|
onFailure(options)
|
|
}
|
|
|
|
return this
|