Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions src/main/groovy/nextflow/slack/SlackMessageBuilder.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class SlackMessageBuilder {
fields << createMarkdownField('Duration', duration.toString())
}
if (shouldIncludeField(includeFields, 'status')) {
fields << createMarkdownField('Status', '✅ Success')
fields << createMarkdownField('Status', getStatusText('completed'))
}

// Add resource usage if configured (via includeResourceUsage or includeFields)
Expand Down Expand Up @@ -373,7 +373,7 @@ class SlackMessageBuilder {
fields << createMarkdownField('Duration', duration.toString())
}
if (shouldIncludeField(includeFields, 'status')) {
fields << createMarkdownField('Status', '❌ Failed')
fields << createMarkdownField('Status', getStatusText('failed'))
}

if (shouldIncludeField(includeFields, 'failedProcess') && errorRecord) {
Expand Down Expand Up @@ -487,7 +487,7 @@ class SlackMessageBuilder {
fields << createMarkdownField('Duration', duration.toString())
}
if (includeFields.contains('status')) {
fields << createMarkdownField('Status', getStatusEmoji(status))
fields << createMarkdownField('Status', getStatusText(status))
}
if (includeFields.contains('failedProcess') && errorRecord) {
def processName = errorRecord.get('process')
Expand Down Expand Up @@ -566,16 +566,16 @@ class SlackMessageBuilder {
}

/**
* Get status emoji
* Get status text for the Status field (emoji-free; header carries the emoji).
*/
private static String getStatusEmoji(String status) {
private static String getStatusText(String status) {
switch (status) {
case 'started':
return '🚀 Running'
return 'Running'
case 'completed':
return 'Success'
return 'Success'
case 'failed':
return 'Failed'
return 'Failed'
default:
return 'Unknown'
}
Expand Down
92 changes: 92 additions & 0 deletions src/test/groovy/nextflow/slack/SlackMessageBuilderTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -879,4 +879,96 @@ class SlackMessageBuilderTest extends Specification {
!fields.find { it.text.contains('Duration') }
!fields.find { it.text.contains('Status') }
}

def 'status field should be text-only in default complete message'() {
given:
def metadata = Mock(WorkflowMetadata)
metadata.scriptName >> 'test-workflow.nf'
metadata.duration >> Duration.of('1h')
metadata.stats >> null
session.workflowMetadata >> metadata

when:
def message = messageBuilder.buildWorkflowCompleteMessage()
def json = new JsonSlurper().parseText(message)

then:
def headerBlock = json.blocks.find { it.type == 'section' && it.text?.text?.contains('Pipeline completed successfully') }
headerBlock.text.text.contains('✅')
statusFieldValue(json) == 'Success'
!statusFieldValue(json).contains('✅')
}

def 'status field should be text-only in default error message'() {
given:
def errorSession = Mock(Session)
def metadata = Mock(WorkflowMetadata)
metadata.scriptName >> 'test-workflow.nf'
metadata.duration >> Duration.of('30m')
metadata.errorMessage >> 'Process failed'
errorSession.workflowMetadata >> metadata
errorSession.runName >> 'test-run'
def builder = new SlackMessageBuilder(config, errorSession)

when:
def message = builder.buildWorkflowErrorMessage(null)
def json = new JsonSlurper().parseText(message)

then:
def headerBlock = json.blocks.find { it.type == 'section' && it.text?.text?.contains('Pipeline failed') }
headerBlock.text.text.contains('❌')
statusFieldValue(json) == 'Failed'
!statusFieldValue(json).contains('❌')
}

def 'status field should be text-only in map-based custom messages'() {
given:
def metadata = Mock(WorkflowMetadata)
metadata.scriptName >> 'test-workflow.nf'
metadata.duration >> Duration.of('1h')
session.workflowMetadata >> metadata

def startConfig = new SlackConfig([
enabled: true,
webhook: 'https://hooks.slack.com/services/TEST/TEST/TEST',
onStart: [
enabled: true,
message: [text: '🚀 *Starting*', includeFields: ['status']]
]
])
def completeConfig = new SlackConfig([
enabled: true,
webhook: 'https://hooks.slack.com/services/TEST/TEST/TEST',
onComplete: [
enabled: true,
message: [text: '✅ *Done*', includeFields: ['status']]
]
])
def errorConfig = new SlackConfig([
enabled: true,
webhook: 'https://hooks.slack.com/services/TEST/TEST/TEST',
onError: [
enabled: true,
message: [text: '❌ *Boom*', includeFields: ['status']]
]
])
def errorSession = Mock(Session)
def errorMetadata = Mock(WorkflowMetadata)
errorMetadata.scriptName >> 'test-workflow.nf'
errorMetadata.duration >> Duration.of('30m')
errorMetadata.errorMessage >> 'failed'
errorSession.workflowMetadata >> errorMetadata
errorSession.runName >> 'test-run'

expect:
statusFieldValue(new JsonSlurper().parseText(new SlackMessageBuilder(startConfig, session).buildWorkflowStartMessage())) == 'Running'
statusFieldValue(new JsonSlurper().parseText(new SlackMessageBuilder(completeConfig, session).buildWorkflowCompleteMessage())) == 'Success'
statusFieldValue(new JsonSlurper().parseText(new SlackMessageBuilder(errorConfig, errorSession).buildWorkflowErrorMessage(null))) == 'Failed'
}

private static String statusFieldValue(json) {
def fieldsBlock = json.blocks.find { it.type == 'section' && it.fields }
def statusField = fieldsBlock?.fields?.find { it.text.startsWith('*Status*') }
return statusField?.text?.replace('*Status*\n', '') ?: ''
}
}
Loading