diff --git a/CHANGELOG.md b/CHANGELOG.md index d77bc04f..d47ae6e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [[5.1.0dev]($tag_url)] - $date +### Patches + +#### Enhancements & fixes + +- Pass the collated software versions to the experiment summary report. By @Aratz [#248](https://github.com/nf-core/pixelator/pull/248) + ## [[5.0.1](https://github.com/nf-core/pixelator/releases/tag/5.0.1)] - 2026-08-17 ### Patches diff --git a/modules/local/experiment_summary/main.nf b/modules/local/experiment_summary/main.nf index d64d7792..e92cbd79 100644 --- a/modules/local/experiment_summary/main.nf +++ b/modules/local/experiment_summary/main.nf @@ -10,10 +10,15 @@ process EXPERIMENT_SUMMARY { input: path samplesheet_path tuple val(meta), val(result_stages), path(results_data, arity: "1..*", stageAs: "results_raw/?/*") + path versions_yml, stageAs: 'software_versions.yml' output: tuple val(meta), path("*experiment-summary.html") , emit: html - tuple val("${task.process}"), val('experiment-summary'), eval("Rscript -e 'cat(as.character(packageVersion(\"pixelatorES\")), \"\\n\")'"), emit: versions_experiment_summary, topic: versions + // This process must not push its version to the `versions` topic. It consumes a file + // collated from that topic, and a topic only closes once all of its publishers have + // finished, so publishing to it would hang the pipeline forever. + // This mimics how MultiQC handles version reporting. + tuple val("${task.process}"), val('experiment-summary'), eval("Rscript -e 'cat(as.character(packageVersion(\"pixelatorES\")), \"\\n\")'"), emit: versions_experiment_summary script: def args = task.ext.args ?: '' @@ -28,6 +33,8 @@ process EXPERIMENT_SUMMARY { cp -r /workspace/inst/quarto/ ./quarto/ mkdir -p results + cp software_versions.yml results/software_versions.yml + # Stage each result file into results//. Files are staged into # results_raw/1, results_raw/2, ... in the same order as the stage names. stages=(${stageArray}) diff --git a/modules/local/experiment_summary/meta.yml b/modules/local/experiment_summary/meta.yml index 46509e73..3cbc7bdb 100644 --- a/modules/local/experiment_summary/meta.yml +++ b/modules/local/experiment_summary/meta.yml @@ -31,6 +31,11 @@ input: type: file description: Result files to organize under stage folders in the report input. ontologies: [] + - - versions_yml: + type: file + description: Collated software versions, copied into the report data folder. + pattern: "*.{yml,yaml}" + ontologies: [] output: html: - - meta: @@ -51,17 +56,6 @@ output: - Rscript -e 'cat(as.character(packageVersion("pixelatorES")), "\n")': type: eval description: The expression used to obtain the tool version. -topics: - versions: - - - ${task.process}: - type: string - description: The process the versions were collected from. - - experiment-summary: - type: string - description: The tool name. - - Rscript -e 'cat(as.character(packageVersion("pixelatorES")), "\n")': - type: eval - description: The expression used to obtain the tool version. authors: - "@Aratz" - "@johandahlberg" diff --git a/modules/local/experiment_summary/tests/main.nf.test b/modules/local/experiment_summary/tests/main.nf.test index b8748d5e..dd583ff7 100644 --- a/modules/local/experiment_summary/tests/main.nf.test +++ b/modules/local/experiment_summary/tests/main.nf.test @@ -29,6 +29,13 @@ nextflow_process { file(params.pipelines_testdata_base_path + 'new-test-data/pna/modules/amplicon/pool1.report.json', checkIfExists: true), ] ] + input[2] = channel.of( + 'PIXELATOR_AMPLICON:', + ' pixelator: 0.30.0', + 'Workflow:', + ' nf-core/pixelator: 5.1.0dev', + ' Nextflow: 25.10.4', + ).collectFile(name: 'software_versions.yml', newLine: true) """ } } @@ -130,6 +137,13 @@ nextflow_process { file(params.pipelines_testdata_base_path + 'new-test-data/pna/modules/sample_calling/sample4.report.json', checkIfExists: true), ] ] + input[2] = channel.of( + 'PIXELATOR_AMPLICON:', + ' pixelator: 0.30.0', + 'Workflow:', + ' nf-core/pixelator: 5.1.0dev', + ' Nextflow: 25.10.4', + ).collectFile(name: 'software_versions.yml', newLine: true) """ } } diff --git a/subworkflows/local/pna/v1/main.nf b/subworkflows/local/pna/v1/main.nf index 81eccb60..17f3cae6 100644 --- a/subworkflows/local/pna/v1/main.nf +++ b/subworkflows/local/pna/v1/main.nf @@ -24,7 +24,8 @@ include { PIXELATOR_DENOISE } from '../../../../modules/local/pixelator include { PIXELATOR_ANALYSIS } from '../../../../modules/local/pixelator/analysis/main' include { PIXELATOR_COMBINE_COLLAPSE } from '../../../../modules/local/pixelator/combine_collapse/main' include { PIXELATOR_LAYOUT } from '../../../../modules/local/pixelator/layout/main' -include { EXPERIMENT_SUMMARY } from '../../../../modules/local/experiment_summary/main' +include { EXPERIMENT_SUMMARY } from '../../../../modules/local/experiment_summary/main' +include { collateVersionsFromTopic } from '../../utils_nfcore_pixelator_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -193,26 +194,31 @@ workflow PIXELATOR_PNA_V1 { PIXELATOR_LAYOUT(ch_analysis) // Prepare all data needed by reporting for each pixelator step - ch_input = channel.fromPath(params.input) - ch_experiment_summary_input = channel - .topic('all_results_for_reports') - .map { stage, files -> - def values = files instanceof List ? files : [files] - values.collect { f -> tuple(stage, f) } - } - .flatMap { it } - .collect(flat: false) - .map { stageFilePairs -> - def meta = [id: 'all'] - def stages = stageFilePairs.collect { it[0] } - def files = stageFilePairs.collect { it[1] } - tuple(meta, stages, files) - } - if (!params.skip_experiment_summary) { + ch_input = channel.fromPath(params.input) + ch_experiment_summary_input = channel + .topic('all_results_for_reports') + .map { stage, files -> + def values = files instanceof List ? files : [files] + values.collect { f -> tuple(stage, f) } + } + .flatMap { it } + .collect(flat: false) + .map { stageFilePairs -> + def meta = [id: 'all'] + def stages = stageFilePairs.collect { it[0] } + def files = stageFilePairs.collect { it[1] } + tuple(meta, stages, files) + } + + ch_versions_yml = collateVersionsFromTopic() + .collectFile(name: 'software_versions.yml', sort: true, newLine: true) + .first() + EXPERIMENT_SUMMARY( ch_input, ch_experiment_summary_input, + ch_versions_yml, ) } diff --git a/subworkflows/local/pna/v1/meta.yml b/subworkflows/local/pna/v1/meta.yml index b0cbc6e1..c60d50c5 100644 --- a/subworkflows/local/pna/v1/meta.yml +++ b/subworkflows/local/pna/v1/meta.yml @@ -17,7 +17,7 @@ components: - pixelator/analysis - pixelator/layout - experiment/summary - - pna_generate_reports + - collateversionsfromtopic input: - fastq: type: file diff --git a/subworkflows/local/pna/v2/main.nf b/subworkflows/local/pna/v2/main.nf index 7f0e46b1..bb489e4e 100644 --- a/subworkflows/local/pna/v2/main.nf +++ b/subworkflows/local/pna/v2/main.nf @@ -27,8 +27,9 @@ include { PIXELATOR_COMBINE_COLLAPSE } from '../../../../modules/local/pixelator include { PIXELATOR_LAYOUT } from '../../../../modules/local/pixelator/layout' -include { EXPERIMENT_SUMMARY } from '../../../../modules/local/experiment_summary/main' -include { CAT_FASTQ } from '../../../../modules/nf-core/cat/fastq/main' +include { EXPERIMENT_SUMMARY } from '../../../../modules/local/experiment_summary/main' +include { CAT_FASTQ } from '../../../../modules/nf-core/cat/fastq/main' +include { collateVersionsFromTopic } from '../../utils_nfcore_pixelator_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -57,9 +58,6 @@ workflow PIXELATOR_PNA_V2 { ch_panel_files // channel: [ meta, path(panel_file) | ] main: - ch_versions = Channel.empty() - - ch_fastq_grouped_by_pool = ch_fastq .map { meta, fq -> tuple(meta.pool, [meta, fq]) } .groupTuple() @@ -239,26 +237,31 @@ workflow PIXELATOR_PNA_V2 { PIXELATOR_LAYOUT( ch_analysis ) // Prepare all data needed by reporting for each pixelator step - ch_input = channel.fromPath(params.input) - ch_experiment_summary_input = channel - .topic('all_results_for_reports') - .map { stage, files -> - def values = files instanceof List ? files : [files] - values.collect { f -> tuple(stage, f) } - } - .flatMap { it } - .collect(flat: false) - .map { stageFilePairs -> - def meta = [id: 'all'] - def stages = stageFilePairs.collect { it[0] } - def files = stageFilePairs.collect { it[1] } - tuple(meta, stages, files) - } - if (!params.skip_experiment_summary) { + ch_input = channel.fromPath(params.input) + ch_experiment_summary_input = channel + .topic('all_results_for_reports') + .map { stage, files -> + def values = files instanceof List ? files : [files] + values.collect { f -> tuple(stage, f) } + } + .flatMap { it } + .collect(flat: false) + .map { stageFilePairs -> + def meta = [id: 'all'] + def stages = stageFilePairs.collect { it[0] } + def files = stageFilePairs.collect { it[1] } + tuple(meta, stages, files) + } + + ch_versions_yml = collateVersionsFromTopic() + .collectFile(name: 'software_versions.yml', sort: true, newLine: true) + .first() + EXPERIMENT_SUMMARY( ch_input, ch_experiment_summary_input, + ch_versions_yml, ) } diff --git a/subworkflows/local/pna/v2/meta.yml b/subworkflows/local/pna/v2/meta.yml index 5b35aa11..49d193cb 100644 --- a/subworkflows/local/pna/v2/meta.yml +++ b/subworkflows/local/pna/v2/meta.yml @@ -18,7 +18,7 @@ components: - pixelator/analysis - pixelator/layout - experiment/summary - - pna_generate_reports + - collateversionsfromtopic input: - fastq: type: file diff --git a/subworkflows/local/utils_nfcore_pixelator_pipeline/main.nf b/subworkflows/local/utils_nfcore_pixelator_pipeline/main.nf index 321e5ccd..d64e4ad5 100644 --- a/subworkflows/local/utils_nfcore_pixelator_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_pixelator_pipeline/main.nf @@ -13,6 +13,7 @@ include { paramsSummaryMap } from 'plugin/nf-schema' include { samplesheetToList } from 'plugin/nf-schema' include { completionEmail } from '../../nf-core/utils_nfcore_pipeline' include { completionSummary } from '../../nf-core/utils_nfcore_pipeline' +include { softwareVersionsToYAML } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' @@ -106,11 +107,6 @@ workflow PIPELINE_INITIALISATION { nextflow_cli_args ) - // - // Create channel from input file provided through params.input - // - ch_versions = channel.empty() - // // Resolve relative paths and validate fastq files existence // @@ -239,6 +235,33 @@ def getGenomeAttribute(attribute) { return null } +// +// Collate the `versions` topic into a channel of YAML fragments +// +// Any process consuming the result must not emit to the `versions` topic itself, +// as the topic only closes once all of its publishers have finished. +// +def collateVersionsFromTopic() { + def topic_versions = channel.topic("versions") + .distinct() + .branch { entry -> + versions_file: entry instanceof Path + versions_tuple: true + } + + def topic_versions_string = topic_versions.versions_tuple + .map { process, tool, version -> + [ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ] + } + .groupTuple(by:0) + .map { process, tool_versions -> + tool_versions.unique().sort() + "${process}:\n${tool_versions.join('\n')}" + } + + return softwareVersionsToYAML(topic_versions.versions_file).mix(topic_versions_string) +} + // // Generate methods description for MultiQC // diff --git a/subworkflows/local/utils_nfcore_pixelator_pipeline/meta.yml b/subworkflows/local/utils_nfcore_pixelator_pipeline/meta.yml index 6dadbe56..9489696e 100644 --- a/subworkflows/local/utils_nfcore_pixelator_pipeline/meta.yml +++ b/subworkflows/local/utils_nfcore_pixelator_pipeline/meta.yml @@ -13,6 +13,7 @@ components: - pixelator/list/options - completionemail - completionsummary + - softwareversionstoyaml input: - version: type: boolean diff --git a/tests/proxiome_v1.nf.test.snap b/tests/proxiome_v1.nf.test.snap index f094db87..b65bf869 100644 --- a/tests/proxiome_v1.nf.test.snap +++ b/tests/proxiome_v1.nf.test.snap @@ -102,9 +102,6 @@ "CAT_FASTQ": { "cat": 9.5 }, - "EXPERIMENT_SUMMARY": { - "experiment-summary": "0.12.0" - }, "PIXELATOR_AMPLICON": { "pixelator": "0.30.0" }, @@ -137,7 +134,7 @@ } } ], - "timestamp": "2026-08-11T08:23:21.795999696", + "timestamp": "2026-08-17T11:09:58.197955852", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.2" @@ -150,9 +147,6 @@ "CAT_FASTQ": { "cat": 9.5 }, - "EXPERIMENT_SUMMARY": { - "experiment-summary": "0.12.0" - }, "PIXELATOR_AMPLICON": { "pixelator": "0.30.0" }, @@ -279,7 +273,7 @@ ] ], - "timestamp": "2026-08-11T08:22:39.094080602", + "timestamp": "2026-08-17T11:09:13.418847968", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.2" diff --git a/tests/proxiome_v2.nf.test.snap b/tests/proxiome_v2.nf.test.snap index e178cde1..2f7086cc 100644 --- a/tests/proxiome_v2.nf.test.snap +++ b/tests/proxiome_v2.nf.test.snap @@ -133,9 +133,6 @@ "CAT_FASTQ": { "cat": 9.5 }, - "EXPERIMENT_SUMMARY": { - "experiment-summary": "0.12.0" - }, "PIXELATOR_AMPLICON": { "pixelator": "0.30.0" }, @@ -171,7 +168,7 @@ } } ], - "timestamp": "2026-08-11T08:27:56.30759346", + "timestamp": "2026-08-17T11:14:37.297215197", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.2" @@ -184,9 +181,6 @@ "CAT_FASTQ": { "cat": 9.5 }, - "EXPERIMENT_SUMMARY": { - "experiment-summary": "0.12.0" - }, "PIXELATOR_AMPLICON": { "pixelator": "0.30.0" }, @@ -357,7 +351,7 @@ ] ], - "timestamp": "2026-08-11T08:27:04.705573587", + "timestamp": "2026-08-17T11:13:43.166533768", "meta": { "nf-test": "0.9.5", "nextflow": "26.04.2" diff --git a/workflows/pixelator.nf b/workflows/pixelator.nf index 1d3c0e8b..e7bbae93 100644 --- a/workflows/pixelator.nf +++ b/workflows/pixelator.nf @@ -4,9 +4,9 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { paramsSummaryMap } from 'plugin/nf-schema' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_pixelator_pipeline' +include { paramsSummaryMap } from 'plugin/nf-schema' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_pixelator_pipeline' +include { collateVersionsFromTopic } from '../subworkflows/local/utils_nfcore_pixelator_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -80,25 +80,7 @@ workflow PIXELATOR { // // Collate and save software versions // - def topic_versions = channel.topic("versions") - .distinct() - .branch { entry -> - versions_file: entry instanceof Path - versions_tuple: true - } - - def topic_versions_string = topic_versions.versions_tuple - .map { process, tool, version -> - [ process[process.lastIndexOf(':')+1..-1], " ${tool}: ${version}" ] - } - .groupTuple(by:0) - .map { process, tool_versions -> - tool_versions.unique().sort() - "${process}:\n${tool_versions.join('\n')}" - } - - softwareVersionsToYAML(ch_versions.mix(topic_versions.versions_file)) - .mix(topic_versions_string) + collateVersionsFromTopic() .collectFile( storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_' + 'pixelator_software_' + 'versions.yml',