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
14 changes: 2 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Below are detailed instructions for new users.

### Step 1: Install Nextflow

First, ensure you have Java 11 or later installed, then install Nextflow:
This pipeline requires Nextflow 26.04.0 or later. First, ensure you have Java 17 or later installed, then install Nextflow:

```bash
# Install Nextflow
Expand All @@ -56,17 +56,7 @@ Verify the installation:
nextflow -version
```

You should see something like this:

```console
> nextflow -version

N E X T F L O W
version 25.04.6 build 5954
created 01-07-2025 11:27 UTC (12:27 BST)
cite doi:10.1038/nbt.3820
http://nextflow.io
```
Check that the reported version is 26.04.0 or later. Older versions stop with a version error before the pipeline starts.

### Step 2: Create a Seqera Platform Account

Expand Down
144 changes: 87 additions & 57 deletions main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,34 @@ include { ENTER_RAFFLE } from './modules/local/enter_raffle/main'
include { PRINT_PRIVACY_MESSAGE } from './modules/local/print_privacy_message/main'
include { PUBLISH_REPORT } from './modules/local/publish_report/main'

workflow {
// Default event to ECCB 2026 if not specified
def event = params.event ?: 'eccb_2026'
params {
help: Boolean = false
event: String = 'eccb_2026'
email: String = ''
affiliation: String = ''
first_name: String = ''
last_name: String = ''
outdir: String = 'results'
ticket_number_emit_session_id: Boolean = false
}

workflow {
main:
// Validate required parameters
if (!params.email) {
error("Please provide --email parameter")
}

// Load event configuration
def config_file = file("${projectDir}/event_configs/${event}.json", checkIfExists: true)

def config = new groovy.json.JsonSlurper().parse(config_file)
def config_file = file("${projectDir}/event_configs/${params.event}.json", checkIfExists: true)
def parsed = new groovy.json.JsonSlurper().parse(config_file)
def config = record(
event_name : parsed.event_name,
help : parsed.help,
destination_url : parsed.destination_url,
form_fields : parsed.form_fields,
required_fields : parsed.required_fields,
winner_announcement: parsed.winner_announcement,
)

// Validate participant-supplied fields this event marks as required. The
// required_fields list is per-event, so other events are unaffected
Expand All @@ -31,72 +46,87 @@ workflow {
]
config.required_fields?.each { fld ->
if (required_field_params.containsKey(fld) && !required_field_params[fld]) {
error("This event (${event}) requires --${fld}")
error("This event (${params.event}) requires --${fld}")
}
}

// Print privacy policy information
PRINT_PRIVACY_MESSAGE(config)

// Standard raffle entry for all events
ENTER_RAFFLE(
PRINT_PRIVACY_MESSAGE.out,
params.email,
params.affiliation,
params.first_name,
params.last_name,
config
def ch_ready = PRINT_PRIVACY_MESSAGE(config)

// Read platform_enabled here (workflow.session.config is unavailable inside typed processes)
def platform_enabled = workflow.session.config.navigate('tower.enabled') ?: false

def participant = record(
email : params.email,
first_name : params.first_name,
last_name : params.last_name,
affiliation: params.affiliation,
)

def ch_participant = ch_ready.map { _ready -> participant }
def ch_entry = ENTER_RAFFLE(
ch_participant,
config,
platform_enabled
)

// Generate ticket
html_report_template = Channel.fromPath("${projectDir}/assets/ticket_template.html")
event_name = config.event_name
ticket_number = params.ticket_number_emit_session_id ? ENTER_RAFFLE.out.session_id : ENTER_RAFFLE.out.run_name
def html_report_template = channel.fromPath("${projectDir}/assets/ticket_template.html")
def event_name = config.event_name
def ticket_number = params.ticket_number_emit_session_id
? ch_entry.map { entry -> entry.session_id }
: ch_entry.map { entry -> entry.run_name }
def winner_announcement = config.winner_announcement ?: ""

winner_announcement = config.winner_announcement ?: ""
PUBLISH_REPORT(html_report_template, event_name, ticket_number, winner_announcement)

workflow.onComplete = {
// Check if Tower/Platform is disabled or access token is missing
def towerEnabled = workflow.session.config.navigate('tower.enabled') ?: false
def towerToken = workflow.session.config.navigate('tower.accessToken') ?: System.getenv('TOWER_ACCESS_TOKEN')
publish:
raffle_ticket = PUBLISH_REPORT.out

if (!towerEnabled || !towerToken) {
log.warn """
=====================================
💡 Win more entries to the raffle! 💡
=====================================
onComplete:
def towerEnabled = workflow.session.config.navigate('tower.enabled') ?: false
def towerToken = workflow.session.config.navigate('tower.accessToken') ?: System.getenv('TOWER_ACCESS_TOKEN')

Create a free account on https://cloud.seqera.io/ to get additional raffle entries!
Simply enable Seqera Platform monitoring by:
if (!towerEnabled || !towerToken) {
log.warn """
=====================================
💡 Win more entries to the raffle! 💡
=====================================

1. Create an account on https://cloud.seqera.io/
Create a free account on https://cloud.seqera.io/ to get additional raffle entries!
Simply enable Seqera Platform monitoring by:

2. Create an access token at https://cloud.seqera.io/tokens
1. Create an account on https://cloud.seqera.io/

3. Adding to your nextflow.config:
tower {
enabled = true
accessToken = 'your-token-here'
}
2. Create an access token at https://cloud.seqera.io/tokens

4. Run the pipeline with the additional configuration:
nextflow run seqeralabs/nf-raffle --email <your email> -c nextflow.config
3. Adding to your nextflow.config:
tower {
enabled = true
accessToken = 'your-token-here'
}

=====================================
""".stripIndent()
} else {
log.info """
============================================\n
🎉 You earned extra raffle tickets! 🎉
============================================
4. Run the pipeline with the additional configuration:
nextflow run seqeralabs/nf-raffle --email <your email> -c nextflow.config

Because you used Seqera Platform for this workflow,
you have received additional entries to the raffle.
=====================================
""".stripIndent()
} else {
log.info """
============================================\n
🎉 You earned extra raffle tickets! 🎉
============================================

Thank you for using Seqera Platform and good luck!
============================================
""".stripIndent()
}
Because you used Seqera Platform for this workflow,
you have received additional entries to the raffle.

Thank you for using Seqera Platform and good luck!
============================================
""".stripIndent()
}
}

output {
raffle_ticket {
path '.'
mode 'copy'
}
}
6 changes: 4 additions & 2 deletions modules/local/congratulations/main.nf
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
nextflow.enable.types = true

process CONGRATULATIONS {
tag "${congrats}"
label 'process_single'
container 'community.wave.seqera.io/library/sed_coreutils_procps-ng:749edc0a4a6c3ef9'
conda "${moduleDir}/environment.yml"

input:
path congrats
val next
congrats: Path
next: Boolean

script:
"""
Expand Down
57 changes: 33 additions & 24 deletions modules/local/enter_raffle/main.nf
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
// This process stays untyped for output (workflow.sessionId/runName are unavailable
// in typed processes). The onComplete block in main.nf also uses workflow.session.config.
nextflow.enable.types = true

include { EventConfig ; Participant } from '../../../types'

process ENTER_RAFFLE {
tag "${email}"
tag "${participant.email}"
label 'process_single'
container 'community.wave.seqera.io/library/curl_util-linux_procps-ng:72cd763bb8c83eca'
conda "${moduleDir}/environment.yml"

input:
val next
val email
val affiliation
val first_name
val last_name
val config
participant: Participant
config: EventConfig
platform_enabled: Boolean

output:
val workflow.sessionId, emit: session_id
val workflow.runName, emit: run_name
record(
session_id: workflow.sessionId.toString(),
run_name: workflow.runName,
)

script:
def platform_enabled = workflow.session.config.navigate('tower.enabled') ?: false
def email = participant.email
def affiliation = participant.affiliation
def first_name = participant.first_name
def last_name = participant.last_name
def destination = config.destination_url
def form_fields = config.form_fields

Expand All @@ -26,20 +34,21 @@ process ENTER_RAFFLE {
def workspace_id = System.getenv('TOWER_WORKSPACE_ID') ?: ''
def platform_workflow_id = System.getenv('TOWER_WORKFLOW_ID') ?: ''

// Build curl data arguments - collect non-empty args into a list
def curl_args = []
if (form_fields.email) curl_args << "-d \"${form_fields.email}=${email}\""
if (form_fields.run_name) curl_args << "-d \"${form_fields.run_name}=${workflow.runName}\""
if (form_fields.hostname) curl_args << "-d \"${form_fields.hostname}=\$(hostname)\""
if (form_fields.uuid) curl_args << "-d \"${form_fields.uuid}=\$(uuidgen)\""
if (form_fields.platform_enabled) curl_args << "-d \"${form_fields.platform_enabled}=${platform_enabled}\""
if (form_fields.affiliation && affiliation) curl_args << "-d \"${form_fields.affiliation}=${affiliation}\""
if (form_fields.first_name && first_name) curl_args << "-d \"${form_fields.first_name}=${first_name}\""
if (form_fields.last_name && last_name) curl_args << "-d \"${form_fields.last_name}=${last_name}\""
if (form_fields.user_name) curl_args << "-d \"${form_fields.user_name}=${user_name}\""
if (form_fields.workspace_id) curl_args << "-d \"${form_fields.workspace_id}=${workspace_id}\""
if (form_fields.platform_workflow_id) curl_args << "-d \"${form_fields.platform_workflow_id}=${platform_workflow_id}\""
def curl_data = curl_args.join(' ')
// Built as immutable list (typed processes don't allow << or add)
def curl_args = [
form_fields.email ? "-d \"${form_fields.email}=${email}\"" : '',
form_fields.run_name ? "-d \"${form_fields.run_name}=${workflow.runName}\"" : '',
form_fields.hostname ? "-d \"${form_fields.hostname}=\$(hostname)\"" : '',
form_fields.uuid ? "-d \"${form_fields.uuid}=\$(uuidgen)\"" : '',
form_fields.platform_enabled ? "-d \"${form_fields.platform_enabled}=${platform_enabled}\"" : '',
form_fields.affiliation && affiliation ? "-d \"${form_fields.affiliation}=${affiliation}\"" : '',
form_fields.first_name && first_name ? "-d \"${form_fields.first_name}=${first_name}\"" : '',
form_fields.last_name && last_name ? "-d \"${form_fields.last_name}=${last_name}\"" : '',
form_fields.user_name ? "-d \"${form_fields.user_name}=${user_name}\"" : '',
form_fields.workspace_id ? "-d \"${form_fields.workspace_id}=${workspace_id}\"" : '',
form_fields.platform_workflow_id ? "-d \"${form_fields.platform_workflow_id}=${platform_workflow_id}\"" : '',
]
def curl_data = curl_args.findAll { arg -> arg != '' }.join(' ')

"""
curl -X POST ${curl_data} "${destination}"
Expand Down
8 changes: 5 additions & 3 deletions modules/local/print_ascii_logo/main.nf
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
nextflow.enable.types = true

process PRINT_ASCII_LOGO {
tag "${logo}"
label 'process_single'
container 'community.wave.seqera.io/library/sed_coreutils_procps-ng:749edc0a4a6c3ef9'
conda "${moduleDir}/environment.yml"

input:
path logo
val next
logo: Path
next: Boolean

output:
val true
true

script:
"""
Expand Down
13 changes: 9 additions & 4 deletions modules/local/print_privacy_message/main.nf
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
nextflow.enable.types = true

include { EventConfig } from '../../../types'

process PRINT_PRIVACY_MESSAGE {
label 'process_single'

input:
val config

output:
val true
config: EventConfig

exec:
def winner_text = config.winner_announcement ? "\nWinner will be announced at ${config.winner_announcement}.\n" : ""
Expand All @@ -18,4 +19,8 @@ We respect your data. By submitting this form, you agree that we may use ​this
information in accordance with our Privacy Policy (https://seqera.io/privacy-policy/).
${winner_text}""".stripIndent()
)
ready = true

output:
ready
}
13 changes: 7 additions & 6 deletions modules/local/publish_report/main.nf
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
nextflow.enable.types = true

process PUBLISH_REPORT {
tag "${event}: ${ticket_number}"
publishDir "${params.outdir}", mode: 'copy'
container 'community.wave.seqera.io/library/sed_coreutils_procps-ng:749edc0a4a6c3ef9'
conda "${moduleDir}/environment.yml"

input:
path html_report
val event
val ticket_number
val winner_announcement
html_report: Path
event: String
ticket_number: String
winner_announcement: String

output:
path "raffle_ticket.html"
file('raffle_ticket.html')

script:
def winner_text = winner_announcement ? "Winner announced ${winner_announcement}" : ""
Expand Down
17 changes: 8 additions & 9 deletions nextflow.config
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
params.help = false
params.event = "eccb_2026"
params.email = ""
params.affiliation = ""
params.first_name = ""
params.last_name = ""
params.outdir = "results"
params.ticket_number_emit_session_id = false
manifest {
nextflowVersion = '!>=26.04.0'
}

// Maps --outdir to the workflow output{} block. The params{} default in
// main.nf is not visible here because the config is read before the script.
outputDir = params.outdir ?: 'results'

docker {
enabled = false
enabled = false
runOptions = '-u $(id -u):$(id -g)'
}

Expand Down
Loading