-
Notifications
You must be signed in to change notification settings - Fork 249
Add Scripting essentials module #652
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
3ba48a7
Add AI-assisted Groovy essentials
pinin4fjords f5fba63
Refine section 1.1
pinin4fjords 45013e2
Add groovy essentials to nav
pinin4fjords 0630074
Fix list rendering
pinin4fjords e171a4c
Emphasise departure to groovy
pinin4fjords db4bcdc
Shorten intro
pinin4fjords 07dd457
Shorten intro
pinin4fjords 5c92314
Fix highlight
pinin4fjords 4ef9986
Refine section 1.2
pinin4fjords aa468a2
messy updates up to section 4
pinin4fjords 6be8aa5
State after a good chat wiht Claude code
pinin4fjords 28210b4
Tone down intro
pinin4fjords 51245c8
Reset collect.nf to starting state
pinin4fjords df11a32
Tweaks
pinin4fjords cda7f76
Reset collect.nf to starting state
pinin4fjords e37d4ef
Tweaks
pinin4fjords 2c5f0b6
Revert main.nf to starting point
pinin4fjords 954ce99
Tweaks
pinin4fjords cd45c76
backticks fix?
pinin4fjords c0ec86a
Reset fastp module to starting point
pinin4fjords 51e3e1d
Fix fastp
pinin4fjords e2f3681
tweaks
pinin4fjords 5ae910d
Merge branch 'master' into groovy-essentials-side-quest
pinin4fjords 5913c17
latest tweaks, simplify the variable interpretation part
pinin4fjords 4bf4d05
Reorg subsections a bit
pinin4fjords 4232b7c
Fix highlights
pinin4fjords 93198e5
Fix up dynamic resources
pinin4fjords 7e708a4
Fix up dynamic routing
pinin4fjords a9e3c28
Final tweaks
pinin4fjords ef9124c
Language/ clarity improvements
pinin4fjords 9f1fa17
Prettier
pinin4fjords c4a6eaf
Apply suggestions from code review
pinin4fjords 4d42b2a
Add teach time estimate for groovy
pinin4fjords 124d15c
Merge branch 'groovy-essentials-side-quest' of github.com:nextflow-io…
pinin4fjords 8914150
Fix formatting
pinin4fjords 97bb707
Some section 2 fixes
pinin4fjords 0ef30c6
Fix highlights
pinin4fjords fa8c04d
Fix tiny issues
pinin4fjords b9cd15a
More Groovy fixes
pinin4fjords b8e0c80
update time estimate
pinin4fjords 3b5f11a
Refactor Groovy Essentials to Essential Scripting Patterns
pinin4fjords 9c466b1
Prettier
pinin4fjords 57b2505
Update title to Essential Nextflow Scripting Patterns
pinin4fjords 9f332c5
Apply suggestions from code review
pinin4fjords 2462885
Easy fixes for Ben
pinin4fjords 4793e8d
Iterable -> List
pinin4fjords c6bc005
Fixes up to handlers
pinin4fjords 5d530c9
Fix highlight
pinin4fjords abc25eb
Tighten intro
pinin4fjords be9538d
Merge branch 'master' into groovy-essentials-side-quest
pinin4fjords 0d6022a
Tiny fixes
pinin4fjords 9d56de2
Merge branch 'groovy-essentials-side-quest' of github.com:nextflow-io…
pinin4fjords File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
def sample_ids = ['sample_001', 'sample_002', 'sample_003'] | ||
|
||
// channel.collect() - groups multiple channel emissions into one | ||
ch_input = channel.fromList(sample_ids) | ||
ch_input.view { sample -> "Individual channel item: ${sample}" } | ||
ch_collected = ch_input.collect() | ||
ch_collected.view { list -> "channel.collect() result: ${list} (${list.size()} items grouped into 1)" } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
sample_id,organism,tissue_type,sequencing_depth,file_path,quality_score | ||
SAMPLE_001,human,liver,30000000,data/sequences/SAMPLE_001_S1_L001_R1_001.fastq,38.5 | ||
SAMPLE_002,mouse,brain,25000000,data/sequences/SAMPLE_002_S2_L001_R1_001.fastq,35.2 | ||
SAMPLE_003,human,kidney,45000000,data/sequences/SAMPLE_003_S3_L001_R1_001.fastq,42.1 |
12 changes: 12 additions & 0 deletions
12
side-quests/essential_scripting_patterns/data/sequences/SAMPLE_001_S1_L001_R1_001.fastq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
@sample_001_read_1 | ||
ATGCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC | ||
+ | ||
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | ||
@sample_001_read_2 | ||
GCATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT | ||
+ | ||
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH | ||
@sample_001_read_3 | ||
TCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGA | ||
+ | ||
JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJH |
12 changes: 12 additions & 0 deletions
12
side-quests/essential_scripting_patterns/data/sequences/SAMPLE_002_S2_L001_R1_001.fastq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
@sample_002_read_1 | ||
CGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGAT | ||
+ | ||
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | ||
@sample_002_read_2 | ||
ATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATCG | ||
+ | ||
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH | ||
@sample_002_read_3 | ||
GATCGATCGATCGATCGATCGATCGATCGATCGATCGATCGATC | ||
+ | ||
JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ |
12 changes: 12 additions & 0 deletions
12
side-quests/essential_scripting_patterns/data/sequences/SAMPLE_003_S3_L001_R1_001.fastq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
@sample_003_read_1 | ||
GCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGC | ||
+ | ||
IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII | ||
@sample_003_read_2 | ||
CGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCGCG | ||
+ | ||
HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH | ||
@sample_003_read_3 | ||
ATATATATATATATATATATATATATATATATATATATAT | ||
+ | ||
JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
workflow { | ||
ch_samples = channel.fromPath("./data/samples.csv") | ||
.splitCsv(header: true) | ||
.view() | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
process FASTP { | ||
container 'community.wave.seqera.io/library/fastp:0.24.0--62c97b06e8447690' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
tuple val(meta), path("*_trimmed*.fastq.gz"), emit: reads | ||
|
||
script: | ||
""" | ||
fastp \\ | ||
--in1 ${reads[0]} \\ | ||
--in2 ${reads[1]} \\ | ||
--out1 ${meta.id}_trimmed_R1.fastq.gz \\ | ||
--out2 ${meta.id}_trimmed_R2.fastq.gz \\ | ||
--json ${meta.id}.fastp.json \\ | ||
--html ${meta.id}.fastp.html \\ | ||
--thread $task.cpus | ||
""" | ||
} |
16 changes: 16 additions & 0 deletions
16
side-quests/essential_scripting_patterns/modules/generate_report.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
process GENERATE_REPORT { | ||
|
||
publishDir 'results/reports', mode: 'copy' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
path "${meta.id}_report.txt" | ||
|
||
script: | ||
""" | ||
echo "Processing ${reads}" > ${meta.id}_report.txt | ||
echo "Sample: ${meta.id}" >> ${meta.id}_report.txt | ||
""" | ||
} |
37 changes: 37 additions & 0 deletions
37
side-quests/essential_scripting_patterns/modules/trimgalore.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
process TRIMGALORE { | ||
container 'quay.io/biocontainers/trim-galore:0.6.10--hdfd78af_0' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
tuple val(meta), path("*_trimmed*.fq"), emit: reads | ||
path "*_trimming_report.txt" , emit: reports | ||
|
||
script: | ||
// Simple single-end vs paired-end detection | ||
def is_single = reads instanceof List ? reads.size() == 1 : true | ||
|
||
if (is_single) { | ||
def input_file = reads instanceof List ? reads[0] : reads | ||
""" | ||
trim_galore \\ | ||
--cores $task.cpus \\ | ||
${input_file} | ||
|
||
# Rename output to match expected pattern | ||
mv *_trimmed.fq ${meta.id}_trimmed.fq | ||
""" | ||
} else { | ||
""" | ||
trim_galore \\ | ||
--paired \\ | ||
--cores $task.cpus \\ | ||
${reads[0]} ${reads[1]} | ||
|
||
# Rename outputs to match expected pattern | ||
mv *_val_1.fq ${meta.id}_trimmed_R1.fq | ||
mv *_val_2.fq ${meta.id}_trimmed_R2.fq | ||
""" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Nextflow configuration for Groovy Essentials side quest | ||
|
||
docker.enabled = true |
18 changes: 18 additions & 0 deletions
18
side-quests/solutions/essential_scripting_patterns/collect.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
def sample_ids = ['sample_001', 'sample_002', 'sample_003'] | ||
|
||
// channel.collect() - groups multiple channel emissions into one | ||
ch_input = channel.fromList(sample_ids) | ||
ch_input.view { sample -> "Individual channel item: ${sample}" } | ||
ch_collected = ch_input.collect() | ||
ch_collected.view { list -> "channel.collect() result: ${list} (${list.size()} items grouped into 1)" } | ||
|
||
// List.collect() - transforms each element, preserves structure | ||
def formatted_ids = sample_ids.collect { id -> | ||
id.toUpperCase().replace('SAMPLE_', 'SPECIMEN_') | ||
} | ||
println "List.collect() result: ${formatted_ids} (${sample_ids.size()} items transformed into ${formatted_ids.size()})" | ||
|
||
// Spread operator - concise property access | ||
def sample_data = [[id: 's1', quality: 38.5], [id: 's2', quality: 42.1], [id: 's3', quality: 35.2]] | ||
def all_ids = sample_data*.id | ||
println "Spread operator result: ${all_ids}" |
88 changes: 88 additions & 0 deletions
88
side-quests/solutions/essential_scripting_patterns/main.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
include { FASTP } from './modules/fastp.nf' | ||
include { TRIMGALORE } from './modules/trimgalore.nf' | ||
include { GENERATE_REPORT } from './modules/generate_report.nf' | ||
|
||
def validateInputs() { | ||
// Check input parameter is provided | ||
if (!params.input) { | ||
error("Input CSV file path not provided. Please specify --input <file.csv>") | ||
} | ||
|
||
// Check CSV file exists | ||
if (!file(params.input).exists()) { | ||
error("Input CSV file not found: ${params.input}") | ||
} | ||
} | ||
|
||
def separateMetadata(row) { | ||
def sample_meta = [ | ||
id: row.sample_id.toLowerCase(), | ||
organism: row.organism, | ||
tissue: row.tissue_type.replaceAll('_', ' ').toLowerCase(), | ||
depth: row.sequencing_depth.toInteger(), | ||
quality: row.quality_score?.toDouble() | ||
] | ||
def run_id = row.run_id?.toUpperCase() ?: 'UNSPECIFIED' | ||
sample_meta.run = run_id | ||
def fastq_path = file(row.file_path) | ||
|
||
def m = (fastq_path.name =~ /^(.+)_S(\d+)_L(\d{3})_(R[12])_(\d{3})\.fastq(?:\.gz)?$/) | ||
def file_meta = m ? [ | ||
sample_num: m[0][2].toInteger(), | ||
lane: m[0][3], | ||
read: m[0][4], | ||
chunk: m[0][5] | ||
] : [:] | ||
|
||
def priority = sample_meta.quality > 40 ? 'high' : 'normal' | ||
|
||
// Validate data makes sense | ||
if (sample_meta.depth < 30000000) { | ||
log.warn "Low sequencing depth for ${sample_meta.id}: ${sample_meta.depth}" | ||
} | ||
|
||
return tuple(sample_meta + file_meta + [priority: priority], fastq_path) | ||
} | ||
|
||
workflow { | ||
validateInputs() | ||
|
||
ch_samples = channel.fromPath(params.input) | ||
.splitCsv(header: true) | ||
.map{ row -> separateMetadata(row) } | ||
|
||
// Filter out invalid or low-quality samples | ||
ch_valid_samples = ch_samples | ||
.filter { meta, reads -> | ||
meta.id && meta.organism && meta.depth > 25000000 | ||
} | ||
|
||
trim_branches = ch_valid_samples | ||
.branch { meta, reads -> | ||
fastp: meta.organism == 'human' && meta.depth >= 30000000 | ||
trimgalore: true | ||
} | ||
|
||
ch_fastp = FASTP(trim_branches.fastp) | ||
ch_trimgalore = TRIMGALORE(trim_branches.trimgalore) | ||
GENERATE_REPORT(ch_samples) | ||
|
||
workflow.onComplete = { | ||
println "" | ||
println "Pipeline execution summary:" | ||
println "==========================" | ||
println "Completed at: ${workflow.complete}" | ||
println "Duration : ${workflow.duration}" | ||
println "Success : ${workflow.success}" | ||
println "workDir : ${workflow.workDir}" | ||
println "exit status : ${workflow.exitStatus}" | ||
println "" | ||
|
||
if (workflow.success) { | ||
println "✅ Pipeline completed successfully!" | ||
} else { | ||
println "❌ Pipeline failed!" | ||
println "Error: ${workflow.errorMessage}" | ||
} | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
side-quests/solutions/essential_scripting_patterns/modules/fastp.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
process FASTP { | ||
container 'community.wave.seqera.io/library/fastp:0.24.0--62c97b06e8447690' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
tuple val(meta), path("*_trimmed*.fastq.gz"), emit: reads | ||
path "*.{json,html}" , emit: reports | ||
|
||
script: | ||
// Simple single-end vs paired-end detection | ||
def is_single = reads instanceof List ? reads.size() == 1 : true | ||
|
||
if (is_single) { | ||
def input_file = reads instanceof List ? reads[0] : reads | ||
""" | ||
fastp \\ | ||
--in1 ${input_file} \\ | ||
--out1 ${meta.id}_trimmed.fastq.gz \\ | ||
--json ${meta.id}.fastp.json \\ | ||
--html ${meta.id}.fastp.html \\ | ||
--thread $task.cpus | ||
""" | ||
} else { | ||
""" | ||
fastp \\ | ||
--in1 ${reads[0]} \\ | ||
--in2 ${reads[1]} \\ | ||
--out1 ${meta.id}_trimmed_R1.fastq.gz \\ | ||
--out2 ${meta.id}_trimmed_R2.fastq.gz \\ | ||
--json ${meta.id}.fastp.json \\ | ||
--html ${meta.id}.fastp.html \\ | ||
--thread $task.cpus | ||
""" | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
side-quests/solutions/essential_scripting_patterns/modules/generate_report.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
process GENERATE_REPORT { | ||
|
||
publishDir 'results/reports', mode: 'copy' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
path "${meta.id}_report.txt" | ||
|
||
script: | ||
""" | ||
echo "Processing ${reads}" > ${meta.id}_report.txt | ||
echo "Sample: ${meta.id}" >> ${meta.id}_report.txt | ||
echo "Processed by: \${USER}" >> ${meta.id}_report.txt | ||
echo "Hostname: \$(hostname)" >> ${meta.id}_report.txt | ||
echo "Date: \$(date)" >> ${meta.id}_report.txt | ||
""" | ||
} |
37 changes: 37 additions & 0 deletions
37
side-quests/solutions/essential_scripting_patterns/modules/trimgalore.nf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
process TRIMGALORE { | ||
container 'quay.io/biocontainers/trim-galore:0.6.10--hdfd78af_0' | ||
|
||
input: | ||
tuple val(meta), path(reads) | ||
|
||
output: | ||
tuple val(meta), path("*_trimmed*.fq"), emit: reads | ||
path "*_trimming_report.txt" , emit: reports | ||
|
||
script: | ||
// Simple single-end vs paired-end detection | ||
def is_single = reads instanceof List ? reads.size() == 1 : true | ||
|
||
if (is_single) { | ||
def input_file = reads instanceof List ? reads[0] : reads | ||
""" | ||
trim_galore \\ | ||
--cores $task.cpus \\ | ||
${input_file} | ||
|
||
# Rename output to match expected pattern | ||
mv *_trimmed.fq ${meta.id}_trimmed.fq | ||
""" | ||
} else { | ||
""" | ||
trim_galore \\ | ||
--paired \\ | ||
--cores $task.cpus \\ | ||
${reads[0]} ${reads[1]} | ||
|
||
# Rename outputs to match expected pattern | ||
mv *_val_1.fq ${meta.id}_trimmed_R1.fq | ||
mv *_val_2.fq ${meta.id}_trimmed_R2.fq | ||
""" | ||
} | ||
} |
3 changes: 3 additions & 0 deletions
3
side-quests/solutions/essential_scripting_patterns/nextflow.config
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Nextflow configuration for Groovy Essentials side quest | ||
|
||
docker.enabled = true |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.