From 04072faea7c2fe9fd241c6d4c5b85c764d46de75 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 18 Mar 2024 15:16:00 +0100 Subject: [PATCH 001/530] Generate reports per lane, group and rundir --- conf/modules.config | 20 ++++++- main.nf | 5 +- workflows/seqinspector.nf | 120 ++++++++++++++++++++++++++++++++++---- 3 files changed, 133 insertions(+), 12 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index e3ea8fa6..296ca786 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -35,7 +35,25 @@ process { publishDir = [ path: { "${params.outdir}/multiqc" }, mode: params.publish_dir_mode, - saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + saveAs: { + filename -> + switch (filename) { + case 'versions.yml': + null + break + case ~/L\d+_multiqc_(report\.html|plots|data)/: + "lanes/L${(filename =~ /L(\d+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + break + case ~/G-.+_multiqc_(report\.html|plots|data)/: + "groups/G-${(filename =~ /G-(.+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + break + case ~/D-.+_multiqc_(report\.html|plots|data)/: + "rundirs/D-${(filename =~ /D-(.+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + break + default: + filename + } + } ] } diff --git a/main.nf b/main.nf index 1e9c2ede..58afd1fe 100644 --- a/main.nf +++ b/main.nf @@ -58,7 +58,10 @@ workflow NFCORE_SEQINSPECTOR { ) emit: - multiqc_report = SEQINSPECTOR.out.multiqc_report // channel: /path/to/multiqc_report.html + global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html + lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html + group_report = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html + rundir_report = SEQINSPECTOR.out.rundir_reports // channel: /path/to/multiqc_report.html } /* diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 9ae3384b..b6fd3bc4 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -5,7 +5,10 @@ */ include { FASTQC } from '../modules/nf-core/fastqc/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_LANE } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_GROUP } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_RUNDIR } from '../modules/nf-core/multiqc/main' include { paramsSummaryMap } from 'plugin/nf-validation' include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' @@ -26,6 +29,8 @@ workflow SEQINSPECTOR { ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() + ch_multiqc_extra_files = Channel.empty() + ch_multiqc_reports = Channel.empty() // // MODULE: Run FastQC @@ -33,7 +38,7 @@ workflow SEQINSPECTOR { FASTQC ( ch_samplesheet ) - ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip.collect{it[1]}) + ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip) ch_versions = ch_versions.mix(FASTQC.out.versions.first()) // @@ -46,26 +51,121 @@ workflow SEQINSPECTOR { // // MODULE: MultiQC // - ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multiqc_config, checkIfExists: true) : Channel.empty() + ch_multiqc_config = params.multiqc_config ? + Channel.fromPath(params.multiqc_config, checkIfExists: true) : + Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) ch_methods_description = Channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: false)) + ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_collated_versions) + ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: false)) MULTIQC ( - ch_multiqc_files.collect(), + ch_multiqc_files + .map { meta, file -> file } + .mix(ch_multiqc_extra_files) + .collect(), ch_multiqc_config.toList(), - ch_multiqc_custom_config.toList(), + Channel.empty().toList(), + ch_multiqc_logo.toList() + ) + + multiqc_extra_files = ch_multiqc_extra_files.toList() + + // Generate reports by lane + lane_mqc_files = ch_multiqc_files + .map { meta, sample -> [ "L${meta.lane}", meta, sample ] } + .groupTuple() + .tap { mqc_by_lane } + .collectFile{ + lane, meta, samples -> [ + "${lane}_multiqc_extra_config.yml", + "output_fn_name: \"${lane}_multiqc_report.html\"\ndata_dir_name: \"${lane}_multiqc_data\"\nplots_dir_name: \"${lane}_multiqc_plots\"" + ] + } + .map { file -> def fileparts = file.name.split("_") + [ fileparts[0], file ] + } + .join(mqc_by_lane) + .multiMap { lane, config, meta , samples_per_lane -> + samples_per_lane: samples_per_lane + config: config + } + + MULTIQC_PER_LANE( + lane_mqc_files.samples_per_lane + .map { samples -> samples + multiqc_extra_files.value }, + ch_multiqc_config.toList(), + lane_mqc_files.config, + ch_multiqc_logo.toList() + ) + + // Generate reports by group + group_mqc_files = ch_multiqc_files + .filter { meta, sample -> meta.group } + .map { meta, sample -> [ "G-${meta.group}", meta, sample ] } + .groupTuple() + .tap { mqc_by_group } + .collectFile{ + group, meta, samples -> [ + "${group}_multiqc_extra_config.yml", + "output_fn_name: \"${group}_multiqc_report.html\"\ndata_dir_name: \"${group}_multiqc_data\"\nplots_dir_name: \"${group}_multiqc_plots\"" + ] + } + .map { file -> def fileparts = file.name.split("_") + [ fileparts[0], file ] + } + .join(mqc_by_group) + .multiMap { group, config, meta , samples_per_group -> + samples_per_group: samples_per_group + config: config + } + + MULTIQC_PER_GROUP( + group_mqc_files.samples_per_group + .map { samples -> samples + multiqc_extra_files.value }, + ch_multiqc_config.toList(), + group_mqc_files.config, + ch_multiqc_logo.toList() + ) + + // Generate reports by rundir + rundir_mqc_files = ch_multiqc_files + .filter { meta, sample -> meta.rundir } + .map { meta, sample -> [ "D-${meta.rundir.name}", meta, sample ] } + .groupTuple() + .tap { mqc_by_rundir } + .collectFile{ + rundir, meta, samples -> [ + "${rundir}_multiqc_extra_config.yml", + "output_fn_name: \"${rundir}_multiqc_report.html\"\ndata_dir_name: \"${rundir}_multiqc_data\"\nplots_dir_name: \"${rundir}_multiqc_plots\"" + ] + } + .map { file -> def fileparts = file.name.split("_") + [ fileparts[0], file ] + } + .join(mqc_by_rundir) + .multiMap { rundir, config, meta , samples_per_rundir -> + samples_per_rundir: samples_per_rundir + config: config + } + + MULTIQC_PER_RUNDIR( + rundir_mqc_files.samples_per_rundir + .map { samples -> samples + multiqc_extra_files.value }, + ch_multiqc_config.toList(), + rundir_mqc_files.config, ch_multiqc_logo.toList() ) emit: - multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + global_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + lane_reports = MULTIQC_PER_LANE.out.report.toList() // channel: [ /path/to/multiqc_report.html ] + group_reports = MULTIQC_PER_GROUP.out.report.toList() // channel: [ /path/to/multiqc_report.html ] + rundir_reports = MULTIQC_PER_RUNDIR.out.report.toList() // channel: [ /path/to/multiqc_report.html ] versions = ch_versions // channel: [ path(versions.yml) ] } From 36976bc769dae4dde31b93a058ab4d539e242196 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Thu, 28 Mar 2024 16:21:45 +0100 Subject: [PATCH 002/530] Improve formatting --- conf/modules.config | 2 +- main.nf | 6 ++-- workflows/seqinspector.nf | 71 +++++++++++++++++++++++++-------------- 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 296ca786..8b7a9c69 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -52,7 +52,7 @@ process { break default: filename - } + } } ] } diff --git a/main.nf b/main.nf index 58afd1fe..1ee043e9 100644 --- a/main.nf +++ b/main.nf @@ -58,9 +58,9 @@ workflow NFCORE_SEQINSPECTOR { ) emit: - global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html - lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html - group_report = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html + global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html + lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html + group_report = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html rundir_report = SEQINSPECTOR.out.rundir_reports // channel: /path/to/multiqc_report.html } diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index b6fd3bc4..f0a269a0 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -4,15 +4,17 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { FASTQC } from '../modules/nf-core/fastqc/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' -include { MULTIQC as MULTIQC_PER_LANE } from '../modules/nf-core/multiqc/main' -include { MULTIQC as MULTIQC_PER_GROUP } from '../modules/nf-core/multiqc/main' +include { FASTQC } from '../modules/nf-core/fastqc/main' + +include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_LANE } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_GROUP } from '../modules/nf-core/multiqc/main' include { MULTIQC as MULTIQC_PER_RUNDIR } from '../modules/nf-core/multiqc/main' -include { paramsSummaryMap } from 'plugin/nf-validation' -include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' -include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_seqinspector_pipeline' + +include { paramsSummaryMap } from 'plugin/nf-validation' +include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' +include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_seqinspector_pipeline' /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -27,10 +29,10 @@ workflow SEQINSPECTOR { main: - ch_versions = Channel.empty() - ch_multiqc_files = Channel.empty() + ch_versions = Channel.empty() + ch_multiqc_files = Channel.empty() ch_multiqc_extra_files = Channel.empty() - ch_multiqc_reports = Channel.empty() + ch_multiqc_reports = Channel.empty() // // MODULE: Run FastQC @@ -45,23 +47,42 @@ workflow SEQINSPECTOR { // Collate and save software versions // softwareVersionsToYAML(ch_versions) - .collectFile(storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_pipeline_software_mqc_versions.yml', sort: true, newLine: true) - .set { ch_collated_versions } + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'nf_core_pipeline_software_mqc_versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } // // MODULE: MultiQC // - ch_multiqc_config = params.multiqc_config ? + ch_multiqc_config = params.multiqc_config ? Channel.fromPath(params.multiqc_config, checkIfExists: true) : Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = Channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_collated_versions) - ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: false)) + ch_multiqc_logo = params.multiqc_logo ? + Channel.fromPath(params.multiqc_logo, checkIfExists: true) : + Channel.empty() + + summary_params = paramsSummaryMap( + workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = Channel.value( + paramsSummaryMultiqc(summary_params)) + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? + file(params.multiqc_methods_description, checkIfExists: true) : + file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = Channel.value( + methodsDescriptionText(ch_multiqc_custom_methods_description)) + + ch_multiqc_extra_files = ch_multiqc_extra_files.mix( + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_extra_files = ch_multiqc_extra_files.mix(ch_collated_versions) + ch_multiqc_extra_files = ch_multiqc_extra_files.mix( + ch_methods_description.collectFile( + name: 'methods_description_mqc.yaml', + sort: false + ) + ) MULTIQC ( ch_multiqc_files @@ -163,10 +184,10 @@ workflow SEQINSPECTOR { emit: global_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html - lane_reports = MULTIQC_PER_LANE.out.report.toList() // channel: [ /path/to/multiqc_report.html ] - group_reports = MULTIQC_PER_GROUP.out.report.toList() // channel: [ /path/to/multiqc_report.html ] + lane_reports = MULTIQC_PER_LANE.out.report.toList() // channel: [ /path/to/multiqc_report.html ] + group_reports = MULTIQC_PER_GROUP.out.report.toList() // channel: [ /path/to/multiqc_report.html ] rundir_reports = MULTIQC_PER_RUNDIR.out.report.toList() // channel: [ /path/to/multiqc_report.html ] - versions = ch_versions // channel: [ path(versions.yml) ] + versions = ch_versions // channel: [ path(versions.yml) ] } /* From be329eee36a9f3170ddd869a94e38ce2ecaf6f67 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Thu, 28 Mar 2024 16:56:51 +0100 Subject: [PATCH 003/530] Improve output sorting --- conf/modules.config | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 8b7a9c69..3b2fc025 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -42,13 +42,25 @@ process { null break case ~/L\d+_multiqc_(report\.html|plots|data)/: - "lanes/L${(filename =~ /L(\d+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + def lane = (filename =~ /L(\d+)_multiqc_(report\.html|plots|data)/)[0][1] + def new_filename = filename.replaceFirst( + "(?.*)L${lane}_(?multiqc_(report\\.html|plots|data).*)", + '${prefix}${suffix}') + "lanes/L${lane}/${new_filename}" break case ~/G-.+_multiqc_(report\.html|plots|data)/: - "groups/G-${(filename =~ /G-(.+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + def group = (filename =~ /G-(.+)_multiqc_(report\.html|plots|data)/)[0][1] + def new_filename = filename.replaceFirst( + "(?.*)G-${group}_(?multiqc_(report\\.html|plots|data).*)", + '${prefix}${suffix}') + "groups/${group}/${new_filename}" break case ~/D-.+_multiqc_(report\.html|plots|data)/: - "rundirs/D-${(filename =~ /D-(.+)_multiqc_(report\.html|plots|data)/)[0][1]}/${filename}" + def rundir = (filename =~ /D-(.+)_multiqc_(report\.html|plots|data)/)[0][1] + def new_filename = filename.replaceFirst( + "(?.*)D-${rundir}_(?multiqc_(report\\.html|plots|data).*)", + '${prefix}${suffix}') + "rundirs/${rundir}/${new_filename}" break default: filename From 82eafbe0b63b2914f2841e3c7b82a3265074cff9 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 8 Apr 2024 14:11:47 +0200 Subject: [PATCH 004/530] Use `group` instead of `project` --- assets/schema_input.json | 4 ++-- subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 9fb321b5..1648944f 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -19,11 +19,11 @@ "errorMessage": "Lane ID must be a number", "meta": ["lane"] }, - "project": { + "group": { "type": "string", "pattern": "^\\S+$", "errorMessage": "Project ID cannot contain spaces", - "meta": ["project"] + "meta": ["group"] }, "fastq_1": { "type": "string", diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index cdbb6400..e1f8e4de 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -84,7 +84,7 @@ workflow PIPELINE_INITIALISATION { .fromSamplesheet("input") // Validates samplesheet against $projectDir/assets/schema_input.json. Path to validation schema is defined by $projectDir/nextflow_schema.json .map { meta, fastq_1, fastq_2 -> - def id_string = "${meta.sample}_${meta.project ?: "ungrouped"}_${meta.lane}" + def id_string = "${meta.sample}_${meta.group ?: "ungrouped"}_${meta.lane}" def updated_meta = meta + [ id: id_string ] if (!fastq_2) { return [ updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] From 72f47025d53427730302c62beb30f6f8bc32c1c2 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 8 Apr 2024 14:21:59 +0200 Subject: [PATCH 005/530] Fix output channel --- main.nf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.nf b/main.nf index 1ee043e9..9de4f94e 100644 --- a/main.nf +++ b/main.nf @@ -104,7 +104,7 @@ workflow { params.outdir, params.monochrome_logs, params.hook_url, - NFCORE_SEQINSPECTOR.out.multiqc_report + NFCORE_SEQINSPECTOR.out.global_report, ) } From 1846607816f276fdbf3a03dbd9e02af828c98693 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 8 Apr 2024 14:22:17 +0200 Subject: [PATCH 006/530] Fix linting --- conf/modules.config | 2 +- .../local/utils_nfcore_seqinspector_pipeline/main.nf | 1 - workflows/seqinspector.nf | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 3b2fc025..44a35137 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -36,7 +36,7 @@ process { path: { "${params.outdir}/multiqc" }, mode: params.publish_dir_mode, saveAs: { - filename -> + filename -> switch (filename) { case 'versions.yml': null diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index e1f8e4de..9001400e 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -101,7 +101,6 @@ workflow PIPELINE_INITIALISATION { // meta, fastqs -> // return [ meta, fastqs.flatten() ] // } - .view() .set { ch_samplesheet } emit: diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index f0a269a0..815bc40f 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -108,7 +108,7 @@ workflow SEQINSPECTOR { ] } .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] + [ fileparts[0], file ] } .join(mqc_by_lane) .multiMap { lane, config, meta , samples_per_lane -> @@ -137,7 +137,7 @@ workflow SEQINSPECTOR { ] } .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] + [ fileparts[0], file ] } .join(mqc_by_group) .multiMap { group, config, meta , samples_per_group -> @@ -166,7 +166,7 @@ workflow SEQINSPECTOR { ] } .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] + [ fileparts[0], file ] } .join(mqc_by_rundir) .multiMap { rundir, config, meta , samples_per_rundir -> From 9cf987c7a6868af900802b3dbfdd26fba5094bc0 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 8 Apr 2024 15:48:41 +0200 Subject: [PATCH 007/530] Give credits back to NGI --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e2bc4ca..abe0d4ef 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,11 @@ For more details about the output files and reports, please refer to the ## Credits -nf-core/seqinspector was originally written by Adrien Coulier. +nf-core/seqinspector was originally written by the Swedish [@NationalGenomicsInfrastructure](https://github.com/NationalGenomicsInfrastructure/). We thank the following people for their extensive assistance in the development of this pipeline: - +- [@mahesh-panchal](https://github.com/mahesh-panchal) ## Contributions and Support From b24a280854941adf2d6fd5e67f4fb7b717d77fd5 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 9 Apr 2024 09:26:32 +0200 Subject: [PATCH 008/530] Fix file names --- conf/modules.config | 18 +++++++++--------- workflows/seqinspector.nf | 24 ++++++++++++++++++------ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index 44a35137..b2e48f3d 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -41,24 +41,24 @@ process { case 'versions.yml': null break - case ~/L\d+_multiqc_(report\.html|plots|data)/: - def lane = (filename =~ /L(\d+)_multiqc_(report\.html|plots|data)/)[0][1] + case ~/\[LANE:\d+\]_multiqc_(report\.html|plots|data)/: + def lane = (filename =~ /\[LANE:(\d+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( - "(?.*)L${lane}_(?multiqc_(report\\.html|plots|data).*)", + "(?.*)\\[LANE:${lane}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') "lanes/L${lane}/${new_filename}" break - case ~/G-.+_multiqc_(report\.html|plots|data)/: - def group = (filename =~ /G-(.+)_multiqc_(report\.html|plots|data)/)[0][1] + case ~/\[GROUP:.+\]_multiqc_(report\.html|plots|data)/: + def group = (filename =~ /\[GROUP:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( - "(?.*)G-${group}_(?multiqc_(report\\.html|plots|data).*)", + "(?.*)\\[GROUP:${group}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') "groups/${group}/${new_filename}" break - case ~/D-.+_multiqc_(report\.html|plots|data)/: - def rundir = (filename =~ /D-(.+)_multiqc_(report\.html|plots|data)/)[0][1] + case ~/\[RUNDIR:.+\]_multiqc_(report\.html|plots|data)/: + def rundir = (filename =~ /\[RUNDIR:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( - "(?.*)D-${rundir}_(?multiqc_(report\\.html|plots|data).*)", + "(?.*)\\[RUNDIR:${rundir}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') "rundirs/${rundir}/${new_filename}" break diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 815bc40f..2acafc3f 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -98,13 +98,17 @@ workflow SEQINSPECTOR { // Generate reports by lane lane_mqc_files = ch_multiqc_files - .map { meta, sample -> [ "L${meta.lane}", meta, sample ] } + .map { meta, sample -> [ "[LANE:${meta.lane}]", meta, sample ] } .groupTuple() .tap { mqc_by_lane } .collectFile{ lane, meta, samples -> [ "${lane}_multiqc_extra_config.yml", - "output_fn_name: \"${lane}_multiqc_report.html\"\ndata_dir_name: \"${lane}_multiqc_data\"\nplots_dir_name: \"${lane}_multiqc_plots\"" + """ + |output_fn_name: \"${lane}_multiqc_report.html\" + |data_dir_name: \"${lane}_multiqc_data\" + |plots_dir_name: \"${lane}_multiqc_plots\" + """.stripMargin() ] } .map { file -> def fileparts = file.name.split("_") @@ -127,13 +131,17 @@ workflow SEQINSPECTOR { // Generate reports by group group_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.group } - .map { meta, sample -> [ "G-${meta.group}", meta, sample ] } + .map { meta, sample -> [ "[GROUP:${meta.group}]", meta, sample ] } .groupTuple() .tap { mqc_by_group } .collectFile{ group, meta, samples -> [ "${group}_multiqc_extra_config.yml", - "output_fn_name: \"${group}_multiqc_report.html\"\ndata_dir_name: \"${group}_multiqc_data\"\nplots_dir_name: \"${group}_multiqc_plots\"" + """ + |output_fn_name: \"${group}_multiqc_report.html\" + |data_dir_name: \"${group}_multiqc_data\" + |plots_dir_name: \"${group}_multiqc_plots\" + """.stripMargin() ] } .map { file -> def fileparts = file.name.split("_") @@ -156,13 +164,17 @@ workflow SEQINSPECTOR { // Generate reports by rundir rundir_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.rundir } - .map { meta, sample -> [ "D-${meta.rundir.name}", meta, sample ] } + .map { meta, sample -> [ "[RUNDIR:${meta.rundir.name}]", meta, sample ] } .groupTuple() .tap { mqc_by_rundir } .collectFile{ rundir, meta, samples -> [ "${rundir}_multiqc_extra_config.yml", - "output_fn_name: \"${rundir}_multiqc_report.html\"\ndata_dir_name: \"${rundir}_multiqc_data\"\nplots_dir_name: \"${rundir}_multiqc_plots\"" + """ + |output_fn_name: \"${rundir}_multiqc_report.html\" + |data_dir_name: \"${rundir}_multiqc_data\" + |plots_dir_name: \"${rundir}_multiqc_plots\" + """.stripMargin() ] } .map { file -> def fileparts = file.name.split("_") From a0ce8a3b5b338eacb2b3abec49f85ff0ad7648e1 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Fri, 3 May 2024 09:50:42 +0200 Subject: [PATCH 009/530] Set up tests --- .gitignore | 1 + nf-test.config | 8 ++++++++ tests/main.nf.test | 20 +++++++++++++++++++ tests/nextflow.config | 5 +++++ tests/workflows/seqinspector.nf.test | 30 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+) create mode 100644 nf-test.config create mode 100644 tests/main.nf.test create mode 100644 tests/nextflow.config create mode 100644 tests/workflows/seqinspector.nf.test diff --git a/.gitignore b/.gitignore index 5124c9ac..089a4079 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ results/ testing/ testing* *.pyc +.nf-test diff --git a/nf-test.config b/nf-test.config new file mode 100644 index 00000000..6969c085 --- /dev/null +++ b/nf-test.config @@ -0,0 +1,8 @@ +config { + + testsDir "tests" + workDir ".nf-test" + configFile "tests/nextflow.config" + profile "test,docker" + +} diff --git a/tests/main.nf.test b/tests/main.nf.test new file mode 100644 index 00000000..72498b5b --- /dev/null +++ b/tests/main.nf.test @@ -0,0 +1,20 @@ +nextflow_pipeline { + + name "Test Workflow main.nf" + script "main.nf" + + test("Should run without failures") { + + when { + params { + outdir = "tests/results" + } + } + + then { + assert workflow.success + } + + } + +} diff --git a/tests/nextflow.config b/tests/nextflow.config new file mode 100644 index 00000000..c19b1ad0 --- /dev/null +++ b/tests/nextflow.config @@ -0,0 +1,5 @@ +/* +======================================================================================== + Nextflow config file for running tests +======================================================================================== +*/ diff --git a/tests/workflows/seqinspector.nf.test b/tests/workflows/seqinspector.nf.test new file mode 100644 index 00000000..bbb529dd --- /dev/null +++ b/tests/workflows/seqinspector.nf.test @@ -0,0 +1,30 @@ +nextflow_workflow { + + name "Test Workflow SEQINSPECTOR" + script "workflows/seqinspector.nf" + workflow "SEQINSPECTOR" + + test("Should run without failures pipeline") { + + when { + params { + // define parameters here. Example: + // outdir = "tests/results" + } + workflow { + """ + // define inputs of the workflow here. Example: + // input[0] = file("https://raw.githubusercontent.com/nf-core/test-datasets/e47966b63444ec0fcdef23bfc410eeca22535ac7/testdata/MiSeq/samplesheet.csv") + input[0] = file("assets/samplesheet.csv") + """ + } + } + + then { + assert workflow.success + assert snapshot(workflow.out).match() + } + + } + +} From 1aaf73b41733f6d91c0d617ad4d30a05f099b880 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 12:18:42 +0200 Subject: [PATCH 010/530] point test conf upstream --- conf/test.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/test.config b/conf/test.config index 38e9ee32..2c26a9c1 100644 --- a/conf/test.config +++ b/conf/test.config @@ -22,7 +22,7 @@ params { // Input data // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = 'https://raw.githubusercontent.com/KarNair/test-datasets/seqinspector/testdata/MiSeq/samplesheet.csv' + input = 'https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/MiSeq/samplesheet.csv' // Genome references genome = 'R64-1-1' From 0c38819be827c7c320af4210b94c9f835a2a3f88 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 13:05:47 +0200 Subject: [PATCH 011/530] project -> group --- assets/samplesheet.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index fbe5de2d..fef3b4e6 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,3 +1,3 @@ -sample,lane,project,fastq_1,fastq_2,rundir +sample,lane,group,fastq_1,fastq_2,rundir SAMPLE_PAIRED_END,1,P001,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir SAMPLE_SINGLE_END,2,P002,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir From f842ad0ff6ce7cb3eba1e386cd91d6ed0e1c0b07 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 13:06:48 +0200 Subject: [PATCH 012/530] project -> group --- assets/schema_input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 1648944f..7115bfab 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -22,7 +22,7 @@ "group": { "type": "string", "pattern": "^\\S+$", - "errorMessage": "Project ID cannot contain spaces", + "errorMessage": "Group ID cannot contain spaces", "meta": ["group"] }, "fastq_1": { From 2ffc86726fe4b89b4ae52251c360d8f5b0f02a5a Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 13:53:31 +0200 Subject: [PATCH 013/530] make lane non-compulsory --- assets/schema_input.json | 2 +- workflows/seqinspector.nf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 7115bfab..c9800d5d 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -47,7 +47,7 @@ "meta": ["rundir"] } }, - "required": ["sample", "lane", "fastq_1"], + "required": ["sample", "fastq_1"], "dependentRequired": { "fastq_2": ["fastq_1"] } diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 2acafc3f..018e079d 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -98,6 +98,7 @@ workflow SEQINSPECTOR { // Generate reports by lane lane_mqc_files = ch_multiqc_files + .filter { meta, sample -> meta.lane } .map { meta, sample -> [ "[LANE:${meta.lane}]", meta, sample ] } .groupTuple() .tap { mqc_by_lane } From 978836605ea655b9a6b9aa72add50bb059a59a6c Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 15:59:10 +0200 Subject: [PATCH 014/530] remove unused file --- assets/samplesheet.csv | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 assets/samplesheet.csv diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv deleted file mode 100644 index fef3b4e6..00000000 --- a/assets/samplesheet.csv +++ /dev/null @@ -1,3 +0,0 @@ -sample,lane,group,fastq_1,fastq_2,rundir -SAMPLE_PAIRED_END,1,P001,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir -SAMPLE_SINGLE_END,2,P002,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir From b0c02e3803a31d28c6bbb85a60306bbd9ba15168 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 3 May 2024 16:38:52 +0200 Subject: [PATCH 015/530] revamp nf-test, run once for each sequencing platform --- tests/MiSeq.main.nf.test | 23 +++++++++++++++++++++ tests/NovaSeq6000.main.nf.test | 23 +++++++++++++++++++++ tests/PromethION.main.nf.test | 23 +++++++++++++++++++++ tests/main.nf.test | 20 ------------------- tests/nextflow.config | 11 ++++++++++ tests/workflows/seqinspector.nf.test | 30 ---------------------------- 6 files changed, 80 insertions(+), 50 deletions(-) create mode 100644 tests/MiSeq.main.nf.test create mode 100644 tests/NovaSeq6000.main.nf.test create mode 100644 tests/PromethION.main.nf.test delete mode 100644 tests/main.nf.test delete mode 100644 tests/workflows/seqinspector.nf.test diff --git a/tests/MiSeq.main.nf.test b/tests/MiSeq.main.nf.test new file mode 100644 index 00000000..0246a75b --- /dev/null +++ b/tests/MiSeq.main.nf.test @@ -0,0 +1,23 @@ +nextflow_pipeline { + + name "Test Workflow main.nf on MiSeq data" + script "../main.nf" + tag "seqinspector" + tag "PIPELINE" + + test("MiSeq data test") { + + when { + params { + outdir = "tests/results/MiSeq" + input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/MiSeq/samplesheet.csv" + } + } + + then { + assert workflow.success + } + + } + +} diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test new file mode 100644 index 00000000..7c410e57 --- /dev/null +++ b/tests/NovaSeq6000.main.nf.test @@ -0,0 +1,23 @@ +nextflow_pipeline { + + name "Test Workflow main.nf on NovaSeq6000 data" + script "../main.nf" + tag "seqinspector" + tag "PIPELINE" + + test("NovaSeq6000 data test") { + + when { + params { + outdir = "tests/results/NovaSeq6000" + input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/NovaSeq6000/samplesheet.csv" + } + } + + then { + assert workflow.success + } + + } + +} diff --git a/tests/PromethION.main.nf.test b/tests/PromethION.main.nf.test new file mode 100644 index 00000000..9987c953 --- /dev/null +++ b/tests/PromethION.main.nf.test @@ -0,0 +1,23 @@ +nextflow_pipeline { + + name "Test Workflow main.nf on PromethION data" + script "../main.nf" + tag "seqinspector" + tag "PIPELINE" + + test("PromethION data test") { + + when { + params { + outdir = "tests/results/PromethION" + input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/PromethION/samplesheet.csv" + } + } + + then { + assert workflow.success + } + + } + +} diff --git a/tests/main.nf.test b/tests/main.nf.test deleted file mode 100644 index 72498b5b..00000000 --- a/tests/main.nf.test +++ /dev/null @@ -1,20 +0,0 @@ -nextflow_pipeline { - - name "Test Workflow main.nf" - script "main.nf" - - test("Should run without failures") { - - when { - params { - outdir = "tests/results" - } - } - - then { - assert workflow.success - } - - } - -} diff --git a/tests/nextflow.config b/tests/nextflow.config index c19b1ad0..422545be 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -3,3 +3,14 @@ Nextflow config file for running tests ======================================================================================== */ + +params { + config_profile_name = 'nf-test profile' + config_profile_description = 'Configuration profile to use for nf-test.' + + // Limit resources so that this can run on GitHub Actions + max_cpus = 2 + max_memory = '3.GB' + max_time = '2.h' + +} diff --git a/tests/workflows/seqinspector.nf.test b/tests/workflows/seqinspector.nf.test deleted file mode 100644 index bbb529dd..00000000 --- a/tests/workflows/seqinspector.nf.test +++ /dev/null @@ -1,30 +0,0 @@ -nextflow_workflow { - - name "Test Workflow SEQINSPECTOR" - script "workflows/seqinspector.nf" - workflow "SEQINSPECTOR" - - test("Should run without failures pipeline") { - - when { - params { - // define parameters here. Example: - // outdir = "tests/results" - } - workflow { - """ - // define inputs of the workflow here. Example: - // input[0] = file("https://raw.githubusercontent.com/nf-core/test-datasets/e47966b63444ec0fcdef23bfc410eeca22535ac7/testdata/MiSeq/samplesheet.csv") - input[0] = file("assets/samplesheet.csv") - """ - } - } - - then { - assert workflow.success - assert snapshot(workflow.out).match() - } - - } - -} From f60e951516b500dc42c7773219ffe3d2c1d6f526 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Mon, 6 May 2024 15:54:05 +0200 Subject: [PATCH 016/530] Update modules and subworkflows --- modules.json | 4 ++-- modules/nf-core/fastqc/main.nf | 6 ++++++ subworkflows/nf-core/utils_nfcore_pipeline/main.nf | 8 +++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/modules.json b/modules.json index ebbc5dc2..87fe816c 100644 --- a/modules.json +++ b/modules.json @@ -7,7 +7,7 @@ "nf-core": { "fastqc": { "branch": "master", - "git_sha": "f4ae1d942bd50c5c0b9bd2de1393ce38315ba57c", + "git_sha": "285a50500f9e02578d90b3ce6382ea3c30216acd", "installed_by": ["modules"] }, "multiqc": { @@ -26,7 +26,7 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", + "git_sha": "92de218a329bfc9a9033116eb5f65fd270e72ba3", "installed_by": ["subworkflows"] }, "utils_nfvalidation_plugin": { diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index 9e19a74c..d79f1c86 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -25,6 +25,11 @@ process FASTQC { def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } def rename_to = old_new_pairs*.join(' ').join(' ') def renamed_files = old_new_pairs.collect{ old_name, new_name -> new_name }.join(' ') + + def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') + // FastQC memory value allowed range (100 - 10000) + def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) + """ printf "%s %s\\n" $rename_to | while read old_name new_name; do [ -f "\${new_name}" ] || ln -s \$old_name \$new_name @@ -33,6 +38,7 @@ process FASTQC { fastqc \\ $args \\ --threads $task.cpus \\ + --memory $fastqc_memory \\ $renamed_files cat <<-END_VERSIONS > versions.yml diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index a8b55d6f..14558c39 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -65,9 +65,15 @@ def checkProfileProvided(nextflow_cli_args) { // Citation string for pipeline // def workflowCitation() { + def temp_doi_ref = "" + String[] manifest_doi = workflow.manifest.doi.tokenize(",") + // Using a loop to handle multiple DOIs + // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers + // Removing ` ` since the manifest.doi is a string and not a proper list + for (String doi_ref: manifest_doi) temp_doi_ref += " https://doi.org/${doi_ref.replace('https://doi.org/', '').replace(' ', '')}\n" return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + "* The pipeline\n" + - " ${workflow.manifest.doi}\n\n" + + temp_doi_ref + "\n" + "* The nf-core framework\n" + " https://doi.org/10.1038/s41587-020-0439-x\n\n" + "* Software dependencies\n" + From 16e36206b726ba21ec9803caac2f3b305bcfb19f Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 7 May 2024 16:45:09 +0200 Subject: [PATCH 017/530] Fix multiqc extra files Issue with the previous implementation was that sometimes MULTIQC_PER_LANE would execute before the extra files were collected into `ch_multiqc_extra_files`, causing `null` to be added to the list of files passed to multiqc. --- workflows/seqinspector.nf | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 018e079d..65343a8b 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -94,11 +94,17 @@ workflow SEQINSPECTOR { ch_multiqc_logo.toList() ) - multiqc_extra_files = ch_multiqc_extra_files.toList() - // Generate reports by lane + multiqc_extra_files_per_lane = ch_multiqc_files + .filter { meta, sample -> meta.lane } + .map { meta, sample -> meta.lane } + .unique() + .map { lane -> [lane:lane] } + .cross(ch_multiqc_extra_files) + lane_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.lane } + .mix(multiqc_extra_files_per_lane) .map { meta, sample -> [ "[LANE:${meta.lane}]", meta, sample ] } .groupTuple() .tap { mqc_by_lane } @@ -122,16 +128,23 @@ workflow SEQINSPECTOR { } MULTIQC_PER_LANE( - lane_mqc_files.samples_per_lane - .map { samples -> samples + multiqc_extra_files.value }, + lane_mqc_files.samples_per_lane, ch_multiqc_config.toList(), lane_mqc_files.config, ch_multiqc_logo.toList() ) // Generate reports by group + multiqc_extra_files_per_group = ch_multiqc_files + .filter { meta, sample -> meta.group } + .map { meta, sample -> meta.group } + .unique() + .map { group -> [group:group] } + .cross(ch_multiqc_extra_files) + group_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.group } + .mix(multiqc_extra_files_per_group) .map { meta, sample -> [ "[GROUP:${meta.group}]", meta, sample ] } .groupTuple() .tap { mqc_by_group } @@ -155,16 +168,23 @@ workflow SEQINSPECTOR { } MULTIQC_PER_GROUP( - group_mqc_files.samples_per_group - .map { samples -> samples + multiqc_extra_files.value }, + group_mqc_files.samples_per_group, ch_multiqc_config.toList(), group_mqc_files.config, ch_multiqc_logo.toList() ) // Generate reports by rundir + multiqc_extra_files_per_rundir = ch_multiqc_files + .filter { meta, sample -> meta.rundir } + .map { meta, sample -> meta.rundir } + .unique() + .map { rundir -> [rundir:rundir] } + .cross(ch_multiqc_extra_files) + rundir_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.rundir } + .mix(multiqc_extra_files_per_rundir) .map { meta, sample -> [ "[RUNDIR:${meta.rundir.name}]", meta, sample ] } .groupTuple() .tap { mqc_by_rundir } @@ -188,8 +208,7 @@ workflow SEQINSPECTOR { } MULTIQC_PER_RUNDIR( - rundir_mqc_files.samples_per_rundir - .map { samples -> samples + multiqc_extra_files.value }, + rundir_mqc_files.samples_per_rundir, ch_multiqc_config.toList(), rundir_mqc_files.config, ch_multiqc_logo.toList() From 93c66a40091d6228c28a965af19f0aa64dfe9f0d Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Wed, 8 May 2024 14:01:12 +0000 Subject: [PATCH 018/530] Template update for nf-core/tools version 2.14.0 --- .editorconfig | 6 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/awsfulltest.yml | 10 +- .github/workflows/awstest.yml | 12 +- .github/workflows/ci.yml | 4 +- .github/workflows/download_pipeline.yml | 22 ++- .github/workflows/fix-linting.yml | 6 +- .github/workflows/linting.yml | 18 +- .github/workflows/linting_comment.yml | 2 +- .github/workflows/release-announcements.yml | 6 +- .nf-core.yml | 1 + .pre-commit-config.yaml | 3 + README.md | 2 +- conf/base.config | 3 - conf/modules.config | 8 - conf/test.config | 2 +- conf/test_full.config | 2 +- docs/usage.md | 2 + modules.json | 4 +- modules/nf-core/fastqc/main.nf | 6 + nextflow.config | 176 +++++++++--------- nextflow_schema.json | 7 + pyproject.toml | 15 -- .../main.nf | 16 +- .../nf-core/utils_nfcore_pipeline/main.nf | 8 +- workflows/seqinspector.nf | 46 +++-- 26 files changed, 220 insertions(+), 169 deletions(-) delete mode 100644 pyproject.toml diff --git a/.editorconfig b/.editorconfig index dd9ffa53..72dda289 100644 --- a/.editorconfig +++ b/.editorconfig @@ -28,10 +28,6 @@ indent_style = unset [/assets/email*] indent_size = unset -# ignore Readme -[README.md] -indent_style = unset - -# ignore python +# ignore python and markdown [*.{py,md}] indent_style = unset diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5d27c53a..6de151f7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/seqi - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/seqinspector/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/seqinspector _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core lint`). -- [ ] Ensure the test suite passes (`nf-test test main.nf.test -profile test,docker`). +- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. - [ ] Output Documentation in `docs/output.md` is updated. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 1b153a69..9278410e 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -8,12 +8,12 @@ on: types: [published] workflow_dispatch: jobs: - run-tower: + run-platform: name: Run AWS full tests if: github.repository == 'nf-core/seqinspector' runs-on: ubuntu-latest steps: - - name: Launch workflow via tower + - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 # TODO nf-core: You can customise AWS full pipeline tests as required # Add full size test data (but still relatively small datasets for few samples) @@ -33,7 +33,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: Tower debug log file + name: Seqera Platform debug log file path: | - tower_action_*.log - tower_action_*.json + seqera_platform_action_*.log + seqera_platform_action_*.json diff --git a/.github/workflows/awstest.yml b/.github/workflows/awstest.yml index 6def58c8..eef6f2c3 100644 --- a/.github/workflows/awstest.yml +++ b/.github/workflows/awstest.yml @@ -5,13 +5,13 @@ name: nf-core AWS test on: workflow_dispatch: jobs: - run-tower: + run-platform: name: Run AWS tests if: github.repository == 'nf-core/seqinspector' runs-on: ubuntu-latest steps: - # Launch workflow using Tower CLI tool action - - name: Launch workflow via tower + # Launch workflow using Seqera Platform CLI tool action + - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 with: workspace_id: ${{ secrets.TOWER_WORKSPACE_ID }} @@ -27,7 +27,7 @@ jobs: - uses: actions/upload-artifact@v4 with: - name: Tower debug log file + name: Seqera Platform debug log file path: | - tower_action_*.log - tower_action_*.json + seqera_platform_action_*.log + seqera_platform_action_*.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a2be9af..59c94105 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,10 +28,10 @@ jobs: - "latest-everything" steps: - name: Check out pipeline code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@v2 with: version: "${{ matrix.NXF_VER }}" diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 08622fd5..2d20d644 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -14,6 +14,8 @@ on: pull_request: types: - opened + - edited + - synchronize branches: - master pull_request_target: @@ -28,11 +30,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - name: Disk space cleanup + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: "3.11" + python-version: "3.12" architecture: "x64" - uses: eWaterCycle/setup-singularity@931d4e31109e875b13309ae1d07c70ca8fbc8537 # v7 with: @@ -65,8 +70,17 @@ jobs: - name: Inspect download run: tree ./${{ env.REPOTITLE_LOWERCASE }} - - name: Run the downloaded pipeline + - name: Run the downloaded pipeline (stub) + id: stub_run_pipeline + continue-on-error: true env: NXF_SINGULARITY_CACHEDIR: ./ NXF_SINGULARITY_HOME_MOUNT: true run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results + - name: Run the downloaded pipeline (stub run not supported) + id: run_pipeline + if: ${{ job.steps.stub_run_pipeline.status == failure() }} + env: + NXF_SINGULARITY_CACHEDIR: ./ + NXF_SINGULARITY_HOME_MOUNT: true + run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -profile test,singularity --outdir ./results diff --git a/.github/workflows/fix-linting.yml b/.github/workflows/fix-linting.yml index 45e06861..1984e17d 100644 --- a/.github/workflows/fix-linting.yml +++ b/.github/workflows/fix-linting.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Use the @nf-core-bot token to check out so we can push later - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 with: token: ${{ secrets.nf_core_bot_auth_token }} @@ -32,9 +32,9 @@ jobs: GITHUB_TOKEN: ${{ secrets.nf_core_bot_auth_token }} # Install and run pre-commit - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: 3.11 + python-version: "3.12" - name: Install pre-commit run: pip install pre-commit diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 073e1876..a3fb2541 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -14,12 +14,12 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - - name: Set up Python 3.11 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - name: Set up Python 3.12 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: 3.11 + python-version: "3.12" cache: "pip" - name: Install pre-commit @@ -32,14 +32,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: "3.11" + python-version: "3.12" architecture: "x64" - name: Install dependencies @@ -60,7 +60,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index b706875f..40acc23f 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@f6b0bace624032e30a85a8fd9c1a7f8f611f5737 # v3 + uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3 with: workflow: linting.yml workflow_conclusion: completed diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index d468aeaa..03ecfcf7 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -12,7 +12,7 @@ jobs: - name: get topics and convert to hashtags id: get_topics run: | - curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ' >> $GITHUB_OUTPUT + echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" >> $GITHUB_OUTPUT - uses: rzr/fediverse-action@master with: @@ -25,13 +25,13 @@ jobs: Please see the changelog: ${{ github.event.release.html_url }} - ${{ steps.get_topics.outputs.GITHUB_OUTPUT }} #nfcore #openscience #nextflow #bioinformatics + ${{ steps.get_topics.outputs.topics }} #nfcore #openscience #nextflow #bioinformatics send-tweet: runs-on: ubuntu-latest steps: - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: python-version: "3.10" - name: Install dependencies diff --git a/.nf-core.yml b/.nf-core.yml index 3805dc81..d6daa403 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1 +1,2 @@ repository_type: pipeline +nf_core_version: "2.14.0" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index af57081f..4dc0f1dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,9 @@ repos: rev: "v3.1.0" hooks: - id: prettier + additional_dependencies: + - prettier@3.2.5 + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python rev: "2.7.3" hooks: diff --git a/README.md b/README.md index 5e2bc4ca..bfb53fee 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) -[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://tower.nf/launch?pipeline=https://github.com/nf-core/seqinspector) +[![Launch on Seqera Platform](https://img.shields.io/badge/Launch%20%F0%9F%9A%80-Seqera%20Platform-%234256e7)](https://cloud.seqera.io/launch?pipeline=https://github.com/nf-core/seqinspector) [![Get help on Slack](http://img.shields.io/badge/slack-nf--core%20%23seqinspector-4A154B?labelColor=000000&logo=slack)](https://nfcore.slack.com/channels/seqinspector)[![Follow on Twitter](http://img.shields.io/badge/twitter-%40nf__core-1DA1F2?labelColor=000000&logo=twitter)](https://twitter.com/nf_core)[![Follow on Mastodon](https://img.shields.io/badge/mastodon-nf__core-6364ff?labelColor=FFFFFF&logo=mastodon)](https://mstdn.science/@nf_core)[![Watch on YouTube](http://img.shields.io/badge/youtube-nf--core-FF0000?labelColor=000000&logo=youtube)](https://www.youtube.com/c/nf-core) diff --git a/conf/base.config b/conf/base.config index 8d7dffc6..aab50f93 100644 --- a/conf/base.config +++ b/conf/base.config @@ -59,7 +59,4 @@ process { errorStrategy = 'retry' maxRetries = 2 } - withName:CUSTOM_DUMPSOFTWAREVERSIONS { - cache = false - } } diff --git a/conf/modules.config b/conf/modules.config index e3ea8fa6..d203d2b6 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,14 +22,6 @@ process { ext.args = '--quiet' } - withName: CUSTOM_DUMPSOFTWAREVERSIONS { - publishDir = [ - path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - pattern: '*_versions.yml' - ] - } - withName: 'MULTIQC' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ diff --git a/conf/test.config b/conf/test.config index fbbffdd6..6a7b9846 100644 --- a/conf/test.config +++ b/conf/test.config @@ -22,7 +22,7 @@ params { // Input data // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_test_illumina_amplicon.csv' // Genome references genome = 'R64-1-1' diff --git a/conf/test_full.config b/conf/test_full.config index 6fbea31a..53b22883 100644 --- a/conf/test_full.config +++ b/conf/test_full.config @@ -17,7 +17,7 @@ params { // Input data for full size test // TODO nf-core: Specify the paths to your full test data ( on nf-core/test-datasets or directly in repositories, e.g. SRA) // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = 'https://raw.githubusercontent.com/nf-core/test-datasets/viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' + input = params.pipelines_testdata_base_path + 'viralrecon/samplesheet/samplesheet_full_illumina_amplicon.csv' // Genome references genome = 'R64-1-1' diff --git a/docs/usage.md b/docs/usage.md index f926de73..ec180e49 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -156,6 +156,8 @@ If `-profile` is not specified, the pipeline will run locally and expect all sof - A generic configuration profile to be used with [Charliecloud](https://hpc.github.io/charliecloud/) - `apptainer` - A generic configuration profile to be used with [Apptainer](https://apptainer.org/) +- `wave` + - A generic configuration profile to enable [Wave](https://seqera.io/wave/) containers. Use together with one of the above (requires Nextflow ` 24.03.0-edge` or later). - `conda` - A generic configuration profile to be used with [Conda](https://conda.io/docs/). Please only use Conda as a last resort i.e. when it's not possible to run the pipeline with Docker, Singularity, Podman, Shifter, Charliecloud, or Apptainer. diff --git a/modules.json b/modules.json index ebbc5dc2..87fe816c 100644 --- a/modules.json +++ b/modules.json @@ -7,7 +7,7 @@ "nf-core": { "fastqc": { "branch": "master", - "git_sha": "f4ae1d942bd50c5c0b9bd2de1393ce38315ba57c", + "git_sha": "285a50500f9e02578d90b3ce6382ea3c30216acd", "installed_by": ["modules"] }, "multiqc": { @@ -26,7 +26,7 @@ }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", + "git_sha": "92de218a329bfc9a9033116eb5f65fd270e72ba3", "installed_by": ["subworkflows"] }, "utils_nfvalidation_plugin": { diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index 9e19a74c..d79f1c86 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -25,6 +25,11 @@ process FASTQC { def old_new_pairs = reads instanceof Path || reads.size() == 1 ? [[ reads, "${prefix}.${reads.extension}" ]] : reads.withIndex().collect { entry, index -> [ entry, "${prefix}_${index + 1}.${entry.extension}" ] } def rename_to = old_new_pairs*.join(' ').join(' ') def renamed_files = old_new_pairs.collect{ old_name, new_name -> new_name }.join(' ') + + def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') + // FastQC memory value allowed range (100 - 10000) + def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) + """ printf "%s %s\\n" $rename_to | while read old_name new_name; do [ -f "\${new_name}" ] || ln -s \$old_name \$new_name @@ -33,6 +38,7 @@ process FASTQC { fastqc \\ $args \\ --threads $task.cpus \\ + --memory $fastqc_memory \\ $renamed_files cat <<-END_VERSIONS > versions.yml diff --git a/nextflow.config b/nextflow.config index 776814f7..a73f66ba 100644 --- a/nextflow.config +++ b/nextflow.config @@ -16,7 +16,8 @@ params { genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false - fasta = null// MultiQC options + + // MultiQC options multiqc_config = null multiqc_title = null multiqc_logo = null @@ -24,15 +25,16 @@ params { multiqc_methods_description = null // Boilerplate options - outdir = null - publish_dir_mode = 'copy' - email = null - email_on_fail = null - plaintext_email = false - monochrome_logs = false - hook_url = null - help = false - version = false + outdir = null + publish_dir_mode = 'copy' + email = null + email_on_fail = null + plaintext_email = false + monochrome_logs = false + hook_url = null + help = false + version = false + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' // Config options config_profile_name = null @@ -68,103 +70,109 @@ try { } // Load nf-core/seqinspector custom profiles from different institutions. -// Warning: Uncomment only if a pipeline-specific institutional config already exists on nf-core/configs! -// try { -// includeConfig "${params.custom_config_base}/pipeline/seqinspector.config" -// } catch (Exception e) { -// System.err.println("WARNING: Could not load nf-core/config/seqinspector profiles: ${params.custom_config_base}/pipeline/seqinspector.config") -// } +try { + includeConfig "${params.custom_config_base}/pipeline/seqinspector.config" +} catch (Exception e) { + System.err.println("WARNING: Could not load nf-core/config/seqinspector profiles: ${params.custom_config_base}/pipeline/seqinspector.config") +} profiles { debug { - dumpHashes = true - process.beforeScript = 'echo $HOSTNAME' - cleanup = false + dumpHashes = true + process.beforeScript = 'echo $HOSTNAME' + cleanup = false nextflow.enable.configProcessNamesValidation = true } conda { - conda.enabled = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - channels = ['conda-forge', 'bioconda', 'defaults'] - apptainer.enabled = false + conda.enabled = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + conda.channels = ['conda-forge', 'bioconda', 'defaults'] + apptainer.enabled = false } mamba { - conda.enabled = true - conda.useMamba = true - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + conda.enabled = true + conda.useMamba = true + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } docker { - docker.enabled = true - conda.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false - docker.runOptions = '-u $(id -u):$(id -g)' + docker.enabled = true + conda.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false + docker.runOptions = '-u $(id -u):$(id -g)' } arm { - docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' + docker.runOptions = '-u $(id -u):$(id -g) --platform=linux/amd64' } singularity { - singularity.enabled = true - singularity.autoMounts = true - conda.enabled = false - docker.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + singularity.enabled = true + singularity.autoMounts = true + conda.enabled = false + docker.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } podman { - podman.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - shifter.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + podman.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + shifter.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } shifter { - shifter.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - charliecloud.enabled = false - apptainer.enabled = false + shifter.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + charliecloud.enabled = false + apptainer.enabled = false } charliecloud { - charliecloud.enabled = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - apptainer.enabled = false + charliecloud.enabled = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + apptainer.enabled = false } apptainer { - apptainer.enabled = true - apptainer.autoMounts = true - conda.enabled = false - docker.enabled = false - singularity.enabled = false - podman.enabled = false - shifter.enabled = false - charliecloud.enabled = false + apptainer.enabled = true + apptainer.autoMounts = true + conda.enabled = false + docker.enabled = false + singularity.enabled = false + podman.enabled = false + shifter.enabled = false + charliecloud.enabled = false + } + wave { + apptainer.ociAutoPull = true + singularity.ociAutoPull = true + wave.enabled = true + wave.freeze = true + wave.strategy = 'conda,container' } gitpod { - executor.name = 'local' - executor.cpus = 4 - executor.memory = 8.GB + executor.name = 'local' + executor.cpus = 4 + executor.memory = 8.GB } test { includeConfig 'conf/test.config' } test_full { includeConfig 'conf/test_full.config' } diff --git a/nextflow_schema.json b/nextflow_schema.json index ec323b90..36308a0d 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -265,6 +265,13 @@ "description": "Validation of parameters in lenient more.", "hidden": true, "help_text": "Allows string values that are parseable as numbers or booleans. For further information see [JSONSchema docs](https://github.com/everit-org/json-schema#lenient-mode)." + }, + "pipelines_testdata_base_path": { + "type": "string", + "fa_icon": "far fa-check-circle", + "description": "Base URL or local path to location of pipeline test dataset files", + "default": "https://raw.githubusercontent.com/nf-core/test-datasets/", + "hidden": true } } } diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 56110621..00000000 --- a/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -# Config file for Python. Mostly used to configure linting of bin/*.py with Ruff. -# Should be kept the same as nf-core/tools to avoid fighting with template synchronisation. -[tool.ruff] -line-length = 120 -target-version = "py38" -cache-dir = "~/.cache/ruff" - -[tool.ruff.lint] -select = ["I", "E1", "E4", "E7", "E9", "F", "UP", "N"] - -[tool.ruff.lint.isort] -known-first-party = ["nf_core"] - -[tool.ruff.lint.per-file-ignores] -"__init__.py" = ["E402", "F401"] diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index 56b144f3..87281414 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -140,6 +140,10 @@ workflow PIPELINE_COMPLETION { imNotification(summary_params, hook_url) } } + + workflow.onError { + log.error "Pipeline failed. Please refer to troubleshooting docs: https://nf-co.re/docs/usage/troubleshooting" + } } /* @@ -230,8 +234,16 @@ def methodsDescriptionText(mqc_methods_yaml) { meta["manifest_map"] = workflow.manifest.toMap() // Pipeline DOI - meta["doi_text"] = meta.manifest_map.doi ? "(doi: ${meta.manifest_map.doi})" : "" - meta["nodoi_text"] = meta.manifest_map.doi ? "": "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " + if (meta.manifest_map.doi) { + // Using a loop to handle multiple DOIs + // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers + // Removing ` ` since the manifest.doi is a string and not a proper list + def temp_doi_ref = "" + String[] manifest_doi = meta.manifest_map.doi.tokenize(",") + for (String doi_ref: manifest_doi) temp_doi_ref += "(doi: ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " + meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) + } else meta["doi_text"] = "" + meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " // Tool references meta["tool_citations"] = "" diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index a8b55d6f..14558c39 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -65,9 +65,15 @@ def checkProfileProvided(nextflow_cli_args) { // Citation string for pipeline // def workflowCitation() { + def temp_doi_ref = "" + String[] manifest_doi = workflow.manifest.doi.tokenize(",") + // Using a loop to handle multiple DOIs + // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers + // Removing ` ` since the manifest.doi is a string and not a proper list + for (String doi_ref: manifest_doi) temp_doi_ref += " https://doi.org/${doi_ref.replace('https://doi.org/', '').replace(' ', '')}\n" return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + "* The pipeline\n" + - " ${workflow.manifest.doi}\n\n" + + temp_doi_ref + "\n" + "* The nf-core framework\n" + " https://doi.org/10.1038/s41587-020-0439-x\n\n" + "* Software dependencies\n" + diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 9ae3384b..cf7a85c4 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -40,22 +40,44 @@ workflow SEQINSPECTOR { // Collate and save software versions // softwareVersionsToYAML(ch_versions) - .collectFile(storeDir: "${params.outdir}/pipeline_info", name: 'nf_core_pipeline_software_mqc_versions.yml', sort: true, newLine: true) - .set { ch_collated_versions } + .collectFile( + storeDir: "${params.outdir}/pipeline_info", + name: 'nf_core_pipeline_software_mqc_versions.yml', + sort: true, + newLine: true + ).set { ch_collated_versions } // // MODULE: MultiQC // - ch_multiqc_config = Channel.fromPath("$projectDir/assets/multiqc_config.yml", checkIfExists: true) - ch_multiqc_custom_config = params.multiqc_config ? Channel.fromPath(params.multiqc_config, checkIfExists: true) : Channel.empty() - ch_multiqc_logo = params.multiqc_logo ? Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") - ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) - ch_methods_description = Channel.value(methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_files = ch_multiqc_files.mix(ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) - ch_multiqc_files = ch_multiqc_files.mix(ch_methods_description.collectFile(name: 'methods_description_mqc.yaml', sort: false)) + ch_multiqc_config = Channel.fromPath( + "$projectDir/assets/multiqc_config.yml", checkIfExists: true) + ch_multiqc_custom_config = params.multiqc_config ? + Channel.fromPath(params.multiqc_config, checkIfExists: true) : + Channel.empty() + ch_multiqc_logo = params.multiqc_logo ? + Channel.fromPath(params.multiqc_logo, checkIfExists: true) : + Channel.empty() + + summary_params = paramsSummaryMap( + workflow, parameters_schema: "nextflow_schema.json") + ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) + + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? + file(params.multiqc_methods_description, checkIfExists: true) : + file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) + ch_methods_description = Channel.value( + methodsDescriptionText(ch_multiqc_custom_methods_description)) + + ch_multiqc_files = ch_multiqc_files.mix( + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) + ch_multiqc_files = ch_multiqc_files.mix( + ch_methods_description.collectFile( + name: 'methods_description_mqc.yaml', + sort: true + ) + ) MULTIQC ( ch_multiqc_files.collect(), From 88ca813165306088552d29cbf9ae4e93ae46a912 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Thu, 9 May 2024 11:43:27 +0000 Subject: [PATCH 019/530] Template update for nf-core/tools version 2.14.1 --- .github/workflows/linting.yml | 1 - .nf-core.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index a3fb2541..1fcafe88 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -20,7 +20,6 @@ jobs: uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: python-version: "3.12" - cache: "pip" - name: Install pre-commit run: pip install pre-commit diff --git a/.nf-core.yml b/.nf-core.yml index d6daa403..e0b85a77 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,2 +1,2 @@ repository_type: pipeline -nf_core_version: "2.14.0" +nf_core_version: "2.14.1" From 6525874499c0dde7ae8ef4532294f30717c93454 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 10:29:17 +0200 Subject: [PATCH 020/530] Add test snapshots --- tests/MiSeq.main.nf.test | 25 +++++++++++++++--- tests/MiSeq.main.nf.test.snap | 19 ++++++++++++++ tests/NovaSeq6000.main.nf.test | 40 ++++++++++++++++++++++++++--- tests/NovaSeq6000.main.nf.test.snap | 31 ++++++++++++++++++++++ tests/PromethION.main.nf.test | 20 ++++++++++++--- tests/PromethION.main.nf.test.snap | 15 +++++++++++ workflows/seqinspector.nf | 18 +++++-------- 7 files changed, 144 insertions(+), 24 deletions(-) create mode 100644 tests/MiSeq.main.nf.test.snap create mode 100644 tests/NovaSeq6000.main.nf.test.snap create mode 100644 tests/PromethION.main.nf.test.snap diff --git a/tests/MiSeq.main.nf.test b/tests/MiSeq.main.nf.test index 0246a75b..1e72de95 100644 --- a/tests/MiSeq.main.nf.test +++ b/tests/MiSeq.main.nf.test @@ -9,15 +9,32 @@ nextflow_pipeline { when { params { - outdir = "tests/results/MiSeq" + outdir = "$outputDir" input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/MiSeq/samplesheet.csv" } } then { - assert workflow.success - } + assertAll( + { assert workflow.success }, + { assert snapshot( + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_software_versions.txt"), - } + path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + ).match() + } + ) + } + } } diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap new file mode 100644 index 00000000..e222b515 --- /dev/null +++ b/tests/MiSeq.main.nf.test.snap @@ -0,0 +1,19 @@ +{ + "MiSeq data test": { + "content": [ + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", + "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", + "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", + "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + ], + "timestamp": "2024-05-08T16:29:33.284003" + } +} \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test index 7c410e57..f5de9aa3 100644 --- a/tests/NovaSeq6000.main.nf.test +++ b/tests/NovaSeq6000.main.nf.test @@ -9,15 +9,47 @@ nextflow_pipeline { when { params { - outdir = "tests/results/NovaSeq6000" + outdir = "$outputDir" input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/NovaSeq6000/samplesheet.csv" } } then { - assert workflow.success - } + assertAll( + { assert workflow.success }, + { assert snapshot( + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_software_versions.txt"), - } + path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + ).match() + }, + ) + } + } } diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap new file mode 100644 index 00000000..a406b891 --- /dev/null +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -0,0 +1,31 @@ +{ + "NovaSeq6000 data test": { + "content": [ + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", + "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,9b4fd8a6d6e8a9acabecd592f633472e", + "multiqc_general_stats.txt:md5,8237b88ceb018d3cb1edcea62d10f4a2", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,9246a5b6b7b0410c79049fc3dbd08e92", + "multiqc_general_stats.txt:md5,44328403f423c6f5ac9ee0a8a01e6725", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,84820276fae52d4d492831280ae6207c", + "multiqc_general_stats.txt:md5,dd07799e5e4b9d389f9de49a852c3363", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,59ae05d89453da6f57010ffb6466f902", + "multiqc_general_stats.txt:md5,e4629691992bfe639c01a84b90563334", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", + "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + ], + "timestamp": "2024-05-14T10:17:35.440827" + } +} \ No newline at end of file diff --git a/tests/PromethION.main.nf.test b/tests/PromethION.main.nf.test index 9987c953..de2beb35 100644 --- a/tests/PromethION.main.nf.test +++ b/tests/PromethION.main.nf.test @@ -9,15 +9,27 @@ nextflow_pipeline { when { params { - outdir = "tests/results/PromethION" + outdir = "$outputDir" input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/PromethION/samplesheet.csv" } } then { - assert workflow.success - } + assertAll( + { assert workflow.success }, + { assert snapshot( + path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + ).match() + }, + ) + } } - } diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap new file mode 100644 index 00000000..24e52bd1 --- /dev/null +++ b/tests/PromethION.main.nf.test.snap @@ -0,0 +1,15 @@ +{ + "PromethION data test": { + "content": [ + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", + "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", + "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + ], + "timestamp": "2024-05-08T17:17:23.151259" + } +} \ No newline at end of file diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 65343a8b..3e2a08dc 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -100,7 +100,7 @@ workflow SEQINSPECTOR { .map { meta, sample -> meta.lane } .unique() .map { lane -> [lane:lane] } - .cross(ch_multiqc_extra_files) + .combine(ch_multiqc_extra_files) lane_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.lane } @@ -118,9 +118,7 @@ workflow SEQINSPECTOR { """.stripMargin() ] } - .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] - } + .map { file -> [ (file =~ /(\[LANE:.+\])/)[0][1], file ] } .join(mqc_by_lane) .multiMap { lane, config, meta , samples_per_lane -> samples_per_lane: samples_per_lane @@ -140,7 +138,7 @@ workflow SEQINSPECTOR { .map { meta, sample -> meta.group } .unique() .map { group -> [group:group] } - .cross(ch_multiqc_extra_files) + .combine(ch_multiqc_extra_files) group_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.group } @@ -158,9 +156,7 @@ workflow SEQINSPECTOR { """.stripMargin() ] } - .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] - } + .map { file -> [ (file =~ /(\[GROUP:.+\])/)[0][1], file ] } .join(mqc_by_group) .multiMap { group, config, meta , samples_per_group -> samples_per_group: samples_per_group @@ -180,7 +176,7 @@ workflow SEQINSPECTOR { .map { meta, sample -> meta.rundir } .unique() .map { rundir -> [rundir:rundir] } - .cross(ch_multiqc_extra_files) + .combine(ch_multiqc_extra_files) rundir_mqc_files = ch_multiqc_files .filter { meta, sample -> meta.rundir } @@ -198,9 +194,7 @@ workflow SEQINSPECTOR { """.stripMargin() ] } - .map { file -> def fileparts = file.name.split("_") - [ fileparts[0], file ] - } + .map { file -> [ (file =~ /(\[RUNDIR:.+\])/)[0][1], file ] } .join(mqc_by_rundir) .multiMap { rundir, config, meta , samples_per_rundir -> samples_per_rundir: samples_per_rundir From ca41c810dfd9384f89391d6aaf8bd521da18f9de Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 11:08:13 +0200 Subject: [PATCH 021/530] Remove unused module configuration --- conf/base.config | 3 --- conf/modules.config | 8 -------- 2 files changed, 11 deletions(-) diff --git a/conf/base.config b/conf/base.config index 8d7dffc6..aab50f93 100644 --- a/conf/base.config +++ b/conf/base.config @@ -59,7 +59,4 @@ process { errorStrategy = 'retry' maxRetries = 2 } - withName:CUSTOM_DUMPSOFTWAREVERSIONS { - cache = false - } } diff --git a/conf/modules.config b/conf/modules.config index b2e48f3d..da3d2ca2 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,14 +22,6 @@ process { ext.args = '--quiet' } - withName: CUSTOM_DUMPSOFTWAREVERSIONS { - publishDir = [ - path: { "${params.outdir}/pipeline_info" }, - mode: params.publish_dir_mode, - pattern: '*_versions.yml' - ] - } - withName: 'MULTIQC' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ From fd40f7be7532363bdc02a285d38e99001771719c Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 11:16:41 +0200 Subject: [PATCH 022/530] Update usage docs and restore example samplesheet --- assets/samplesheet.csv | 3 +++ docs/usage.md | 36 ++++++++++++------------------------ 2 files changed, 15 insertions(+), 24 deletions(-) create mode 100644 assets/samplesheet.csv diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv new file mode 100644 index 00000000..fef3b4e6 --- /dev/null +++ b/assets/samplesheet.csv @@ -0,0 +1,3 @@ +sample,lane,group,fastq_1,fastq_2,rundir +SAMPLE_PAIRED_END,1,P001,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir +SAMPLE_SINGLE_END,2,P002,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir diff --git a/docs/usage.md b/docs/usage.md index f926de73..df2be203 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -16,39 +16,27 @@ You will need to create a samplesheet with information about the samples you wou --input '[path to samplesheet file]' ``` -### Multiple runs of the same sample - -The `sample` identifiers have to be the same when you have re-sequenced the same sample more than once e.g. to increase sequencing depth. The pipeline will concatenate the raw reads before performing any downstream analysis. Below is an example for the same sample sequenced across 3 lanes: - -```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L003_R1_001.fastq.gz,AEG588A1_S1_L003_R2_001.fastq.gz -CONTROL_REP1,AEG588A1_S1_L004_R1_001.fastq.gz,AEG588A1_S1_L004_R2_001.fastq.gz -``` - ### Full samplesheet -The pipeline will auto-detect whether a sample is single- or paired-end using the information provided in the samplesheet. The samplesheet can have as many columns as you desire, however, there is a strict requirement for the first 3 columns to match those defined in the table below. - -A final samplesheet file consisting of both single- and paired-end data may look something like the one below. This is for 6 samples, where `TREATMENT_REP3` has been sequenced twice. - ```csv title="samplesheet.csv" -sample,fastq_1,fastq_2 -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz -CONTROL_REP2,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz -CONTROL_REP3,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz -TREATMENT_REP1,AEG588A4_S4_L003_R1_001.fastq.gz, -TREATMENT_REP2,AEG588A5_S5_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L003_R1_001.fastq.gz, -TREATMENT_REP3,AEG588A6_S6_L004_R1_001.fastq.gz, +sample,lane,group,fastq_1,fastq_2,rundir +CONTROL_REP1,1,,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX +CONTROL_REP2,1,,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX +CONTROL_REP3,1,,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX +TREATMENT_REP1,2,GROUP1,AEG588A4_S4_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX +TREATMENT_REP2,2,GROUP1,AEG588A5_S5_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX +TREATMENT_REP3,2,GROUP2,AEG588A6_S6_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX +TREATMENT_REP3,2,GROUP2,AEG588A6_S6_L004_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX ``` | Column | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | +| `lane` | Lane where the sample was processed on an Illumina instrument (optional). | +| `group` | Group the sample belongs too, useful when several groups are pooled together (optional). | +| `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | | `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | -| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | +| `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz" (optional). | An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. From a7dfd886aacaeb2f8be255c4252d750c372817de Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 12:09:51 +0200 Subject: [PATCH 023/530] Update output docs --- docs/output.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/output.md b/docs/output.md index 77e4cc1c..f83e1fa0 100644 --- a/docs/output.md +++ b/docs/output.md @@ -6,8 +6,6 @@ This document describes the output produced by the pipeline. Most of the plots a The directories listed below will be created in the results directory after the pipeline has finished. All paths are relative to the top-level results directory. - - ## Pipeline overview The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: @@ -48,6 +46,31 @@ The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They m - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. - `multiqc_plots/`: directory containing static images from the report in various formats. + - `lanes/` [1] + - `L1/` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. + - `L2/` + - ... + - `groups/` [1] + - `GROUPNAME1/` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. + - `GROUPNAME2/` + - ... + - `rundir/` [1] + - `RUNDIR1/` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. + - `RUNDIR2/` + - ... + + +[1] These files will only be generated if `lane`, `group` or `rundir` were specified for some samples. + From 2b1eeb43b88159fec6033a564729fd2698454c9b Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 12:16:38 +0200 Subject: [PATCH 024/530] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a53ef4..6430addb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ Initial release of nf-core/seqinspector, created with the [nf-core](https://nf-c ### `Added` +- [#13](https://github.com/nf-core/seqinspector/pull/13) Generate reports per run, per project and per lane. + ### `Fixed` ### `Dependencies` From 61648ccc1ac0c2aae3dec422eafd0ec225f54d6a Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 12:20:34 +0200 Subject: [PATCH 025/530] Update samplesheet in readme file --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index abe0d4ef..edc54ae5 100644 --- a/README.md +++ b/README.md @@ -39,26 +39,19 @@ > [!NOTE] > If you are new to Nextflow and nf-core, please refer to [this page](https://nf-co.re/docs/usage/installation) on how to set-up Nextflow. Make sure to [test your setup](https://nf-co.re/docs/usage/introduction#how-to-run-a-pipeline) with `-profile test` before running the workflow on actual data. - - Now, you can run the pipeline using: - - ```bash nextflow run nf-core/seqinspector \ -profile \ From 74dbadde15348677bf83428f824701ad24890934 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 14 May 2024 12:43:27 +0200 Subject: [PATCH 026/530] Run prettier --- docs/output.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/output.md b/docs/output.md index f83e1fa0..7af7806c 100644 --- a/docs/output.md +++ b/docs/output.md @@ -68,10 +68,8 @@ The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They m - `RUNDIR2/` - ... - [1] These files will only be generated if `lane`, `group` or `rundir` were specified for some samples. - [MultiQC](http://multiqc.info) is a visualization tool that generates a single HTML report summarising all samples in your project. Most of the pipeline QC results are visualised in the report and further statistics are available in the report data directory. From 79f99dddc3bb014de0e3221061decd38556a3621 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com> Date: Thu, 16 May 2024 16:00:32 +0200 Subject: [PATCH 027/530] Use testdata base path param in tests/MiSeq.main.nf.test Co-authored-by: Matthias Zepper <6963520+MatthiasZepper@users.noreply.github.com> --- tests/MiSeq.main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MiSeq.main.nf.test b/tests/MiSeq.main.nf.test index 1e72de95..a3dc0a5a 100644 --- a/tests/MiSeq.main.nf.test +++ b/tests/MiSeq.main.nf.test @@ -10,7 +10,7 @@ nextflow_pipeline { when { params { outdir = "$outputDir" - input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/MiSeq/samplesheet.csv" + input = params.pipelines_testdata_base_path + "seqinspector/testdata/MiSeq/samplesheet.csv" } } From f91eace5e050adc0462e17d3c31afca815c73340 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com> Date: Thu, 16 May 2024 16:01:00 +0200 Subject: [PATCH 028/530] Use testdata base path param in tests/NovaSeq6000.main.nf.test Co-authored-by: Matthias Zepper <6963520+MatthiasZepper@users.noreply.github.com> --- tests/NovaSeq6000.main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test index f5de9aa3..bc687abe 100644 --- a/tests/NovaSeq6000.main.nf.test +++ b/tests/NovaSeq6000.main.nf.test @@ -10,7 +10,7 @@ nextflow_pipeline { when { params { outdir = "$outputDir" - input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/NovaSeq6000/samplesheet.csv" + input = params.pipelines_testdata_base_path + "seqinspector/testdata/NovaSeq6000/samplesheet.csv" } } From d055ce3d64933ad9db9d41ce53b91349857fc6cd Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com> Date: Thu, 16 May 2024 16:01:20 +0200 Subject: [PATCH 029/530] Use testdata base path param in tests/PromethION.main.nf.test Co-authored-by: Matthias Zepper <6963520+MatthiasZepper@users.noreply.github.com> --- tests/PromethION.main.nf.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PromethION.main.nf.test b/tests/PromethION.main.nf.test index de2beb35..d1969f3e 100644 --- a/tests/PromethION.main.nf.test +++ b/tests/PromethION.main.nf.test @@ -10,7 +10,7 @@ nextflow_pipeline { when { params { outdir = "$outputDir" - input = "https://raw.githubusercontent.com/nf-core/test-datasets/seqinspector/testdata/PromethION/samplesheet.csv" + input = params.pipelines_testdata_base_path + "seqinspector/testdata/PromethION/samplesheet.csv" } } From 391fc43d3c292a2c59548c9f75cbc047509cfcb7 Mon Sep 17 00:00:00 2001 From: Matthias Zepper Date: Fri, 17 May 2024 11:38:29 +0200 Subject: [PATCH 030/530] Make the tests work with 'pipelines_testdata_base_path' parameter. --- tests/MiSeq.main.nf.test | 2 +- tests/MiSeq.main.nf.test.config | 7 +++++++ tests/NovaSeq6000.main.nf.test | 2 +- tests/NovaSeq6000.main.nf.test.config | 7 +++++++ tests/PromethION.main.nf.test | 2 +- tests/PromethION.main.nf.test.config | 7 +++++++ tests/nextflow.config | 5 +++++ 7 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 tests/MiSeq.main.nf.test.config create mode 100644 tests/NovaSeq6000.main.nf.test.config create mode 100644 tests/PromethION.main.nf.test.config diff --git a/tests/MiSeq.main.nf.test b/tests/MiSeq.main.nf.test index a3dc0a5a..bfabd954 100644 --- a/tests/MiSeq.main.nf.test +++ b/tests/MiSeq.main.nf.test @@ -8,9 +8,9 @@ nextflow_pipeline { test("MiSeq data test") { when { + config "./MiSeq.main.nf.test.config" params { outdir = "$outputDir" - input = params.pipelines_testdata_base_path + "seqinspector/testdata/MiSeq/samplesheet.csv" } } diff --git a/tests/MiSeq.main.nf.test.config b/tests/MiSeq.main.nf.test.config new file mode 100644 index 00000000..073a9774 --- /dev/null +++ b/tests/MiSeq.main.nf.test.config @@ -0,0 +1,7 @@ +// Load the basic test config +includeConfig 'nextflow.config' + +// Load the correct samplesheet for that test +params { + input = params.pipelines_testdata_base_path + 'seqinspector/testdata/MiSeq/samplesheet.csv' +} diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test index bc687abe..174e215d 100644 --- a/tests/NovaSeq6000.main.nf.test +++ b/tests/NovaSeq6000.main.nf.test @@ -8,9 +8,9 @@ nextflow_pipeline { test("NovaSeq6000 data test") { when { + config "./NovaSeq6000.main.nf.test.config" params { outdir = "$outputDir" - input = params.pipelines_testdata_base_path + "seqinspector/testdata/NovaSeq6000/samplesheet.csv" } } diff --git a/tests/NovaSeq6000.main.nf.test.config b/tests/NovaSeq6000.main.nf.test.config new file mode 100644 index 00000000..cad5edd9 --- /dev/null +++ b/tests/NovaSeq6000.main.nf.test.config @@ -0,0 +1,7 @@ +// Load the basic test config +includeConfig 'nextflow.config' + +// Load the correct samplesheet for that test +params { + input = params.pipelines_testdata_base_path + 'seqinspector/testdata/NovaSeq6000/samplesheet.csv' +} diff --git a/tests/PromethION.main.nf.test b/tests/PromethION.main.nf.test index d1969f3e..39284786 100644 --- a/tests/PromethION.main.nf.test +++ b/tests/PromethION.main.nf.test @@ -8,9 +8,9 @@ nextflow_pipeline { test("PromethION data test") { when { + config "./PromethION.main.nf.test.config" params { outdir = "$outputDir" - input = params.pipelines_testdata_base_path + "seqinspector/testdata/PromethION/samplesheet.csv" } } diff --git a/tests/PromethION.main.nf.test.config b/tests/PromethION.main.nf.test.config new file mode 100644 index 00000000..e1498a49 --- /dev/null +++ b/tests/PromethION.main.nf.test.config @@ -0,0 +1,7 @@ +// Load the basic test config +includeConfig 'nextflow.config' + +// Load the correct samplesheet for that test +params { + input = params.pipelines_testdata_base_path + 'seqinspector/testdata/PromethION/samplesheet.csv' +} diff --git a/tests/nextflow.config b/tests/nextflow.config index 422545be..8d9ef461 100644 --- a/tests/nextflow.config +++ b/tests/nextflow.config @@ -13,4 +13,9 @@ params { max_memory = '3.GB' max_time = '2.h' + pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + + validationSchemaIgnoreParams = 'genomes,igenomes_base,pipelines_testdata_base_path' + + } From 2147a12a1e5000d439ea784e8d337c24cc58bba5 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 17 May 2024 13:28:56 +0200 Subject: [PATCH 031/530] update snapshots, add nf-test.log to gitignore --- .gitignore | 1 + tests/MiSeq.main.nf.test.snap | 12 ++++++++---- tests/NovaSeq6000.main.nf.test.snap | 18 +++++++++++------- tests/PromethION.main.nf.test.snap | 10 +++++++--- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 089a4079..72277655 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ testing/ testing* *.pyc .nf-test +.nf-test.log diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap index e222b515..96896382 100644 --- a/tests/MiSeq.main.nf.test.snap +++ b/tests/MiSeq.main.nf.test.snap @@ -4,16 +4,20 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" ], - "timestamp": "2024-05-08T16:29:33.284003" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-17T12:59:21.531493" } } \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap index a406b891..cbb75383 100644 --- a/tests/NovaSeq6000.main.nf.test.snap +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -4,28 +4,32 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,9b4fd8a6d6e8a9acabecd592f633472e", "multiqc_general_stats.txt:md5,8237b88ceb018d3cb1edcea62d10f4a2", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,9246a5b6b7b0410c79049fc3dbd08e92", "multiqc_general_stats.txt:md5,44328403f423c6f5ac9ee0a8a01e6725", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,84820276fae52d4d492831280ae6207c", "multiqc_general_stats.txt:md5,dd07799e5e4b9d389f9de49a852c3363", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,59ae05d89453da6f57010ffb6466f902", "multiqc_general_stats.txt:md5,e4629691992bfe639c01a84b90563334", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" ], - "timestamp": "2024-05-14T10:17:35.440827" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-17T13:02:20.874181" } } \ No newline at end of file diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap index 24e52bd1..951c5550 100644 --- a/tests/PromethION.main.nf.test.snap +++ b/tests/PromethION.main.nf.test.snap @@ -4,12 +4,16 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" ], - "timestamp": "2024-05-08T17:17:23.151259" + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-05-17T12:58:02.572837" } } \ No newline at end of file From aa75066cd064883bd7359f5625623f798f0b72c0 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Fri, 17 May 2024 15:49:01 +0200 Subject: [PATCH 032/530] visualize example run dir corresponsing to samplesheet --- docs/usage.md | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 57314973..7a4ec735 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -10,7 +10,7 @@ ## Samplesheet input -You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row as shown in the examples below. +You will need to create a samplesheet with information about the samples you would like to analyse before running the pipeline. Use this parameter to specify its location. ```bash --input '[path to samplesheet file]' @@ -18,15 +18,25 @@ You will need to create a samplesheet with information about the samples you wou ### Full samplesheet +The following simple run dir structure... + +``` +run_dir +├── sample1_lane1_group1_r1.fq.gz +├── sample2_lane1_group1_r1.fq.gz +├── sample3_lane2_group2_r1.fq.gz +└── sample4_lane2_group3_r1.fq.gz +``` + +...would be represented in the following samplesheet (shown as .tsv for readability) + ```csv title="samplesheet.csv" -sample,lane,group,fastq_1,fastq_2,rundir -CONTROL_REP1,1,,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX -CONTROL_REP2,1,,AEG588A2_S2_L002_R1_001.fastq.gz,AEG588A2_S2_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX -CONTROL_REP3,1,,AEG588A3_S3_L002_R1_001.fastq.gz,AEG588A3_S3_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX -TREATMENT_REP1,2,GROUP1,AEG588A4_S4_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX -TREATMENT_REP2,2,GROUP1,AEG588A5_S5_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX -TREATMENT_REP3,2,GROUP2,AEG588A6_S6_L003_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX -TREATMENT_REP3,2,GROUP2,AEG588A6_S6_L004_R1_001.fastq.gz,,200624_A00834_0183_BHMTFYDRXX +sample lane group fastq_1 fastq_2 rundir +sample1 1 group1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir +sample2 1 group1 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir +sample3 2 group2 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir +sample4 2 group3 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir + ``` | Column | Description | @@ -34,11 +44,11 @@ TREATMENT_REP3,2,GROUP2,AEG588A6_S6_L004_R1_001.fastq.gz,,200624_A00834_0183_BHM | `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | | `lane` | Lane where the sample was processed on an Illumina instrument (optional). | | `group` | Group the sample belongs too, useful when several groups are pooled together (optional). | -| `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | | `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | | `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz" (optional). | +| `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | -An [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. +Another [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. ## Running the pipeline From 09f12cc54a91972e9c450542c0e9ccfb462123c9 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Mon, 20 May 2024 12:41:50 +0200 Subject: [PATCH 033/530] naming fixes --- main.nf | 4 ++-- workflows/seqinspector.nf | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/main.nf b/main.nf index 9de4f94e..fb00bd5d 100644 --- a/main.nf +++ b/main.nf @@ -59,8 +59,8 @@ workflow NFCORE_SEQINSPECTOR { emit: global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html - lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html - group_report = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html + lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html + group_reports = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html rundir_report = SEQINSPECTOR.out.rundir_reports // channel: /path/to/multiqc_report.html } diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 0eb375dd..50fa481f 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -6,7 +6,7 @@ include { FASTQC } from '../modules/nf-core/fastqc/main' -include { MULTIQC } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_GLOBAL } from '../modules/nf-core/multiqc/main' include { MULTIQC as MULTIQC_PER_LANE } from '../modules/nf-core/multiqc/main' include { MULTIQC as MULTIQC_PER_GROUP } from '../modules/nf-core/multiqc/main' include { MULTIQC as MULTIQC_PER_RUNDIR } from '../modules/nf-core/multiqc/main' @@ -84,7 +84,7 @@ workflow SEQINSPECTOR { ) ) - MULTIQC ( + MULTIQC_GLOBAL ( ch_multiqc_files .map { meta, file -> file } .mix(ch_multiqc_extra_files) @@ -97,9 +97,8 @@ workflow SEQINSPECTOR { // Generate reports by lane multiqc_extra_files_per_lane = ch_multiqc_files .filter { meta, sample -> meta.lane } - .map { meta, sample -> meta.lane } + .map { meta, sample -> [lane: meta.lane] } .unique() - .map { lane -> [lane:lane] } .combine(ch_multiqc_extra_files) lane_mqc_files = ch_multiqc_files @@ -209,7 +208,7 @@ workflow SEQINSPECTOR { ) emit: - global_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + global_report = MULTIQC_GLOBAL.out.report.toList() // channel: /path/to/multiqc_report.html lane_reports = MULTIQC_PER_LANE.out.report.toList() // channel: [ /path/to/multiqc_report.html ] group_reports = MULTIQC_PER_GROUP.out.report.toList() // channel: [ /path/to/multiqc_report.html ] rundir_reports = MULTIQC_PER_RUNDIR.out.report.toList() // channel: [ /path/to/multiqc_report.html ] From 3d0be5a1cf662b84c31b0b95881e99a966b53e6f Mon Sep 17 00:00:00 2001 From: kedhammar Date: Mon, 20 May 2024 12:44:01 +0200 Subject: [PATCH 034/530] nf-core sync --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/linting.yml | 19 ++++++++++--------- .github/workflows/linting_comment.yml | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6de151f7..5d27c53a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/seqi - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/seqinspector/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/seqinspector _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core lint`). -- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). +- [ ] Ensure the test suite passes (`nf-test test main.nf.test -profile test,docker`). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. - [ ] Output Documentation in `docs/output.md` is updated. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1fcafe88..073e1876 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -14,12 +14,13 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - name: Set up Python 3.12 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + - name: Set up Python 3.11 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: - python-version: "3.12" + python-version: 3.11 + cache: "pip" - name: Install pre-commit run: pip install pre-commit @@ -31,14 +32,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v2 + uses: nf-core/setup-nextflow@v1 - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 + - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 with: - python-version: "3.12" + python-version: "3.11" architecture: "x64" - name: Install dependencies @@ -59,7 +60,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 40acc23f..b706875f 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3 + uses: dawidd6/action-download-artifact@f6b0bace624032e30a85a8fd9c1a7f8f611f5737 # v3 with: workflow: linting.yml workflow_conclusion: completed From 055d7033ab5caaa42eb3102a8ea1dc113a8fc3e2 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Mon, 20 May 2024 12:44:54 +0200 Subject: [PATCH 035/530] nf-core fixes --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/linting.yml | 19 +++++++++---------- .github/workflows/linting_comment.yml | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5d27c53a..6de151f7 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,7 +18,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/seqi - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/seqinspector/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/seqinspector _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. - [ ] Make sure your code lints (`nf-core lint`). -- [ ] Ensure the test suite passes (`nf-test test main.nf.test -profile test,docker`). +- [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. - [ ] Output Documentation in `docs/output.md` is updated. diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 073e1876..1fcafe88 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -14,13 +14,12 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - - name: Set up Python 3.11 - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - name: Set up Python 3.12 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: 3.11 - cache: "pip" + python-version: "3.12" - name: Install pre-commit run: pip install pre-commit @@ -32,14 +31,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out pipeline code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - name: Install Nextflow - uses: nf-core/setup-nextflow@v1 + uses: nf-core/setup-nextflow@v2 - - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5 + - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5 with: - python-version: "3.11" + python-version: "3.12" architecture: "x64" - name: Install dependencies @@ -60,7 +59,7 @@ jobs: - name: Upload linting log file artifact if: ${{ always() }} - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4 with: name: linting-logs path: | diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index b706875f..40acc23f 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@f6b0bace624032e30a85a8fd9c1a7f8f611f5737 # v3 + uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3 with: workflow: linting.yml workflow_conclusion: completed From 40691f59717daca640f43e2040a9e86b7eceb6b6 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Thu, 30 May 2024 13:18:36 +0200 Subject: [PATCH 036/530] Improve publishDir logic --- conf/modules.config | 56 +++++++++++++++++++++++++---- tests/MiSeq.main.nf.test.snap | 12 +++---- tests/NovaSeq6000.main.nf.test.snap | 18 ++++------ tests/PromethION.main.nf.test.snap | 10 ++---- 4 files changed, 64 insertions(+), 32 deletions(-) diff --git a/conf/modules.config b/conf/modules.config index da3d2ca2..f7e78457 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -22,11 +22,20 @@ process { ext.args = '--quiet' } - withName: 'MULTIQC' { + withName: 'MULTIQC_GLOBAL' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ path: { "${params.outdir}/multiqc" }, mode: params.publish_dir_mode, + saveAs: { filename -> filename.equals('versions.yml') ? null : filename } + ] + } + + withName: 'MULTIQC_PER_LANE' { + ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + publishDir = [ + path: { "${params.outdir}/multiqc/lanes" }, + mode: params.publish_dir_mode, saveAs: { filename -> switch (filename) { @@ -38,27 +47,62 @@ process { def new_filename = filename.replaceFirst( "(?.*)\\[LANE:${lane}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') - "lanes/L${lane}/${new_filename}" + "L${lane}/${new_filename}" + break + default: + filename + } + } + ] + } + + withName: 'MULTIQC_PER_GROUP' { + ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + publishDir = [ + path: { "${params.outdir}/multiqc/groups" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + switch (filename) { + case 'versions.yml': + null break case ~/\[GROUP:.+\]_multiqc_(report\.html|plots|data)/: def group = (filename =~ /\[GROUP:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( "(?.*)\\[GROUP:${group}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') - "groups/${group}/${new_filename}" + "${group}/${new_filename}" + break + default: + filename + } + } + ] + } + + withName: 'MULTIQC_PER_RUNDIR' { + ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } + publishDir = [ + path: { "${params.outdir}/multiqc/rundirss" }, + mode: params.publish_dir_mode, + saveAs: { + filename -> + switch (filename) { + case 'versions.yml': + null break case ~/\[RUNDIR:.+\]_multiqc_(report\.html|plots|data)/: def rundir = (filename =~ /\[RUNDIR:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( "(?.*)\\[RUNDIR:${rundir}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') - "rundirs/${rundir}/${new_filename}" + "${rundir}/${new_filename}" break default: filename - } + } } ] } - } diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap index 96896382..87c1a561 100644 --- a/tests/MiSeq.main.nf.test.snap +++ b/tests/MiSeq.main.nf.test.snap @@ -4,20 +4,16 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-17T12:59:21.531493" + "timestamp": "2024-05-30T13:14:20.263485" } } \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap index cbb75383..22600251 100644 --- a/tests/NovaSeq6000.main.nf.test.snap +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -4,32 +4,28 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,9b4fd8a6d6e8a9acabecd592f633472e", "multiqc_general_stats.txt:md5,8237b88ceb018d3cb1edcea62d10f4a2", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,9246a5b6b7b0410c79049fc3dbd08e92", "multiqc_general_stats.txt:md5,44328403f423c6f5ac9ee0a8a01e6725", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,84820276fae52d4d492831280ae6207c", "multiqc_general_stats.txt:md5,dd07799e5e4b9d389f9de49a852c3363", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,59ae05d89453da6f57010ffb6466f902", "multiqc_general_stats.txt:md5,e4629691992bfe639c01a84b90563334", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-17T13:02:20.874181" + "timestamp": "2024-05-30T13:13:49.062282" } } \ No newline at end of file diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap index 951c5550..0ac213c1 100644 --- a/tests/PromethION.main.nf.test.snap +++ b/tests/PromethION.main.nf.test.snap @@ -4,16 +4,12 @@ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921", + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", - "multiqc_software_versions.txt:md5,b1e01403f9bdaa81ebabd388d5f9a921" + "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" - }, - "timestamp": "2024-05-17T12:58:02.572837" + "timestamp": "2024-05-30T13:14:40.99246" } } \ No newline at end of file From 97cdfdd9f8b58ebf824c702d204a926c14d72d7d Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 27 Aug 2024 13:17:02 +0200 Subject: [PATCH 037/530] Implement tagging system --- README.md | 4 +- assets/samplesheet.csv | 10 +- assets/schema_input.json | 16 +-- conf/modules.config | 64 +-------- docs/output.md | 45 +++--- docs/usage.md | 13 +- main.nf | 6 +- .../main.nf | 4 +- tests/MiSeq.main.nf.test | 18 +-- tests/MiSeq.main.nf.test.snap | 14 +- tests/NovaSeq6000.main.nf.test | 53 ++++--- tests/NovaSeq6000.main.nf.test.snap | 26 ++-- tests/PromethION.main.nf.test | 13 +- tests/PromethION.main.nf.test.snap | 10 +- workflows/seqinspector.nf | 131 ++++-------------- 15 files changed, 126 insertions(+), 301 deletions(-) diff --git a/README.md b/README.md index 7efdd3e9..31018e7b 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,8 @@ First, prepare a samplesheet with your input data that looks as follows: `samplesheet.csv`: ```csv -sample,lane,group,fastq_1,fastq_2,rundir -CONTROL_REP1,1,GROUP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX +sample,fastq_1,fastq_2,rundir,tags +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,"patient1" ``` Each row represents a fastq file (single-end) or a pair of fastq files (paired end). diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index fef3b4e6..00019e58 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,3 +1,7 @@ -sample,lane,group,fastq_1,fastq_2,rundir -SAMPLE_PAIRED_END,1,P001,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir -SAMPLE_SINGLE_END,2,P002,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir +sample,fastq_1,fastq_2,rundir,tags +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A2_S2_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A2_S2_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A3_S3_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A3_S3_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort2" +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient1" +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient2" +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient3" diff --git a/assets/schema_input.json b/assets/schema_input.json index c9800d5d..62922b79 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -13,18 +13,6 @@ "errorMessage": "Sample name must be provided and cannot contain spaces", "meta": ["sample"] }, - "lane": { - "type": "integer", - "pattern": "^\\d+$", - "errorMessage": "Lane ID must be a number", - "meta": ["lane"] - }, - "group": { - "type": "string", - "pattern": "^\\S+$", - "errorMessage": "Group ID cannot contain spaces", - "meta": ["group"] - }, "fastq_1": { "type": "string", "format": "file-path", @@ -45,6 +33,10 @@ "exists": true, "errorMessage": "Run directory must be a path", "meta": ["rundir"] + }, + "tags": { + "type": "string", + "meta": ["tags"] } }, "required": ["sample", "fastq_1"], diff --git a/conf/modules.config b/conf/modules.config index f7e78457..c8838224 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -25,16 +25,16 @@ process { withName: 'MULTIQC_GLOBAL' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ - path: { "${params.outdir}/multiqc" }, + path: { "${params.outdir}/multiqc/global_report" }, mode: params.publish_dir_mode, saveAs: { filename -> filename.equals('versions.yml') ? null : filename } ] } - withName: 'MULTIQC_PER_LANE' { + withName: 'MULTIQC_PER_TAG' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ - path: { "${params.outdir}/multiqc/lanes" }, + path: { "${params.outdir}/multiqc/group_reports" }, mode: params.publish_dir_mode, saveAs: { filename -> @@ -42,62 +42,12 @@ process { case 'versions.yml': null break - case ~/\[LANE:\d+\]_multiqc_(report\.html|plots|data)/: - def lane = (filename =~ /\[LANE:(\d+)\]_multiqc_(report\.html|plots|data)/)[0][1] + case ~/\[TAG:.+\]_multiqc_(report\.html|plots|data)/: + def tag = (filename =~ /\[TAG:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] def new_filename = filename.replaceFirst( - "(?.*)\\[LANE:${lane}\\]_(?multiqc_(report\\.html|plots|data).*)", + "(?.*)\\[TAG:${tag}\\]_(?multiqc_(report\\.html|plots|data).*)", '${prefix}${suffix}') - "L${lane}/${new_filename}" - break - default: - filename - } - } - ] - } - - withName: 'MULTIQC_PER_GROUP' { - ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } - publishDir = [ - path: { "${params.outdir}/multiqc/groups" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - switch (filename) { - case 'versions.yml': - null - break - case ~/\[GROUP:.+\]_multiqc_(report\.html|plots|data)/: - def group = (filename =~ /\[GROUP:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] - def new_filename = filename.replaceFirst( - "(?.*)\\[GROUP:${group}\\]_(?multiqc_(report\\.html|plots|data).*)", - '${prefix}${suffix}') - "${group}/${new_filename}" - break - default: - filename - } - } - ] - } - - withName: 'MULTIQC_PER_RUNDIR' { - ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } - publishDir = [ - path: { "${params.outdir}/multiqc/rundirss" }, - mode: params.publish_dir_mode, - saveAs: { - filename -> - switch (filename) { - case 'versions.yml': - null - break - case ~/\[RUNDIR:.+\]_multiqc_(report\.html|plots|data)/: - def rundir = (filename =~ /\[RUNDIR:(.+)\]_multiqc_(report\.html|plots|data)/)[0][1] - def new_filename = filename.replaceFirst( - "(?.*)\\[RUNDIR:${rundir}\\]_(?multiqc_(report\\.html|plots|data).*)", - '${prefix}${suffix}') - "${rundir}/${new_filename}" + "${tag}/${new_filename}" break default: filename diff --git a/docs/output.md b/docs/output.md index 7af7806c..15c29ce2 100644 --- a/docs/output.md +++ b/docs/output.md @@ -39,36 +39,29 @@ The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They m ### MultiQC +nf-core/seqinspector will generate the following MultiQC reports: + +- one global reports including all the samples listed in the samplesheet +- one group report per unique tag. These reports compile samples that share the same tag. +
    Output files - `multiqc/` - - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. - - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. - - `multiqc_plots/`: directory containing static images from the report in various formats. - - `lanes/` [1] - - `L1/` - - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. - - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. - - `multiqc_plots/`: directory containing static images from the report in various formats. - - `L2/` - - ... - - `groups/` [1] - - `GROUPNAME1/` - - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. - - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. - - `multiqc_plots/`: directory containing static images from the report in various formats. - - `GROUPNAME2/` - - ... - - `rundir/` [1] - - `RUNDIR1/` - - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. - - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. - - `multiqc_plots/`: directory containing static images from the report in various formats. - - `RUNDIR2/` - - ... - -[1] These files will only be generated if `lane`, `group` or `rundir` were specified for some samples. + - `global_report` + - `multiqc_report.html`: a standalone HTML file that can be viewed in your web browser. + - `multiqc_data/`: directory containing parsed statistics from the different tools used in the pipeline. + - `multiqc_plots/`: directory containing static images from the report in various formats. + - `group_reports` + - `tag1/` + - `multiqc_report.html` + - `multiqc_data/` + - `multiqc_plots/` + - `tag2/` + - `multiqc_report.html` + - `multiqc_data/` + - `multiqc_plots/` + - ...
    diff --git a/docs/usage.md b/docs/usage.md index 7a4ec735..42d596bb 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -31,22 +31,21 @@ run_dir ...would be represented in the following samplesheet (shown as .tsv for readability) ```csv title="samplesheet.csv" -sample lane group fastq_1 fastq_2 rundir -sample1 1 group1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir -sample2 1 group1 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir -sample3 2 group2 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir -sample4 2 group3 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir +sample fastq_1 fastq_2 rundir tags +sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient1" +sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient2" +sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir "cohort1,patient3" +sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "control" ``` | Column | Description | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | -| `lane` | Lane where the sample was processed on an Illumina instrument (optional). | -| `group` | Group the sample belongs too, useful when several groups are pooled together (optional). | | `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | | `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz" (optional). | | `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | +| `tags` | Comma-separated list of tags to group samples in special reports. | Another [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. diff --git a/main.nf b/main.nf index fb00bd5d..a55408ef 100644 --- a/main.nf +++ b/main.nf @@ -58,10 +58,8 @@ workflow NFCORE_SEQINSPECTOR { ) emit: - global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html - lane_reports = SEQINSPECTOR.out.lane_reports // channel: /path/to/multiqc_report.html - group_reports = SEQINSPECTOR.out.group_reports // channel: /path/to/multiqc_report.html - rundir_report = SEQINSPECTOR.out.rundir_reports // channel: /path/to/multiqc_report.html + global_report = SEQINSPECTOR.out.global_report // channel: /path/to/multiqc_report.html + grouped_reports = SEQINSPECTOR.out.grouped_reports // channel: /path/to/multiqc_report.html } /* diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index ea9c9b8a..afcfc68b 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -84,8 +84,8 @@ workflow PIPELINE_INITIALISATION { .fromSamplesheet("input") // Validates samplesheet against $projectDir/assets/schema_input.json. Path to validation schema is defined by $projectDir/nextflow_schema.json .map { meta, fastq_1, fastq_2 -> - def id_string = "${meta.sample}_${meta.group ?: "ungrouped"}_${meta.lane}" - def updated_meta = meta + [ id: id_string ] + def tags = meta.tags ? meta.tags.tokenize(",") : [] + def updated_meta = meta + [ id:meta.sample, tags:tags ] if (!fastq_2) { return [ updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] } else { diff --git a/tests/MiSeq.main.nf.test b/tests/MiSeq.main.nf.test index bfabd954..8fbff4a3 100644 --- a/tests/MiSeq.main.nf.test +++ b/tests/MiSeq.main.nf.test @@ -18,20 +18,10 @@ nextflow_pipeline { assertAll( { assert workflow.success }, { assert snapshot( - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/P001/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_software_versions.txt"), ).match() } ) diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap index 87c1a561..26f0c2a8 100644 --- a/tests/MiSeq.main.nf.test.snap +++ b/tests/MiSeq.main.nf.test.snap @@ -2,18 +2,10 @@ "MiSeq data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", - "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", - "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", - "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", - "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,692b8aed0614ed1655f2c1cbea1ba312", - "multiqc_general_stats.txt:md5,630167d67d3f92408cd1a04422c7196f", + "multiqc_fastqc.txt:md5,e46e7baa8f57d4cf54d973925b5eadf9", + "multiqc_general_stats.txt:md5,a5e626a2e1a3c986092e4f89091cc41c", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "timestamp": "2024-05-30T13:14:20.263485" + "timestamp": "2024-08-26T17:55:16.152573" } } \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test index 174e215d..f93891bf 100644 --- a/tests/NovaSeq6000.main.nf.test +++ b/tests/NovaSeq6000.main.nf.test @@ -18,35 +18,30 @@ nextflow_pipeline { assertAll( { assert workflow.success }, { assert snapshot( - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/lanes/L1/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/S1/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/S2/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/S3/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/S4/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_software_versions.txt"), ).match() }, ) diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap index 22600251..f5c4776b 100644 --- a/tests/NovaSeq6000.main.nf.test.snap +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -2,30 +2,26 @@ "NovaSeq6000 data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", - "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", + "multiqc_fastqc.txt:md5,e8ed6dca928396b8873d24e60ea1a133", + "multiqc_general_stats.txt:md5,fd9d46c5b441908cd07e5373d116db17", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,9b4fd8a6d6e8a9acabecd592f633472e", - "multiqc_general_stats.txt:md5,8237b88ceb018d3cb1edcea62d10f4a2", + "multiqc_fastqc.txt:md5,ff9b31c6024f11a8135456e7ea01fc8f", + "multiqc_general_stats.txt:md5,f36bd6e27e92c25be076efea411d3a8e", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,9246a5b6b7b0410c79049fc3dbd08e92", - "multiqc_general_stats.txt:md5,44328403f423c6f5ac9ee0a8a01e6725", + "multiqc_fastqc.txt:md5,62d51280dcd7634f6bed95ffe0d8dab8", + "multiqc_general_stats.txt:md5,2012002b6a057be981a97fcc96142a6c", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,84820276fae52d4d492831280ae6207c", - "multiqc_general_stats.txt:md5,dd07799e5e4b9d389f9de49a852c3363", + "multiqc_fastqc.txt:md5,63749e803a2d5fc7ecc7cd93fa68df1f", + "multiqc_general_stats.txt:md5,656931993032400dea3d441b8b61b4d2", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,59ae05d89453da6f57010ffb6466f902", - "multiqc_general_stats.txt:md5,e4629691992bfe639c01a84b90563334", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", - "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,5ba3f4f4ec6026a5f5d55418384dcd3e", - "multiqc_general_stats.txt:md5,123cd6b64c9c15607405bcdd45a843d4", + "multiqc_fastqc.txt:md5,91cc62e1b4059bdbe4b88affa43378af", + "multiqc_general_stats.txt:md5,6e500f82550e00b07c3e7aa1d46ab9e9", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "timestamp": "2024-05-30T13:13:49.062282" + "timestamp": "2024-08-26T18:03:33.089142" } } \ No newline at end of file diff --git a/tests/PromethION.main.nf.test b/tests/PromethION.main.nf.test index 39284786..8fec8b33 100644 --- a/tests/PromethION.main.nf.test +++ b/tests/PromethION.main.nf.test @@ -18,15 +18,10 @@ nextflow_pipeline { assertAll( { assert workflow.success }, { assert snapshot( - path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/groups/r10p41_e8p2_human_runs_jkw/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_software_versions.txt"), ).match() }, ) diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap index 0ac213c1..e3b34f23 100644 --- a/tests/PromethION.main.nf.test.snap +++ b/tests/PromethION.main.nf.test.snap @@ -2,14 +2,10 @@ "PromethION data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", - "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", - "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,333360ff12007d64f2bf7673b0658bed", - "multiqc_general_stats.txt:md5,b1999255f9a502618d59be2f2e93bad2", + "multiqc_fastqc.txt:md5,cecee3cb343c75c80180d3169c6f3ea1", + "multiqc_general_stats.txt:md5,e63c25089c4fc10618414ba2254d18c7", "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" ], - "timestamp": "2024-05-30T13:14:40.99246" + "timestamp": "2024-08-26T17:55:38.755385" } } \ No newline at end of file diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 50fa481f..e37df509 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -7,9 +7,7 @@ include { FASTQC } from '../modules/nf-core/fastqc/main' include { MULTIQC as MULTIQC_GLOBAL } from '../modules/nf-core/multiqc/main' -include { MULTIQC as MULTIQC_PER_LANE } from '../modules/nf-core/multiqc/main' -include { MULTIQC as MULTIQC_PER_GROUP } from '../modules/nf-core/multiqc/main' -include { MULTIQC as MULTIQC_PER_RUNDIR } from '../modules/nf-core/multiqc/main' +include { MULTIQC as MULTIQC_PER_TAG } from '../modules/nf-core/multiqc/main' include { paramsSummaryMap } from 'plugin/nf-validation' include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' @@ -94,124 +92,51 @@ workflow SEQINSPECTOR { ch_multiqc_logo.toList() ) - // Generate reports by lane - multiqc_extra_files_per_lane = ch_multiqc_files - .filter { meta, sample -> meta.lane } - .map { meta, sample -> [lane: meta.lane] } + ch_tags = ch_multiqc_files + .map { meta, sample -> meta.tags } + .flatten() .unique() - .combine(ch_multiqc_extra_files) - lane_mqc_files = ch_multiqc_files - .filter { meta, sample -> meta.lane } - .mix(multiqc_extra_files_per_lane) - .map { meta, sample -> [ "[LANE:${meta.lane}]", meta, sample ] } - .groupTuple() - .tap { mqc_by_lane } - .collectFile{ - lane, meta, samples -> [ - "${lane}_multiqc_extra_config.yml", - """ - |output_fn_name: \"${lane}_multiqc_report.html\" - |data_dir_name: \"${lane}_multiqc_data\" - |plots_dir_name: \"${lane}_multiqc_plots\" - """.stripMargin() - ] - } - .map { file -> [ (file =~ /(\[LANE:.+\])/)[0][1], file ] } - .join(mqc_by_lane) - .multiMap { lane, config, meta , samples_per_lane -> - samples_per_lane: samples_per_lane - config: config - } - - MULTIQC_PER_LANE( - lane_mqc_files.samples_per_lane, - ch_multiqc_config.toList(), - lane_mqc_files.config, - ch_multiqc_logo.toList() - ) - - // Generate reports by group - multiqc_extra_files_per_group = ch_multiqc_files - .filter { meta, sample -> meta.group } - .map { meta, sample -> meta.group } - .unique() - .map { group -> [group:group] } - .combine(ch_multiqc_extra_files) - - group_mqc_files = ch_multiqc_files - .filter { meta, sample -> meta.group } - .mix(multiqc_extra_files_per_group) - .map { meta, sample -> [ "[GROUP:${meta.group}]", meta, sample ] } - .groupTuple() - .tap { mqc_by_group } - .collectFile{ - group, meta, samples -> [ - "${group}_multiqc_extra_config.yml", - """ - |output_fn_name: \"${group}_multiqc_report.html\" - |data_dir_name: \"${group}_multiqc_data\" - |plots_dir_name: \"${group}_multiqc_plots\" - """.stripMargin() - ] - } - .map { file -> [ (file =~ /(\[GROUP:.+\])/)[0][1], file ] } - .join(mqc_by_group) - .multiMap { group, config, meta , samples_per_group -> - samples_per_group: samples_per_group - config: config - } - - MULTIQC_PER_GROUP( - group_mqc_files.samples_per_group, - ch_multiqc_config.toList(), - group_mqc_files.config, - ch_multiqc_logo.toList() - ) - - // Generate reports by rundir - multiqc_extra_files_per_rundir = ch_multiqc_files - .filter { meta, sample -> meta.rundir } - .map { meta, sample -> meta.rundir } - .unique() - .map { rundir -> [rundir:rundir] } + multiqc_extra_files_per_tag = ch_tags .combine(ch_multiqc_extra_files) - rundir_mqc_files = ch_multiqc_files - .filter { meta, sample -> meta.rundir } - .mix(multiqc_extra_files_per_rundir) - .map { meta, sample -> [ "[RUNDIR:${meta.rundir.name}]", meta, sample ] } + // Group samples by tag + tagged_mqc_files = ch_tags + .combine(ch_multiqc_files) + .filter { sample_tag, meta, sample -> sample_tag in meta.tags } + .map { sample_tag, meta, sample -> [sample_tag, sample] } + .mix(multiqc_extra_files_per_tag) .groupTuple() - .tap { mqc_by_rundir } - .collectFile{ - rundir, meta, samples -> [ - "${rundir}_multiqc_extra_config.yml", + .tap { mqc_by_tag } + .collectFile { + sample_tag, samples -> + def prefix_tag = "[TAG:${sample_tag}]" + [ + "${prefix_tag}_multiqc_extra_config.yml", """ - |output_fn_name: \"${rundir}_multiqc_report.html\" - |data_dir_name: \"${rundir}_multiqc_data\" - |plots_dir_name: \"${rundir}_multiqc_plots\" + |output_fn_name: \"${prefix_tag}_multiqc_report.html\" + |data_dir_name: \"${prefix_tag}_multiqc_data\" + |plots_dir_name: \"${prefix_tag}_multiqc_plots\" """.stripMargin() ] } - .map { file -> [ (file =~ /(\[RUNDIR:.+\])/)[0][1], file ] } - .join(mqc_by_rundir) - .multiMap { rundir, config, meta , samples_per_rundir -> - samples_per_rundir: samples_per_rundir + .map { file -> [ (file =~ /\[TAG:(.+)\]/)[0][1], file ] } + .join(mqc_by_tag) + .multiMap { sample_tag, config, samples -> + samples_per_tag: samples config: config } - MULTIQC_PER_RUNDIR( - rundir_mqc_files.samples_per_rundir, + MULTIQC_PER_TAG( + tagged_mqc_files.samples_per_tag, ch_multiqc_config.toList(), - rundir_mqc_files.config, + tagged_mqc_files.config, ch_multiqc_logo.toList() ) emit: global_report = MULTIQC_GLOBAL.out.report.toList() // channel: /path/to/multiqc_report.html - lane_reports = MULTIQC_PER_LANE.out.report.toList() // channel: [ /path/to/multiqc_report.html ] - group_reports = MULTIQC_PER_GROUP.out.report.toList() // channel: [ /path/to/multiqc_report.html ] - rundir_reports = MULTIQC_PER_RUNDIR.out.report.toList() // channel: [ /path/to/multiqc_report.html ] + grouped_reports = MULTIQC_PER_TAG.out.report.toList() // channel: [ /path/to/multiqc_report.html ] versions = ch_versions // channel: [ path(versions.yml) ] } From 81d79a15d2a6b14d88a3c9b8ea2dcab0829c2405 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 27 Aug 2024 15:08:20 +0200 Subject: [PATCH 038/530] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6430addb..3aa5bacc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Initial release of nf-core/seqinspector, created with the [nf-core](https://nf-c ### `Added` +- [#20](https://github.com/nf-core/seqinspector/pull/20) Use tags to generate group reports - [#13](https://github.com/nf-core/seqinspector/pull/13) Generate reports per run, per project and per lane. ### `Fixed` From aee596e51b8fc5d5f23b422144a7f2176a260ce0 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:52:46 +0200 Subject: [PATCH 039/530] Try adding regex assertion and error for samplesheet tags --- assets/schema_input.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/schema_input.json b/assets/schema_input.json index 62922b79..bde27fa7 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -36,6 +36,8 @@ }, "tags": { "type": "string", + "pattern": "^\\\"([A-Za-z0-9_-]+,\\ ?)*([A-Za-z0-9_-]+)\\\"$", + "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a comma-separated list flanked by a pair of double-quotes, e.g. \"patient_01, lane1, pos-CTRL_2\"." "meta": ["tags"] } }, From 9b1c7074a27a12b387202cef9cca7012cf0a9a88 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar <89784800+kedhammar@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:55:26 +0200 Subject: [PATCH 040/530] Add missing comma --- assets/schema_input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index bde27fa7..d4a6dc70 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -37,7 +37,7 @@ "tags": { "type": "string", "pattern": "^\\\"([A-Za-z0-9_-]+,\\ ?)*([A-Za-z0-9_-]+)\\\"$", - "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a comma-separated list flanked by a pair of double-quotes, e.g. \"patient_01, lane1, pos-CTRL_2\"." + "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a comma-separated list flanked by a pair of double-quotes, e.g. \"patient_01, lane1, pos-CTRL_2\".", "meta": ["tags"] } }, From 830aa46601aa46329362776e0569f2a4323c27df Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 3 Sep 2024 10:35:51 +0200 Subject: [PATCH 041/530] Change example tags to more neutral ones --- README.md | 2 +- docs/usage.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31018e7b..679b6f1c 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ First, prepare a samplesheet with your input data that looks as follows: ```csv sample,fastq_1,fastq_2,rundir,tags -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,"patient1" +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,"group1" ``` Each row represents a fastq file (single-end) or a pair of fastq files (paired end). diff --git a/docs/usage.md b/docs/usage.md index 42d596bb..621c7c0a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -32,9 +32,9 @@ run_dir ```csv title="samplesheet.csv" sample fastq_1 fastq_2 rundir tags -sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient1" -sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient2" -sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir "cohort1,patient3" +sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir "project1,group1" +sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir "project1,group1" +sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir "project1,group2" sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "control" ``` From 8b59f0b31f654f8e49203a6e29794833a70d4a2d Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 3 Sep 2024 12:36:42 +0200 Subject: [PATCH 042/530] Check for tag collisions --- .../utils_nfcore_seqinspector_pipeline/main.nf | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index afcfc68b..bb875af6 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -103,6 +103,20 @@ workflow PIPELINE_INITIALISATION { // } .set { ch_samplesheet } + ch_samplesheet + .map { + meta, fastqs -> meta.tags + } + .flatten() + .unique() + .map { tag_name -> [tag_name.toLowerCase(), tag_name] } + .groupTuple() + .map { + tag_lowercase, tags -> + assert tags.size() == 1 : + "Tag name collision: " + tags.join(", ") + } + emit: samplesheet = ch_samplesheet versions = ch_versions From 4b54f546fcde02aa28521cbdff224b0b7e31658f Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 3 Sep 2024 12:58:50 +0200 Subject: [PATCH 043/530] Use columns to separate tags --- README.md | 2 +- assets/samplesheet.csv | 12 ++++---- assets/schema_input.json | 4 +-- docs/usage.md | 10 +++---- .../main.nf | 2 +- tests/NovaSeq6000.main.nf.test | 28 +++++++++---------- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 679b6f1c..d7cd233f 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ First, prepare a samplesheet with your input data that looks as follows: ```csv sample,fastq_1,fastq_2,rundir,tags -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,"group1" +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,group1 ``` Each row represents a fastq file (single-end) or a pair of fastq files (paired end). diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index 00019e58..ba2542dd 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,7 +1,7 @@ sample,fastq_1,fastq_2,rundir,tags -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A2_S2_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A2_S2_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A3_S3_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A3_S3_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort2" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient1" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient2" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient3" +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:lane1 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A2_S2_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A2_S2_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:lane1 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A3_S3_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A3_S3_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:lane2 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,group1 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,group2 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,group3 diff --git a/assets/schema_input.json b/assets/schema_input.json index d4a6dc70..3569ae2f 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -36,8 +36,8 @@ }, "tags": { "type": "string", - "pattern": "^\\\"([A-Za-z0-9_-]+,\\ ?)*([A-Za-z0-9_-]+)\\\"$", - "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a comma-separated list flanked by a pair of double-quotes, e.g. \"patient_01, lane1, pos-CTRL_2\".", + "pattern": "^([A-Za-z0-9_-]+:)*([A-Za-z0-9_-]+)$", + "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a column-separated list, e.g. group_01:lane1:pos-CTRL_2.", "meta": ["tags"] } }, diff --git a/docs/usage.md b/docs/usage.md index 621c7c0a..38a039c4 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -32,10 +32,10 @@ run_dir ```csv title="samplesheet.csv" sample fastq_1 fastq_2 rundir tags -sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir "project1,group1" -sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir "project1,group1" -sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir "project1,group2" -sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "control" +sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir project1:group1 +sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir project1:group1 +sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir project1:group2 +sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir control ``` @@ -45,7 +45,7 @@ sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "c | `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | | `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz" (optional). | | `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | -| `tags` | Comma-separated list of tags to group samples in special reports. | +| `tags` | Column-separated list of tags to group samples in special reports. | Another [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index bb875af6..eec4d09c 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -84,7 +84,7 @@ workflow PIPELINE_INITIALISATION { .fromSamplesheet("input") // Validates samplesheet against $projectDir/assets/schema_input.json. Path to validation schema is defined by $projectDir/nextflow_schema.json .map { meta, fastq_1, fastq_2 -> - def tags = meta.tags ? meta.tags.tokenize(",") : [] + def tags = meta.tags ? meta.tags.tokenize(":") : [] def updated_meta = meta + [ id:meta.sample, tags:tags ] if (!fastq_2) { return [ updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] diff --git a/tests/NovaSeq6000.main.nf.test b/tests/NovaSeq6000.main.nf.test index f93891bf..d050399e 100644 --- a/tests/NovaSeq6000.main.nf.test +++ b/tests/NovaSeq6000.main.nf.test @@ -23,20 +23,20 @@ nextflow_pipeline { path("$outputDir/multiqc/global_report/multiqc_data/multiqc_general_stats.txt"), path("$outputDir/multiqc/global_report/multiqc_data/multiqc_software_versions.txt"), - path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/group_reports/cohort1/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/group_reports/patient1/multiqc_data/multiqc_software_versions.txt"), - - path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_citations.txt"), - path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_fastqc.txt"), - path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_general_stats.txt"), - path("$outputDir/multiqc/group_reports/patient2/multiqc_data/multiqc_software_versions.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_software_versions.txt"), path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_citations.txt"), path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_fastqc.txt"), From 97f848a166c44996e75b7f92e3758a543d61cb33 Mon Sep 17 00:00:00 2001 From: kedhammar Date: Tue, 3 Sep 2024 13:55:23 +0200 Subject: [PATCH 044/530] switch to colon-separated tags --- README.md | 2 +- assets/samplesheet.csv | 12 ++++++------ assets/schema_input.json | 4 ++-- docs/usage.md | 12 ++++++------ .../local/utils_nfcore_seqinspector_pipeline/main.nf | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 31018e7b..acba2cec 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ First, prepare a samplesheet with your input data that looks as follows: ```csv sample,fastq_1,fastq_2,rundir,tags -CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,"patient1" +CONTROL_REP1,AEG588A1_S1_L002_R1_001.fastq.gz,AEG588A1_S1_L002_R2_001.fastq.gz,200624_A00834_0183_BHMTFYDRXX,lane1:project5:group2 ``` Each row represents a fastq file (single-end) or a pair of fastq files (paired end). diff --git a/assets/samplesheet.csv b/assets/samplesheet.csv index 00019e58..de5f1a68 100644 --- a/assets/samplesheet.csv +++ b/assets/samplesheet.csv @@ -1,7 +1,7 @@ sample,fastq_1,fastq_2,rundir,tags -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A2_S2_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A2_S2_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort1" -SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A3_S3_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A3_S3_L002_R2_001.fastq.gz,/path/to/rundir,"paired_sample,cohort2" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient1" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient2" -SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,"patient3" +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A1_S1_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A1_S1_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:cohort1 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A2_S2_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A2_S2_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:cohort1 +SAMPLE_PAIRED_END,/path/to/fastq/files/AEG588A3_S3_L002_R1_001.fastq.gz,/path/to/fastq/files/AEG588A3_S3_L002_R2_001.fastq.gz,/path/to/rundir,paired_sample:cohort2 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,patient1 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,patient2 +SAMPLE_SINGLE_END,/path/to/fastq/files/AEG588A4_S4_L003_R1_001.fastq.gz,,/path/to/rundir,patient3 diff --git a/assets/schema_input.json b/assets/schema_input.json index d4a6dc70..18941557 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -36,8 +36,8 @@ }, "tags": { "type": "string", - "pattern": "^\\\"([A-Za-z0-9_-]+,\\ ?)*([A-Za-z0-9_-]+)\\\"$", - "errorMessage": "Tags must consist of numbers, letters, underscores or dashes and must be provided as a comma-separated list flanked by a pair of double-quotes, e.g. \"patient_01, lane1, pos-CTRL_2\".", + "pattern": "^([a-z0-9_-]+:)*([a-z0-9_-]+)$", + "errorMessage": "Tags must be separated by colons and only consist of lowercase letters, numbers, underscores and hyphens.", "meta": ["tags"] } }, diff --git a/docs/usage.md b/docs/usage.md index 42d596bb..e2da41de 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -32,10 +32,10 @@ run_dir ```csv title="samplesheet.csv" sample fastq_1 fastq_2 rundir tags -sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient1" -sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir "cohort1,patient2" -sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir "cohort1,patient3" -sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "control" +sample1 path/to/run_dir/sample1_lane1_group1_r1.fq.gz path/to/run_dir cohort1:patient1 +sample2 path/to/run_dir/sample2_lane1_group1_r1.fq.gz path/to/run_dir cohort1:patient2 +sample3 path/to/run_dir/sample3_lane2_group2_r1.fq.gz path/to/run_dir cohort1:patient3 +sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir control ``` @@ -44,8 +44,8 @@ sample4 path/to/run_dir/sample4_lane2_group3_r1.fq.gz path/to/run_dir "c | `sample` | Custom sample name. This entry will be identical for multiple sequencing libraries/runs from the same sample. Spaces in sample names are automatically converted to underscores (`_`). | | `fastq_1` | Full path to FastQ file for Illumina short reads 1. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz". | | `fastq_2` | Full path to FastQ file for Illumina short reads 2. File has to be gzipped and have the extension ".fastq.gz" or ".fq.gz" (optional). | -| `rundir` | Path to the runfolder containing extra information about the sequencing run (optional) . | -| `tags` | Comma-separated list of tags to group samples in special reports. | +| `rundir` | Path to the runfolder containing extra information about the sequencing run (optional). | +| `tags` | Colon-separated list of tags to group samples in special reports. | Another [example samplesheet](../assets/samplesheet.csv) has been provided with the pipeline. diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index afcfc68b..0ebfdd0a 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -84,7 +84,7 @@ workflow PIPELINE_INITIALISATION { .fromSamplesheet("input") // Validates samplesheet against $projectDir/assets/schema_input.json. Path to validation schema is defined by $projectDir/nextflow_schema.json .map { meta, fastq_1, fastq_2 -> - def tags = meta.tags ? meta.tags.tokenize(",") : [] + def tags = meta.tags ? meta.tags.tokenize(":") : [] def updated_meta = meta + [ id:meta.sample, tags:tags ] if (!fastq_2) { return [ updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] From cd4c0e6f410b0ecdb9ef1ad9f5cc58cf55488ccb Mon Sep 17 00:00:00 2001 From: kedhammar Date: Tue, 3 Sep 2024 13:55:30 +0200 Subject: [PATCH 045/530] use testdata with tags --- conf/test.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/test.config b/conf/test.config index 658e75f3..c458ad0f 100644 --- a/conf/test.config +++ b/conf/test.config @@ -22,7 +22,7 @@ params { // Input data // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets // TODO nf-core: Give any required params for the test so that command line flags are not needed - input = params.pipelines_testdata_base_path + 'seqinspector/testdata/MiSeq/samplesheet.csv' + input = params.pipelines_testdata_base_path + 'seqinspector/testdata/NovaSeq6000/samplesheet.csv' // Genome references genome = 'R64-1-1' From 519ee5e5afab01ae87c36492bc500fd34b7efeb4 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 3 Sep 2024 14:10:16 +0200 Subject: [PATCH 046/530] Make ids unique --- .../local/utils_nfcore_seqinspector_pipeline/main.nf | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index eec4d09c..632acb38 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -87,9 +87,17 @@ workflow PIPELINE_INITIALISATION { def tags = meta.tags ? meta.tags.tokenize(":") : [] def updated_meta = meta + [ id:meta.sample, tags:tags ] if (!fastq_2) { - return [ updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] + return [ + updated_meta.id + fastq_1.toString().replaceAll('/', '_'), + updated_meta + [ single_end:true ], + [ fastq_1 ] + ] } else { - return [ updated_meta.id, updated_meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] + return [ + updated_meta.id + fastq_1.toString().replaceAll('/', '_') + '_' + fastq_2.toString().replaceAll('/', '_'), + updated_meta + [ single_end:false ], + [ fastq_1, fastq_2 ] + ] } } .groupTuple() From 5e7a81da13b2f2ca7d27aac8dd2cafd9c39dd596 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Tue, 3 Sep 2024 12:26:11 +0000 Subject: [PATCH 047/530] bump multiqc to prevent gitpod crashing, populate the new process inputs w. empty channels --- modules.json | 2 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 14 ++++++++++--- modules/nf-core/multiqc/meta.yml | 13 ++++++++++++ modules/nf-core/multiqc/tests/main.nf.test | 8 ++++++++ .../nf-core/multiqc/tests/main.nf.test.snap | 20 +++++++++---------- modules/nf-core/multiqc/tests/nextflow.config | 5 +++++ workflows/seqinspector.nf | 8 ++++++-- 8 files changed, 55 insertions(+), 17 deletions(-) create mode 100644 modules/nf-core/multiqc/tests/nextflow.config diff --git a/modules.json b/modules.json index 87fe816c..70f3486c 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "b7ebe95761cd389603f9cc0e0dc384c0f663815a", + "git_sha": "19ca321db5d8bd48923262c2eca6422359633491", "installed_by": ["modules"] } } diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index ca39fb67..a31464c9 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -4,4 +4,4 @@ channels: - bioconda - defaults dependencies: - - bioconda::multiqc=1.21 + - bioconda::multiqc=1.24.1 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 47ac352f..ceaec139 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,14 +3,16 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.21--pyhdfd78af_0' : - 'biocontainers/multiqc:1.21--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.24.1--pyhdfd78af_0' : + 'biocontainers/multiqc:1.24.1--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" path(multiqc_config) path(extra_multiqc_config) path(multiqc_logo) + path(replace_names) + path(sample_names) output: path "*multiqc_report.html", emit: report @@ -23,16 +25,22 @@ process MULTIQC { script: def args = task.ext.args ?: '' + def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' def config = multiqc_config ? "--config $multiqc_config" : '' def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' - def logo = multiqc_logo ? /--cl-config 'custom_logo: "${multiqc_logo}"'/ : '' + def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' + def replace = replace_names ? "--replace-names ${replace_names}" : '' + def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ $args \\ $config \\ + $prefix \\ $extra_config \\ $logo \\ + $replace \\ + $samples \\ . cat <<-END_VERSIONS > versions.yml diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 45a9bc35..382c08cb 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -29,6 +29,19 @@ input: type: file description: Optional logo file for MultiQC pattern: "*.{png}" + - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" output: - report: type: file diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index f1c4242e..33316a7d 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -8,6 +8,8 @@ nextflow_process { tag "modules_nfcore" tag "multiqc" + config "./nextflow.config" + test("sarscov2 single-end [fastqc]") { when { @@ -17,6 +19,8 @@ nextflow_process { input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -41,6 +45,8 @@ nextflow_process { input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -66,6 +72,8 @@ nextflow_process { input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index bfebd802..83fa080c 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:48:55.657331" + "timestamp": "2024-07-10T12:41:34.562023" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:49:49.071937" + "timestamp": "2024-07-10T11:27:11.933869532" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:49:25.457567" + "timestamp": "2024-07-10T11:26:56.709849369" } -} \ No newline at end of file +} diff --git a/modules/nf-core/multiqc/tests/nextflow.config b/modules/nf-core/multiqc/tests/nextflow.config new file mode 100644 index 00000000..c537a6a3 --- /dev/null +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = null + } +} diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index e37df509..1ba00c62 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -89,7 +89,9 @@ workflow SEQINSPECTOR { .collect(), ch_multiqc_config.toList(), Channel.empty().toList(), - ch_multiqc_logo.toList() + ch_multiqc_logo.toList(), + Channel.empty().toList(), + Channel.empty().toList() ) ch_tags = ch_multiqc_files @@ -131,7 +133,9 @@ workflow SEQINSPECTOR { tagged_mqc_files.samples_per_tag, ch_multiqc_config.toList(), tagged_mqc_files.config, - ch_multiqc_logo.toList() + ch_multiqc_logo.toList(), + Channel.empty().toList(), + Channel.empty().toList() ) emit: From a6a7b351059364f79275c15997e06d9f6e3387a3 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 3 Sep 2024 16:36:59 +0200 Subject: [PATCH 048/530] Allow for uppercase letters --- assets/schema_input.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/schema_input.json b/assets/schema_input.json index 18941557..f2440839 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -36,7 +36,7 @@ }, "tags": { "type": "string", - "pattern": "^([a-z0-9_-]+:)*([a-z0-9_-]+)$", + "pattern": "^([A-Za-z0-9_-]+:)*([A-Za-z0-9_-]+)$", "errorMessage": "Tags must be separated by colons and only consist of lowercase letters, numbers, underscores and hyphens.", "meta": ["tags"] } From fe757ebca7caff802ab5e8b877718aa74f44bdbd Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 10 Sep 2024 08:54:03 +0200 Subject: [PATCH 049/530] Use samplesheet row in id Co-authored-by: Matthias Zepper --- .../local/utils_nfcore_seqinspector_pipeline/main.nf | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index 632acb38..3c63037b 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -82,19 +82,21 @@ workflow PIPELINE_INITIALISATION { // Channel .fromSamplesheet("input") // Validates samplesheet against $projectDir/assets/schema_input.json. Path to validation schema is defined by $projectDir/nextflow_schema.json + .toList() + .flatMap { it.withIndex().collect { entry, idx -> entry + "${idx+1}" } } .map { - meta, fastq_1, fastq_2 -> + meta, fastq_1, fastq_2, idx -> def tags = meta.tags ? meta.tags.tokenize(":") : [] - def updated_meta = meta + [ id:meta.sample, tags:tags ] + def updated_meta = meta + [ id:"${meta.sample}_${idx}", tags:tags ] if (!fastq_2) { return [ - updated_meta.id + fastq_1.toString().replaceAll('/', '_'), + updated_meta.id, updated_meta + [ single_end:true ], [ fastq_1 ] ] } else { return [ - updated_meta.id + fastq_1.toString().replaceAll('/', '_') + '_' + fastq_2.toString().replaceAll('/', '_'), + updated_meta.id, updated_meta + [ single_end:false ], [ fastq_1, fastq_2 ] ] From 0122993e9217787fbe65ee2d136da8c537e7d351 Mon Sep 17 00:00:00 2001 From: Adrien Coulier Date: Tue, 10 Sep 2024 09:03:50 +0200 Subject: [PATCH 050/530] Update snapshots --- tests/MiSeq.main.nf.test.snap | 12 ++++++---- tests/NovaSeq6000.main.nf.test.snap | 36 ++++++++++++++++------------- tests/PromethION.main.nf.test.snap | 12 ++++++---- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap index 26f0c2a8..9dacaf8a 100644 --- a/tests/MiSeq.main.nf.test.snap +++ b/tests/MiSeq.main.nf.test.snap @@ -2,10 +2,14 @@ "MiSeq data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,e46e7baa8f57d4cf54d973925b5eadf9", - "multiqc_general_stats.txt:md5,a5e626a2e1a3c986092e4f89091cc41c", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_fastqc.txt:md5,7b1b7fd457b60404768045b148d4c0a8", + "multiqc_general_stats.txt:md5,962713a1473a318f2cb29bb5290c4c8e", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" ], - "timestamp": "2024-08-26T17:55:16.152573" + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-10T08:57:05.870194" } } \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap index f5c4776b..3f27e5a2 100644 --- a/tests/NovaSeq6000.main.nf.test.snap +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -2,26 +2,30 @@ "NovaSeq6000 data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,e8ed6dca928396b8873d24e60ea1a133", - "multiqc_general_stats.txt:md5,fd9d46c5b441908cd07e5373d116db17", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_fastqc.txt:md5,3730f9046b20ac5c17a86db0a33f8d5d", + "multiqc_general_stats.txt:md5,d521de54d1e659bf7892105f7d23d4db", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,ff9b31c6024f11a8135456e7ea01fc8f", - "multiqc_general_stats.txt:md5,f36bd6e27e92c25be076efea411d3a8e", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_fastqc.txt:md5,8284e25ccc21041cf3b5a32eb6a51e78", + "multiqc_general_stats.txt:md5,d52544eb1a505c889a2f9117cf94a5fa", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,62d51280dcd7634f6bed95ffe0d8dab8", - "multiqc_general_stats.txt:md5,2012002b6a057be981a97fcc96142a6c", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_fastqc.txt:md5,f38ffdc112c73af3a41ed15848a3761f", + "multiqc_general_stats.txt:md5,5b1190093085ef073d4bd5818c9cde79", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,63749e803a2d5fc7ecc7cd93fa68df1f", - "multiqc_general_stats.txt:md5,656931993032400dea3d441b8b61b4d2", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95", + "multiqc_fastqc.txt:md5,7ff71ceb8ecdf086331047f8860c3347", + "multiqc_general_stats.txt:md5,79c1090dd8a97912893f8491641b9dc9", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,91cc62e1b4059bdbe4b88affa43378af", - "multiqc_general_stats.txt:md5,6e500f82550e00b07c3e7aa1d46ab9e9", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_fastqc.txt:md5,519ff344a896ac369bba4d5c5b8be7b5", + "multiqc_general_stats.txt:md5,41611bd5ab9e79425c466bf976b03bdc", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" ], - "timestamp": "2024-08-26T18:03:33.089142" + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-10T08:58:26.732622" } } \ No newline at end of file diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap index e3b34f23..fb8cda25 100644 --- a/tests/PromethION.main.nf.test.snap +++ b/tests/PromethION.main.nf.test.snap @@ -2,10 +2,14 @@ "PromethION data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,cecee3cb343c75c80180d3169c6f3ea1", - "multiqc_general_stats.txt:md5,e63c25089c4fc10618414ba2254d18c7", - "multiqc_software_versions.txt:md5,7452f1f7aae2a8a4066c2ef6cd5ceb95" + "multiqc_fastqc.txt:md5,35984961f25a0d4e7352cab4d5650178", + "multiqc_general_stats.txt:md5,1465b0b1959e3864b28ecc2340df351b", + "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" ], - "timestamp": "2024-08-26T17:55:38.755385" + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.4" + }, + "timestamp": "2024-09-10T08:58:57.180636" } } \ No newline at end of file From f530e314af225eb3d5fa61fd31a59e3861bae1d6 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Tue, 8 Oct 2024 12:33:12 +0000 Subject: [PATCH 051/530] Template update for nf-core/tools version 3.0.0 --- .editorconfig | 4 + .github/CONTRIBUTING.md | 10 +- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/awsfulltest.yml | 23 +- .github/workflows/ci.yml | 17 +- .github/workflows/download_pipeline.yml | 53 ++- .github/workflows/linting.yml | 23 +- .github/workflows/linting_comment.yml | 2 +- .github/workflows/release-announcements.yml | 2 +- .../workflows/template_version_comment.yml | 43 ++ .gitpod.yml | 7 +- .nf-core.yml | 16 +- .pre-commit-config.yaml | 2 +- .prettierignore | 1 + CITATIONS.md | 4 +- README.md | 5 +- assets/schema_input.json | 2 +- conf/base.config | 34 +- conf/igenomes_ignored.config | 9 + conf/modules.config | 1 - conf/test.config | 13 +- docs/images/mqc_fastqc_adapter.png | Bin 23458 -> 0 bytes docs/images/mqc_fastqc_counts.png | Bin 33918 -> 0 bytes docs/images/mqc_fastqc_quality.png | Bin 55769 -> 0 bytes docs/output.md | 11 +- docs/usage.md | 12 +- main.nf | 10 +- modules.json | 12 +- modules/nf-core/fastqc/environment.yml | 2 - modules/nf-core/fastqc/main.nf | 5 +- modules/nf-core/fastqc/meta.yml | 57 +-- modules/nf-core/fastqc/tests/main.nf.test | 225 ++++++++--- .../nf-core/fastqc/tests/main.nf.test.snap | 370 ++++++++++++++++-- modules/nf-core/multiqc/environment.yml | 4 +- modules/nf-core/multiqc/main.nf | 14 +- modules/nf-core/multiqc/meta.yml | 78 ++-- modules/nf-core/multiqc/tests/main.nf.test | 8 + .../nf-core/multiqc/tests/main.nf.test.snap | 20 +- modules/nf-core/multiqc/tests/nextflow.config | 5 + nextflow.config | 146 ++++--- nextflow_schema.json | 85 +--- .../main.nf | 56 +-- .../nf-core/utils_nextflow_pipeline/main.nf | 24 +- .../tests/nextflow.config | 2 +- .../nf-core/utils_nfcore_pipeline/main.nf | 45 ++- .../nf-core/utils_nfschema_plugin/main.nf | 46 +++ .../nf-core/utils_nfschema_plugin/meta.yml | 35 ++ .../utils_nfschema_plugin/tests/main.nf.test | 117 ++++++ .../tests/nextflow.config | 8 + .../tests/nextflow_schema.json | 8 +- .../nf-core/utils_nfvalidation_plugin/main.nf | 62 --- .../utils_nfvalidation_plugin/meta.yml | 44 --- .../tests/main.nf.test | 200 ---------- .../utils_nfvalidation_plugin/tests/tags.yml | 2 - workflows/seqinspector.nf | 23 +- 55 files changed, 1203 insertions(+), 806 deletions(-) create mode 100644 .github/workflows/template_version_comment.yml create mode 100644 conf/igenomes_ignored.config delete mode 100755 docs/images/mqc_fastqc_adapter.png delete mode 100755 docs/images/mqc_fastqc_counts.png delete mode 100755 docs/images/mqc_fastqc_quality.png create mode 100644 modules/nf-core/multiqc/tests/nextflow.config create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/main.nf create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/meta.yml create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test create mode 100644 subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config rename subworkflows/nf-core/{utils_nfvalidation_plugin => utils_nfschema_plugin}/tests/nextflow_schema.json (95%) delete mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/main.nf delete mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml delete mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test delete mode 100644 subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml diff --git a/.editorconfig b/.editorconfig index 72dda289..e1058815 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,7 @@ indent_style = space [*.{md,yml,yaml,html,css,scss,js}] indent_size = 2 + # These files are edited and tested upstream in nf-core/modules [/modules/nf-core/**] charset = unset @@ -25,9 +26,12 @@ insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset + + [/assets/email*] indent_size = unset + # ignore python and markdown [*.{py,md}] indent_style = unset diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ee3dfbd0..78893ac8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,7 +19,7 @@ If you'd like to write some code for nf-core/seqinspector, the standard workflow 1. Check that there isn't already an issue about your idea in the [nf-core/seqinspector issues](https://github.com/nf-core/seqinspector/issues) to avoid duplicating work. If there isn't one already, please create one so that others know you're working on this 2. [Fork](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) the [nf-core/seqinspector repository](https://github.com/nf-core/seqinspector) to your GitHub account 3. Make the necessary changes / additions within your forked repository following [Pipeline conventions](#pipeline-contribution-conventions) -4. Use `nf-core schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). +4. Use `nf-core pipelines schema build` and add any new parameters to the pipeline JSON schema (requires [nf-core tools](https://github.com/nf-core/tools) >= 1.10). 5. Submit a Pull Request against the `dev` branch and wait for the code to be reviewed and merged If you're not used to this workflow with git, you can start with some [docs from GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests) or even their [excellent `git` resources](https://try.github.io/). @@ -40,7 +40,7 @@ There are typically two types of tests that run: ### Lint tests `nf-core` has a [set of guidelines](https://nf-co.re/developers/guidelines) which all pipelines must adhere to. -To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core lint ` command. +To enforce these and ensure that all pipelines stay in sync, we have developed a helper tool which runs checks on the pipeline code. This is in the [nf-core/tools repository](https://github.com/nf-core/tools) and once installed can be run locally with the `nf-core pipelines lint ` command. If any failures or warnings are encountered, please follow the listed URL for more documentation. @@ -75,7 +75,7 @@ If you wish to contribute a new step, please use the following coding standards: 2. Write the process block (see below). 3. Define the output channel if needed (see below). 4. Add any new parameters to `nextflow.config` with a default (see below). -5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core schema build` tool). +5. Add any new parameters to `nextflow_schema.json` with help text (via the `nf-core pipelines schema build` tool). 6. Add sanity checks and validation for all relevant parameters. 7. Perform local tests to validate that the new code works as expected. 8. If applicable, add a new test command in `.github/workflow/ci.yml`. @@ -86,7 +86,7 @@ If you wish to contribute a new step, please use the following coding standards: Parameters should be initialised / defined with default values in `nextflow.config` under the `params` scope. -Once there, use `nf-core schema build` to add to `nextflow_schema.json`. +Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json`. ### Default processes resource requirements @@ -103,7 +103,7 @@ Please use the following naming schemes, to make it easy to understand what is g ### Nextflow version bumping -If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core bump-version --nextflow . [min-nf-version]` +If you are using a new feature from core Nextflow, you may bump the minimum required version of nextflow in the pipeline with: `nf-core pipelines bump-version --nextflow . [min-nf-version]` ### Images and figures diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6de151f7..9dcf6a33 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -17,7 +17,7 @@ Learn more about contributing: [CONTRIBUTING.md](https://github.com/nf-core/seqi - [ ] If you've fixed a bug or added code that should be tested, add tests! - [ ] If you've added a new tool - have you followed the pipeline conventions in the [contribution docs](https://github.com/nf-core/seqinspector/tree/master/.github/CONTRIBUTING.md) - [ ] If necessary, also make a PR on the nf-core/seqinspector _branch_ on the [nf-core/test-datasets](https://github.com/nf-core/test-datasets) repository. -- [ ] Make sure your code lints (`nf-core lint`). +- [ ] Make sure your code lints (`nf-core pipelines lint`). - [ ] Ensure the test suite passes (`nextflow run . -profile test,docker --outdir `). - [ ] Check for unexpected warnings in debug mode (`nextflow run . -profile debug,test,docker --outdir `). - [ ] Usage Documentation in `docs/usage.md` is updated. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index 9278410e..c9462a57 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -1,18 +1,33 @@ name: nf-core AWS full size tests -# This workflow is triggered on published releases. +# This workflow is triggered on PRs opened against the master branch. # It can be additionally triggered manually with GitHub actions workflow dispatch button. # It runs the -profile 'test_full' on AWS batch on: - release: - types: [published] + pull_request: + branches: + - master workflow_dispatch: + pull_request_review: + types: [submitted] + jobs: run-platform: name: Run AWS full tests - if: github.repository == 'nf-core/seqinspector' + if: github.repository == 'nf-core/seqinspector' && github.event.review.state == 'approved' runs-on: ubuntu-latest steps: + - uses: octokit/request-action@v2.x + id: check_approvals + with: + route: GET /repos/${{ github.repository }}/pulls/${{ github.event.review.number }}/reviews + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - id: test_variables + run: | + JSON_RESPONSE='${{ steps.check_approvals.outputs.data }}' + CURRENT_APPROVALS_COUNT=$(echo $JSON_RESPONSE | jq -c '[.[] | select(.state | contains("APPROVED")) ] | length') + test $CURRENT_APPROVALS_COUNT -ge 2 || exit 1 # At least 2 approvals are required - name: Launch workflow via Seqera Platform uses: seqeralabs/action-tower-launch@v2 # TODO nf-core: You can customise AWS full pipeline tests as required diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59c94105..5666673f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: pull_request: release: types: [published] + workflow_dispatch: env: NXF_ANSI_LOG: false @@ -24,7 +25,7 @@ jobs: strategy: matrix: NXF_VER: - - "23.04.0" + - "24.04.2" - "latest-everything" steps: - name: Check out pipeline code @@ -38,9 +39,21 @@ jobs: - name: Disk space cleanup uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - name: Run pipeline with test data + - name: Run pipeline with test data (docker) # TODO nf-core: You can customise CI pipeline run tests as required # For example: adding multiple test runs with different parameters # Remember that you can parallelise this by using strategy.matrix run: | nextflow run ${GITHUB_WORKSPACE} -profile test,docker --outdir ./results + + - name: Run pipeline with test data (singularity) + # TODO nf-core: You can customise CI pipeline run tests as required + run: | + nextflow run ${GITHUB_WORKSPACE} -profile test,singularity --outdir ./results + if: "${{ github.base_ref == 'master' }}" + + - name: Run pipeline with test data (conda) + # TODO nf-core: You can customise CI pipeline run tests as required + run: | + nextflow run ${GITHUB_WORKSPACE} -profile test,conda --outdir ./results + if: "${{ github.base_ref == 'master' }}" diff --git a/.github/workflows/download_pipeline.yml b/.github/workflows/download_pipeline.yml index 2d20d644..713dc3e7 100644 --- a/.github/workflows/download_pipeline.yml +++ b/.github/workflows/download_pipeline.yml @@ -1,4 +1,4 @@ -name: Test successful pipeline download with 'nf-core download' +name: Test successful pipeline download with 'nf-core pipelines download' # Run the workflow when: # - dispatched manually @@ -8,7 +8,7 @@ on: workflow_dispatch: inputs: testbranch: - description: "The specific branch you wish to utilize for the test execution of nf-core download." + description: "The specific branch you wish to utilize for the test execution of nf-core pipelines download." required: true default: "dev" pull_request: @@ -39,9 +39,11 @@ jobs: with: python-version: "3.12" architecture: "x64" - - uses: eWaterCycle/setup-singularity@931d4e31109e875b13309ae1d07c70ca8fbc8537 # v7 + + - name: Setup Apptainer + uses: eWaterCycle/setup-apptainer@4bb22c52d4f63406c49e94c804632975787312b3 # v2.0.0 with: - singularity-version: 3.8.3 + apptainer-version: 1.3.4 - name: Install dependencies run: | @@ -54,33 +56,64 @@ jobs: echo "REPOTITLE_LOWERCASE=$(basename ${GITHUB_REPOSITORY,,})" >> ${GITHUB_ENV} echo "REPO_BRANCH=${{ github.event.inputs.testbranch || 'dev' }}" >> ${GITHUB_ENV} + - name: Make a cache directory for the container images + run: | + mkdir -p ./singularity_container_images + - name: Download the pipeline env: - NXF_SINGULARITY_CACHEDIR: ./ + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images run: | - nf-core download ${{ env.REPO_LOWERCASE }} \ + nf-core pipelines download ${{ env.REPO_LOWERCASE }} \ --revision ${{ env.REPO_BRANCH }} \ --outdir ./${{ env.REPOTITLE_LOWERCASE }} \ --compress "none" \ --container-system 'singularity' \ - --container-library "quay.io" -l "docker.io" -l "ghcr.io" \ + --container-library "quay.io" -l "docker.io" -l "community.wave.seqera.io" \ --container-cache-utilisation 'amend' \ - --download-configuration + --download-configuration 'yes' - name: Inspect download run: tree ./${{ env.REPOTITLE_LOWERCASE }} + - name: Count the downloaded number of container images + id: count_initial + run: | + image_count=$(ls -1 ./singularity_container_images | wc -l | xargs) + echo "Initial container image count: $image_count" + echo "IMAGE_COUNT_INITIAL=$image_count" >> ${GITHUB_ENV} + - name: Run the downloaded pipeline (stub) id: stub_run_pipeline continue-on-error: true env: - NXF_SINGULARITY_CACHEDIR: ./ + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images NXF_SINGULARITY_HOME_MOUNT: true run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -stub -profile test,singularity --outdir ./results - name: Run the downloaded pipeline (stub run not supported) id: run_pipeline if: ${{ job.steps.stub_run_pipeline.status == failure() }} env: - NXF_SINGULARITY_CACHEDIR: ./ + NXF_SINGULARITY_CACHEDIR: ./singularity_container_images NXF_SINGULARITY_HOME_MOUNT: true run: nextflow run ./${{ env.REPOTITLE_LOWERCASE }}/$( sed 's/\W/_/g' <<< ${{ env.REPO_BRANCH }}) -profile test,singularity --outdir ./results + + - name: Count the downloaded number of container images + id: count_afterwards + run: | + image_count=$(ls -1 ./singularity_container_images | wc -l | xargs) + echo "Post-pipeline run container image count: $image_count" + echo "IMAGE_COUNT_AFTER=$image_count" >> ${GITHUB_ENV} + + - name: Compare container image counts + run: | + if [ "${{ env.IMAGE_COUNT_INITIAL }}" -ne "${{ env.IMAGE_COUNT_AFTER }}" ]; then + initial_count=${{ env.IMAGE_COUNT_INITIAL }} + final_count=${{ env.IMAGE_COUNT_AFTER }} + difference=$((final_count - initial_count)) + echo "$difference additional container images were \n downloaded at runtime . The pipeline has no support for offline runs!" + tree ./singularity_container_images + exit 1 + else + echo "The pipeline can be downloaded successfully!" + fi diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 1fcafe88..b882838a 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -1,6 +1,6 @@ name: nf-core linting # This workflow is triggered on pushes and PRs to the repository. -# It runs the `nf-core lint` and markdown lint tests to ensure +# It runs the `nf-core pipelines lint` and markdown lint tests to ensure # that the code meets the nf-core guidelines. on: push: @@ -41,17 +41,32 @@ jobs: python-version: "3.12" architecture: "x64" + - name: read .nf-core.yml + uses: pietrobolcato/action-read-yaml@1.0.0 + id: read_yml + with: + config: ${{ github.workspace }}/.nf-core.yaml + - name: Install dependencies run: | python -m pip install --upgrade pip - pip install nf-core + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + + - name: Run nf-core pipelines lint + if: ${{ github.base_ref != 'master' }} + env: + GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} + run: nf-core -l lint_log.txt pipelines lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - - name: Run nf-core lint + - name: Run nf-core pipelines lint --release + if: ${{ github.base_ref == 'master' }} env: GITHUB_COMMENTS_URL: ${{ github.event.pull_request.comments_url }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_PR_COMMIT: ${{ github.event.pull_request.head.sha }} - run: nf-core -l lint_log.txt lint --dir ${GITHUB_WORKSPACE} --markdown lint_results.md + run: nf-core -l lint_log.txt pipelines lint --release --dir ${GITHUB_WORKSPACE} --markdown lint_results.md - name: Save PR number if: ${{ always() }} diff --git a/.github/workflows/linting_comment.yml b/.github/workflows/linting_comment.yml index 40acc23f..42e519bf 100644 --- a/.github/workflows/linting_comment.yml +++ b/.github/workflows/linting_comment.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download lint results - uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3 + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6 with: workflow: linting.yml workflow_conclusion: completed diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 03ecfcf7..c6ba35df 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -12,7 +12,7 @@ jobs: - name: get topics and convert to hashtags id: get_topics run: | - echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" >> $GITHUB_OUTPUT + echo "topics=$(curl -s https://nf-co.re/pipelines.json | jq -r '.remote_workflows[] | select(.full_name == "${{ github.repository }}") | .topics[]' | awk '{print "#"$0}' | tr '\n' ' ')" | sed 's/-//g' >> $GITHUB_OUTPUT - uses: rzr/fediverse-action@master with: diff --git a/.github/workflows/template_version_comment.yml b/.github/workflows/template_version_comment.yml new file mode 100644 index 00000000..9dea41f0 --- /dev/null +++ b/.github/workflows/template_version_comment.yml @@ -0,0 +1,43 @@ +name: nf-core template version comment +# This workflow is triggered on PRs to check if the pipeline template version matches the latest nf-core version. +# It posts a comment to the PR, even if it comes from a fork. + +on: pull_request_target + +jobs: + template_version: + runs-on: ubuntu-latest + steps: + - name: Check out pipeline code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + + - name: Read template version from .nf-core.yml + uses: pietrobolcato/action-read-yaml@1.0.0 + id: read_yml + with: + config: ${{ github.workspace }}/.nf-core.yml + + - name: Install nf-core + run: | + python -m pip install --upgrade pip + pip install nf-core==${{ steps.read_yml.outputs['nf_core_version'] }} + + - name: Check nf-core outdated + id: nf_core_outdated + run: pip list --outdated | grep nf-core + + - name: Post nf-core template version comment + uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 + if: | + ${{ steps.nf_core_outdated.outputs.stdout }} =~ 'nf-core' + with: + repo-token: ${{ secrets.NF_CORE_BOT_AUTH_TOKEN }} + allow-repeats: false + message: | + ## :warning: Newer version of the nf-core template is available. + + Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. + Please update your pipeline to the latest version. + + For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). + # diff --git a/.gitpod.yml b/.gitpod.yml index 105a1821..46118637 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -4,17 +4,14 @@ tasks: command: | pre-commit install --install-hooks nextflow self-update - - name: unset JAVA_TOOL_OPTIONS - command: | - unset JAVA_TOOL_OPTIONS vscode: extensions: # based on nf-core.nf-core-extensionpack - - esbenp.prettier-vscode # Markdown/CommonMark linting and style checking for Visual Studio Code + #- esbenp.prettier-vscode # Markdown/CommonMark linting and style checking for Visual Studio Code - EditorConfig.EditorConfig # override user/workspace settings with settings found in .editorconfig files - Gruntfuggly.todo-tree # Display TODO and FIXME in a tree view in the activity bar - mechatroner.rainbow-csv # Highlight columns in csv files in different colors - # - nextflow.nextflow # Nextflow syntax highlighting + - nextflow.nextflow # Nextflow syntax highlighting - oderwat.indent-rainbow # Highlight indentation level - streetsidesoftware.code-spell-checker # Spelling checker for source code - charliermarsh.ruff # Code linter Ruff diff --git a/.nf-core.yml b/.nf-core.yml index e0b85a77..ca26523b 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,2 +1,16 @@ +bump_version: null +lint: null +nf_core_version: 3.0.0 +org_path: null repository_type: pipeline -nf_core_version: "2.14.1" +template: + author: Adrien Coulier + description: Pipeline to QC your sequences + force: false + is_nfcore: true + name: seqinspector + org: nf-core + outdir: . + skip_features: null + version: 1.0dev +update: null diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4dc0f1dc..9e9f0e1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - prettier@3.2.5 - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: "2.7.3" + rev: "3.0.3" hooks: - id: editorconfig-checker alias: ec diff --git a/.prettierignore b/.prettierignore index 437d763d..610e5069 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ + email_template.html adaptivecard.json slackreport.json diff --git a/CITATIONS.md b/CITATIONS.md index a5854252..ecbfb16e 100644 --- a/CITATIONS.md +++ b/CITATIONS.md @@ -12,11 +12,11 @@ - [FastQC](https://www.bioinformatics.babraham.ac.uk/projects/fastqc/) - > Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. +> Andrews, S. (2010). FastQC: A Quality Control Tool for High Throughput Sequence Data [Online]. - [MultiQC](https://pubmed.ncbi.nlm.nih.gov/27312411/) - > Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. +> Ewels P, Magnusson M, Lundin S, Käller M. MultiQC: summarize analysis results for multiple tools and samples in a single report. Bioinformatics. 2016 Oct 1;32(19):3047-8. doi: 10.1093/bioinformatics/btw354. Epub 2016 Jun 16. PubMed PMID: 27312411; PubMed Central PMCID: PMC5039924. ## Software packaging/containerisation tools diff --git a/README.md b/README.md index bfb53fee..2b5e2c85 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![GitHub Actions Linting Status](https://github.com/nf-core/seqinspector/actions/workflows/linting.yml/badge.svg)](https://github.com/nf-core/seqinspector/actions/workflows/linting.yml)[![AWS CI](https://img.shields.io/badge/CI%20tests-full%20size-FF9900?labelColor=000000&logo=Amazon%20AWS)](https://nf-co.re/seqinspector/results)[![Cite with Zenodo](http://img.shields.io/badge/DOI-10.5281/zenodo.XXXXXXX-1073c8?labelColor=000000)](https://doi.org/10.5281/zenodo.XXXXXXX) [![nf-test](https://img.shields.io/badge/unit_tests-nf--test-337ab7.svg)](https://www.nf-test.com) -[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A523.04.0-23aa62.svg)](https://www.nextflow.io/) +[![Nextflow](https://img.shields.io/badge/nextflow%20DSL2-%E2%89%A524.04.2-23aa62.svg)](https://www.nextflow.io/) [![run with conda](http://img.shields.io/badge/run%20with-conda-3EB049?labelColor=000000&logo=anaconda)](https://docs.conda.io/en/latest/) [![run with docker](https://img.shields.io/badge/run%20with-docker-0db7ed?labelColor=000000&logo=docker)](https://www.docker.com/) [![run with singularity](https://img.shields.io/badge/run%20with-singularity-1d355c.svg?labelColor=000000)](https://sylabs.io/docs/) @@ -67,8 +67,7 @@ nextflow run nf-core/seqinspector \ ``` > [!WARNING] -> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; -> see [docs](https://nf-co.re/usage/configuration#custom-configuration-files). +> Please provide pipeline parameters via the CLI or Nextflow `-params-file` option. Custom config files including those provided by the `-c` Nextflow option can be used to provide any configuration _**except for parameters**_; see [docs](https://nf-co.re/docs/usage/getting_started/configuration#custom-configuration-files). For more details and further functionality, please refer to the [usage documentation](https://nf-co.re/seqinspector/usage) and the [parameter documentation](https://nf-co.re/seqinspector/parameters). diff --git a/assets/schema_input.json b/assets/schema_input.json index 338d3557..d7d48374 100644 --- a/assets/schema_input.json +++ b/assets/schema_input.json @@ -1,5 +1,5 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/nf-core/seqinspector/master/assets/schema_input.json", "title": "nf-core/seqinspector pipeline - params.input schema", "description": "Schema for the file provided with params.input", diff --git a/conf/base.config b/conf/base.config index aab50f93..b8e828de 100644 --- a/conf/base.config +++ b/conf/base.config @@ -11,9 +11,9 @@ process { // TODO nf-core: Check the defaults for all processes - cpus = { check_max( 1 * task.attempt, 'cpus' ) } - memory = { check_max( 6.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 1 * task.attempt } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } errorStrategy = { task.exitStatus in ((130..145) + 104) ? 'retry' : 'finish' } maxRetries = 1 @@ -27,30 +27,30 @@ process { // TODO nf-core: Customise requirements for specific processes. // See https://www.nextflow.io/docs/latest/config.html#config-process-selectors withLabel:process_single { - cpus = { check_max( 1 , 'cpus' ) } - memory = { check_max( 6.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 1 } + memory = { 6.GB * task.attempt } + time = { 4.h * task.attempt } } withLabel:process_low { - cpus = { check_max( 2 * task.attempt, 'cpus' ) } - memory = { check_max( 12.GB * task.attempt, 'memory' ) } - time = { check_max( 4.h * task.attempt, 'time' ) } + cpus = { 2 * task.attempt } + memory = { 12.GB * task.attempt } + time = { 4.h * task.attempt } } withLabel:process_medium { - cpus = { check_max( 6 * task.attempt, 'cpus' ) } - memory = { check_max( 36.GB * task.attempt, 'memory' ) } - time = { check_max( 8.h * task.attempt, 'time' ) } + cpus = { 6 * task.attempt } + memory = { 36.GB * task.attempt } + time = { 8.h * task.attempt } } withLabel:process_high { - cpus = { check_max( 12 * task.attempt, 'cpus' ) } - memory = { check_max( 72.GB * task.attempt, 'memory' ) } - time = { check_max( 16.h * task.attempt, 'time' ) } + cpus = { 12 * task.attempt } + memory = { 72.GB * task.attempt } + time = { 16.h * task.attempt } } withLabel:process_long { - time = { check_max( 20.h * task.attempt, 'time' ) } + time = { 20.h * task.attempt } } withLabel:process_high_memory { - memory = { check_max( 200.GB * task.attempt, 'memory' ) } + memory = { 200.GB * task.attempt } } withLabel:error_ignore { errorStrategy = 'ignore' diff --git a/conf/igenomes_ignored.config b/conf/igenomes_ignored.config new file mode 100644 index 00000000..b4034d82 --- /dev/null +++ b/conf/igenomes_ignored.config @@ -0,0 +1,9 @@ +/* +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Nextflow config file for iGenomes paths +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Empty genomes dictionary to use when igenomes is ignored. +---------------------------------------------------------------------------------------- +*/ + +params.genomes = [:] diff --git a/conf/modules.config b/conf/modules.config index d203d2b6..d266a387 100644 --- a/conf/modules.config +++ b/conf/modules.config @@ -21,7 +21,6 @@ process { withName: FASTQC { ext.args = '--quiet' } - withName: 'MULTIQC' { ext.args = { params.multiqc_title ? "--title \"$params.multiqc_title\"" : '' } publishDir = [ diff --git a/conf/test.config b/conf/test.config index 6a7b9846..1838ae52 100644 --- a/conf/test.config +++ b/conf/test.config @@ -10,15 +10,18 @@ ---------------------------------------------------------------------------------------- */ +process { + resourceLimits = [ + cpus: 4, + memory: '15.GB', + time: '1.h' + ] +} + params { config_profile_name = 'Test profile' config_profile_description = 'Minimal test dataset to check pipeline function' - // Limit resources so that this can run on GitHub Actions - max_cpus = 2 - max_memory = '6.GB' - max_time = '6.h' - // Input data // TODO nf-core: Specify the paths to your test data on nf-core/test-datasets // TODO nf-core: Give any required params for the test so that command line flags are not needed diff --git a/docs/images/mqc_fastqc_adapter.png b/docs/images/mqc_fastqc_adapter.png deleted file mode 100755 index 361d0e47acfb424dea1f326590d1eb2f6dfa26b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23458 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 diff --git a/docs/images/mqc_fastqc_quality.png b/docs/images/mqc_fastqc_quality.png deleted file mode 100755 index a4b89bf56ab2ba88cab87841916eb680a816deae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55769 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�)v~C(0C)c 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 M)78&qol`;+0Fu7JivR!s diff --git a/docs/output.md b/docs/output.md index 77e4cc1c..9184bd0a 100644 --- a/docs/output.md +++ b/docs/output.md @@ -14,6 +14,7 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d - [FastQC](#fastqc) - Raw read QC - [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline + - [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution ### FastQC @@ -29,16 +30,6 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d [FastQC](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/) gives general quality metrics about your sequenced reads. It provides information about the quality score distribution across your reads, per base sequence content (%A/T/G/C), adapter contamination and overrepresented sequences. For further reading and documentation see the [FastQC help pages](http://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/). -![MultiQC - FastQC sequence counts plot](images/mqc_fastqc_counts.png) - -![MultiQC - FastQC mean quality scores plot](images/mqc_fastqc_quality.png) - -![MultiQC - FastQC adapter content plot](images/mqc_fastqc_adapter.png) - -:::note -The FastQC plots displayed in the MultiQC report shows _untrimmed_ reads. They may contain adapter sequence and potentially regions with low quality. -::: - ### MultiQC
    diff --git a/docs/usage.md b/docs/usage.md index ec180e49..bbc141ef 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -85,9 +85,9 @@ The above pipeline run specified with a params file in yaml format: nextflow run nf-core/seqinspector -profile docker -params-file params.yaml ``` -with `params.yaml` containing: +with: -```yaml +```yaml title="params.yaml" input: './samplesheet.csv' outdir: './results/' genome: 'GRCh37' @@ -199,14 +199,6 @@ See the main [Nextflow documentation](https://www.nextflow.io/docs/latest/config If you have any questions or issues please send us a message on [Slack](https://nf-co.re/join/slack) on the [`#configs` channel](https://nfcore.slack.com/channels/configs). -## Azure Resource Requests - -To be used with the `azurebatch` profile by specifying the `-profile azurebatch`. -We recommend providing a compute `params.vm_type` of `Standard_D16_v3` VMs by default but these options can be changed if required. - -Note that the choice of VM size depends on your quota and the overall workload during the analysis. -For a thorough list, please refer the [Azure Sizes for virtual machines in Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/sizes). - ## Running in the background Nextflow handles job submissions and supervises the running jobs. The Nextflow process must run until the pipeline is finished. diff --git a/main.nf b/main.nf index 2e9b1e37..df5d2162 100644 --- a/main.nf +++ b/main.nf @@ -9,8 +9,6 @@ ---------------------------------------------------------------------------------------- */ -nextflow.enable.dsl = 2 - /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ IMPORT FUNCTIONS / MODULES / SUBWORKFLOWS / WORKFLOWS @@ -20,7 +18,6 @@ nextflow.enable.dsl = 2 include { SEQINSPECTOR } from './workflows/seqinspector' include { PIPELINE_INITIALISATION } from './subworkflows/local/utils_nfcore_seqinspector_pipeline' include { PIPELINE_COMPLETION } from './subworkflows/local/utils_nfcore_seqinspector_pipeline' - include { getGenomeAttribute } from './subworkflows/local/utils_nfcore_seqinspector_pipeline' /* @@ -56,10 +53,8 @@ workflow NFCORE_SEQINSPECTOR { SEQINSPECTOR ( samplesheet ) - emit: multiqc_report = SEQINSPECTOR.out.multiqc_report // channel: /path/to/multiqc_report.html - } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -70,27 +65,24 @@ workflow NFCORE_SEQINSPECTOR { workflow { main: - // // SUBWORKFLOW: Run initialisation tasks // PIPELINE_INITIALISATION ( params.version, - params.help, params.validate_params, params.monochrome_logs, args, params.outdir, params.input ) - + // // WORKFLOW: Run main workflow // NFCORE_SEQINSPECTOR ( PIPELINE_INITIALISATION.out.samplesheet ) - // // SUBWORKFLOW: Run completion tasks // diff --git a/modules.json b/modules.json index 87fe816c..0ab8a797 100644 --- a/modules.json +++ b/modules.json @@ -7,12 +7,12 @@ "nf-core": { "fastqc": { "branch": "master", - "git_sha": "285a50500f9e02578d90b3ce6382ea3c30216acd", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "b7ebe95761cd389603f9cc0e0dc384c0f663815a", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] } } @@ -21,17 +21,17 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", + "git_sha": "d20fb2a9cc3e2835e9d067d1046a63252eb17352", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "92de218a329bfc9a9033116eb5f65fd270e72ba3", + "git_sha": "2fdce49d30c0254f76bc0f13c55c17455c1251ab", "installed_by": ["subworkflows"] }, - "utils_nfvalidation_plugin": { + "utils_nfschema_plugin": { "branch": "master", - "git_sha": "5caf7640a9ef1d18d765d55339be751bb0969dfa", + "git_sha": "bbd5a41f4535a8defafe6080e00ea74c45f4f96c", "installed_by": ["subworkflows"] } } diff --git a/modules/nf-core/fastqc/environment.yml b/modules/nf-core/fastqc/environment.yml index 1787b38a..691d4c76 100644 --- a/modules/nf-core/fastqc/environment.yml +++ b/modules/nf-core/fastqc/environment.yml @@ -1,7 +1,5 @@ -name: fastqc channels: - conda-forge - bioconda - - defaults dependencies: - bioconda::fastqc=0.12.1 diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index d79f1c86..d8989f48 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -26,7 +26,10 @@ process FASTQC { def rename_to = old_new_pairs*.join(' ').join(' ') def renamed_files = old_new_pairs.collect{ old_name, new_name -> new_name }.join(' ') - def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') + // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) + // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 + // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label + def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') / task.cpus // FastQC memory value allowed range (100 - 10000) def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml index ee5507e0..4827da7a 100644 --- a/modules/nf-core/fastqc/meta.yml +++ b/modules/nf-core/fastqc/meta.yml @@ -16,35 +16,44 @@ tools: homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ licence: ["GPL-2.0-only"] + identifier: biotools:fastqc input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - html: - type: file - description: FastQC report - pattern: "*_{fastqc.html}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.html": + type: file + description: FastQC report + pattern: "*_{fastqc.html}" - zip: - type: file - description: FastQC report archive - pattern: "*_{fastqc.zip}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.zip": + type: file + description: FastQC report archive + pattern: "*_{fastqc.zip}" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@drpatelh" - "@grst" diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test index 70edae4d..e9d79a07 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ b/modules/nf-core/fastqc/tests/main.nf.test @@ -23,17 +23,14 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. - // looks like this:
    Mon 2 Oct 2023
    test.gz
    - // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_single") } + { assert process.success }, + // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. + // looks like this:
    Mon 2 Oct 2023
    test.gz
    + // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -54,16 +51,14 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_paired") } + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -83,13 +78,11 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_interleaved") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -109,13 +102,11 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_bam") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -138,22 +129,20 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, - { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, - { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_multiple") } + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, + { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, + { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -173,21 +162,18 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_custom_prefix") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } test("sarscov2 single-end [fastq] - stub") { - options "-stub" - + options "-stub" when { process { """ @@ -201,12 +187,123 @@ nextflow_process { then { assertAll ( - { assert process.success }, - { assert snapshot(process.out.html.collect { file(it[1]).getName() } + - process.out.zip.collect { file(it[1]).getName() } + - process.out.versions ).match("fastqc_stub") } + { assert process.success }, + { assert snapshot(process.out).match() } ) } } + test("sarscov2 paired-end [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 interleaved [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 paired-end [bam] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 multiple [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 custom_prefix - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'mysample', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } } diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap index 86f7c311..d5db3092 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ b/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -1,88 +1,392 @@ { - "fastqc_versions_interleaved": { + "sarscov2 custom_prefix": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:07.293713" + "timestamp": "2024-07-22T11:02:16.374038" }, - "fastqc_stub": { + "sarscov2 single-end [fastq] - stub": { "content": [ - [ - "test.html", - "test.zip", - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:24.993809" + }, + "sarscov2 custom_prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:31:01.425198" + "timestamp": "2024-07-22T11:03:10.93942" }, - "fastqc_versions_multiple": { + "sarscov2 interleaved [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:55.797907" + "timestamp": "2024-07-22T11:01:42.355718" }, - "fastqc_versions_bam": { + "sarscov2 paired-end [bam]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:26.795862" + "timestamp": "2024-07-22T11:01:53.276274" }, - "fastqc_versions_single": { + "sarscov2 multiple [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:39:27.043675" + "timestamp": "2024-07-22T11:02:05.527626" }, - "fastqc_versions_paired": { + "sarscov2 paired-end [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:01:31.188871" + }, + "sarscov2 paired-end [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:34.273566" + }, + "sarscov2 multiple [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:39:47.584191" + "timestamp": "2024-07-22T11:03:02.304411" }, - "fastqc_versions_custom_prefix": { + "sarscov2 single-end [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:01:19.095607" + }, + "sarscov2 interleaved [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:44.640184" + }, + "sarscov2 paired-end [bam] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:41:14.576531" + "timestamp": "2024-07-22T11:02:53.550742" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index ca39fb67..f1cd99b0 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -1,7 +1,5 @@ -name: multiqc channels: - conda-forge - bioconda - - defaults dependencies: - - bioconda::multiqc=1.21 + - bioconda::multiqc=1.24.1 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 47ac352f..b9ccebdb 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,14 +3,16 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.21--pyhdfd78af_0' : - 'biocontainers/multiqc:1.21--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.25--pyhdfd78af_0' : + 'biocontainers/multiqc:1.25--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" path(multiqc_config) path(extra_multiqc_config) path(multiqc_logo) + path(replace_names) + path(sample_names) output: path "*multiqc_report.html", emit: report @@ -23,16 +25,22 @@ process MULTIQC { script: def args = task.ext.args ?: '' + def prefix = task.ext.prefix ? "--filename ${task.ext.prefix}.html" : '' def config = multiqc_config ? "--config $multiqc_config" : '' def extra_config = extra_multiqc_config ? "--config $extra_multiqc_config" : '' - def logo = multiqc_logo ? /--cl-config 'custom_logo: "${multiqc_logo}"'/ : '' + def logo = multiqc_logo ? "--cl-config 'custom_logo: \"${multiqc_logo}\"'" : '' + def replace = replace_names ? "--replace-names ${replace_names}" : '' + def samples = sample_names ? "--sample-names ${sample_names}" : '' """ multiqc \\ --force \\ $args \\ $config \\ + $prefix \\ $extra_config \\ $logo \\ + $replace \\ + $samples \\ . cat <<-END_VERSIONS > versions.yml diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 45a9bc35..b16c1879 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,5 +1,6 @@ name: multiqc -description: Aggregate results from bioinformatics analyses across many samples into a single report +description: Aggregate results from bioinformatics analyses across many samples into + a single report keywords: - QC - bioinformatics tools @@ -12,40 +13,59 @@ tools: homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ licence: ["GPL-3.0-or-later"] + identifier: biotools:multiqc input: - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections in multiqc_config. - pattern: "*.{yml,yaml}" - - multiqc_logo: - type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" + - - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + - - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + - - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections + in multiqc_config. + pattern: "*.{yml,yaml}" + - - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + - - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + - - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" output: - report: - type: file - description: MultiQC report file - pattern: "multiqc_report.html" + - "*multiqc_report.html": + type: file + description: MultiQC report file + pattern: "multiqc_report.html" - data: - type: directory - description: MultiQC data dir - pattern: "multiqc_data" + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" - plots: - type: file - description: Plots created by MultiQC - pattern: "*_data" + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_data" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" diff --git a/modules/nf-core/multiqc/tests/main.nf.test b/modules/nf-core/multiqc/tests/main.nf.test index f1c4242e..33316a7d 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test +++ b/modules/nf-core/multiqc/tests/main.nf.test @@ -8,6 +8,8 @@ nextflow_process { tag "modules_nfcore" tag "multiqc" + config "./nextflow.config" + test("sarscov2 single-end [fastqc]") { when { @@ -17,6 +19,8 @@ nextflow_process { input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -41,6 +45,8 @@ nextflow_process { input[1] = Channel.of(file("https://github.com/nf-core/tools/raw/dev/nf_core/pipeline-template/assets/multiqc_config.yml", checkIfExists: true)) input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } @@ -66,6 +72,8 @@ nextflow_process { input[1] = [] input[2] = [] input[3] = [] + input[4] = [] + input[5] = [] """ } } diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index bfebd802..b779e469 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:48:55.657331" + "timestamp": "2024-07-10T12:41:34.562023" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:49:49.071937" + "timestamp": "2024-07-10T11:27:11.933869532" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,21f35ee29416b9b3073c28733efe4b7d" + "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" ] ], "meta": { "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nextflow": "24.04.2" }, - "timestamp": "2024-02-29T08:49:25.457567" + "timestamp": "2024-07-10T11:26:56.709849369" } -} \ No newline at end of file +} diff --git a/modules/nf-core/multiqc/tests/nextflow.config b/modules/nf-core/multiqc/tests/nextflow.config new file mode 100644 index 00000000..c537a6a3 --- /dev/null +++ b/modules/nf-core/multiqc/tests/nextflow.config @@ -0,0 +1,5 @@ +process { + withName: 'MULTIQC' { + ext.prefix = null + } +} diff --git a/nextflow.config b/nextflow.config index a73f66ba..90df1661 100644 --- a/nextflow.config +++ b/nextflow.config @@ -16,7 +16,6 @@ params { genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false - // MultiQC options multiqc_config = null multiqc_title = null @@ -33,48 +32,26 @@ params { monochrome_logs = false hook_url = null help = false + help_full = false + show_hidden = false version = false pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' - // Config options config_profile_name = null config_profile_description = null + custom_config_version = 'master' custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" config_profile_contact = null config_profile_url = null - - // Max resource options - // Defaults only, expecting to be overwritten - max_memory = '128.GB' - max_cpus = 16 - max_time = '240.h' - // Schema validation default options - validationFailUnrecognisedParams = false - validationLenientMode = false - validationSchemaIgnoreParams = 'genomes,igenomes_base' - validationShowHiddenParams = false - validate_params = true - + validate_params = true + } // Load base.config by default for all pipelines includeConfig 'conf/base.config' -// Load nf-core custom profiles from different Institutions -try { - includeConfig "${params.custom_config_base}/nfcore_custom.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config profiles: ${params.custom_config_base}/nfcore_custom.config") -} - -// Load nf-core/seqinspector custom profiles from different institutions. -try { - includeConfig "${params.custom_config_base}/pipeline/seqinspector.config" -} catch (Exception e) { - System.err.println("WARNING: Could not load nf-core/config/seqinspector profiles: ${params.custom_config_base}/pipeline/seqinspector.config") -} profiles { debug { dumpHashes = true @@ -89,7 +66,7 @@ profiles { podman.enabled = false shifter.enabled = false charliecloud.enabled = false - conda.channels = ['conda-forge', 'bioconda', 'defaults'] + conda.channels = ['conda-forge', 'bioconda'] apptainer.enabled = false } mamba { @@ -178,25 +155,23 @@ profiles { test_full { includeConfig 'conf/test_full.config' } } -// Set default registry for Apptainer, Docker, Podman and Singularity independent of -profile -// Will not be used unless Apptainer / Docker / Podman / Singularity are enabled -// Set to your registry if you have a mirror of containers -apptainer.registry = 'quay.io' -docker.registry = 'quay.io' -podman.registry = 'quay.io' -singularity.registry = 'quay.io' +// Load nf-core custom profiles from different Institutions +includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/nfcore_custom.config" : "/dev/null" -// Nextflow plugins -plugins { - id 'nf-validation@1.1.3' // Validation of pipeline parameters and creation of an input channel from a sample sheet -} +// Load nf-core/seqinspector custom profiles from different institutions. +// TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs +// includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/seqinspector.config" : "/dev/null" +// Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile +// Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled +// Set to your registry if you have a mirror of containers +apptainer.registry = 'quay.io' +docker.registry = 'quay.io' +podman.registry = 'quay.io' +singularity.registry = 'quay.io' +charliecloud.registry = 'quay.io' // Load igenomes.config if required -if (!params.igenomes_ignore) { - includeConfig 'conf/igenomes.config' -} else { - params.genomes = [:] -} +includeConfig !params.igenomes_ignore ? 'conf/igenomes.config' : 'conf/igenomes_ignored.config' // Export these variables to prevent local Python/R libraries from conflicting with those in the container // The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. // See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. @@ -208,8 +183,15 @@ env { JULIA_DEPOT_PATH = "/usr/local/share/julia" } -// Capture exit codes from upstream processes when piping -process.shell = ['/bin/bash', '-euo', 'pipefail'] +// Set bash options +process.shell = """\ +bash + +set -e # Exit if a tool returns a non-zero status/exit code +set -u # Treat unset variables and parameters as an error +set -o pipefail # Returns the status of the last command to exit with a non-zero status or zero if all successfully execute +set -C # No clobber - prevent output redirection from overwriting files. +""" // Disable process selector warnings by default. Use debug profile to enable warnings. nextflow.enable.configProcessNamesValidation = false @@ -238,43 +220,47 @@ manifest { homePage = 'https://github.com/nf-core/seqinspector' description = """Pipeline to QC your sequences""" mainScript = 'main.nf' - nextflowVersion = '!>=23.04.0' + nextflowVersion = '!>=24.04.2' version = '1.0dev' doi = '' } -// Load modules.config for DSL2 module specific options -includeConfig 'conf/modules.config' +// Nextflow plugins +plugins { + id 'nf-schema@2.1.1' // Validation of pipeline parameters and creation of an input channel from a sample sheet +} + +validation { + defaultIgnoreParams = ["genomes"] + help { + enabled = true + command = "nextflow run $manifest.name -profile --input samplesheet.csv --outdir " + fullParameter = "help_full" + showHiddenParameter = "show_hidden" + beforeText = """ +-\033[2m----------------------------------------------------\033[0m- + \033[0;32m,--.\033[0;30m/\033[0;32m,-.\033[0m +\033[0;34m ___ __ __ __ ___ \033[0;32m/,-._.--~\'\033[0m +\033[0;34m |\\ | |__ __ / ` / \\ |__) |__ \033[0;33m} {\033[0m +\033[0;34m | \\| | \\__, \\__/ | \\ |___ \033[0;32m\\`-._,-`-,\033[0m + \033[0;32m`._,._,\'\033[0m +\033[0;35m ${manifest.name} ${manifest.version}\033[0m +-\033[2m----------------------------------------------------\033[0m- +""" + afterText = """${manifest.doi ? "* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${manifest.doi ? "\n" : ""} +* The nf-core framework + https://doi.org/10.1038/s41587-020-0439-x -// Function to ensure that resource requirements don't go beyond -// a maximum limit -def check_max(obj, type) { - if (type == 'memory') { - try { - if (obj.compareTo(params.max_memory as nextflow.util.MemoryUnit) == 1) - return params.max_memory as nextflow.util.MemoryUnit - else - return obj - } catch (all) { - println " ### ERROR ### Max memory '${params.max_memory}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'time') { - try { - if (obj.compareTo(params.max_time as nextflow.util.Duration) == 1) - return params.max_time as nextflow.util.Duration - else - return obj - } catch (all) { - println " ### ERROR ### Max time '${params.max_time}' is not valid! Using default value: $obj" - return obj - } - } else if (type == 'cpus') { - try { - return Math.min( obj, params.max_cpus as int ) - } catch (all) { - println " ### ERROR ### Max cpus '${params.max_cpus}' is not valid! Using default value: $obj" - return obj - } +* Software dependencies + https://github.com/${manifest.name}/blob/master/CITATIONS.md +""" + } + summary { + beforeText = validation.help.beforeText + afterText = validation.help.afterText } } + +// Load modules.config for DSL2 module specific options +includeConfig 'conf/modules.config' + diff --git a/nextflow_schema.json b/nextflow_schema.json index 36308a0d..88fd607b 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -1,10 +1,10 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/nf-core/seqinspector/master/nextflow_schema.json", "title": "nf-core/seqinspector pipeline parameters", "description": "Pipeline to QC your sequences", "type": "object", - "definitions": { + "$defs": { "input_output_options": { "title": "Input/output options", "type": "object", @@ -71,6 +71,14 @@ "fa_icon": "fas fa-ban", "hidden": true, "help_text": "Do not load `igenomes.config` when running the pipeline. You may choose this option if you observe clashes between custom parameters and those supplied in `igenomes.config`." + }, + "igenomes_base": { + "type": "string", + "format": "directory-path", + "description": "The base path to the igenomes reference files", + "fa_icon": "fas fa-ban", + "hidden": true, + "default": "s3://ngi-igenomes/igenomes/" } } }, @@ -122,41 +130,6 @@ } } }, - "max_job_request_options": { - "title": "Max job request options", - "type": "object", - "fa_icon": "fab fa-acquisitions-incorporated", - "description": "Set the top limit for requested resources for any single job.", - "help_text": "If you are running on a smaller system, a pipeline step requesting more resources than are available may cause the Nextflow to stop the run with an error. These options allow you to cap the maximum resources requested by any single job so that the pipeline will run on your system.\n\nNote that you can not _increase_ the resources requested by any job using these options. For that you will need your own configuration file. See [the nf-core website](https://nf-co.re/usage/configuration) for details.", - "properties": { - "max_cpus": { - "type": "integer", - "description": "Maximum number of CPUs that can be requested for any single job.", - "default": 16, - "fa_icon": "fas fa-microchip", - "hidden": true, - "help_text": "Use to set an upper-limit for the CPU requirement for each process. Should be an integer e.g. `--max_cpus 1`" - }, - "max_memory": { - "type": "string", - "description": "Maximum amount of memory that can be requested for any single job.", - "default": "128.GB", - "fa_icon": "fas fa-memory", - "pattern": "^\\d+(\\.\\d+)?\\.?\\s*(K|M|G|T)?B$", - "hidden": true, - "help_text": "Use to set an upper-limit for the memory requirement for each process. Should be a string in the format integer-unit e.g. `--max_memory '8.GB'`" - }, - "max_time": { - "type": "string", - "description": "Maximum amount of time that can be requested for any single job.", - "default": "240.h", - "fa_icon": "far fa-clock", - "pattern": "^(\\d+\\.?\\s*(s|m|h|d|day)\\s*)+$", - "hidden": true, - "help_text": "Use to set an upper-limit for the time requirement for each process. Should be a string in the format integer-unit e.g. `--max_time '2.h'`" - } - } - }, "generic_options": { "title": "Generic options", "type": "object", @@ -164,12 +137,6 @@ "description": "Less common options for the pipeline, typically set in a config file.", "help_text": "These options are common to all nf-core pipelines and allow you to customise some of the core preferences for how the pipeline runs.\n\nTypically these options would be set in a Nextflow config file loaded for all pipeline runs, such as `~/.nextflow/config`.", "properties": { - "help": { - "type": "boolean", - "description": "Display help text.", - "fa_icon": "fas fa-question-circle", - "hidden": true - }, "version": { "type": "boolean", "description": "Display version and exit.", @@ -245,27 +212,6 @@ "fa_icon": "fas fa-check-square", "hidden": true }, - "validationShowHiddenParams": { - "type": "boolean", - "fa_icon": "far fa-eye-slash", - "description": "Show all params when using `--help`", - "hidden": true, - "help_text": "By default, parameters set as _hidden_ in the schema are not shown on the command line when a user runs with `--help`. Specifying this option will tell the pipeline to show all parameters." - }, - "validationFailUnrecognisedParams": { - "type": "boolean", - "fa_icon": "far fa-check-circle", - "description": "Validation of parameters fails when an unrecognised parameter is found.", - "hidden": true, - "help_text": "By default, when an unrecognised parameter is found, it returns a warinig." - }, - "validationLenientMode": { - "type": "boolean", - "fa_icon": "far fa-check-circle", - "description": "Validation of parameters in lenient more.", - "hidden": true, - "help_text": "Allows string values that are parseable as numbers or booleans. For further information see [JSONSchema docs](https://github.com/everit-org/json-schema#lenient-mode)." - }, "pipelines_testdata_base_path": { "type": "string", "fa_icon": "far fa-check-circle", @@ -278,19 +224,16 @@ }, "allOf": [ { - "$ref": "#/definitions/input_output_options" - }, - { - "$ref": "#/definitions/reference_genome_options" + "$ref": "#/$defs/input_output_options" }, { - "$ref": "#/definitions/institutional_config_options" + "$ref": "#/$defs/reference_genome_options" }, { - "$ref": "#/definitions/max_job_request_options" + "$ref": "#/$defs/institutional_config_options" }, { - "$ref": "#/definitions/generic_options" + "$ref": "#/$defs/generic_options" } ] } diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index 87281414..2571a5f5 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -8,17 +8,14 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -include { UTILS_NFVALIDATION_PLUGIN } from '../../nf-core/utils_nfvalidation_plugin' -include { paramsSummaryMap } from 'plugin/nf-validation' -include { fromSamplesheet } from 'plugin/nf-validation' -include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' +include { UTILS_NFSCHEMA_PLUGIN } from '../../nf-core/utils_nfschema_plugin' +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 { dashedLine } from '../../nf-core/utils_nfcore_pipeline' -include { nfCoreLogo } from '../../nf-core/utils_nfcore_pipeline' include { imNotification } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' -include { workflowCitation } from '../../nf-core/utils_nfcore_pipeline' +include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' /* ======================================================================================== @@ -30,7 +27,6 @@ workflow PIPELINE_INITIALISATION { take: version // boolean: Display version and exit - help // boolean: Display help text validate_params // boolean: Boolean whether to validate parameters against the schema at runtime monochrome_logs // boolean: Do not use coloured log outputs nextflow_cli_args // array: List of positional nextflow CLI args @@ -51,20 +47,16 @@ workflow PIPELINE_INITIALISATION { workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 ) + // // Validate parameters and generate parameter summary to stdout // - pre_help_text = nfCoreLogo(monochrome_logs) - post_help_text = '\n' + workflowCitation() + '\n' + dashedLine(monochrome_logs) - def String workflow_command = "nextflow run ${workflow.manifest.name} -profile --input samplesheet.csv --outdir " - UTILS_NFVALIDATION_PLUGIN ( - help, - workflow_command, - pre_help_text, - post_help_text, + UTILS_NFSCHEMA_PLUGIN ( + workflow, validate_params, - "nextflow_schema.json" + null ) + // // Check config provided to the pipeline @@ -80,8 +72,9 @@ workflow PIPELINE_INITIALISATION { // // Create channel from input file provided through params.input // + Channel - .fromSamplesheet("input") + .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) .map { meta, fastq_1, fastq_2 -> if (!fastq_2) { @@ -91,8 +84,8 @@ workflow PIPELINE_INITIALISATION { } } .groupTuple() - .map { - validateInputSamplesheet(it) + .map { samplesheet -> + validateInputSamplesheet(samplesheet) } .map { meta, fastqs -> @@ -117,13 +110,13 @@ workflow PIPELINE_COMPLETION { email // string: email address email_on_fail // string: email address sent on pipeline failure plaintext_email // boolean: Send plain-text email instead of HTML + outdir // path: Path to output directory where results will be published monochrome_logs // boolean: Disable ANSI colour codes in log output hook_url // string: hook URL for notifications multiqc_report // string: Path to MultiQC report main: - summary_params = paramsSummaryMap(workflow, parameters_schema: "nextflow_schema.json") // @@ -131,11 +124,18 @@ workflow PIPELINE_COMPLETION { // workflow.onComplete { if (email || email_on_fail) { - completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs, multiqc_report.toList()) + completionEmail( + summary_params, + email, + email_on_fail, + plaintext_email, + outdir, + monochrome_logs, + multiqc_report.toList() + ) } completionSummary(monochrome_logs) - if (hook_url) { imNotification(summary_params, hook_url) } @@ -165,7 +165,7 @@ def validateInputSamplesheet(input) { def (metas, fastqs) = input[1..2] // Check that multiple runs of the same sample are of the same datatype i.e. single-end / paired-end - def endedness_ok = metas.collect{ it.single_end }.unique().size == 1 + def endedness_ok = metas.collect{ meta -> meta.single_end }.unique().size == 1 if (!endedness_ok) { error("Please check input samplesheet -> Multiple runs of a sample must be of the same datatype i.e. single-end or paired-end: ${metas[0].id}") } @@ -197,7 +197,6 @@ def genomeExistsError() { error(error_string) } } - // // Generate methods description for MultiQC // @@ -239,8 +238,10 @@ def methodsDescriptionText(mqc_methods_yaml) { // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers // Removing ` ` since the manifest.doi is a string and not a proper list def temp_doi_ref = "" - String[] manifest_doi = meta.manifest_map.doi.tokenize(",") - for (String doi_ref: manifest_doi) temp_doi_ref += "(doi:
    ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " + def manifest_doi = meta.manifest_map.doi.tokenize(",") + manifest_doi.each { doi_ref -> + temp_doi_ref += "(doi: ${doi_ref.replace("https://doi.org/", "").replace(" ", "")}), " + } meta["doi_text"] = temp_doi_ref.substring(0, temp_doi_ref.length() - 2) } else meta["doi_text"] = "" meta["nodoi_text"] = meta.manifest_map.doi ? "" : "
  • If available, make sure to update the text to include the Zenodo DOI of version of the pipeline used.
  • " @@ -261,3 +262,4 @@ def methodsDescriptionText(mqc_methods_yaml) { return description_html.toString() } + diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf index ac31f28f..28e32b20 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -2,10 +2,6 @@ // Subworkflow with functionality that may be useful for any Nextflow pipeline // -import org.yaml.snakeyaml.Yaml -import groovy.json.JsonOutput -import nextflow.extension.FilesEx - /* ======================================================================================== SUBWORKFLOW DEFINITION @@ -58,7 +54,7 @@ workflow UTILS_NEXTFLOW_PIPELINE { // Generate version string // def getWorkflowVersion() { - String version_string = "" + def version_string = "" as String if (workflow.manifest.version) { def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' version_string += "${prefix_v}${workflow.manifest.version}" @@ -79,10 +75,10 @@ def dumpParametersToJSON(outdir) { def timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') def filename = "params_${timestamp}.json" def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") - def jsonStr = JsonOutput.toJson(params) - temp_pf.text = JsonOutput.prettyPrint(jsonStr) + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) - FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") + nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") temp_pf.delete() } @@ -90,7 +86,7 @@ def dumpParametersToJSON(outdir) { // When running with -profile conda, warn if channels have not been set-up appropriately // def checkCondaChannels() { - Yaml parser = new Yaml() + def parser = new org.yaml.snakeyaml.Yaml() def channels = [] try { def config = parser.load("conda config --show channels".execute().text) @@ -102,14 +98,16 @@ def checkCondaChannels() { // Check that all channels are present // This channel list is ordered by required channel priority. - def required_channels_in_order = ['conda-forge', 'bioconda', 'defaults'] + def required_channels_in_order = ['conda-forge', 'bioconda'] def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean // Check that they are in the right order def channel_priority_violation = false - def n = required_channels_in_order.size() - for (int i = 0; i < n - 1; i++) { - channel_priority_violation |= !(channels.indexOf(required_channels_in_order[i]) < channels.indexOf(required_channels_in_order[i+1])) + + required_channels_in_order.eachWithIndex { channel, index -> + if (index < required_channels_in_order.size() - 1) { + channel_priority_violation |= !(channels.indexOf(channel) < channels.indexOf(required_channels_in_order[index+1])) + } } if (channels_missing | channel_priority_violation) { diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config index d0a926bf..a09572e5 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config +++ b/subworkflows/nf-core/utils_nextflow_pipeline/tests/nextflow.config @@ -3,7 +3,7 @@ manifest { author = """nf-core""" homePage = 'https://127.0.0.1' description = """Dummy pipeline""" - nextflowVersion = '!>=23.04.0' + nextflowVersion = '!>=23.04.0' version = '9.9.9' doi = 'https://doi.org/10.5281/zenodo.5070524' } diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index 14558c39..cbd8495b 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -2,9 +2,6 @@ // Subworkflow with utility functions specific to the nf-core pipeline template // -import org.yaml.snakeyaml.Yaml -import nextflow.extension.FilesEx - /* ======================================================================================== SUBWORKFLOW DEFINITION @@ -34,7 +31,7 @@ workflow UTILS_NFCORE_PIPELINE { // Warn if a -profile or Nextflow config has not been provided to run the pipeline // def checkConfigProvided() { - valid_config = true + def valid_config = true as Boolean if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + @@ -66,11 +63,13 @@ def checkProfileProvided(nextflow_cli_args) { // def workflowCitation() { def temp_doi_ref = "" - String[] manifest_doi = workflow.manifest.doi.tokenize(",") + def manifest_doi = workflow.manifest.doi.tokenize(",") // Using a loop to handle multiple DOIs // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers // Removing ` ` since the manifest.doi is a string and not a proper list - for (String doi_ref: manifest_doi) temp_doi_ref += " https://doi.org/${doi_ref.replace('https://doi.org/', '').replace(' ', '')}\n" + manifest_doi.each { doi_ref -> + temp_doi_ref += " https://doi.org/${doi_ref.replace('https://doi.org/', '').replace(' ', '')}\n" + } return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + "* The pipeline\n" + temp_doi_ref + "\n" + @@ -84,7 +83,7 @@ def workflowCitation() { // Generate workflow version string // def getWorkflowVersion() { - String version_string = "" + def version_string = "" as String if (workflow.manifest.version) { def prefix_v = workflow.manifest.version[0] != 'v' ? 'v' : '' version_string += "${prefix_v}${workflow.manifest.version}" @@ -102,8 +101,8 @@ def getWorkflowVersion() { // Get software versions for pipeline // def processVersionsFromYAML(yaml_file) { - Yaml yaml = new Yaml() - versions = yaml.load(yaml_file).collectEntries { k, v -> [ k.tokenize(':')[-1], v ] } + def yaml = new org.yaml.snakeyaml.Yaml() + def versions = yaml.load(yaml_file).collectEntries { k, v -> [ k.tokenize(':')[-1], v ] } return yaml.dumpAsMap(versions).trim() } @@ -124,7 +123,7 @@ def workflowVersionToYAML() { def softwareVersionsToYAML(ch_versions) { return ch_versions .unique() - .map { processVersionsFromYAML(it) } + .map { version -> processVersionsFromYAML(version) } .unique() .mix(Channel.of(workflowVersionToYAML())) } @@ -134,19 +133,19 @@ def softwareVersionsToYAML(ch_versions) { // def paramsSummaryMultiqc(summary_params) { def summary_section = '' - for (group in summary_params.keySet()) { + summary_params.keySet().each { group -> def group_params = summary_params.get(group) // This gets the parameters of that particular group if (group_params) { summary_section += "

    $group

    \n" summary_section += "
    \n" - for (param in group_params.keySet()) { + group_params.keySet().sort().each { param -> summary_section += "
    $param
    ${group_params.get(param) ?: 'N/A'}
    \n" } summary_section += "
    \n" } } - String yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" + def yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" as String yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" @@ -161,7 +160,7 @@ def paramsSummaryMultiqc(summary_params) { // nf-core logo // def nfCoreLogo(monochrome_logs=true) { - Map colors = logColours(monochrome_logs) + def colors = logColours(monochrome_logs) as Map String.format( """\n ${dashedLine(monochrome_logs)} @@ -180,7 +179,7 @@ def nfCoreLogo(monochrome_logs=true) { // Return dashed line // def dashedLine(monochrome_logs=true) { - Map colors = logColours(monochrome_logs) + def colors = logColours(monochrome_logs) as Map return "-${colors.dim}----------------------------------------------------${colors.reset}-" } @@ -188,7 +187,7 @@ def dashedLine(monochrome_logs=true) { // ANSII colours used for terminal logging // def logColours(monochrome_logs=true) { - Map colorcodes = [:] + def colorcodes = [:] as Map // Reset / Meta colorcodes['reset'] = monochrome_logs ? '' : "\033[0m" @@ -287,7 +286,7 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi } def summary = [:] - for (group in summary_params.keySet()) { + summary_params.keySet().sort().each { group -> summary << summary_params[group] } @@ -344,10 +343,10 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi def sendmail_html = sendmail_template.toString() // Send the HTML e-mail - Map colors = logColours(monochrome_logs) + def colors = logColours(monochrome_logs) as Map if (email_address) { try { - if (plaintext_email) { throw GroovyException('Send plaintext e-mail, not HTML') } + if (plaintext_email) { throw new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') } // Try to send HTML e-mail using sendmail def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") sendmail_tf.withWriter { w -> w << sendmail_html } @@ -364,13 +363,13 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi // Write summary e-mail HTML to a file def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") output_hf.withWriter { w -> w << email_html } - FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html"); + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html"); output_hf.delete() // Write summary e-mail TXT to a file def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") output_tf.withWriter { w -> w << email_txt } - FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt"); + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt"); output_tf.delete() } @@ -378,7 +377,7 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi // Print pipeline summary on completion // def completionSummary(monochrome_logs=true) { - Map colors = logColours(monochrome_logs) + def colors = logColours(monochrome_logs) as Map if (workflow.success) { if (workflow.stats.ignoredCount == 0) { log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-" @@ -395,7 +394,7 @@ def completionSummary(monochrome_logs=true) { // def imNotification(summary_params, hook_url) { def summary = [:] - for (group in summary_params.keySet()) { + summary_params.keySet().sort().each { group -> summary << summary_params[group] } diff --git a/subworkflows/nf-core/utils_nfschema_plugin/main.nf b/subworkflows/nf-core/utils_nfschema_plugin/main.nf new file mode 100644 index 00000000..4994303e --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/main.nf @@ -0,0 +1,46 @@ +// +// Subworkflow that uses the nf-schema plugin to validate parameters and render the parameter summary +// + +include { paramsSummaryLog } from 'plugin/nf-schema' +include { validateParameters } from 'plugin/nf-schema' + +workflow UTILS_NFSCHEMA_PLUGIN { + + take: + input_workflow // workflow: the workflow object used by nf-schema to get metadata from the workflow + validate_params // boolean: validate the parameters + parameters_schema // string: path to the parameters JSON schema. + // this has to be the same as the schema given to `validation.parametersSchema` + // when this input is empty it will automatically use the configured schema or + // "${projectDir}/nextflow_schema.json" as default. This input should not be empty + // for meta pipelines + + main: + + // + // Print parameter summary to stdout. This will display the parameters + // that differ from the default given in the JSON schema + // + if(parameters_schema) { + log.info paramsSummaryLog(input_workflow, parameters_schema:parameters_schema) + } else { + log.info paramsSummaryLog(input_workflow) + } + + // + // Validate the parameters using nextflow_schema.json or the schema + // given via the validation.parametersSchema configuration option + // + if(validate_params) { + if(parameters_schema) { + validateParameters(parameters_schema:parameters_schema) + } else { + validateParameters() + } + } + + emit: + dummy_emit = true +} + diff --git a/subworkflows/nf-core/utils_nfschema_plugin/meta.yml b/subworkflows/nf-core/utils_nfschema_plugin/meta.yml new file mode 100644 index 00000000..f7d9f028 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/meta.yml @@ -0,0 +1,35 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json +name: "utils_nfschema_plugin" +description: Run nf-schema to validate parameters and create a summary of changed parameters +keywords: + - validation + - JSON schema + - plugin + - parameters + - summary +components: [] +input: + - input_workflow: + type: object + description: | + The workflow object of the used pipeline. + This object contains meta data used to create the params summary log + - validate_params: + type: boolean + description: Validate the parameters and error if invalid. + - parameters_schema: + type: string + description: | + Path to the parameters JSON schema. + This has to be the same as the schema given to the `validation.parametersSchema` config + option. When this input is empty it will automatically use the configured schema or + "${projectDir}/nextflow_schema.json" as default. The schema should not be given in this way + for meta pipelines. +output: + - dummy_emit: + type: boolean + description: Dummy emit to make nf-core subworkflows lint happy +authors: + - "@nvnieuwk" +maintainers: + - "@nvnieuwk" diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test new file mode 100644 index 00000000..842dc432 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/main.nf.test @@ -0,0 +1,117 @@ +nextflow_workflow { + + name "Test Subworkflow UTILS_NFSCHEMA_PLUGIN" + script "../main.nf" + workflow "UTILS_NFSCHEMA_PLUGIN" + + tag "subworkflows" + tag "subworkflows_nfcore" + tag "subworkflows/utils_nfschema_plugin" + tag "plugin/nf-schema" + + config "./nextflow.config" + + test("Should run nothing") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params") { + + when { + + params { + test_data = '' + outdir = 1 + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } + + test("Should run nothing - custom schema") { + + when { + + params { + test_data = '' + } + + workflow { + """ + validate_params = false + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.success } + ) + } + } + + test("Should validate params - custom schema") { + + when { + + params { + test_data = '' + outdir = 1 + } + + workflow { + """ + validate_params = true + input[0] = workflow + input[1] = validate_params + input[2] = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + """ + } + } + + then { + assertAll( + { assert workflow.failed }, + { assert workflow.stdout.any { it.contains('ERROR ~ Validation of pipeline parameters failed!') } } + ) + } + } +} diff --git a/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config new file mode 100644 index 00000000..0907ac58 --- /dev/null +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow.config @@ -0,0 +1,8 @@ +plugins { + id "nf-schema@2.1.0" +} + +validation { + parametersSchema = "${projectDir}/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json" + monochromeLogs = true +} \ No newline at end of file diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json similarity index 95% rename from subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json rename to subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json index 7626c1c9..331e0d2f 100644 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/nextflow_schema.json +++ b/subworkflows/nf-core/utils_nfschema_plugin/tests/nextflow_schema.json @@ -1,10 +1,10 @@ { - "$schema": "http://json-schema.org/draft-07/schema", + "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://raw.githubusercontent.com/./master/nextflow_schema.json", "title": ". pipeline parameters", "description": "", "type": "object", - "definitions": { + "$defs": { "input_output_options": { "title": "Input/output options", "type": "object", @@ -87,10 +87,10 @@ }, "allOf": [ { - "$ref": "#/definitions/input_output_options" + "$ref": "#/$defs/input_output_options" }, { - "$ref": "#/definitions/generic_options" + "$ref": "#/$defs/generic_options" } ] } diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf b/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf deleted file mode 100644 index 2585b65d..00000000 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/main.nf +++ /dev/null @@ -1,62 +0,0 @@ -// -// Subworkflow that uses the nf-validation plugin to render help text and parameter summary -// - -/* -======================================================================================== - IMPORT NF-VALIDATION PLUGIN -======================================================================================== -*/ - -include { paramsHelp } from 'plugin/nf-validation' -include { paramsSummaryLog } from 'plugin/nf-validation' -include { validateParameters } from 'plugin/nf-validation' - -/* -======================================================================================== - SUBWORKFLOW DEFINITION -======================================================================================== -*/ - -workflow UTILS_NFVALIDATION_PLUGIN { - - take: - print_help // boolean: print help - workflow_command // string: default commmand used to run pipeline - pre_help_text // string: string to be printed before help text and summary log - post_help_text // string: string to be printed after help text and summary log - validate_params // boolean: validate parameters - schema_filename // path: JSON schema file, null to use default value - - main: - - log.debug "Using schema file: ${schema_filename}" - - // Default values for strings - pre_help_text = pre_help_text ?: '' - post_help_text = post_help_text ?: '' - workflow_command = workflow_command ?: '' - - // - // Print help message if needed - // - if (print_help) { - log.info pre_help_text + paramsHelp(workflow_command, parameters_schema: schema_filename) + post_help_text - System.exit(0) - } - - // - // Print parameter summary to stdout - // - log.info pre_help_text + paramsSummaryLog(workflow, parameters_schema: schema_filename) + post_help_text - - // - // Validate parameters relative to the parameter JSON schema - // - if (validate_params){ - validateParameters(parameters_schema: schema_filename) - } - - emit: - dummy_emit = true -} diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml b/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml deleted file mode 100644 index 3d4a6b04..00000000 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/meta.yml +++ /dev/null @@ -1,44 +0,0 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/subworkflows/yaml-schema.json -name: "UTILS_NFVALIDATION_PLUGIN" -description: Use nf-validation to initiate and validate a pipeline -keywords: - - utility - - pipeline - - initialise - - validation -components: [] -input: - - print_help: - type: boolean - description: | - Print help message and exit - - workflow_command: - type: string - description: | - The command to run the workflow e.g. "nextflow run main.nf" - - pre_help_text: - type: string - description: | - Text to print before the help message - - post_help_text: - type: string - description: | - Text to print after the help message - - validate_params: - type: boolean - description: | - Validate the parameters and error if invalid. - - schema_filename: - type: string - description: | - The filename of the schema to validate against. -output: - - dummy_emit: - type: boolean - description: | - Dummy emit to make nf-core subworkflows lint happy -authors: - - "@adamrtalbot" -maintainers: - - "@adamrtalbot" - - "@maxulysse" diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test deleted file mode 100644 index 5784a33f..00000000 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/main.nf.test +++ /dev/null @@ -1,200 +0,0 @@ -nextflow_workflow { - - name "Test Workflow UTILS_NFVALIDATION_PLUGIN" - script "../main.nf" - workflow "UTILS_NFVALIDATION_PLUGIN" - tag "subworkflows" - tag "subworkflows_nfcore" - tag "plugin/nf-validation" - tag "'plugin/nf-validation'" - tag "utils_nfvalidation_plugin" - tag "subworkflows/utils_nfvalidation_plugin" - - test("Should run nothing") { - - when { - - params { - monochrome_logs = true - test_data = '' - } - - workflow { - """ - help = false - workflow_command = null - pre_help_text = null - post_help_text = null - validate_params = false - schema_filename = "$moduleTestDir/nextflow_schema.json" - - input[0] = help - input[1] = workflow_command - input[2] = pre_help_text - input[3] = post_help_text - input[4] = validate_params - input[5] = schema_filename - """ - } - } - - then { - assertAll( - { assert workflow.success } - ) - } - } - - test("Should run help") { - - - when { - - params { - monochrome_logs = true - test_data = '' - } - workflow { - """ - help = true - workflow_command = null - pre_help_text = null - post_help_text = null - validate_params = false - schema_filename = "$moduleTestDir/nextflow_schema.json" - - input[0] = help - input[1] = workflow_command - input[2] = pre_help_text - input[3] = post_help_text - input[4] = validate_params - input[5] = schema_filename - """ - } - } - - then { - assertAll( - { assert workflow.success }, - { assert workflow.exitStatus == 0 }, - { assert workflow.stdout.any { it.contains('Input/output options') } }, - { assert workflow.stdout.any { it.contains('--outdir') } } - ) - } - } - - test("Should run help with command") { - - when { - - params { - monochrome_logs = true - test_data = '' - } - workflow { - """ - help = true - workflow_command = "nextflow run noorg/doesntexist" - pre_help_text = null - post_help_text = null - validate_params = false - schema_filename = "$moduleTestDir/nextflow_schema.json" - - input[0] = help - input[1] = workflow_command - input[2] = pre_help_text - input[3] = post_help_text - input[4] = validate_params - input[5] = schema_filename - """ - } - } - - then { - assertAll( - { assert workflow.success }, - { assert workflow.exitStatus == 0 }, - { assert workflow.stdout.any { it.contains('nextflow run noorg/doesntexist') } }, - { assert workflow.stdout.any { it.contains('Input/output options') } }, - { assert workflow.stdout.any { it.contains('--outdir') } } - ) - } - } - - test("Should run help with extra text") { - - - when { - - params { - monochrome_logs = true - test_data = '' - } - workflow { - """ - help = true - workflow_command = "nextflow run noorg/doesntexist" - pre_help_text = "pre-help-text" - post_help_text = "post-help-text" - validate_params = false - schema_filename = "$moduleTestDir/nextflow_schema.json" - - input[0] = help - input[1] = workflow_command - input[2] = pre_help_text - input[3] = post_help_text - input[4] = validate_params - input[5] = schema_filename - """ - } - } - - then { - assertAll( - { assert workflow.success }, - { assert workflow.exitStatus == 0 }, - { assert workflow.stdout.any { it.contains('pre-help-text') } }, - { assert workflow.stdout.any { it.contains('nextflow run noorg/doesntexist') } }, - { assert workflow.stdout.any { it.contains('Input/output options') } }, - { assert workflow.stdout.any { it.contains('--outdir') } }, - { assert workflow.stdout.any { it.contains('post-help-text') } } - ) - } - } - - test("Should validate params") { - - when { - - params { - monochrome_logs = true - test_data = '' - outdir = 1 - } - workflow { - """ - help = false - workflow_command = null - pre_help_text = null - post_help_text = null - validate_params = true - schema_filename = "$moduleTestDir/nextflow_schema.json" - - input[0] = help - input[1] = workflow_command - input[2] = pre_help_text - input[3] = post_help_text - input[4] = validate_params - input[5] = schema_filename - """ - } - } - - then { - assertAll( - { assert workflow.failed }, - { assert workflow.stdout.any { it.contains('ERROR ~ ERROR: Validation of pipeline parameters failed!') } } - ) - } - } -} diff --git a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml b/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml deleted file mode 100644 index 60b1cfff..00000000 --- a/subworkflows/nf-core/utils_nfvalidation_plugin/tests/tags.yml +++ /dev/null @@ -1,2 +0,0 @@ -subworkflows/utils_nfvalidation_plugin: - - subworkflows/nf-core/utils_nfvalidation_plugin/** diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index cf7a85c4..af3719c2 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -3,10 +3,9 @@ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - include { FASTQC } from '../modules/nf-core/fastqc/main' include { MULTIQC } from '../modules/nf-core/multiqc/main' -include { paramsSummaryMap } from 'plugin/nf-validation' +include { paramsSummaryMap } from 'plugin/nf-schema' include { paramsSummaryMultiqc } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { softwareVersionsToYAML } from '../subworkflows/nf-core/utils_nfcore_pipeline' include { methodsDescriptionText } from '../subworkflows/local/utils_nfcore_seqinspector_pipeline' @@ -21,12 +20,10 @@ workflow SEQINSPECTOR { take: ch_samplesheet // channel: samplesheet read in from --input - main: ch_versions = Channel.empty() ch_multiqc_files = Channel.empty() - // // MODULE: Run FastQC // @@ -42,11 +39,12 @@ workflow SEQINSPECTOR { softwareVersionsToYAML(ch_versions) .collectFile( storeDir: "${params.outdir}/pipeline_info", - name: 'nf_core_pipeline_software_mqc_versions.yml', + name: 'nf_core_' + 'pipeline_software_' + 'mqc_' + 'versions.yml', sort: true, newLine: true ).set { ch_collated_versions } + // // MODULE: MultiQC // @@ -59,18 +57,19 @@ workflow SEQINSPECTOR { Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() + summary_params = paramsSummaryMap( workflow, parameters_schema: "nextflow_schema.json") ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) - + ch_multiqc_files = ch_multiqc_files.mix( + ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) + ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) ch_methods_description = Channel.value( methodsDescriptionText(ch_multiqc_custom_methods_description)) - ch_multiqc_files = ch_multiqc_files.mix( - ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) ch_multiqc_files = ch_multiqc_files.mix(ch_collated_versions) ch_multiqc_files = ch_multiqc_files.mix( ch_methods_description.collectFile( @@ -83,12 +82,14 @@ workflow SEQINSPECTOR { ch_multiqc_files.collect(), ch_multiqc_config.toList(), ch_multiqc_custom_config.toList(), - ch_multiqc_logo.toList() + ch_multiqc_logo.toList(), + [], + [] ) - emit: - multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html + emit:multiqc_report = MULTIQC.out.report.toList() // channel: /path/to/multiqc_report.html versions = ch_versions // channel: [ path(versions.yml) ] + } /* From d5d1a04b0b48531e74fa8e63f7a994f05dcd7cc8 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Wed, 9 Oct 2024 11:07:36 +0000 Subject: [PATCH 052/530] Template update for nf-core/tools version 3.0.1 --- .editorconfig | 4 - .github/CONTRIBUTING.md | 2 +- .github/workflows/awsfulltest.yml | 6 +- .github/workflows/linting.yml | 4 +- .nf-core.yml | 2 +- .prettierignore | 1 - docs/output.md | 1 - modules.json | 6 +- modules/nf-core/multiqc/environment.yml | 2 +- modules/nf-core/multiqc/main.nf | 4 +- .../nf-core/multiqc/tests/main.nf.test.snap | 26 +- nextflow.config | 8 +- .../main.nf | 12 +- .../nf-core/utils_nextflow_pipeline/main.nf | 46 ++- .../nf-core/utils_nfcore_pipeline/main.nf | 279 ++++++++++-------- 15 files changed, 209 insertions(+), 194 deletions(-) diff --git a/.editorconfig b/.editorconfig index e1058815..72dda289 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,6 @@ indent_style = space [*.{md,yml,yaml,html,css,scss,js}] indent_size = 2 - # These files are edited and tested upstream in nf-core/modules [/modules/nf-core/**] charset = unset @@ -26,12 +25,9 @@ insert_final_newline = unset trim_trailing_whitespace = unset indent_style = unset - - [/assets/email*] indent_size = unset - # ignore python and markdown [*.{py,md}] indent_style = unset diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 78893ac8..4bcb559a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -90,7 +90,7 @@ Once there, use `nf-core pipelines schema build` to add to `nextflow_schema.json ### Default processes resource requirements -Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/master/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. +Sensible defaults for process resource requirements (CPUs / memory / time) for a process should be defined in `conf/base.config`. These should generally be specified generic with `withLabel:` selectors so they can be shared across multiple processes/steps of the pipeline. A nf-core standard set of labels that should be followed where possible can be seen in the [nf-core pipeline template](https://github.com/nf-core/tools/blob/main/nf_core/pipeline-template/conf/base.config), which has the default process as a single core-process, and then different levels of multi-core configurations for increasingly large memory requirements defined with standardised labels. The process resources can be passed on to the tool dynamically within the process with the `${task.cpus}` and `${task.memory}` variables in the `script:` block. diff --git a/.github/workflows/awsfulltest.yml b/.github/workflows/awsfulltest.yml index c9462a57..5fdd2769 100644 --- a/.github/workflows/awsfulltest.yml +++ b/.github/workflows/awsfulltest.yml @@ -14,16 +14,18 @@ on: jobs: run-platform: name: Run AWS full tests - if: github.repository == 'nf-core/seqinspector' && github.event.review.state == 'approved' + # run only if the PR is approved by at least 2 reviewers and against the master branch or manually triggered + if: github.repository == 'nf-core/seqinspector' && github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'master' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - uses: octokit/request-action@v2.x id: check_approvals with: - route: GET /repos/${{ github.repository }}/pulls/${{ github.event.review.number }}/reviews + route: GET /repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - id: test_variables + if: github.event_name != 'workflow_dispatch' run: | JSON_RESPONSE='${{ steps.check_approvals.outputs.data }}' CURRENT_APPROVALS_COUNT=$(echo $JSON_RESPONSE | jq -c '[.[] | select(.state | contains("APPROVED")) ] | length') diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index b882838a..a502573c 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -42,10 +42,10 @@ jobs: architecture: "x64" - name: read .nf-core.yml - uses: pietrobolcato/action-read-yaml@1.0.0 + uses: pietrobolcato/action-read-yaml@1.1.0 id: read_yml with: - config: ${{ github.workspace }}/.nf-core.yaml + config: ${{ github.workspace }}/.nf-core.yml - name: Install dependencies run: | diff --git a/.nf-core.yml b/.nf-core.yml index ca26523b..4e856727 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,6 +1,6 @@ bump_version: null lint: null -nf_core_version: 3.0.0 +nf_core_version: 3.0.1 org_path: null repository_type: pipeline template: diff --git a/.prettierignore b/.prettierignore index 610e5069..437d763d 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ - email_template.html adaptivecard.json slackreport.json diff --git a/docs/output.md b/docs/output.md index 9184bd0a..e8665e59 100644 --- a/docs/output.md +++ b/docs/output.md @@ -14,7 +14,6 @@ The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes d - [FastQC](#fastqc) - Raw read QC - [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline - - [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution ### FastQC diff --git a/modules.json b/modules.json index 0ab8a797..41789602 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "git_sha": "b8d36829fa84b6e404364abff787e8b07f6d058c", "installed_by": ["modules"] } } @@ -21,12 +21,12 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "d20fb2a9cc3e2835e9d067d1046a63252eb17352", + "git_sha": "9d05360da397692321d377b6102d2fb22507c6ef", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "2fdce49d30c0254f76bc0f13c55c17455c1251ab", + "git_sha": "772684d9d66f37b650c8ba5146ac1ee3ecba2acb", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index f1cd99b0..6f5b867b 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -2,4 +2,4 @@ channels: - conda-forge - bioconda dependencies: - - bioconda::multiqc=1.24.1 + - bioconda::multiqc=1.25.1 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index b9ccebdb..9724d2f3 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.25--pyhdfd78af_0' : - 'biocontainers/multiqc:1.25--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.25.1--pyhdfd78af_0' : + 'biocontainers/multiqc:1.25.1--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index b779e469..2fcbb5ff 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T12:41:34.562023" + "timestamp": "2024-10-02T17:51:46.317523" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T11:27:11.933869532" + "timestamp": "2024-10-02T17:52:20.680978" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,8c8724363a5efe0c6f43ab34faa57efd" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T11:26:56.709849369" + "timestamp": "2024-10-02T17:52:09.185842" } -} +} \ No newline at end of file diff --git a/nextflow.config b/nextflow.config index 90df1661..caea85ac 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,10 +12,12 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options input = null + // References genome = null igenomes_base = 's3://ngi-igenomes/igenomes/' igenomes_ignore = false + // MultiQC options multiqc_config = null multiqc_title = null @@ -36,6 +38,7 @@ params { show_hidden = false version = false pipelines_testdata_base_path = 'https://raw.githubusercontent.com/nf-core/test-datasets/' + // Config options config_profile_name = null config_profile_description = null @@ -44,9 +47,9 @@ params { custom_config_base = "https://raw.githubusercontent.com/nf-core/configs/${params.custom_config_version}" config_profile_contact = null config_profile_url = null + // Schema validation default options validate_params = true - } // Load base.config by default for all pipelines @@ -161,6 +164,7 @@ includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${pa // Load nf-core/seqinspector custom profiles from different institutions. // TODO nf-core: Optionally, you can add a pipeline-specific nf-core config at https://github.com/nf-core/configs // includeConfig !System.getenv('NXF_OFFLINE') && params.custom_config_base ? "${params.custom_config_base}/pipeline/seqinspector.config" : "/dev/null" + // Set default registry for Apptainer, Docker, Podman, Charliecloud and Singularity independent of -profile // Will not be used unless Apptainer / Docker / Podman / Charliecloud / Singularity are enabled // Set to your registry if you have a mirror of containers @@ -172,6 +176,7 @@ charliecloud.registry = 'quay.io' // Load igenomes.config if required includeConfig !params.igenomes_ignore ? 'conf/igenomes.config' : 'conf/igenomes_ignored.config' + // Export these variables to prevent local Python/R libraries from conflicting with those in the container // The JULIA depot path has been adjusted to a fixed path `/usr/local/share/julia` that needs to be used for packages in the container. // See https://apeltzer.github.io/post/03-julia-lang-nextflow/ for details on that. Once we have a common agreement on where to keep Julia packages, this is adjustable. @@ -263,4 +268,3 @@ validation { // Load modules.config for DSL2 module specific options includeConfig 'conf/modules.config' - diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index 2571a5f5..b2558211 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -18,9 +18,9 @@ include { UTILS_NFCORE_PIPELINE } from '../../nf-core/utils_nfcore_pipeline' include { UTILS_NEXTFLOW_PIPELINE } from '../../nf-core/utils_nextflow_pipeline' /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW TO INITIALISE PIPELINE -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow PIPELINE_INITIALISATION { @@ -99,9 +99,9 @@ workflow PIPELINE_INITIALISATION { } /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW FOR PIPELINE COMPLETION -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow PIPELINE_COMPLETION { @@ -147,9 +147,9 @@ workflow PIPELINE_COMPLETION { } /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTIONS -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // // Check and validate pipeline parameters diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf index 28e32b20..2b0dc67a 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -3,13 +3,12 @@ // /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW DEFINITION -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow UTILS_NEXTFLOW_PIPELINE { - take: print_version // boolean: print version dump_parameters // boolean: dump parameters @@ -22,7 +21,7 @@ workflow UTILS_NEXTFLOW_PIPELINE { // Print workflow version and exit on --version // if (print_version) { - log.info "${workflow.manifest.name} ${getWorkflowVersion()}" + log.info("${workflow.manifest.name} ${getWorkflowVersion()}") System.exit(0) } @@ -45,9 +44,9 @@ workflow UTILS_NEXTFLOW_PIPELINE { } /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTIONS -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // @@ -72,11 +71,11 @@ def getWorkflowVersion() { // Dump pipeline parameters to a JSON file // def dumpParametersToJSON(outdir) { - def timestamp = new java.util.Date().format( 'yyyy-MM-dd_HH-mm-ss') - def filename = "params_${timestamp}.json" - def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") - def jsonStr = groovy.json.JsonOutput.toJson(params) - temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) + def timestamp = new java.util.Date().format('yyyy-MM-dd_HH-mm-ss') + def filename = "params_${timestamp}.json" + def temp_pf = new File(workflow.launchDir.toString(), ".${filename}") + def jsonStr = groovy.json.JsonOutput.toJson(params) + temp_pf.text = groovy.json.JsonOutput.prettyPrint(jsonStr) nextflow.extension.FilesEx.copyTo(temp_pf.toPath(), "${outdir}/pipeline_info/params_${timestamp}.json") temp_pf.delete() @@ -91,9 +90,14 @@ def checkCondaChannels() { try { def config = parser.load("conda config --show channels".execute().text) channels = config.channels - } catch(NullPointerException | IOException e) { - log.warn "Could not verify conda channel configuration." - return + } + catch (NullPointerException e) { + log.warn("Could not verify conda channel configuration.") + return null + } + catch (IOException e) { + log.warn("Could not verify conda channel configuration.") + return null } // Check that all channels are present @@ -106,19 +110,13 @@ def checkCondaChannels() { required_channels_in_order.eachWithIndex { channel, index -> if (index < required_channels_in_order.size() - 1) { - channel_priority_violation |= !(channels.indexOf(channel) < channels.indexOf(required_channels_in_order[index+1])) + channel_priority_violation |= !(channels.indexOf(channel) < channels.indexOf(required_channels_in_order[index + 1])) } } if (channels_missing | channel_priority_violation) { - log.warn "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + - " There is a problem with your Conda configuration!\n\n" + - " You will need to set-up the conda-forge and bioconda channels correctly.\n" + - " Please refer to https://bioconda.github.io/\n" + - " The observed channel order is \n" + - " ${channels}\n" + - " but the following channel order is required:\n" + - " ${required_channels_in_order}\n" + - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + log.warn( + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + " There is a problem with your Conda configuration!\n\n" + " You will need to set-up the conda-forge and bioconda channels correctly.\n" + " Please refer to https://bioconda.github.io/\n" + " The observed channel order is \n" + " ${channels}\n" + " but the following channel order is required:\n" + " ${required_channels_in_order}\n" + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + ) } } diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index cbd8495b..b78273ca 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -3,13 +3,12 @@ // /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW DEFINITION -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow UTILS_NFCORE_PIPELINE { - take: nextflow_cli_args @@ -22,9 +21,9 @@ workflow UTILS_NFCORE_PIPELINE { } /* -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTIONS -======================================================================================== +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // @@ -33,12 +32,9 @@ workflow UTILS_NFCORE_PIPELINE { def checkConfigProvided() { def valid_config = true as Boolean if (workflow.profile == 'standard' && workflow.configFiles.size() <= 1) { - log.warn "[$workflow.manifest.name] You are attempting to run the pipeline without any custom configuration!\n\n" + - "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + - " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + - " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + - " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + - "Please refer to the quick start section and usage docs for the pipeline.\n " + log.warn( + "[${workflow.manifest.name}] You are attempting to run the pipeline without any custom configuration!\n\n" + "This will be dependent on your local compute environment but can be achieved via one or more of the following:\n" + " (1) Using an existing pipeline profile e.g. `-profile docker` or `-profile singularity`\n" + " (2) Using an existing nf-core/configs for your Institution e.g. `-profile crick` or `-profile uppmax`\n" + " (3) Using your own local custom config e.g. `-c /path/to/your/custom.config`\n\n" + "Please refer to the quick start section and usage docs for the pipeline.\n " + ) valid_config = false } return valid_config @@ -49,12 +45,14 @@ def checkConfigProvided() { // def checkProfileProvided(nextflow_cli_args) { if (workflow.profile.endsWith(',')) { - error "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + - "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + error( + "The `-profile` option cannot end with a trailing comma, please remove it and re-run the pipeline!\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) } if (nextflow_cli_args[0]) { - log.warn "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + - "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + log.warn( + "nf-core pipelines do not accept positional arguments. The positional argument `${nextflow_cli_args[0]}` has been detected.\n" + "HINT: A common mistake is to provide multiple values separated by spaces e.g. `-profile test, docker`.\n" + ) } } @@ -70,13 +68,7 @@ def workflowCitation() { manifest_doi.each { doi_ref -> temp_doi_ref += " https://doi.org/${doi_ref.replace('https://doi.org/', '').replace(' ', '')}\n" } - return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + - "* The pipeline\n" + - temp_doi_ref + "\n" + - "* The nf-core framework\n" + - " https://doi.org/10.1038/s41587-020-0439-x\n\n" + - "* Software dependencies\n" + - " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" + return "If you use ${workflow.manifest.name} for your analysis please cite:\n\n" + "* The pipeline\n" + temp_doi_ref + "\n" + "* The nf-core framework\n" + " https://doi.org/10.1038/s41587-020-0439-x\n\n" + "* Software dependencies\n" + " https://github.com/${workflow.manifest.name}/blob/master/CITATIONS.md" } // @@ -102,7 +94,7 @@ def getWorkflowVersion() { // def processVersionsFromYAML(yaml_file) { def yaml = new org.yaml.snakeyaml.Yaml() - def versions = yaml.load(yaml_file).collectEntries { k, v -> [ k.tokenize(':')[-1], v ] } + def versions = yaml.load(yaml_file).collectEntries { k, v -> [k.tokenize(':')[-1], v] } return yaml.dumpAsMap(versions).trim() } @@ -112,8 +104,8 @@ def processVersionsFromYAML(yaml_file) { def workflowVersionToYAML() { return """ Workflow: - $workflow.manifest.name: ${getWorkflowVersion()} - Nextflow: $workflow.nextflow.version + ${workflow.manifest.name}: ${getWorkflowVersion()} + Nextflow: ${workflow.nextflow.version} """.stripIndent().trim() } @@ -121,11 +113,7 @@ def workflowVersionToYAML() { // Get channel of software versions used in pipeline in YAML format // def softwareVersionsToYAML(ch_versions) { - return ch_versions - .unique() - .map { version -> processVersionsFromYAML(version) } - .unique() - .mix(Channel.of(workflowVersionToYAML())) + return ch_versions.unique().map { version -> processVersionsFromYAML(version) }.unique().mix(Channel.of(workflowVersionToYAML())) } // @@ -133,25 +121,31 @@ def softwareVersionsToYAML(ch_versions) { // def paramsSummaryMultiqc(summary_params) { def summary_section = '' - summary_params.keySet().each { group -> - def group_params = summary_params.get(group) // This gets the parameters of that particular group - if (group_params) { - summary_section += "

    $group

    \n" - summary_section += "
    \n" - group_params.keySet().sort().each { param -> - summary_section += "
    $param
    ${group_params.get(param) ?: 'N/A'}
    \n" + summary_params + .keySet() + .each { group -> + def group_params = summary_params.get(group) + // This gets the parameters of that particular group + if (group_params) { + summary_section += "

    ${group}

    \n" + summary_section += "
    \n" + group_params + .keySet() + .sort() + .each { param -> + summary_section += "
    ${param}
    ${group_params.get(param) ?: 'N/A'}
    \n" + } + summary_section += "
    \n" } - summary_section += "
    \n" } - } - def yaml_file_text = "id: '${workflow.manifest.name.replace('/','-')}-summary'\n" as String - yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" - yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" - yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" - yaml_file_text += "plot_type: 'html'\n" - yaml_file_text += "data: |\n" - yaml_file_text += "${summary_section}" + def yaml_file_text = "id: '${workflow.manifest.name.replace('/', '-')}-summary'\n" as String + yaml_file_text += "description: ' - this information is collected when the pipeline is started.'\n" + yaml_file_text += "section_name: '${workflow.manifest.name} Workflow Summary'\n" + yaml_file_text += "section_href: 'https://github.com/${workflow.manifest.name}'\n" + yaml_file_text += "plot_type: 'html'\n" + yaml_file_text += "data: |\n" + yaml_file_text += "${summary_section}" return yaml_file_text } @@ -199,54 +193,54 @@ def logColours(monochrome_logs=true) { colorcodes['hidden'] = monochrome_logs ? '' : "\033[8m" // Regular Colors - colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" - colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" - colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" - colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" - colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" - colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" - colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" - colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" + colorcodes['black'] = monochrome_logs ? '' : "\033[0;30m" + colorcodes['red'] = monochrome_logs ? '' : "\033[0;31m" + colorcodes['green'] = monochrome_logs ? '' : "\033[0;32m" + colorcodes['yellow'] = monochrome_logs ? '' : "\033[0;33m" + colorcodes['blue'] = monochrome_logs ? '' : "\033[0;34m" + colorcodes['purple'] = monochrome_logs ? '' : "\033[0;35m" + colorcodes['cyan'] = monochrome_logs ? '' : "\033[0;36m" + colorcodes['white'] = monochrome_logs ? '' : "\033[0;37m" // Bold - colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" - colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" - colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" - colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" - colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" - colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" - colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" - colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" + colorcodes['bblack'] = monochrome_logs ? '' : "\033[1;30m" + colorcodes['bred'] = monochrome_logs ? '' : "\033[1;31m" + colorcodes['bgreen'] = monochrome_logs ? '' : "\033[1;32m" + colorcodes['byellow'] = monochrome_logs ? '' : "\033[1;33m" + colorcodes['bblue'] = monochrome_logs ? '' : "\033[1;34m" + colorcodes['bpurple'] = monochrome_logs ? '' : "\033[1;35m" + colorcodes['bcyan'] = monochrome_logs ? '' : "\033[1;36m" + colorcodes['bwhite'] = monochrome_logs ? '' : "\033[1;37m" // Underline - colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" - colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" - colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" - colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" - colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" - colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" - colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" - colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" + colorcodes['ublack'] = monochrome_logs ? '' : "\033[4;30m" + colorcodes['ured'] = monochrome_logs ? '' : "\033[4;31m" + colorcodes['ugreen'] = monochrome_logs ? '' : "\033[4;32m" + colorcodes['uyellow'] = monochrome_logs ? '' : "\033[4;33m" + colorcodes['ublue'] = monochrome_logs ? '' : "\033[4;34m" + colorcodes['upurple'] = monochrome_logs ? '' : "\033[4;35m" + colorcodes['ucyan'] = monochrome_logs ? '' : "\033[4;36m" + colorcodes['uwhite'] = monochrome_logs ? '' : "\033[4;37m" // High Intensity - colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" - colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" - colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" - colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" - colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" - colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" - colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" - colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" + colorcodes['iblack'] = monochrome_logs ? '' : "\033[0;90m" + colorcodes['ired'] = monochrome_logs ? '' : "\033[0;91m" + colorcodes['igreen'] = monochrome_logs ? '' : "\033[0;92m" + colorcodes['iyellow'] = monochrome_logs ? '' : "\033[0;93m" + colorcodes['iblue'] = monochrome_logs ? '' : "\033[0;94m" + colorcodes['ipurple'] = monochrome_logs ? '' : "\033[0;95m" + colorcodes['icyan'] = monochrome_logs ? '' : "\033[0;96m" + colorcodes['iwhite'] = monochrome_logs ? '' : "\033[0;97m" // Bold High Intensity - colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" - colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" - colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" - colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" - colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" - colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" - colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" - colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" + colorcodes['biblack'] = monochrome_logs ? '' : "\033[1;90m" + colorcodes['bired'] = monochrome_logs ? '' : "\033[1;91m" + colorcodes['bigreen'] = monochrome_logs ? '' : "\033[1;92m" + colorcodes['biyellow'] = monochrome_logs ? '' : "\033[1;93m" + colorcodes['biblue'] = monochrome_logs ? '' : "\033[1;94m" + colorcodes['bipurple'] = monochrome_logs ? '' : "\033[1;95m" + colorcodes['bicyan'] = monochrome_logs ? '' : "\033[1;96m" + colorcodes['biwhite'] = monochrome_logs ? '' : "\033[1;97m" return colorcodes } @@ -261,14 +255,15 @@ def attachMultiqcReport(multiqc_report) { mqc_report = multiqc_report.getVal() if (mqc_report.getClass() == ArrayList && mqc_report.size() >= 1) { if (mqc_report.size() > 1) { - log.warn "[$workflow.manifest.name] Found multiple reports from process 'MULTIQC', will use only one" + log.warn("[${workflow.manifest.name}] Found multiple reports from process 'MULTIQC', will use only one") } mqc_report = mqc_report[0] } } - } catch (all) { + } + catch (Exception all) { if (multiqc_report) { - log.warn "[$workflow.manifest.name] Could not attach MultiQC report to summary email" + log.warn("[${workflow.manifest.name}] Could not attach MultiQC report to summary email") } } return mqc_report @@ -280,26 +275,35 @@ def attachMultiqcReport(multiqc_report) { def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdir, monochrome_logs=true, multiqc_report=null) { // Set up the e-mail variables - def subject = "[$workflow.manifest.name] Successful: $workflow.runName" + def subject = "[${workflow.manifest.name}] Successful: ${workflow.runName}" if (!workflow.success) { - subject = "[$workflow.manifest.name] FAILED: $workflow.runName" + subject = "[${workflow.manifest.name}] FAILED: ${workflow.runName}" } def summary = [:] - summary_params.keySet().sort().each { group -> - summary << summary_params[group] - } + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } def misc_fields = [:] misc_fields['Date Started'] = workflow.start misc_fields['Date Completed'] = workflow.complete misc_fields['Pipeline script file path'] = workflow.scriptFile misc_fields['Pipeline script hash ID'] = workflow.scriptId - if (workflow.repository) misc_fields['Pipeline repository Git URL'] = workflow.repository - if (workflow.commitId) misc_fields['Pipeline repository Git Commit'] = workflow.commitId - if (workflow.revision) misc_fields['Pipeline Git branch/tag'] = workflow.revision - misc_fields['Nextflow Version'] = workflow.nextflow.version - misc_fields['Nextflow Build'] = workflow.nextflow.build + if (workflow.repository) { + misc_fields['Pipeline repository Git URL'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['Pipeline repository Git Commit'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['Pipeline Git branch/tag'] = workflow.revision + } + misc_fields['Nextflow Version'] = workflow.nextflow.version + misc_fields['Nextflow Build'] = workflow.nextflow.build misc_fields['Nextflow Compile Timestamp'] = workflow.nextflow.timestamp def email_fields = [:] @@ -337,7 +341,7 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi // Render the sendmail template def max_multiqc_email_size = (params.containsKey('max_multiqc_email_size') ? params.max_multiqc_email_size : 0) as nextflow.util.MemoryUnit - def smail_fields = [ email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes() ] + def smail_fields = [email: email_address, subject: subject, email_txt: email_txt, email_html: email_html, projectDir: "${workflow.projectDir}", mqcFile: mqc_report, mqcMaxSize: max_multiqc_email_size.toBytes()] def sf = new File("${workflow.projectDir}/assets/sendmail_template.txt") def sendmail_template = engine.createTemplate(sf).make(smail_fields) def sendmail_html = sendmail_template.toString() @@ -346,30 +350,32 @@ def completionEmail(summary_params, email, email_on_fail, plaintext_email, outdi def colors = logColours(monochrome_logs) as Map if (email_address) { try { - if (plaintext_email) { throw new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') } + if (plaintext_email) { +new org.codehaus.groovy.GroovyException('Send plaintext e-mail, not HTML') } // Try to send HTML e-mail using sendmail def sendmail_tf = new File(workflow.launchDir.toString(), ".sendmail_tmp.html") sendmail_tf.withWriter { w -> w << sendmail_html } - [ 'sendmail', '-t' ].execute() << sendmail_html - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (sendmail)-" - } catch (all) { + ['sendmail', '-t'].execute() << sendmail_html + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (sendmail)-") + } + catch (Exception all) { // Catch failures and try with plaintext - def mail_cmd = [ 'mail', '-s', subject, '--content-type=text/html', email_address ] + def mail_cmd = ['mail', '-s', subject, '--content-type=text/html', email_address] mail_cmd.execute() << email_html - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Sent summary e-mail to $email_address (mail)-" + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Sent summary e-mail to ${email_address} (mail)-") } } // Write summary e-mail HTML to a file def output_hf = new File(workflow.launchDir.toString(), ".pipeline_report.html") output_hf.withWriter { w -> w << email_html } - nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html"); + nextflow.extension.FilesEx.copyTo(output_hf.toPath(), "${outdir}/pipeline_info/pipeline_report.html") output_hf.delete() // Write summary e-mail TXT to a file def output_tf = new File(workflow.launchDir.toString(), ".pipeline_report.txt") output_tf.withWriter { w -> w << email_txt } - nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt"); + nextflow.extension.FilesEx.copyTo(output_tf.toPath(), "${outdir}/pipeline_info/pipeline_report.txt") output_tf.delete() } @@ -380,12 +386,14 @@ def completionSummary(monochrome_logs=true) { def colors = logColours(monochrome_logs) as Map if (workflow.success) { if (workflow.stats.ignoredCount == 0) { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.green} Pipeline completed successfully${colors.reset}-" - } else { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-" + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.green} Pipeline completed successfully${colors.reset}-") + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.yellow} Pipeline completed successfully, but with errored process(es) ${colors.reset}-") } - } else { - log.info "-${colors.purple}[$workflow.manifest.name]${colors.red} Pipeline completed with errors${colors.reset}-" + } + else { + log.info("-${colors.purple}[${workflow.manifest.name}]${colors.red} Pipeline completed with errors${colors.reset}-") } } @@ -394,21 +402,30 @@ def completionSummary(monochrome_logs=true) { // def imNotification(summary_params, hook_url) { def summary = [:] - summary_params.keySet().sort().each { group -> - summary << summary_params[group] - } + summary_params + .keySet() + .sort() + .each { group -> + summary << summary_params[group] + } def misc_fields = [:] - misc_fields['start'] = workflow.start - misc_fields['complete'] = workflow.complete - misc_fields['scriptfile'] = workflow.scriptFile - misc_fields['scriptid'] = workflow.scriptId - if (workflow.repository) misc_fields['repository'] = workflow.repository - if (workflow.commitId) misc_fields['commitid'] = workflow.commitId - if (workflow.revision) misc_fields['revision'] = workflow.revision - misc_fields['nxf_version'] = workflow.nextflow.version - misc_fields['nxf_build'] = workflow.nextflow.build - misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp + misc_fields['start'] = workflow.start + misc_fields['complete'] = workflow.complete + misc_fields['scriptfile'] = workflow.scriptFile + misc_fields['scriptid'] = workflow.scriptId + if (workflow.repository) { + misc_fields['repository'] = workflow.repository + } + if (workflow.commitId) { + misc_fields['commitid'] = workflow.commitId + } + if (workflow.revision) { + misc_fields['revision'] = workflow.revision + } + misc_fields['nxf_version'] = workflow.nextflow.version + misc_fields['nxf_build'] = workflow.nextflow.build + misc_fields['nxf_timestamp'] = workflow.nextflow.timestamp def msg_fields = [:] msg_fields['version'] = getWorkflowVersion() @@ -433,13 +450,13 @@ def imNotification(summary_params, hook_url) { def json_message = json_template.toString() // POST - def post = new URL(hook_url).openConnection(); + def post = new URL(hook_url).openConnection() post.setRequestMethod("POST") post.setDoOutput(true) post.setRequestProperty("Content-Type", "application/json") - post.getOutputStream().write(json_message.getBytes("UTF-8")); - def postRC = post.getResponseCode(); - if (! postRC.equals(200)) { - log.warn(post.getErrorStream().getText()); + post.getOutputStream().write(json_message.getBytes("UTF-8")) + def postRC = post.getResponseCode() + if (!postRC.equals(200)) { + log.warn(post.getErrorStream().getText()) } } From 7566c9fb5396391f4a321806484ece1c1fd627f1 Mon Sep 17 00:00:00 2001 From: nf-core-bot Date: Fri, 11 Oct 2024 12:35:06 +0000 Subject: [PATCH 053/530] Template update for nf-core/tools version 3.0.2 --- .github/workflows/ci.yml | 60 +++++++++++++------ .../workflows/template_version_comment.yml | 21 ++++--- .gitignore | 1 + .nf-core.yml | 2 +- main.nf | 2 +- modules.json | 6 +- modules/nf-core/multiqc/main.nf | 2 +- nextflow.config | 4 +- .../main.nf | 4 +- .../nf-core/utils_nextflow_pipeline/main.nf | 30 +++++----- .../nf-core/utils_nfcore_pipeline/main.nf | 10 ++-- workflows/seqinspector.nf | 2 - 12 files changed, 86 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5666673f..81d03080 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,8 @@ on: env: NXF_ANSI_LOG: false + NXF_SINGULARITY_CACHEDIR: ${{ github.workspace }}/.singularity + NXF_SINGULARITY_LIBRARYDIR: ${{ github.workspace }}/.singularity concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" @@ -18,7 +20,7 @@ concurrency: jobs: test: - name: Run pipeline with test data + name: "Run pipeline with test data (${{ matrix.NXF_VER }} | ${{ matrix.test_name }} | ${{ matrix.profile }})" # Only run on push if this is the nf-core dev branch (merged PRs) if: "${{ github.event_name != 'push' || (github.event_name == 'push' && github.repository == 'nf-core/seqinspector') }}" runs-on: ubuntu-latest @@ -27,33 +29,57 @@ jobs: NXF_VER: - "24.04.2" - "latest-everything" + profile: + - "conda" + - "docker" + - "singularity" + test_name: + - "test" + isMaster: + - ${{ github.base_ref == 'master' }} + # Exclude conda and singularity on dev + exclude: + - isMaster: false + profile: "conda" + - isMaster: false + profile: "singularity" steps: - name: Check out pipeline code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 - - name: Install Nextflow + - name: Set up Nextflow uses: nf-core/setup-nextflow@v2 with: version: "${{ matrix.NXF_VER }}" - - name: Disk space cleanup - uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + - name: Set up Apptainer + if: matrix.profile == 'singularity' + uses: eWaterCycle/setup-apptainer@main - - name: Run pipeline with test data (docker) - # TODO nf-core: You can customise CI pipeline run tests as required - # For example: adding multiple test runs with different parameters - # Remember that you can parallelise this by using strategy.matrix + - name: Set up Singularity + if: matrix.profile == 'singularity' run: | - nextflow run ${GITHUB_WORKSPACE} -profile test,docker --outdir ./results + mkdir -p $NXF_SINGULARITY_CACHEDIR + mkdir -p $NXF_SINGULARITY_LIBRARYDIR + + - name: Set up Miniconda + if: matrix.profile == 'conda' + uses: conda-incubator/setup-miniconda@a4260408e20b96e80095f42ff7f1a15b27dd94ca # v3 + with: + miniconda-version: "latest" + auto-update-conda: true + conda-solver: libmamba + channels: conda-forge,bioconda - - name: Run pipeline with test data (singularity) - # TODO nf-core: You can customise CI pipeline run tests as required + - name: Set up Conda + if: matrix.profile == 'conda' run: | - nextflow run ${GITHUB_WORKSPACE} -profile test,singularity --outdir ./results - if: "${{ github.base_ref == 'master' }}" + echo $(realpath $CONDA)/condabin >> $GITHUB_PATH + echo $(realpath python) >> $GITHUB_PATH + + - name: Clean up Disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 - - name: Run pipeline with test data (conda) - # TODO nf-core: You can customise CI pipeline run tests as required + - name: "Run pipeline with test data ${{ matrix.NXF_VER }} | ${{ matrix.test_name }} | ${{ matrix.profile }}" run: | - nextflow run ${GITHUB_WORKSPACE} -profile test,conda --outdir ./results - if: "${{ github.base_ref == 'master' }}" + nextflow run ${GITHUB_WORKSPACE} -profile ${{ matrix.test_name }},${{ matrix.profile }} --outdir ./results diff --git a/.github/workflows/template_version_comment.yml b/.github/workflows/template_version_comment.yml index 9dea41f0..e8aafe44 100644 --- a/.github/workflows/template_version_comment.yml +++ b/.github/workflows/template_version_comment.yml @@ -10,9 +10,11 @@ jobs: steps: - name: Check out pipeline code uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Read template version from .nf-core.yml - uses: pietrobolcato/action-read-yaml@1.0.0 + uses: nichmor/minimal-read-yaml@v0.0.2 id: read_yml with: config: ${{ github.workspace }}/.nf-core.yml @@ -24,20 +26,21 @@ jobs: - name: Check nf-core outdated id: nf_core_outdated - run: pip list --outdated | grep nf-core + run: echo "OUTPUT=$(pip list --outdated | grep nf-core)" >> ${GITHUB_ENV} - name: Post nf-core template version comment uses: mshick/add-pr-comment@b8f338c590a895d50bcbfa6c5859251edc8952fc # v2 if: | - ${{ steps.nf_core_outdated.outputs.stdout }} =~ 'nf-core' + contains(env.OUTPUT, 'nf-core') with: repo-token: ${{ secrets.NF_CORE_BOT_AUTH_TOKEN }} allow-repeats: false message: | - ## :warning: Newer version of the nf-core template is available. - - Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. - Please update your pipeline to the latest version. - - For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). + > [!WARNING] + > Newer version of the nf-core template is available. + > + > Your pipeline is using an old version of the nf-core template: ${{ steps.read_yml.outputs['nf_core_version'] }}. + > Please update your pipeline to the latest version. + > + > For more documentation on how to update your pipeline, please see the [nf-core documentation](https://github.com/nf-core/tools?tab=readme-ov-file#sync-a-pipeline-with-the-template) and [Synchronisation documentation](https://nf-co.re/docs/contributing/sync). # diff --git a/.gitignore b/.gitignore index 5124c9ac..a42ce016 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ results/ testing/ testing* *.pyc +null/ diff --git a/.nf-core.yml b/.nf-core.yml index 4e856727..fb856008 100644 --- a/.nf-core.yml +++ b/.nf-core.yml @@ -1,6 +1,6 @@ bump_version: null lint: null -nf_core_version: 3.0.1 +nf_core_version: 3.0.2 org_path: null repository_type: pipeline template: diff --git a/main.nf b/main.nf index df5d2162..58e593b1 100644 --- a/main.nf +++ b/main.nf @@ -76,7 +76,7 @@ workflow { params.outdir, params.input ) - + // // WORKFLOW: Run main workflow // diff --git a/modules.json b/modules.json index 41789602..8e632d50 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "multiqc": { "branch": "master", - "git_sha": "b8d36829fa84b6e404364abff787e8b07f6d058c", + "git_sha": "cf17ca47590cc578dfb47db1c2a44ef86f89976d", "installed_by": ["modules"] } } @@ -21,12 +21,12 @@ "nf-core": { "utils_nextflow_pipeline": { "branch": "master", - "git_sha": "9d05360da397692321d377b6102d2fb22507c6ef", + "git_sha": "3aa0aec1d52d492fe241919f0c6100ebf0074082", "installed_by": ["subworkflows"] }, "utils_nfcore_pipeline": { "branch": "master", - "git_sha": "772684d9d66f37b650c8ba5146ac1ee3ecba2acb", + "git_sha": "1b6b9a3338d011367137808b49b923515080e3ba", "installed_by": ["subworkflows"] }, "utils_nfschema_plugin": { diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index 9724d2f3..cc0643e1 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -52,7 +52,7 @@ process MULTIQC { stub: """ mkdir multiqc_data - touch multiqc_plots + mkdir multiqc_plots touch multiqc_report.html cat <<-END_VERSIONS > versions.yml diff --git a/nextflow.config b/nextflow.config index caea85ac..1c1ca6b1 100644 --- a/nextflow.config +++ b/nextflow.config @@ -254,10 +254,10 @@ validation { """ afterText = """${manifest.doi ? "* The pipeline\n" : ""}${manifest.doi.tokenize(",").collect { " https://doi.org/${it.trim().replace('https://doi.org/','')}"}.join("\n")}${manifest.doi ? "\n" : ""} * The nf-core framework - https://doi.org/10.1038/s41587-020-0439-x + https://doi.org/10.1038/s41587-020-0439-x * Software dependencies - https://github.com/${manifest.name}/blob/master/CITATIONS.md + https://github.com/${manifest.name}/blob/master/CITATIONS.md """ } summary { diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index b2558211..8384b833 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -47,7 +47,6 @@ workflow PIPELINE_INITIALISATION { workflow.profile.tokenize(',').intersect(['conda', 'mamba']).size() >= 1 ) - // // Validate parameters and generate parameter summary to stdout // @@ -56,7 +55,6 @@ workflow PIPELINE_INITIALISATION { validate_params, null ) - // // Check config provided to the pipeline @@ -64,6 +62,7 @@ workflow PIPELINE_INITIALISATION { UTILS_NFCORE_PIPELINE ( nextflow_cli_args ) + // // Custom validation for pipeline parameters // @@ -110,7 +109,6 @@ workflow PIPELINE_COMPLETION { email // string: email address email_on_fail // string: email address sent on pipeline failure plaintext_email // boolean: Send plain-text email instead of HTML - outdir // path: Path to output directory where results will be published monochrome_logs // boolean: Disable ANSI colour codes in log output hook_url // string: hook URL for notifications diff --git a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf index 2b0dc67a..0fcbf7b3 100644 --- a/subworkflows/nf-core/utils_nextflow_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nextflow_pipeline/main.nf @@ -3,9 +3,9 @@ // /* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW DEFINITION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow UTILS_NEXTFLOW_PIPELINE { @@ -44,9 +44,9 @@ workflow UTILS_NEXTFLOW_PIPELINE { } /* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // @@ -106,17 +106,19 @@ def checkCondaChannels() { def channels_missing = ((required_channels_in_order as Set) - (channels as Set)) as Boolean // Check that they are in the right order - def channel_priority_violation = false - - required_channels_in_order.eachWithIndex { channel, index -> - if (index < required_channels_in_order.size() - 1) { - channel_priority_violation |= !(channels.indexOf(channel) < channels.indexOf(required_channels_in_order[index + 1])) - } - } + def channel_priority_violation = required_channels_in_order != channels.findAll { ch -> ch in required_channels_in_order } if (channels_missing | channel_priority_violation) { - log.warn( - "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" + " There is a problem with your Conda configuration!\n\n" + " You will need to set-up the conda-forge and bioconda channels correctly.\n" + " Please refer to https://bioconda.github.io/\n" + " The observed channel order is \n" + " ${channels}\n" + " but the following channel order is required:\n" + " ${required_channels_in_order}\n" + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" - ) + log.warn """\ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + There is a problem with your Conda configuration! + You will need to set-up the conda-forge and bioconda channels correctly. + Please refer to https://bioconda.github.io/ + The observed channel order is + ${channels} + but the following channel order is required: + ${required_channels_in_order} + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + """.stripIndent(true) } } diff --git a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf index b78273ca..5cb7bafe 100644 --- a/subworkflows/nf-core/utils_nfcore_pipeline/main.nf +++ b/subworkflows/nf-core/utils_nfcore_pipeline/main.nf @@ -3,9 +3,9 @@ // /* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SUBWORKFLOW DEFINITION -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ workflow UTILS_NFCORE_PIPELINE { @@ -21,9 +21,9 @@ workflow UTILS_NFCORE_PIPELINE { } /* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FUNCTIONS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ // @@ -62,7 +62,7 @@ def checkProfileProvided(nextflow_cli_args) { def workflowCitation() { def temp_doi_ref = "" def manifest_doi = workflow.manifest.doi.tokenize(",") - // Using a loop to handle multiple DOIs + // Handling multiple DOIs // Removing `https://doi.org/` to handle pipelines using DOIs vs DOI resolvers // Removing ` ` since the manifest.doi is a string and not a proper list manifest_doi.each { doi_ref -> diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index af3719c2..4a9df1ca 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -57,13 +57,11 @@ workflow SEQINSPECTOR { Channel.fromPath(params.multiqc_logo, checkIfExists: true) : Channel.empty() - summary_params = paramsSummaryMap( workflow, parameters_schema: "nextflow_schema.json") ch_workflow_summary = Channel.value(paramsSummaryMultiqc(summary_params)) ch_multiqc_files = ch_multiqc_files.mix( ch_workflow_summary.collectFile(name: 'workflow_summary_mqc.yaml')) - ch_multiqc_custom_methods_description = params.multiqc_methods_description ? file(params.multiqc_methods_description, checkIfExists: true) : file("$projectDir/assets/methods_description_template.yml", checkIfExists: true) From 1faa8db5858b1c780e1f42b1b2da1cda462ffe51 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 11:12:03 +0000 Subject: [PATCH 054/530] nf-core tools 3.0.2 modules update --- modules.json | 4 +- modules/nf-core/fastqc/environment.yml | 2 - modules/nf-core/fastqc/main.nf | 5 +- modules/nf-core/fastqc/meta.yml | 57 +-- modules/nf-core/fastqc/tests/main.nf.test | 225 ++++++++--- .../nf-core/fastqc/tests/main.nf.test.snap | 370 ++++++++++++++++-- modules/nf-core/multiqc/environment.yml | 4 +- modules/nf-core/multiqc/main.nf | 6 +- modules/nf-core/multiqc/meta.yml | 91 +++-- .../nf-core/multiqc/tests/main.nf.test.snap | 26 +- 10 files changed, 603 insertions(+), 187 deletions(-) diff --git a/modules.json b/modules.json index 70f3486c..a84d111b 100644 --- a/modules.json +++ b/modules.json @@ -7,12 +7,12 @@ "nf-core": { "fastqc": { "branch": "master", - "git_sha": "285a50500f9e02578d90b3ce6382ea3c30216acd", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, "multiqc": { "branch": "master", - "git_sha": "19ca321db5d8bd48923262c2eca6422359633491", + "git_sha": "cf17ca47590cc578dfb47db1c2a44ef86f89976d", "installed_by": ["modules"] } } diff --git a/modules/nf-core/fastqc/environment.yml b/modules/nf-core/fastqc/environment.yml index 1787b38a..691d4c76 100644 --- a/modules/nf-core/fastqc/environment.yml +++ b/modules/nf-core/fastqc/environment.yml @@ -1,7 +1,5 @@ -name: fastqc channels: - conda-forge - bioconda - - defaults dependencies: - bioconda::fastqc=0.12.1 diff --git a/modules/nf-core/fastqc/main.nf b/modules/nf-core/fastqc/main.nf index d79f1c86..d8989f48 100644 --- a/modules/nf-core/fastqc/main.nf +++ b/modules/nf-core/fastqc/main.nf @@ -26,7 +26,10 @@ process FASTQC { def rename_to = old_new_pairs*.join(' ').join(' ') def renamed_files = old_new_pairs.collect{ old_name, new_name -> new_name }.join(' ') - def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') + // The total amount of allocated RAM by FastQC is equal to the number of threads defined (--threads) time the amount of RAM defined (--memory) + // https://github.com/s-andrews/FastQC/blob/1faeea0412093224d7f6a07f777fad60a5650795/fastqc#L211-L222 + // Dividing the task.memory by task.cpu allows to stick to requested amount of RAM in the label + def memory_in_mb = MemoryUnit.of("${task.memory}").toUnit('MB') / task.cpus // FastQC memory value allowed range (100 - 10000) def fastqc_memory = memory_in_mb > 10000 ? 10000 : (memory_in_mb < 100 ? 100 : memory_in_mb) diff --git a/modules/nf-core/fastqc/meta.yml b/modules/nf-core/fastqc/meta.yml index ee5507e0..4827da7a 100644 --- a/modules/nf-core/fastqc/meta.yml +++ b/modules/nf-core/fastqc/meta.yml @@ -16,35 +16,44 @@ tools: homepage: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/ documentation: https://www.bioinformatics.babraham.ac.uk/projects/fastqc/Help/ licence: ["GPL-2.0-only"] + identifier: biotools:fastqc input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. output: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - html: - type: file - description: FastQC report - pattern: "*_{fastqc.html}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.html": + type: file + description: FastQC report + pattern: "*_{fastqc.html}" - zip: - type: file - description: FastQC report archive - pattern: "*_{fastqc.zip}" + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.zip": + type: file + description: FastQC report archive + pattern: "*_{fastqc.zip}" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@drpatelh" - "@grst" diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test index 70edae4d..e9d79a07 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ b/modules/nf-core/fastqc/tests/main.nf.test @@ -23,17 +23,14 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. - // looks like this:
    Mon 2 Oct 2023
    test.gz
    - // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_single") } + { assert process.success }, + // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. + // looks like this:
    Mon 2 Oct 2023
    test.gz
    + // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -54,16 +51,14 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_paired") } + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -83,13 +78,11 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_interleaved") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -109,13 +102,11 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_bam") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -138,22 +129,20 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, - { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, - { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_multiple") } + { assert process.success }, + { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, + { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, + { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, + { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, + { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, + { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, + { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, + { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, + { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, + { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } @@ -173,21 +162,18 @@ nextflow_process { then { assertAll ( - { assert process.success }, - - { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - - { assert snapshot(process.out.versions).match("fastqc_versions_custom_prefix") } + { assert process.success }, + { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, + { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, + { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, + { assert snapshot(process.out.versions).match() } ) } } test("sarscov2 single-end [fastq] - stub") { - options "-stub" - + options "-stub" when { process { """ @@ -201,12 +187,123 @@ nextflow_process { then { assertAll ( - { assert process.success }, - { assert snapshot(process.out.html.collect { file(it[1]).getName() } + - process.out.zip.collect { file(it[1]).getName() } + - process.out.versions ).match("fastqc_stub") } + { assert process.success }, + { assert snapshot(process.out).match() } ) } } + test("sarscov2 paired-end [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 interleaved [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 paired-end [bam] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 multiple [fastq] - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [id: 'test', single_end: false], // meta map + [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2 custom_prefix - stub") { + + options "-stub" + when { + process { + """ + input[0] = Channel.of([ + [ id:'mysample', single_end:true ], // meta map + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) + ]) + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } } diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap index 86f7c311..d5db3092 100644 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ b/modules/nf-core/fastqc/tests/main.nf.test.snap @@ -1,88 +1,392 @@ { - "fastqc_versions_interleaved": { + "sarscov2 custom_prefix": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:07.293713" + "timestamp": "2024-07-22T11:02:16.374038" }, - "fastqc_stub": { + "sarscov2 single-end [fastq] - stub": { "content": [ - [ - "test.html", - "test.zip", - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": true + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:24.993809" + }, + "sarscov2 custom_prefix - stub": { + "content": [ + { + "0": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "mysample", + "single_end": true + }, + "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:31:01.425198" + "timestamp": "2024-07-22T11:03:10.93942" }, - "fastqc_versions_multiple": { + "sarscov2 interleaved [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:55.797907" + "timestamp": "2024-07-22T11:01:42.355718" }, - "fastqc_versions_bam": { + "sarscov2 paired-end [bam]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:40:26.795862" + "timestamp": "2024-07-22T11:01:53.276274" }, - "fastqc_versions_single": { + "sarscov2 multiple [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:39:27.043675" + "timestamp": "2024-07-22T11:02:05.527626" }, - "fastqc_versions_paired": { + "sarscov2 paired-end [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:01:31.188871" + }, + "sarscov2 paired-end [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:34.273566" + }, + "sarscov2 multiple [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:39:47.584191" + "timestamp": "2024-07-22T11:03:02.304411" }, - "fastqc_versions_custom_prefix": { + "sarscov2 single-end [fastq]": { "content": [ [ "versions.yml:md5,e1cc25ca8af856014824abd842e93978" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:01:19.095607" + }, + "sarscov2 interleaved [fastq] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" + }, + "timestamp": "2024-07-22T11:02:44.640184" + }, + "sarscov2 paired-end [bam] - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "html": [ + [ + { + "id": "test", + "single_end": false + }, + "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,e1cc25ca8af856014824abd842e93978" + ], + "zip": [ + [ + { + "id": "test", + "single_end": false + }, + "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ] + } + ], + "meta": { + "nf-test": "0.9.0", + "nextflow": "24.04.3" }, - "timestamp": "2024-01-31T17:41:14.576531" + "timestamp": "2024-07-22T11:02:53.550742" } } \ No newline at end of file diff --git a/modules/nf-core/multiqc/environment.yml b/modules/nf-core/multiqc/environment.yml index a31464c9..6f5b867b 100644 --- a/modules/nf-core/multiqc/environment.yml +++ b/modules/nf-core/multiqc/environment.yml @@ -1,7 +1,5 @@ -name: multiqc channels: - conda-forge - bioconda - - defaults dependencies: - - bioconda::multiqc=1.24.1 + - bioconda::multiqc=1.25.1 diff --git a/modules/nf-core/multiqc/main.nf b/modules/nf-core/multiqc/main.nf index ceaec139..cc0643e1 100644 --- a/modules/nf-core/multiqc/main.nf +++ b/modules/nf-core/multiqc/main.nf @@ -3,8 +3,8 @@ process MULTIQC { conda "${moduleDir}/environment.yml" container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? - 'https://depot.galaxyproject.org/singularity/multiqc:1.24.1--pyhdfd78af_0' : - 'biocontainers/multiqc:1.24.1--pyhdfd78af_0' }" + 'https://depot.galaxyproject.org/singularity/multiqc:1.25.1--pyhdfd78af_0' : + 'biocontainers/multiqc:1.25.1--pyhdfd78af_0' }" input: path multiqc_files, stageAs: "?/*" @@ -52,7 +52,7 @@ process MULTIQC { stub: """ mkdir multiqc_data - touch multiqc_plots + mkdir multiqc_plots touch multiqc_report.html cat <<-END_VERSIONS > versions.yml diff --git a/modules/nf-core/multiqc/meta.yml b/modules/nf-core/multiqc/meta.yml index 382c08cb..b16c1879 100644 --- a/modules/nf-core/multiqc/meta.yml +++ b/modules/nf-core/multiqc/meta.yml @@ -1,5 +1,6 @@ name: multiqc -description: Aggregate results from bioinformatics analyses across many samples into a single report +description: Aggregate results from bioinformatics analyses across many samples into + a single report keywords: - QC - bioinformatics tools @@ -12,53 +13,59 @@ tools: homepage: https://multiqc.info/ documentation: https://multiqc.info/docs/ licence: ["GPL-3.0-or-later"] + identifier: biotools:multiqc input: - - multiqc_files: - type: file - description: | - List of reports / files recognised by MultiQC, for example the html and zip output of FastQC - - multiqc_config: - type: file - description: Optional config yml for MultiQC - pattern: "*.{yml,yaml}" - - extra_multiqc_config: - type: file - description: Second optional config yml for MultiQC. Will override common sections in multiqc_config. - pattern: "*.{yml,yaml}" - - multiqc_logo: - type: file - description: Optional logo file for MultiQC - pattern: "*.{png}" - - replace_names: - type: file - description: | - Optional two-column sample renaming file. First column a set of - patterns, second column a set of corresponding replacements. Passed via - MultiQC's `--replace-names` option. - pattern: "*.{tsv}" - - sample_names: - type: file - description: | - Optional TSV file with headers, passed to the MultiQC --sample_names - argument. - pattern: "*.{tsv}" + - - multiqc_files: + type: file + description: | + List of reports / files recognised by MultiQC, for example the html and zip output of FastQC + - - multiqc_config: + type: file + description: Optional config yml for MultiQC + pattern: "*.{yml,yaml}" + - - extra_multiqc_config: + type: file + description: Second optional config yml for MultiQC. Will override common sections + in multiqc_config. + pattern: "*.{yml,yaml}" + - - multiqc_logo: + type: file + description: Optional logo file for MultiQC + pattern: "*.{png}" + - - replace_names: + type: file + description: | + Optional two-column sample renaming file. First column a set of + patterns, second column a set of corresponding replacements. Passed via + MultiQC's `--replace-names` option. + pattern: "*.{tsv}" + - - sample_names: + type: file + description: | + Optional TSV file with headers, passed to the MultiQC --sample_names + argument. + pattern: "*.{tsv}" output: - report: - type: file - description: MultiQC report file - pattern: "multiqc_report.html" + - "*multiqc_report.html": + type: file + description: MultiQC report file + pattern: "multiqc_report.html" - data: - type: directory - description: MultiQC data dir - pattern: "multiqc_data" + - "*_data": + type: directory + description: MultiQC data dir + pattern: "multiqc_data" - plots: - type: file - description: Plots created by MultiQC - pattern: "*_data" + - "*_plots": + type: file + description: Plots created by MultiQC + pattern: "*_data" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@abhi18av" - "@bunop" diff --git a/modules/nf-core/multiqc/tests/main.nf.test.snap b/modules/nf-core/multiqc/tests/main.nf.test.snap index 83fa080c..2fcbb5ff 100644 --- a/modules/nf-core/multiqc/tests/main.nf.test.snap +++ b/modules/nf-core/multiqc/tests/main.nf.test.snap @@ -2,14 +2,14 @@ "multiqc_versions_single": { "content": [ [ - "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T12:41:34.562023" + "timestamp": "2024-10-02T17:51:46.317523" }, "multiqc_stub": { "content": [ @@ -17,25 +17,25 @@ "multiqc_report.html", "multiqc_data", "multiqc_plots", - "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T11:27:11.933869532" + "timestamp": "2024-10-02T17:52:20.680978" }, "multiqc_versions_config": { "content": [ [ - "versions.yml:md5,6eb13f3b11bbcbfc98ad3166420ff760" + "versions.yml:md5,41f391dcedce7f93ca188f3a3ffa0916" ] ], "meta": { - "nf-test": "0.8.4", - "nextflow": "24.04.2" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-07-10T11:26:56.709849369" + "timestamp": "2024-10-02T17:52:09.185842" } -} +} \ No newline at end of file From 6caa3f2d4d39364c8b5283f09be9fda8052bf7d3 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 13:30:37 +0000 Subject: [PATCH 055/530] merge fix --- .../local/utils_nfcore_seqinspector_pipeline/main.nf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf index c2270583..a84e064f 100644 --- a/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf +++ b/subworkflows/local/utils_nfcore_seqinspector_pipeline/main.nf @@ -74,6 +74,7 @@ workflow PIPELINE_INITIALISATION { Channel .fromList(samplesheetToList(params.input, "${projectDir}/assets/schema_input.json")) + .toList() .flatMap { it.withIndex().collect { entry, idx -> entry + "${idx+1}" } } .map { meta, fastq_1, fastq_2, idx -> @@ -94,8 +95,8 @@ workflow PIPELINE_INITIALISATION { } } .groupTuple() - .map { samplesheet -> - validateInputSamplesheet(samplesheet) + .map { + validateInputSamplesheet(it) // Applies additional group validation checks that schema_input.json cannot do. } .transpose() // Replace the map below // .map { From da6f21e20cf0135fc9efe42b1921c35a4a1636fe Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 13:30:48 +0000 Subject: [PATCH 056/530] update snapshots --- tests/MiSeq.main.nf.test.snap | 8 ++++---- tests/NovaSeq6000.main.nf.test.snap | 24 ++++++++++++------------ tests/PromethION.main.nf.test.snap | 10 +++++----- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/MiSeq.main.nf.test.snap b/tests/MiSeq.main.nf.test.snap index 9dacaf8a..4613d525 100644 --- a/tests/MiSeq.main.nf.test.snap +++ b/tests/MiSeq.main.nf.test.snap @@ -3,13 +3,13 @@ "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,7b1b7fd457b60404768045b148d4c0a8", - "multiqc_general_stats.txt:md5,962713a1473a318f2cb29bb5290c4c8e", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" + "multiqc_general_stats.txt:md5,5b28a83b14cb2fe88d084d08900ebdbf", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3" ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nextflow": "24.10.0" }, - "timestamp": "2024-09-10T08:57:05.870194" + "timestamp": "2024-10-28T13:18:10.3675973" } } \ No newline at end of file diff --git a/tests/NovaSeq6000.main.nf.test.snap b/tests/NovaSeq6000.main.nf.test.snap index 3f27e5a2..ee3c22b7 100644 --- a/tests/NovaSeq6000.main.nf.test.snap +++ b/tests/NovaSeq6000.main.nf.test.snap @@ -3,29 +3,29 @@ "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,3730f9046b20ac5c17a86db0a33f8d5d", - "multiqc_general_stats.txt:md5,d521de54d1e659bf7892105f7d23d4db", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", + "multiqc_general_stats.txt:md5,25abe0f6a35eb4a3b056fc3cf5c13732", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,8284e25ccc21041cf3b5a32eb6a51e78", - "multiqc_general_stats.txt:md5,d52544eb1a505c889a2f9117cf94a5fa", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", + "multiqc_general_stats.txt:md5,90ee35137492b80aab36ef67f72d8921", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,f38ffdc112c73af3a41ed15848a3761f", - "multiqc_general_stats.txt:md5,5b1190093085ef073d4bd5818c9cde79", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", + "multiqc_general_stats.txt:md5,d62a2fc39e674d98783d408791803148", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,7ff71ceb8ecdf086331047f8860c3347", - "multiqc_general_stats.txt:md5,79c1090dd8a97912893f8491641b9dc9", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5", + "multiqc_general_stats.txt:md5,2f09b8f199ac40cf67ba50843cebd29c", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3", "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", "multiqc_fastqc.txt:md5,519ff344a896ac369bba4d5c5b8be7b5", - "multiqc_general_stats.txt:md5,41611bd5ab9e79425c466bf976b03bdc", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" + "multiqc_general_stats.txt:md5,6a1c16f068d7ba3a9225a17eb570ed9a", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3" ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nextflow": "24.10.0" }, - "timestamp": "2024-09-10T08:58:26.732622" + "timestamp": "2024-10-28T13:19:13.226135825" } } \ No newline at end of file diff --git a/tests/PromethION.main.nf.test.snap b/tests/PromethION.main.nf.test.snap index fb8cda25..026a8cd2 100644 --- a/tests/PromethION.main.nf.test.snap +++ b/tests/PromethION.main.nf.test.snap @@ -2,14 +2,14 @@ "PromethION data test": { "content": [ "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", - "multiqc_fastqc.txt:md5,35984961f25a0d4e7352cab4d5650178", - "multiqc_general_stats.txt:md5,1465b0b1959e3864b28ecc2340df351b", - "multiqc_software_versions.txt:md5,49e3596d49ee49d967d3b6c363b486d5" + "multiqc_fastqc.txt:md5,1a4b472e13cadc770832b0e20d1de7b0", + "multiqc_general_stats.txt:md5,409cefc7f17f95d176ced6032bf8fb32", + "multiqc_software_versions.txt:md5,a3698a2d32e8695c38d50e3d17de5fe3" ], "meta": { "nf-test": "0.9.0", - "nextflow": "24.04.4" + "nextflow": "24.10.0" }, - "timestamp": "2024-09-10T08:58:57.180636" + "timestamp": "2024-10-28T13:19:57.261730412" } } \ No newline at end of file From dde2a8d93a6ec79b132b3d3123547fa81b6286bc Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 13:38:38 +0000 Subject: [PATCH 057/530] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aa5bacc..b0b12de1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Initial release of nf-core/seqinspector, created with the [nf-core](https://nf-c - [#20](https://github.com/nf-core/seqinspector/pull/20) Use tags to generate group reports - [#13](https://github.com/nf-core/seqinspector/pull/13) Generate reports per run, per project and per lane. +- [#49](https://github.com/nf-core/seqinspector/pull/49) Merge with template 3.0.2. ### `Fixed` From ea5fc892deff6bfbf8d23bba48cfe2a6e57d1b02 Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 13:16:54 +0000 Subject: [PATCH 058/530] add subsampling --- modules.json | 5 + modules/nf-core/seqtk/sample/environment.yml | 5 + modules/nf-core/seqtk/sample/main.nf | 58 +++++++++++ modules/nf-core/seqtk/sample/meta.yml | 52 ++++++++++ .../nf-core/seqtk/sample/tests/main.nf.test | 80 ++++++++++++++++ .../seqtk/sample/tests/main.nf.test.snap | 95 +++++++++++++++++++ .../seqtk/sample/tests/standard.config | 6 ++ modules/nf-core/seqtk/sample/tests/tags.yml | 2 + nextflow.config | 2 +- nextflow_schema.json | 7 ++ workflows/seqinspector.nf | 17 +++- 11 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 modules/nf-core/seqtk/sample/environment.yml create mode 100644 modules/nf-core/seqtk/sample/main.nf create mode 100644 modules/nf-core/seqtk/sample/meta.yml create mode 100644 modules/nf-core/seqtk/sample/tests/main.nf.test create mode 100644 modules/nf-core/seqtk/sample/tests/main.nf.test.snap create mode 100644 modules/nf-core/seqtk/sample/tests/standard.config create mode 100644 modules/nf-core/seqtk/sample/tests/tags.yml diff --git a/modules.json b/modules.json index 8e632d50..f01f2a64 100644 --- a/modules.json +++ b/modules.json @@ -14,6 +14,11 @@ "branch": "master", "git_sha": "cf17ca47590cc578dfb47db1c2a44ef86f89976d", "installed_by": ["modules"] + }, + "seqtk/sample": { + "branch": "master", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", + "installed_by": ["modules"] } } }, diff --git a/modules/nf-core/seqtk/sample/environment.yml b/modules/nf-core/seqtk/sample/environment.yml new file mode 100644 index 00000000..693aa5c1 --- /dev/null +++ b/modules/nf-core/seqtk/sample/environment.yml @@ -0,0 +1,5 @@ +channels: + - conda-forge + - bioconda +dependencies: + - bioconda::seqtk=1.4 diff --git a/modules/nf-core/seqtk/sample/main.nf b/modules/nf-core/seqtk/sample/main.nf new file mode 100644 index 00000000..ea9b839e --- /dev/null +++ b/modules/nf-core/seqtk/sample/main.nf @@ -0,0 +1,58 @@ +process SEQTK_SAMPLE { + tag "$meta.id" + label 'process_single' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/seqtk:1.4--he4a0461_1' : + 'biocontainers/seqtk:1.4--he4a0461_1' }" + + input: + tuple val(meta), path(reads), val(sample_size) + + output: + tuple val(meta), path("*.fastq.gz"), emit: reads + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def args = task.ext.args ?: '' + def prefix = task.ext.prefix ?: "${meta.id}" + if (!(args ==~ /.*-s[0-9]+.*/)) { + args += " -s100" + } + if ( !sample_size ) { + error "SEQTK/SAMPLE must have a sample_size value included" + } + """ + printf "%s\\n" $reads | while read f; + do + seqtk \\ + sample \\ + $args \\ + \$f \\ + $sample_size \\ + | gzip --no-name > ${prefix}_\$(basename \$f) + done + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + seqtk: \$(echo \$(seqtk 2>&1) | sed 's/^.*Version: //; s/ .*\$//') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + + """ + echo "" | gzip > ${prefix}.fastq.gz + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + seqtk: \$(echo \$(seqtk 2>&1) | sed 's/^.*Version: //; s/ .*\$//') + END_VERSIONS + """ + +} diff --git a/modules/nf-core/seqtk/sample/meta.yml b/modules/nf-core/seqtk/sample/meta.yml new file mode 100644 index 00000000..42f67d8f --- /dev/null +++ b/modules/nf-core/seqtk/sample/meta.yml @@ -0,0 +1,52 @@ +name: seqtk_sample +description: Subsample reads from FASTQ files +keywords: + - sample + - fastx + - reads +tools: + - seqtk: + description: Seqtk is a fast and lightweight tool for processing sequences in + the FASTA or FASTQ format. Seqtk sample command subsamples sequences. + homepage: https://github.com/lh3/seqtk + documentation: https://docs.csc.fi/apps/seqtk/ + tool_dev_url: https://github.com/lh3/seqtk + licence: ["MIT"] + identifier: biotools:seqtk +input: + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: List of input FastQ files + pattern: "*.{fastq.gz}" + - sample_size: + type: integer + description: Number of reads to sample. +output: + - reads: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastq.gz": + type: file + description: Subsampled FastQ files + pattern: "*.{fastq.gz}" + - versions: + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@kaurravneet4123" + - "@sidorov-si" + - "@adamrtalbot" +maintainers: + - "@kaurravneet4123" + - "@sidorov-si" + - "@adamrtalbot" diff --git a/modules/nf-core/seqtk/sample/tests/main.nf.test b/modules/nf-core/seqtk/sample/tests/main.nf.test new file mode 100644 index 00000000..c121c9d9 --- /dev/null +++ b/modules/nf-core/seqtk/sample/tests/main.nf.test @@ -0,0 +1,80 @@ +nextflow_process { + + name "Test Process SEQTK_SAMPLE" + script "modules/nf-core/seqtk/sample/main.nf" + process "SEQTK_SAMPLE" + config "./standard.config" + + tag "modules" + tag "modules_nfcore" + tag "seqtk" + tag "seqtk/sample" + + test("sarscov2_sample_singleend_fqgz") { + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + 50 + ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2_sample_pairedend_fqgz") { + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + 50 + ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + + test("sarscov2_sample_singlend_fqgz_stub") { + options "-stub" + + when { + process { + """ + input[0] = [ + [ id:'test', single_end:true ], + file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), + 50 + ] + """ + } + } + + then { + assertAll ( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + } + +} diff --git a/modules/nf-core/seqtk/sample/tests/main.nf.test.snap b/modules/nf-core/seqtk/sample/tests/main.nf.test.snap new file mode 100644 index 00000000..a9fec3c4 --- /dev/null +++ b/modules/nf-core/seqtk/sample/tests/main.nf.test.snap @@ -0,0 +1,95 @@ +{ + "sarscov2_sample_singlend_fqgz_stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "1": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled.fastq.gz:md5,68b329da9893e34099c7d8ad5cb9c940" + ] + ], + "versions": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ] + } + ], + "timestamp": "2024-02-22T15:58:45.902956" + }, + "sarscov2_sample_pairedend_fqgz": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled_test_1.fastq.gz:md5,e5f44fafd7351c5abb9925a075132941" + ] + ], + "1": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled_test_1.fastq.gz:md5,e5f44fafd7351c5abb9925a075132941" + ] + ], + "versions": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ] + } + ], + "timestamp": "2024-02-22T15:58:37.679954" + }, + "sarscov2_sample_singleend_fqgz": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled_test_1.fastq.gz:md5,e5f44fafd7351c5abb9925a075132941" + ] + ], + "1": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ], + "reads": [ + [ + { + "id": "test", + "single_end": true + }, + "test.sampled_test_1.fastq.gz:md5,e5f44fafd7351c5abb9925a075132941" + ] + ], + "versions": [ + "versions.yml:md5,0529f2d163df9e2cd2ae8254dfb63806" + ] + } + ], + "timestamp": "2024-02-22T15:58:29.474491" + } +} \ No newline at end of file diff --git a/modules/nf-core/seqtk/sample/tests/standard.config b/modules/nf-core/seqtk/sample/tests/standard.config new file mode 100644 index 00000000..b2dd4b9f --- /dev/null +++ b/modules/nf-core/seqtk/sample/tests/standard.config @@ -0,0 +1,6 @@ +process { + withName: SEQTK_SAMPLE { + ext.args = '-s100' + ext.prefix = { "${meta.id}.sampled" } + } +} \ No newline at end of file diff --git a/modules/nf-core/seqtk/sample/tests/tags.yml b/modules/nf-core/seqtk/sample/tests/tags.yml new file mode 100644 index 00000000..e5d113b8 --- /dev/null +++ b/modules/nf-core/seqtk/sample/tests/tags.yml @@ -0,0 +1,2 @@ +seqtk/sample: + - "modules/nf-core/seqtk/sample/**" diff --git a/nextflow.config b/nextflow.config index 50c1ecbb..10bcb897 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,7 +12,7 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options input = null - + sample_size = null // References genome = null fasta = null diff --git a/nextflow_schema.json b/nextflow_schema.json index 88fd607b..f87e525f 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -23,8 +23,15 @@ "help_text": "You will need to create a design file with information about the samples in your experiment before running the pipeline. Use this parameter to specify its location. It has to be a comma-separated file with 3 columns, and a header row. See [usage docs](https://nf-co.re/seqinspector/usage#samplesheet-input).", "fa_icon": "fas fa-file-csv" }, + "sample_size": { + "type": "integer", + "default": null, + "description": "Subset this number of reads.", + "help_text": "Samples will be subsetted to this number of reads. If null, no subsampling will be performed." + }, "outdir": { "type": "string", + "default": null, "format": "directory-path", "description": "The output directory where the results will be saved. You have to use absolute paths to storage on Cloud infrastructure.", "fa_icon": "fas fa-folder-open" diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index ea628117..70b89679 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -3,6 +3,8 @@ IMPORT MODULES / SUBWORKFLOWS / FUNCTIONS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +include { SEQTK_SAMPLE } from '../modules/nf-core/seqtk/sample/main' include { FASTQC } from '../modules/nf-core/fastqc/main' include { MULTIQC as MULTIQC_GLOBAL } from '../modules/nf-core/multiqc/main' @@ -30,11 +32,24 @@ workflow SEQINSPECTOR { ch_multiqc_extra_files = Channel.empty() ch_multiqc_reports = Channel.empty() + // + // MODULE: Run Seqkit sample to perform subsampling + // + if (params.sample_size) { + ch_sample_sized = SEQTK_SAMPLE(ch_samplesheet.map { + meta, reads -> [meta, reads, params.sample_size] + }).reads + ch_versions = ch_versions.mix(SEQTK_SAMPLE.out.versions.first()) + } else { + // No do subsample + ch_sample_sized = ch_samplesheet + } + // // MODULE: Run FastQC // FASTQC ( - ch_samplesheet + ch_sample_sized ) ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip) ch_versions = ch_versions.mix(FASTQC.out.versions.first()) From 9b2808c62e95099d20615ef0746e209adab33b5a Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 14:03:06 +0000 Subject: [PATCH 059/530] add subsampling nf-test --- tests/NovaSeq6000.main_subsample.nf.test | 50 +++++++++++++++++++ .../NovaSeq6000.main_subsample.nf.test.config | 8 +++ 2 files changed, 58 insertions(+) create mode 100644 tests/NovaSeq6000.main_subsample.nf.test create mode 100644 tests/NovaSeq6000.main_subsample.nf.test.config diff --git a/tests/NovaSeq6000.main_subsample.nf.test b/tests/NovaSeq6000.main_subsample.nf.test new file mode 100644 index 00000000..0919bc23 --- /dev/null +++ b/tests/NovaSeq6000.main_subsample.nf.test @@ -0,0 +1,50 @@ +nextflow_pipeline { + + name "Test Workflow main.nf on NovaSeq6000 data" + script "../main.nf" + tag "seqinspector" + tag "PIPELINE" + + test("NovaSeq6000 data test") { + + when { + config "./NovaSeq6000.main_subsample.nf.test.config" + params { + outdir = "$outputDir" + } + } + + then { + assertAll( + { assert workflow.success }, + { assert snapshot( + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/global_report/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/lane1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/group1/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/group2/multiqc_data/multiqc_software_versions.txt"), + + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_citations.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_fastqc.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_general_stats.txt"), + path("$outputDir/multiqc/group_reports/test/multiqc_data/multiqc_software_versions.txt"), + ).match() + }, + ) + } + } +} diff --git a/tests/NovaSeq6000.main_subsample.nf.test.config b/tests/NovaSeq6000.main_subsample.nf.test.config new file mode 100644 index 00000000..acda74dd --- /dev/null +++ b/tests/NovaSeq6000.main_subsample.nf.test.config @@ -0,0 +1,8 @@ +// Load the basic test config +includeConfig 'nextflow.config' + +// Load the correct samplesheet for that test +params { + input = params.pipelines_testdata_base_path + 'seqinspector/testdata/NovaSeq6000/samplesheet.csv' + sample_size = 90 +} From a39925c8deed21a2fca735d91a6df9bca034ad6e Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 14:09:57 +0000 Subject: [PATCH 060/530] add snapshots --- tests/NovaSeq6000.main_subsample.nf.test.snap | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 tests/NovaSeq6000.main_subsample.nf.test.snap diff --git a/tests/NovaSeq6000.main_subsample.nf.test.snap b/tests/NovaSeq6000.main_subsample.nf.test.snap new file mode 100644 index 00000000..d1dbe4a7 --- /dev/null +++ b/tests/NovaSeq6000.main_subsample.nf.test.snap @@ -0,0 +1,31 @@ +{ + "NovaSeq6000 data test": { + "content": [ + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,aba942d1e6996b579f19798e5673f514", + "multiqc_general_stats.txt:md5,c4f40f2313aadc38619e7487226e8d93", + "multiqc_software_versions.txt:md5,b7d1ca14785a9361f0a39ce1b6a02686", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,aa1b8d6adae86005ea7a8b2e901099b8", + "multiqc_general_stats.txt:md5,d5d73d2888cd9895e5f116e5b869e73c", + "multiqc_software_versions.txt:md5,b7d1ca14785a9361f0a39ce1b6a02686", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,ff996e1d3dc4a46e0c9535e54d51ccab", + "multiqc_general_stats.txt:md5,d01ec30a262b69bc5749b0ed108a950a", + "multiqc_software_versions.txt:md5,b7d1ca14785a9361f0a39ce1b6a02686", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,3df36ecfe76b25b0c22dcda84bce2b3b", + "multiqc_general_stats.txt:md5,4dffc0d1169c49adde819d4467ffb775", + "multiqc_software_versions.txt:md5,b7d1ca14785a9361f0a39ce1b6a02686", + "multiqc_citations.txt:md5,4c806e63a283ec1b7e78cdae3a923d4f", + "multiqc_fastqc.txt:md5,ce61b4ce4b1d76ec3f20de3bf0c9ec7f", + "multiqc_general_stats.txt:md5,05f8dfeea9fca7f4c16ba9d553af4c69", + "multiqc_software_versions.txt:md5,b7d1ca14785a9361f0a39ce1b6a02686" + ], + "meta": { + "nf-test": "0.9.1", + "nextflow": "24.04.4" + }, + "timestamp": "2024-10-28T14:05:44.868455454" + } +} \ No newline at end of file From 23f52fb325ae74256cb00a9721ec765d86799009 Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 14:20:03 +0000 Subject: [PATCH 061/530] better name --- tests/NovaSeq6000.main_subsample.nf.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/NovaSeq6000.main_subsample.nf.test b/tests/NovaSeq6000.main_subsample.nf.test index 0919bc23..72851f91 100644 --- a/tests/NovaSeq6000.main_subsample.nf.test +++ b/tests/NovaSeq6000.main_subsample.nf.test @@ -1,11 +1,11 @@ nextflow_pipeline { - name "Test Workflow main.nf on NovaSeq6000 data" + name "Test Workflow main.nf on NovaSeq6000 data sample size 90" script "../main.nf" tag "seqinspector" tag "PIPELINE" - test("NovaSeq6000 data test") { + test("NovaSeq6000 data test sample size") { when { config "./NovaSeq6000.main_subsample.nf.test.config" From ef66577d5e1e3f6a338e66104267bc28888e1319 Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 14:52:09 +0000 Subject: [PATCH 062/530] update docs --- docs/output.md | 13 +++++++++++++ docs/usage.md | 5 +++++ nextflow.config | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/output.md b/docs/output.md index e14c3ad6..a8257513 100644 --- a/docs/output.md +++ b/docs/output.md @@ -10,10 +10,23 @@ The directories listed below will be created in the results directory after the The pipeline is built using [Nextflow](https://www.nextflow.io/) and processes data using the following steps: +- [seqkit](#seqkit) - Subsample a specific number of reads per sample - [FastQC](#fastqc) - Raw read QC - [MultiQC](#multiqc) - Aggregate report describing results and QC from the whole pipeline - [Pipeline information](#pipeline-information) - Report metrics generated during the workflow execution +### Seqkit + +
    +Output files + +- `seqtk/` + - `*_fastq`: FastQ file after being subsampled to the sample_size value. + +
    + +[SeqKit](https://bioinf.shenwei.me/seqkit/) samples sequences by number. For further reading and documentation see the [FastQC help pages](https://bioinf.shenwei.me/seqkit/usage/#sample). + ### FastQC
    diff --git a/docs/usage.md b/docs/usage.md index d75e0fbd..c11b270f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -93,6 +93,11 @@ genome: 'GRCh37' You can also generate such `YAML`/`JSON` files via [nf-core/launch](https://nf-co.re/launch). +Optionally, the `sample_size` parameter allows you to subset a random number of reads to be analysed. +```bash +nextflow run nf-core/seqinspector --input ./samplesheet.csv --outdir ./results --sample_size 90 -profile docker +``` + ### Updating the pipeline When you run the above command, Nextflow automatically pulls the pipeline code from GitHub and stores it as a cached version. When running the pipeline after this, it will always use the cached version if available - even if the pipeline has been updated since. To make sure that you're running the latest version of the pipeline, make sure that you regularly update the cached version of the pipeline: diff --git a/nextflow.config b/nextflow.config index 10bcb897..38eb3127 100644 --- a/nextflow.config +++ b/nextflow.config @@ -12,7 +12,7 @@ params { // TODO nf-core: Specify your pipeline's command line flags // Input options input = null - sample_size = null + sample_size = 0 // References genome = null fasta = null From 5ce6a098c5bd5fb856d0747b90c7de0f49c14a1c Mon Sep 17 00:00:00 2001 From: ngarcia Date: Mon, 28 Oct 2024 14:52:21 +0000 Subject: [PATCH 063/530] change default to 0 --- nextflow_schema.json | 4 ++-- workflows/seqinspector.nf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nextflow_schema.json b/nextflow_schema.json index f87e525f..edb18ea0 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -25,9 +25,9 @@ }, "sample_size": { "type": "integer", - "default": null, + "default": 0, "description": "Subset this number of reads.", - "help_text": "Samples will be subsetted to this number of reads. If null, no subsampling will be performed." + "help_text": "Samples will be subsetted to this number of reads. If 0 (default), no subsampling will be performed." }, "outdir": { "type": "string", diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index 70b89679..91a699da 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -35,7 +35,7 @@ workflow SEQINSPECTOR { // // MODULE: Run Seqkit sample to perform subsampling // - if (params.sample_size) { + if (params.sample_size > 0 ) { ch_sample_sized = SEQTK_SAMPLE(ch_samplesheet.map { meta, reads -> [meta, reads, params.sample_size] }).reads From 9c449ca83b4e4a0be74e2ddbeb07336147e8f540 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 14:56:37 +0000 Subject: [PATCH 064/530] Fran's draft --- modules.json | 5 ++ .../fastqscreen/fastqscreen/environment.yml | 9 ++ .../nf-core/fastqscreen/fastqscreen/main.nf | 54 ++++++++++++ .../nf-core/fastqscreen/fastqscreen/meta.yml | 44 ++++++++++ .../fastqscreen/tests/main.nf.test | 87 +++++++++++++++++++ .../fastqscreen/tests/main.nf.test.snap | 81 +++++++++++++++++ .../fastqscreen/fastqscreen/tests/tags.yml | 2 + nextflow.config | 1 + nextflow_schema.json | 5 ++ workflows/seqinspector.nf | 12 +++ 10 files changed, 300 insertions(+) create mode 100644 modules/nf-core/fastqscreen/fastqscreen/environment.yml create mode 100644 modules/nf-core/fastqscreen/fastqscreen/main.nf create mode 100644 modules/nf-core/fastqscreen/fastqscreen/meta.yml create mode 100644 modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test create mode 100644 modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap create mode 100644 modules/nf-core/fastqscreen/fastqscreen/tests/tags.yml diff --git a/modules.json b/modules.json index 8e632d50..cb726e0d 100644 --- a/modules.json +++ b/modules.json @@ -10,6 +10,11 @@ "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, + "fastqscreen/fastqscreen": { + "branch": "master", + "git_sha": "e1316cdcbef318b9cdfd35586423f8337c3d45f0", + "installed_by": ["modules"] + }, "multiqc": { "branch": "master", "git_sha": "cf17ca47590cc578dfb47db1c2a44ef86f89976d", diff --git a/modules/nf-core/fastqscreen/fastqscreen/environment.yml b/modules/nf-core/fastqscreen/fastqscreen/environment.yml new file mode 100644 index 00000000..5097f091 --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/environment.yml @@ -0,0 +1,9 @@ +--- +# yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json +name: "fastqscreen_fastqscreen" +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - "bioconda::fastq-screen=0.15.3" diff --git a/modules/nf-core/fastqscreen/fastqscreen/main.nf b/modules/nf-core/fastqscreen/fastqscreen/main.nf new file mode 100644 index 00000000..8686f200 --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/main.nf @@ -0,0 +1,54 @@ +process FASTQSCREEN_FASTQSCREEN { + tag "$meta.id" + label 'process_medium' + + conda "${moduleDir}/environment.yml" + container "${ workflow.containerEngine == 'singularity' && !task.ext.singularity_pull_docker_container ? + 'https://depot.galaxyproject.org/singularity/fastq-screen:0.15.3--pl5321hdfd78af_0': + 'biocontainers/fastq-screen:0.15.3--pl5321hdfd78af_0'}" + + input: + tuple val(meta), path(reads) // .fastq files + path database + + output: + tuple val(meta), path("*.txt") , emit: txt + tuple val(meta), path("*.png") , emit: png + tuple val(meta), path("*.html"), emit: html + path "versions.yml" , emit: versions + + when: + task.ext.when == null || task.ext.when + + script: + def prefix = task.ext.prefix ?: "${meta.id}" + def args = task.ext.args ?: "" + + """ + fastq_screen --threads ${task.cpus} \\ + --aligner bowtie2 \\ + --conf ${database}/fastq_screen.conf \\ + $reads \\ + $args \\ + --outdir . + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqscreen: \$(echo \$(fastq_screen --version 2>&1) | sed 's/^.*FastQ Screen v//; s/ .*\$//') + END_VERSIONS + """ + + stub: + def prefix = task.ext.prefix ?: "${meta.id}" + """ + touch test_1_screen.html + touch test_1_screen.png + touch test_1_screen.txt + + cat <<-END_VERSIONS > versions.yml + "${task.process}": + fastqscreen: \$(echo \$(fastq_screen --version 2>&1) | sed 's/^.*FastQ Screen v//; s/ .*\$//') + END_VERSIONS + """ + +} diff --git a/modules/nf-core/fastqscreen/fastqscreen/meta.yml b/modules/nf-core/fastqscreen/fastqscreen/meta.yml new file mode 100644 index 00000000..623dacf7 --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/meta.yml @@ -0,0 +1,44 @@ +name: fastqscreen_fastqscreen +description: Align reads to multiple reference genomes using fastq-screen +keywords: + - align + - map + - fasta + - fastq + - genome + - reference +tools: + - "fastqscreen": + description: "FastQ Screen allows you to screen a library of sequences in FastQ format against a set of sequence databases so you can see if the composition of the library matches with what you expect." + homepage: "https://www.bioinformatics.babraham.ac.uk/projects/fastq_screen/" + documentation: "https://stevenwingett.github.io/FastQ-Screen/" + tool_dev_url: "https://github.com/StevenWingett/FastQ-Screen/archive/refs/tags/v0.15.3.zip" + doi: "10.5281/zenodo.5838377" + licence: ["GPL-3.0-or-later"] +input: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + - database: + type: directory + description: fastq screen database folder containing config file and index folders + pattern: "FastQ_Screen_Genomes" +output: + - fastq_screen: + type: directory + description: Output fastq_screen file containing alignment statistics + pattern: "*.{_fq_screen}" + - versions: + type: file + description: File containing software versions + pattern: "versions.yml" +authors: + - "@snesic" + - "@JPejovicApis" diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test new file mode 100644 index 00000000..6d858a4d --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test @@ -0,0 +1,87 @@ +nextflow_process { + + name "Test Process FASTQSCREEN_FASTQSCREEN" + script "../main.nf" + process "FASTQSCREEN_FASTQSCREEN" + + tag "modules" + tag "modules_nfcore" + tag "bowtie2/build" + tag "fastqscreen" + tag "fastqscreen/buildfromindex" + tag "fastqscreen/fastqscreen" + + setup { + + run("BOWTIE2_BUILD") { + script "../../../bowtie2/build/main.nf" + process { + """ + input[0] = Channel.from([ + [[id: "sarscov2"], file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true)], + [[id: "human"] , file(params.test_data['homo_sapiens']['genome']['genome_21_fasta'], checkIfExists: true)] + ]) + """ + } + } + + run("FASTQSCREEN_BUILDFROMINDEX") { + script "../../../fastqscreen/buildfromindex/main.nf" + process { + """ + input[0] = BOWTIE2_BUILD.out.index.map{meta, index -> meta.id}.collect() + input[1] = BOWTIE2_BUILD.out.index.map{meta, index -> index}.collect() + """ + } + } + } + + test("sarscov2 - human") { + + when { + process { + """ + input[0] = [[ id:'test', single_end:true ], + [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true) ] + ] + input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out.version).match("version") }, + { assert file(process.out.txt.get(0).get(1)).exists() }, + { assert file(process.out.png.get(0).get(1)).exists() }, + { assert file(process.out.html.get(0).get(1)).exists() } + ) + } + + } + + test("sarscov2 - human - stub") { + + options "-stub" + when { + process { + """ + input[0] = [[ id:'test', single_end:true ], + [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true) ] + ] + input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot(process.out).match() } + ) + } + + } + +} diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap new file mode 100644 index 00000000..b2450191 --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap @@ -0,0 +1,81 @@ +{ + "version": { + "content": null, + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-30T14:22:56.541922683" + }, + "sarscov2 - human - stub": { + "content": [ + { + "0": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "1": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "2": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "3": [ + "versions.yml:md5,8ac0239b5103352958d9a9e562b23103" + ], + "html": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.html:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "png": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.png:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "txt": [ + [ + { + "id": "test", + "single_end": true + }, + "test_1_screen.txt:md5,d41d8cd98f00b204e9800998ecf8427e" + ] + ], + "versions": [ + "versions.yml:md5,8ac0239b5103352958d9a9e562b23103" + ] + } + ], + "meta": { + "nf-test": "0.8.4", + "nextflow": "23.10.1" + }, + "timestamp": "2024-04-30T14:23:12.70922619" + } +} \ No newline at end of file diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/tags.yml b/modules/nf-core/fastqscreen/fastqscreen/tests/tags.yml new file mode 100644 index 00000000..b03bfb45 --- /dev/null +++ b/modules/nf-core/fastqscreen/fastqscreen/tests/tags.yml @@ -0,0 +1,2 @@ +fastqscreen/fastqscreen: + - "modules/nf-core/fastqscreen/fastqscreen/**" diff --git a/nextflow.config b/nextflow.config index 50c1ecbb..210f03ef 100644 --- a/nextflow.config +++ b/nextflow.config @@ -17,6 +17,7 @@ params { genome = null fasta = null igenomes_base = 's3://ngi-igenomes/igenomes/' + config_fastq_screen = "${projectDir}/modules/nf-core/fastqscreen/references" igenomes_ignore = false // MultiQC options diff --git a/nextflow_schema.json b/nextflow_schema.json index 88fd607b..f18a5f19 100644 --- a/nextflow_schema.json +++ b/nextflow_schema.json @@ -65,6 +65,11 @@ "help_text": "This parameter is *mandatory* if `--genome` is not specified. If you don't have a BWA index available this will be generated for you automatically. Combine with `--save_reference` to save BWA index for future runs.", "fa_icon": "far fa-file-code" }, + "config_fastq_screen": { + "type": "string", + "description": "path to directory with fastq_screen config (fastq_screen.conf)", + "fa_icon": "fas fa-braille" + }, "igenomes_ignore": { "type": "boolean", "description": "Do not load the iGenomes reference config.", diff --git a/workflows/seqinspector.nf b/workflows/seqinspector.nf index ea628117..6ec9ca9f 100644 --- a/workflows/seqinspector.nf +++ b/workflows/seqinspector.nf @@ -4,6 +4,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ include { FASTQC } from '../modules/nf-core/fastqc/main' +include { FASTQSCREEN_FASTQSCREEN } from '../modules/nf-core/fastqscreen/fastqscreen/main' include { MULTIQC as MULTIQC_GLOBAL } from '../modules/nf-core/multiqc/main' include { MULTIQC as MULTIQC_PER_TAG } from '../modules/nf-core/multiqc/main' @@ -39,6 +40,17 @@ workflow SEQINSPECTOR { ch_multiqc_files = ch_multiqc_files.mix(FASTQC.out.zip) ch_versions = ch_versions.mix(FASTQC.out.versions.first()) + // + // MODULE: Run FastQ Screen + // + + FASTQSCREEN_FASTQSCREEN ( + ch_samplesheet, + Channel.fromPath(params.config_fastq_screen) + ) + ch_multiqc_files = ch_multiqc_files.mix(FASTQSCREEN_FASTQSCREEN.out.txt) + ch_versions = ch_versions.mix(FASTQSCREEN_FASTQSCREEN.out.versions.first()) + // // Collate and save software versions // From 47c59f3caea301e1af31c0dd3db64ae439a673d9 Mon Sep 17 00:00:00 2001 From: Alfred Kedhammar Date: Mon, 28 Oct 2024 15:02:08 +0000 Subject: [PATCH 065/530] nf-core modules update --> fastqscreen --- modules.json | 2 +- .../fastqscreen/fastqscreen/environment.yml | 3 +- .../nf-core/fastqscreen/fastqscreen/main.nf | 10 +-- .../nf-core/fastqscreen/fastqscreen/meta.yml | 78 +++++++++++++------ .../fastqscreen/tests/main.nf.test | 32 +++++++- .../fastqscreen/tests/main.nf.test.snap | 65 ++++++++++++++-- .../fastqscreen/tests/nextflow.config | 5 ++ 7 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 modules/nf-core/fastqscreen/fastqscreen/tests/nextflow.config diff --git a/modules.json b/modules.json index cb726e0d..f8b48550 100644 --- a/modules.json +++ b/modules.json @@ -12,7 +12,7 @@ }, "fastqscreen/fastqscreen": { "branch": "master", - "git_sha": "e1316cdcbef318b9cdfd35586423f8337c3d45f0", + "git_sha": "666652151335353eef2fcd58880bcef5bc2928e1", "installed_by": ["modules"] }, "multiqc": { diff --git a/modules/nf-core/fastqscreen/fastqscreen/environment.yml b/modules/nf-core/fastqscreen/fastqscreen/environment.yml index 5097f091..c63c61e2 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/environment.yml +++ b/modules/nf-core/fastqscreen/fastqscreen/environment.yml @@ -1,9 +1,8 @@ --- # yaml-language-server: $schema=https://raw.githubusercontent.com/nf-core/modules/master/modules/environment-schema.json -name: "fastqscreen_fastqscreen" channels: - conda-forge - bioconda - - defaults dependencies: - "bioconda::fastq-screen=0.15.3" + - bioconda::perl-gdgraph=1.54 diff --git a/modules/nf-core/fastqscreen/fastqscreen/main.nf b/modules/nf-core/fastqscreen/fastqscreen/main.nf index 8686f200..88c4e5c5 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/main.nf +++ b/modules/nf-core/fastqscreen/fastqscreen/main.nf @@ -12,10 +12,11 @@ process FASTQSCREEN_FASTQSCREEN { path database output: - tuple val(meta), path("*.txt") , emit: txt - tuple val(meta), path("*.png") , emit: png - tuple val(meta), path("*.html"), emit: html - path "versions.yml" , emit: versions + tuple val(meta), path("*.txt") , emit: txt + tuple val(meta), path("*.png") , emit: png , optional: true + tuple val(meta), path("*.html") , emit: html + tuple val(meta), path("*.fastq.gz"), emit: fastq, optional: true + path "versions.yml" , emit: versions when: task.ext.when == null || task.ext.when @@ -30,7 +31,6 @@ process FASTQSCREEN_FASTQSCREEN { --conf ${database}/fastq_screen.conf \\ $reads \\ $args \\ - --outdir . cat <<-END_VERSIONS > versions.yml "${task.process}": diff --git a/modules/nf-core/fastqscreen/fastqscreen/meta.yml b/modules/nf-core/fastqscreen/fastqscreen/meta.yml index 623dacf7..39c86b4f 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/meta.yml +++ b/modules/nf-core/fastqscreen/fastqscreen/meta.yml @@ -9,36 +9,70 @@ keywords: - reference tools: - "fastqscreen": - description: "FastQ Screen allows you to screen a library of sequences in FastQ format against a set of sequence databases so you can see if the composition of the library matches with what you expect." + description: "FastQ Screen allows you to screen a library of sequences in FastQ + format against a set of sequence databases so you can see if the composition + of the library matches with what you expect." homepage: "https://www.bioinformatics.babraham.ac.uk/projects/fastq_screen/" documentation: "https://stevenwingett.github.io/FastQ-Screen/" tool_dev_url: "https://github.com/StevenWingett/FastQ-Screen/archive/refs/tags/v0.15.3.zip" doi: "10.5281/zenodo.5838377" licence: ["GPL-3.0-or-later"] + identifier: "" input: - - meta: - type: map - description: | - Groovy Map containing sample information - e.g. [ id:'test', single_end:false ] - - reads: - type: file - description: | - List of input FastQ files of size 1 and 2 for single-end and paired-end data, - respectively. - - database: - type: directory - description: fastq screen database folder containing config file and index folders - pattern: "FastQ_Screen_Genomes" + - - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - reads: + type: file + description: | + List of input FastQ files of size 1 and 2 for single-end and paired-end data, + respectively. + - - database: + type: directory + description: fastq screen database folder containing config file and index folders + pattern: "FastQ_Screen_Genomes" output: - - fastq_screen: - type: directory - description: Output fastq_screen file containing alignment statistics - pattern: "*.{_fq_screen}" + - txt: + - meta: + type: map + description: Groovy Map containing sample information + - "*.txt": + type: file + description: TXT file containing alignment statistics + pattern: "*.txt" + - png: + - meta: + type: map + description: Groovy Map containing sample information + - "*.png": + type: file + description: PNG file with graphical representation of alignments + pattern: "*.png" + - html: + - meta: + type: map + description: Groovy Map containing sample information + - "*.html": + type: file + description: HTML file containing mapping results as a table and graphical representation + pattern: "*.html" + - fastq: + - meta: + type: map + description: | + Groovy Map containing sample information + e.g. [ id:'test', single_end:false ] + - "*.fastq.gz": + type: file + description: FastQ file containing reads that did not align to any database (optional) + pattern: "*.fastq.gz" - versions: - type: file - description: File containing software versions - pattern: "versions.yml" + - versions.yml: + type: file + description: File containing software versions + pattern: "versions.yml" authors: - "@snesic" - "@JPejovicApis" diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test index 6d858a4d..71230a22 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test +++ b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test @@ -10,6 +10,8 @@ nextflow_process { tag "fastqscreen" tag "fastqscreen/buildfromindex" tag "fastqscreen/fastqscreen" + tag "buildfromindex" + tag "modules_fastqscreen" setup { @@ -52,7 +54,7 @@ nextflow_process { then { assertAll( { assert process.success }, - { assert snapshot(process.out.version).match("version") }, + { assert snapshot(process.out.version).match() }, { assert file(process.out.txt.get(0).get(1)).exists() }, { assert file(process.out.png.get(0).get(1)).exists() }, { assert file(process.out.html.get(0).get(1)).exists() } @@ -61,6 +63,34 @@ nextflow_process { } + test("sarscov2 - human - tags") { + config './nextflow.config' + when { + process { + """ + input[0] = [[ id:'test', single_end:false ], + [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true)] + ] + input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database + """ + } + } + + then { + assertAll( + { assert process.success }, + { assert snapshot( + process.out.version, + process.out.txt, + process.out.fastq, + path(process.out.html.get(0).get(1)).readLines()[0..10], + path(process.out.png.get(0).get(1)).exists() + ).match() } + ) + } + + } + test("sarscov2 - human - stub") { options "-stub" diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap index b2450191..2afffdea 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap +++ b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap @@ -1,11 +1,56 @@ { - "version": { + "sarscov2 - human": { "content": null, "meta": { - "nf-test": "0.8.4", - "nextflow": "23.10.1" + "nf-test": "0.9.0", + "nextflow": "24.04.4" }, - "timestamp": "2024-04-30T14:22:56.541922683" + "timestamp": "2024-08-31T05:42:29.972454812" + }, + "sarscov2 - human - tags": { + "content": [ + null, + [ + [ + { + "id": "test", + "single_end": false + }, + "test_1_screen.txt:md5,b0b0ea58bc26ebaa4d573a85e7898f25" + ] + ], + [ + [ + { + "id": "test", + "single_end": false + }, + [ + "test_1.tagged.fastq.gz:md5,f742b162c43ce28f80b89608d5c47f3d", + "test_1.tagged_filter.fastq.gz:md5,28527a76bb0bb3fce0ee76afe01e90aa" + ] + ] + ], + [ + "", + "", + "", + "", + "", + "", + "\t", + "\tFastQ Screen Processing Report - test_1.fastq.gz", + "\tnf-core/seqinspector diff --git a/docs/images/nf-core-seqinspector_logo_light.png b/docs/images/nf-core-seqinspector_logo_light.png index 098210a5907762e0b35076ecb3f9cfccf22ce81b..f1737c85e3d75b1fece0f9c991c900d71eef91f6 100644 GIT binary patch literal 109618 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 z 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ℑ&YpL_mh@m+m;Nh5Gn_A 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
      ;P;}$(;s)% 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 Sf3|_OKd_PIALWMbH~$X}jAttV literal 24391 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 diff --git a/docs/images/nf-core-seqinspector_logo_light.svg b/docs/images/nf-core-seqinspector_logo_light.svg new file mode 100644 index 00000000..bad479ef --- /dev/null +++ b/docs/images/nf-core-seqinspector_logo_light.svg @@ -0,0 +1,242 @@ + +nf-core/seqinspector diff --git a/docs/output.md b/docs/output.md index 900260b5..cf453e24 100644 --- a/docs/output.md +++ b/docs/output.md @@ -78,7 +78,7 @@ To use FastQ Screen, this pipeline requires a `.csv` detailing: - the working name of the reference - the name of the aligner used to generate its index (which is also the aligner and index used by the tool) -- the file basename of the reference and its index (e.g. the reference `genoma.fa` and its index `genome.bt2` have the basename `genome`) +- the file basename of the reference and its index (e.g. the reference `genome.fa` and its index `genome.bt2` have the basename `genome`) - the path to a dir where the reference and index files both reside. See `assets/example_fastq_screen_references.csv` for example. diff --git a/modules/local/rundirparser/resources/usr/bin/parse_illumina.py b/modules/local/rundirparser/resources/usr/bin/parse_illumina.py index 7c70a6da..d8728832 100755 --- a/modules/local/rundirparser/resources/usr/bin/parse_illumina.py +++ b/modules/local/rundirparser/resources/usr/bin/parse_illumina.py @@ -1,5 +1,8 @@ #!/usr/bin/env python3 +# Author: @matrulda +# Licence: MIT + import os import yaml import sys @@ -18,8 +21,10 @@ def read_run_parameters(directory): with open(alt_2) as f: return xmltodict.parse(f.read()) else: - raise Exception("Could not find Illumina [Rr]unParameters.xml. " - "Please provide RunParameters.xml or skip module.") + raise Exception( + "Could not find Illumina [Rr]unParameters.xml. " + "Please provide RunParameters.xml or skip module." + ) def find(d, tag): @@ -71,12 +76,11 @@ def construct_multiqc_yaml(directory): "description": "Sequencing metadata gathered from the run directory", "plot_type": "table", "pconfig": { - "id": 'mqc_seq_metadata', - "title": 'Run directory Metadata', + "id": "mqc_seq_metadata", + "title": "Run directory Metadata", "col1_header": "Metadata", - }, + }, "data": data, - } return metadata diff --git a/modules/nf-core/bowtie2/build/tests/main.nf.test b/modules/nf-core/bowtie2/build/tests/main.nf.test deleted file mode 100644 index ee94c19c..00000000 --- a/modules/nf-core/bowtie2/build/tests/main.nf.test +++ /dev/null @@ -1,31 +0,0 @@ -nextflow_process { - - name "Test Process BOWTIE2_BUILD" - script "../main.nf" - process "BOWTIE2_BUILD" - tag "modules" - tag "modules_nfcore" - tag "bowtie2" - tag "bowtie2/build" - - test("Should run without failures") { - - when { - process { - """ - input[0] = [ - [ id:'test' ], - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - """ - } - } - - then { - assert process.success - assert snapshot(process.out).match() - } - - } - -} diff --git a/modules/nf-core/bowtie2/build/tests/main.nf.test.snap b/modules/nf-core/bowtie2/build/tests/main.nf.test.snap deleted file mode 100644 index ea5711e4..00000000 --- a/modules/nf-core/bowtie2/build/tests/main.nf.test.snap +++ /dev/null @@ -1,49 +0,0 @@ -{ - "Should run without failures": { - "content": [ - { - "0": [ - [ - { - "id": "test" - }, - [ - "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", - "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", - "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", - "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", - "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", - "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" - ] - ] - ], - "1": [ - "versions.yml:md5,d136fb9c16f0a9fb2ae804b2a5fbc09c" - ], - "index": [ - [ - { - "id": "test" - }, - [ - "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", - "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", - "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", - "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", - "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", - "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" - ] - ] - ], - "versions": [ - "versions.yml:md5,d136fb9c16f0a9fb2ae804b2a5fbc09c" - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.02.1" - }, - "timestamp": "2023-11-23T11:51:01.107681997" - } -} \ No newline at end of file diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test b/modules/nf-core/bwamem2/index/tests/main.nf.test deleted file mode 100644 index adf44785..00000000 --- a/modules/nf-core/bwamem2/index/tests/main.nf.test +++ /dev/null @@ -1,62 +0,0 @@ -nextflow_process { - - name "Test Process BWAMEM2_INDEX" - tag "modules_nfcore" - tag "modules" - tag "bwamem2" - tag "bwamem2/index" - script "../main.nf" - process "BWAMEM2_INDEX" - - test("fasta") { - - when { - process { - """ - input[0] = [ - [id: 'test'], - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.index, - process.out.versions, - path(process.out.versions[0]).yaml - ).match() } - ) - } - } - - test("fasta - stub") { - - options "-stub" - - when { - process { - """ - input[0] = [ - [id: 'test'], - file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true) - ] - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.index, - process.out.versions, - path(process.out.versions[0]).yaml - ).match() } - ) - } - } -} diff --git a/modules/nf-core/bwamem2/index/tests/main.nf.test.snap b/modules/nf-core/bwamem2/index/tests/main.nf.test.snap deleted file mode 100644 index 9ad8b20c..00000000 --- a/modules/nf-core/bwamem2/index/tests/main.nf.test.snap +++ /dev/null @@ -1,64 +0,0 @@ -{ - "fasta - stub": { - "content": [ - [ - [ - { - "id": "test" - }, - [ - "genome.fasta.0123:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.amb:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.ann:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.bwt.2bit.64:md5,d41d8cd98f00b204e9800998ecf8427e", - "genome.fasta.pac:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - ], - [ - "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" - ], - { - "BWAMEM2_INDEX": { - "bwamem2": "2.2.1" - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" - }, - "timestamp": "2025-09-04T08:55:53.219699135" - }, - "fasta": { - "content": [ - [ - [ - { - "id": "test" - }, - [ - "genome.fasta.0123:md5,b02870de80106104abcb03cd9463e7d8", - "genome.fasta.amb:md5,3a68b8b2287e07dd3f5f95f4344ba76e", - "genome.fasta.ann:md5,c32e11f6c859f166c7525a9c1d583567", - "genome.fasta.bwt.2bit.64:md5,d097a1b82dee375d41a1ea69895a9216", - "genome.fasta.pac:md5,983e3d2cd6f36e2546e6d25a0da78d66" - ] - ] - ], - [ - "versions.yml:md5,9ffd13d12e7108ed15c58566bc4717d6" - ], - { - "BWAMEM2_INDEX": { - "bwamem2": "2.2.1" - } - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.6" - }, - "timestamp": "2025-09-04T08:55:45.007921901" - } -} \ No newline at end of file diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test b/modules/nf-core/bwamem2/mem/tests/main.nf.test deleted file mode 100644 index 9e0ab14a..00000000 --- a/modules/nf-core/bwamem2/mem/tests/main.nf.test +++ /dev/null @@ -1,179 +0,0 @@ -nextflow_process { - - name "Test Process BWAMEM2_MEM" - script "../main.nf" - process "BWAMEM2_MEM" - - tag "modules" - tag "modules_nfcore" - tag "bwamem2" - tag "bwamem2/mem" - tag "bwamem2/index" - - setup { - run("BWAMEM2_INDEX") { - script "../../index/main.nf" - process { - """ - input[0] = Channel.of([ - [:], // meta map - [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)] - ]) - """ - } - } - } - - test("sarscov2 - fastq, index, fasta, false") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'test', single_end:true ], // meta map - [file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)] - ]) - input[1] = BWAMEM2_INDEX.out.index - input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) - input[3] = false - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - bam(process.out.bam[0][1]).getHeaderMD5(), - bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions - ).match() } - ) - } - - } - - test("sarscov2 - fastq, index, fasta, true") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'test', single_end:true ], // meta map - [file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true)] - ]) - input[1] = BWAMEM2_INDEX.out.index - input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) - input[3] = true - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - bam(process.out.bam[0][1]).getHeaderMD5(), - bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions - ).match() } - ) - } - - } - - test("sarscov2 - [fastq1, fastq2], index, fasta, false") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) - ] - ]) - input[1] = BWAMEM2_INDEX.out.index - input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) - input[3] = false - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - bam(process.out.bam[0][1]).getHeaderMD5(), - bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions - ).match() } - ) - } - - } - - test("sarscov2 - [fastq1, fastq2], index, fasta, true") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) - ] - ]) - input[1] = BWAMEM2_INDEX.out.index - input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) - input[3] = true - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - bam(process.out.bam[0][1]).getHeaderMD5(), - bam(process.out.bam[0][1]).getReadsMD5(), - process.out.versions - ).match() } - ) - } - - } - - test("sarscov2 - [fastq1, fastq2], index, fasta, true - stub") { - - options "-stub" - - when { - process { - """ - input[0] = Channel.of([ - [ id:'test', single_end:false ], // meta map - [ - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) - ] - ]) - input[1] = BWAMEM2_INDEX.out.index - input[2] = Channel.of([[:], [file(params.modules_testdata_base_path + 'genomics/sarscov2/genome/genome.fasta', checkIfExists: true)]]) - input[3] = true - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - -} diff --git a/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap b/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap deleted file mode 100644 index b7d40a68..00000000 --- a/modules/nf-core/bwamem2/mem/tests/main.nf.test.snap +++ /dev/null @@ -1,129 +0,0 @@ -{ - "sarscov2 - [fastq1, fastq2], index, fasta, false": { - "content": [ - "e414c2d48e2e44c2c52c20ecd88e8bd8", - "57aeef88ed701a8ebc8e2f0a381b2a6", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" - }, - "timestamp": "2025-09-23T11:44:52.73673293" - }, - "sarscov2 - [fastq1, fastq2], index, fasta, true - stub": { - "content": [ - { - "0": [ - - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - - ], - "3": [ - - ], - "4": [ - [ - { - "id": "test", - "single_end": false - }, - "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "5": [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ], - "bam": [ - [ - { - "id": "test", - "single_end": false - }, - "test.bam:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "crai": [ - - ], - "cram": [ - - ], - "csi": [ - [ - { - "id": "test", - "single_end": false - }, - "test.csi:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "sam": [ - - ], - "versions": [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" - }, - "timestamp": "2025-09-23T11:45:14.834888709" - }, - "sarscov2 - [fastq1, fastq2], index, fasta, true": { - "content": [ - "716ed1ef39deaad346ca7cf86e08f959", - "af8628d9df18b2d3d4f6fd47ef2bb872", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" - }, - "timestamp": "2025-09-23T11:45:04.750057645" - }, - "sarscov2 - fastq, index, fasta, false": { - "content": [ - "283a83f604f3f5338acedfee349dccf4", - "798439cbd7fd81cbcc5078022dc5479d", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" - }, - "timestamp": "2025-09-23T11:44:28.57550711" - }, - "sarscov2 - fastq, index, fasta, true": { - "content": [ - "ed99048bb552cac58e39923b550b6d5b", - "94fcf617f5b994584c4e8d4044e16b4f", - [ - "versions.yml:md5,3574188ab1f33fd99cff9f5562dfb885" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.04.7" - }, - "timestamp": "2025-09-23T11:44:40.437183765" - } -} \ No newline at end of file diff --git a/modules/nf-core/fastqc/tests/main.nf.test b/modules/nf-core/fastqc/tests/main.nf.test deleted file mode 100644 index e9d79a07..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test +++ /dev/null @@ -1,309 +0,0 @@ -nextflow_process { - - name "Test Process FASTQC" - script "../main.nf" - process "FASTQC" - - tag "modules" - tag "modules_nfcore" - tag "fastqc" - - test("sarscov2 single-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - // NOTE The report contains the date inside it, which means that the md5sum is stable per day, but not longer than that. So you can't md5sum it. - // looks like this:
      Mon 2 Oct 2023
      test.gz
      - // https://github.com/nf-core/modules/pull/3903#issuecomment-1743620039 - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 paired-end [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 interleaved [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 paired-end [bam]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/test_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/test_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 multiple [fastq]") { - - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1][0] ==~ ".*/test_1_fastqc.html" }, - { assert process.out.html[0][1][1] ==~ ".*/test_2_fastqc.html" }, - { assert process.out.html[0][1][2] ==~ ".*/test_3_fastqc.html" }, - { assert process.out.html[0][1][3] ==~ ".*/test_4_fastqc.html" }, - { assert process.out.zip[0][1][0] ==~ ".*/test_1_fastqc.zip" }, - { assert process.out.zip[0][1][1] ==~ ".*/test_2_fastqc.zip" }, - { assert process.out.zip[0][1][2] ==~ ".*/test_3_fastqc.zip" }, - { assert process.out.zip[0][1][3] ==~ ".*/test_4_fastqc.zip" }, - { assert path(process.out.html[0][1][0]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][1]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][2]).text.contains("File typeConventional base calls") }, - { assert path(process.out.html[0][1][3]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 custom_prefix") { - - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert process.out.html[0][1] ==~ ".*/mysample_fastqc.html" }, - { assert process.out.zip[0][1] ==~ ".*/mysample_fastqc.zip" }, - { assert path(process.out.html[0][1]).text.contains("File typeConventional base calls") }, - { assert snapshot(process.out.versions).match() } - ) - } - } - - test("sarscov2 single-end [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id: 'test', single_end:true ], - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 interleaved [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_interleaved.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 paired-end [bam] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/bam/test.paired_end.sorted.bam', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 multiple [fastq] - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [id: 'test', single_end: false], // meta map - [ file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_2.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_1.fastq.gz', checkIfExists: true), - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test2_2.fastq.gz', checkIfExists: true) ] - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } - - test("sarscov2 custom_prefix - stub") { - - options "-stub" - when { - process { - """ - input[0] = Channel.of([ - [ id:'mysample', single_end:true ], // meta map - file(params.modules_testdata_base_path + 'genomics/sarscov2/illumina/fastq/test_1.fastq.gz', checkIfExists: true) - ]) - """ - } - } - - then { - assertAll ( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - } -} diff --git a/modules/nf-core/fastqc/tests/main.nf.test.snap b/modules/nf-core/fastqc/tests/main.nf.test.snap deleted file mode 100644 index d5db3092..00000000 --- a/modules/nf-core/fastqc/tests/main.nf.test.snap +++ /dev/null @@ -1,392 +0,0 @@ -{ - "sarscov2 custom_prefix": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:16.374038" - }, - "sarscov2 single-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": true - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": true - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:24.993809" - }, - "sarscov2 custom_prefix - stub": { - "content": [ - { - "0": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "mysample", - "single_end": true - }, - "mysample.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:03:10.93942" - }, - "sarscov2 interleaved [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:42.355718" - }, - "sarscov2 paired-end [bam]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:53.276274" - }, - "sarscov2 multiple [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:05.527626" - }, - "sarscov2 paired-end [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:31.188871" - }, - "sarscov2 paired-end [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:34.273566" - }, - "sarscov2 multiple [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:03:02.304411" - }, - "sarscov2 single-end [fastq]": { - "content": [ - [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ] - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:01:19.095607" - }, - "sarscov2 interleaved [fastq] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:44.640184" - }, - "sarscov2 paired-end [bam] - stub": { - "content": [ - { - "0": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "2": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "html": [ - [ - { - "id": "test", - "single_end": false - }, - "test.html:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,e1cc25ca8af856014824abd842e93978" - ], - "zip": [ - [ - { - "id": "test", - "single_end": false - }, - "test.zip:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ] - } - ], - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.3" - }, - "timestamp": "2024-07-22T11:02:53.550742" - } -} \ No newline at end of file diff --git a/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test b/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test deleted file mode 100644 index 5357712a..00000000 --- a/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test +++ /dev/null @@ -1,71 +0,0 @@ -nextflow_process { - - name "Test Process FASTQSCREEN_BUILDFROMINDEX" - script "../main.nf" - process "FASTQSCREEN_BUILDFROMINDEX" - - tag "modules" - tag "modules_nfcore" - tag "fastqscreen" - tag "fastqscreen/buildfromindex" - tag "bowtie2/build" - - setup { - - run("BOWTIE2_BUILD") { - script "../../../bowtie2/build/main.nf" - process { - """ - input[0] = Channel.from([ - [[id: "sarscov2"], file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true)], - [[id: "human"] , file(params.test_data['homo_sapiens']['genome']['genome_21_fasta'], checkIfExists: true)] - ]) - """ - } - } - - } - - test("sarscov2 - human") { - - when { - process { - """ - input[0] = BOWTIE2_BUILD.out.index.map{meta, index -> meta.id}.collect() - input[1] = BOWTIE2_BUILD.out.index.map{meta, index -> index}.collect() - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out.database, process.out.versions).match() } - ) - } - - } - - test("sarscov2 - human - stub") { - - options "-stub" - - when { - process { - """ - input[0] = BOWTIE2_BUILD.out.index.map{meta, index -> meta.id}.collect() - input[1] = BOWTIE2_BUILD.out.index.map{meta, index -> index}.collect() - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - -} diff --git a/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test.snap b/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test.snap deleted file mode 100644 index a485079b..00000000 --- a/modules/nf-core/fastqscreen/buildfromindex/tests/main.nf.test.snap +++ /dev/null @@ -1,95 +0,0 @@ -{ - "sarscov2 - human": { - "content": [ - [ - [ - [ - "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", - "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", - "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", - "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", - "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", - "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" - ], - [ - "genome.1.bt2:md5,2fbc8eeaf480f03b3a2362a782fa5755", - "genome.2.bt2:md5,a5300e3cf590d4cd8bd5521cac6337d6", - "genome.3.bt2:md5,d2a7a0e1c9891e847a15781c0695b84e", - "genome.4.bt2:md5,a55dc4b09e3e00586de6963800c95b9a", - "genome.rev.1.bt2:md5,93c07f7d04e5da6bf1322ad5f46ac806", - "genome.rev.2.bt2:md5,c3fa30132c0c9b9bc7868059e8e23466" - ], - "fastq_screen.conf:md5,ca56e866c7006a46bdfe4e751e787265" - ] - ], - [ - "versions.yml:md5,c2bcf85b00046c72d5dccd2dba8ac35c" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "25.02.1" - }, - "timestamp": "2025-03-25T12:58:36.506586801" - }, - "sarscov2 - human": { - "content": [ - [ - [ - [ - "genome.1.bt2:md5,cbe3d0bbea55bc57c99b4bfa25b5fbdf", - "genome.2.bt2:md5,47b153cd1319abc88dda532462651fcf", - "genome.3.bt2:md5,4ed93abba181d8dfab2e303e33114777", - "genome.4.bt2:md5,c25be5f8b0378abf7a58c8a880b87626", - "genome.rev.1.bt2:md5,52be6950579598a990570fbcf5372184", - "genome.rev.2.bt2:md5,e3b4ef343dea4dd571642010a7d09597" - ], - [ - "genome.1.bt2:md5,2fbc8eeaf480f03b3a2362a782fa5755", - "genome.2.bt2:md5,a5300e3cf590d4cd8bd5521cac6337d6", - "genome.3.bt2:md5,d2a7a0e1c9891e847a15781c0695b84e", - "genome.4.bt2:md5,a55dc4b09e3e00586de6963800c95b9a", - "genome.rev.1.bt2:md5,93c07f7d04e5da6bf1322ad5f46ac806", - "genome.rev.2.bt2:md5,c3fa30132c0c9b9bc7868059e8e23466" - ], - "fastq_screen.conf:md5,ca56e866c7006a46bdfe4e751e787265" - ] - ], - [ - "versions.yml:md5,c2bcf85b00046c72d5dccd2dba8ac35c" - ] - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-03-25T08:34:46.992911211" - }, - "sarscov2 - human - stub": { - "content": [ - { - "0": [ - [ - "fastq_screen.conf:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "1": [ - "versions.yml:md5,c2bcf85b00046c72d5dccd2dba8ac35c" - ], - "database": [ - [ - "fastq_screen.conf:md5,d41d8cd98f00b204e9800998ecf8427e" - ] - ], - "versions": [ - "versions.yml:md5,c2bcf85b00046c72d5dccd2dba8ac35c" - ] - } - ], - "meta": { - "nf-test": "0.9.2", - "nextflow": "24.10.5" - }, - "timestamp": "2025-03-25T08:35:14.073325807" - } -} \ No newline at end of file diff --git a/modules/nf-core/fastqscreen/fastqscreen/fastqscreen-fastqscreen.diff b/modules/nf-core/fastqscreen/fastqscreen/fastqscreen-fastqscreen.diff index c6fdd2f1..9647dd39 100644 --- a/modules/nf-core/fastqscreen/fastqscreen/fastqscreen-fastqscreen.diff +++ b/modules/nf-core/fastqscreen/fastqscreen/fastqscreen-fastqscreen.diff @@ -112,7 +112,7 @@ Changes in 'fastqscreen/fastqscreen/main.nf': - } -'modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap' is unchanged -'modules/nf-core/fastqscreen/fastqscreen/tests/nextflow.config' is unchanged -'modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test' is unchanged +'modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test' was removed +'modules/nf-core/fastqscreen/fastqscreen/tests/nextflow.config' was removed +'modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap' was removed ************************************************************ diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test deleted file mode 100644 index 71230a22..00000000 --- a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test +++ /dev/null @@ -1,117 +0,0 @@ -nextflow_process { - - name "Test Process FASTQSCREEN_FASTQSCREEN" - script "../main.nf" - process "FASTQSCREEN_FASTQSCREEN" - - tag "modules" - tag "modules_nfcore" - tag "bowtie2/build" - tag "fastqscreen" - tag "fastqscreen/buildfromindex" - tag "fastqscreen/fastqscreen" - tag "buildfromindex" - tag "modules_fastqscreen" - - setup { - - run("BOWTIE2_BUILD") { - script "../../../bowtie2/build/main.nf" - process { - """ - input[0] = Channel.from([ - [[id: "sarscov2"], file(params.test_data['sarscov2']['genome']['genome_fasta'], checkIfExists: true)], - [[id: "human"] , file(params.test_data['homo_sapiens']['genome']['genome_21_fasta'], checkIfExists: true)] - ]) - """ - } - } - - run("FASTQSCREEN_BUILDFROMINDEX") { - script "../../../fastqscreen/buildfromindex/main.nf" - process { - """ - input[0] = BOWTIE2_BUILD.out.index.map{meta, index -> meta.id}.collect() - input[1] = BOWTIE2_BUILD.out.index.map{meta, index -> index}.collect() - """ - } - } - } - - test("sarscov2 - human") { - - when { - process { - """ - input[0] = [[ id:'test', single_end:true ], - [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true) ] - ] - input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out.version).match() }, - { assert file(process.out.txt.get(0).get(1)).exists() }, - { assert file(process.out.png.get(0).get(1)).exists() }, - { assert file(process.out.html.get(0).get(1)).exists() } - ) - } - - } - - test("sarscov2 - human - tags") { - config './nextflow.config' - when { - process { - """ - input[0] = [[ id:'test', single_end:false ], - [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true)] - ] - input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot( - process.out.version, - process.out.txt, - process.out.fastq, - path(process.out.html.get(0).get(1)).readLines()[0..10], - path(process.out.png.get(0).get(1)).exists() - ).match() } - ) - } - - } - - test("sarscov2 - human - stub") { - - options "-stub" - when { - process { - """ - input[0] = [[ id:'test', single_end:true ], - [file(params.test_data['sarscov2']['illumina']['test_1_fastq_gz'], checkIfExists: true) ] - ] - input[1] = FASTQSCREEN_BUILDFROMINDEX.out.database - """ - } - } - - then { - assertAll( - { assert process.success }, - { assert snapshot(process.out).match() } - ) - } - - } - -} diff --git a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap b/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap deleted file mode 100644 index 042f3bb4..00000000 --- a/modules/nf-core/fastqscreen/fastqscreen/tests/main.nf.test.snap +++ /dev/null @@ -1,132 +0,0 @@ -{ - "sarscov2 - human": { - "content": null, - "meta": { - "nf-test": "0.9.0", - "nextflow": "24.04.4" - }, - "timestamp": "2024-08-31T05:42:29.972454812" - }, - "sarscov2 - human - tags": { - "content": [ - null, - [ - [ - { - "id": "test", - "single_end": false - }, - "test_1_screen.txt:md5,b0b0ea58bc26ebaa4d573a85e7898f25" - ] - ], - [ - [ - { - "id": "test", - "single_end": false - }, - [ - "test_1.tagged.fastq.gz:md5,f742b162c43ce28f80b89608d5c47f3d", - "test_1.tagged_filter.fastq.gz:md5,28527a76bb0bb3fce0ee76afe01e90aa" - ] - ] - ], - [ - "", - "", - "", - "", - "", - "", - "\t", - "\tFastQ Screen Processing Report - test_1.fastq.gz", - "\t