Create Reports with App Report Builder

The BaseSpace Report Builder allows you to hook into your output files and generate robust reports for the user. Reports can be created for any application you own in BaseSpace. If a report is not created for an application, the output files will be displayed in a file browser with a default report.

Any app can use the Reports Builder tool, whether is it a Web, Desktop, or Native app.

In the above example, the app has generated two AppResults. The app has the result report enabled and the summary report disabled, so you can see that two reports are visible, one result report for each AppResult. If the summary result was also enabled, it would be listed above the two result reports.

How to Use the Reports Builder

The Reports Builder tool can by used by any apps and will generate reports using HTML, CSS, JS, and the Dotliquid languages.

Here is how a developer would first interact with the Reports Builder tool:

  • The app uploads result files to an AppResult while the developer is still developing the app

  • The developer creates a custom report for the app in the developer portal via the Reports Builder tool, where they can see a live preview of what the report will look like in the UI

  • The developer clicks Activate to activate a custom report for the app. All Completed AppSessions for the app are updated to reflect the new report in the BaseSpace UI

  • The developer or the user may now visit Complete AppSessions (Analyses) in the UI to view Reports for the app

Note: In order to start using the Reports Builder, the app must have an AppSession that is in the Complete status.

After creating an application, simply navigate to "My Apps", select your app, click on "Reports Builder"

You will then be asked to select an AppSession for this app. Select an AppSession from the list with AppResult(s) that contain output files.

If you do not see any AppSessions in the list to select from, you will first have to create an output AppResult, upload files to that AppResult, and complete the AppSession tied to that AppResult. You will then see the AppSession display in this window.

Active Report

You will be taken to the Active Report tab the first time that you visit the Reports Builder tab for an app, you will see a button labelled Start building custom reports. Below this section is a live preview of the report for your app currently. Since the app does not have a form yet, the preview will be a placeholder.

The active form is the form that the user will see in BaseSpace when they launch the app. Once you activate a form for your app, you can view that form directly on BaseSpace!

Revisions

After clicking on Start building custom reports, you will be taken to the Revisions tab. From the Revisions tab, you can manage the Active Report for your app.

There are two reports available by default, the Default Report and the Example Report.

  1. The Default Report is actually not a report. Instead, reports are not generated and the user is just shown the output file browser on the Analysis page.

  2. The Example Report is a very basic example of a report that can be created using the Reports Builder. This report's purpose is to show developers a simple report to make it easier to create complex ones.

In addition to these reports, you will also see a few buttons on the Revisions tab.

Here's a quick description of each button's function:

  • Open: If you select a report in the Revisions tab and click Open, the Reports Builder tool will be opened with the selected Report.
  • Activate: This button will activate the selected report. When a report is in the Inactive state, it can only be viewed in the Reports Builder tab. However, when a report is in the Active state, it will be the report that any user will see when they launch the app from BaseSpace (if they have permission to view the app, of course.) Once a report is Active, it cannot be modified in the Reports Builder tool.
  • Duplicate: Duplicates the selected report. To modify an existing report, we suggest using the Duplicate feature on a pre-existing report, making modifications, then activating the new report to keep a history of report changes.
  • Delete: Deletes the selected report.
  • Load More: The last 10 Custom Reports, not including the Default Report, will be displayed in the Revisions tab. If you wish to view more reports, simply click on this button and the next 10 will also load.

Select a report that is not in the "Active" status and click "Open" to begin creating or editing the report.

A report is created for each AppSession that your app creates in BaseSpace. In addition, the output Report will always contain 'Files' if the app wrote data back to an AppResult in BaseSpace. Please use the Writing Back to BaseSpace Guide to write files back to BaseSpace. AppSessions should be marked as Complete.

Once a report is "Active", it cannot be changed directly. To make changes to an "Active" report, simply copy the existing report, make changes to the copy, then "Activate" the new report. To make changes to an active report, simply select and Activate another report, then you should be able to edit the old report as needed until you Activate it once again.

Report Components

Each report has two components, the Summary and the Result. The report is generated for every AppSession that your app has created.

The Summary Report

One Summary Report is generated per AppSession. It is normally used to provide a general overview of all of the AppResults in the AppSession. A Summary Report is generated for every AppSession, even if the AppSession contains no AppResults.

The Result Report

One Result Report is generated for each AppResult in an AppSession. In general, the Result Report is used to give more detailed information and analysis for each AppResult in the AppSession. If an AppSession has no AppResults, then no Result Reports will be generated.

Enabling and Disabling Summary and Result Reports

You can easily enable of disable both Summary and Result reports for your app. By default, both the Summary and Result reports are enabled. To disable or enable these reports for your application:

  • Open a report for your app in the Reports Builder tool
  • Click on "Options"
  • You will see the following screen:

  • Click "Save" once you've finalized your Options

Report Syntax

Reports are written using a conbination of HTML, CSS, JavaScript, Liquid, and some custom BaseSpace reporting syntax.

HTML, CSS, and Javascript

There are extensive examples of HTML, CSS, and Javascript online, so this guide will not go into the details of these languages. There are a few great resources available for free that cover these three languages in depth. W3Schools and Code Academy.

Twitter Bootstrap

An easy way to get started with HTML and CSS is to use the popular Twitter Bootstrap library, which provides CSS classes and JavaScript plugins for common UI patterns. It also provides a convenient grid system for laying out page content. Combined with the DotLiquid syntax, the BaseSpace Report Builder provides a powerful tool for rendering informatics data in a user-friendly web interface.

The latest version of Twitter Bootstrap is available at http://getbootstrap.com, where you can learn how to use the CSS components and JavaScript plugins.

The BaseSpace-flavored variant of Twitter Bootstrap is hosted publicly. You can copy this HTML snippet into your templates for a quick BaseSpace look and feel:

<link href="https://da1s119xsxmu0.cloudfront.net/libraries/basestrap/1.0.0/css/master.min.css" rel="stylesheet" type="text/css" />

The latest version of Twitter Bootstrap itself (without the BaseSpace theme) is available at:

http://www.bootstrapcdn.com/

Liquid

Liquid is a templating originally written by Shopify and is now open source in many languages including ruby and .NET. BaseSpace reports use the Liquid markup language for templating in the reports. The documentation for Liquid can be viewed here and here.

BaseSpace Reporting Syntax

General

To enumerate a dictionary:

{% for key in mydictionary %}
                The key is {{ key }}
                The value is {{ mydictionary[key] }}
{% endfor %}    

Accessing an array by index number is supported:

{{ myarray[4] }}

BaseSpace Liquid Syntax

Object Filters

Filters can be applied to Liquid statements in the output reports.

Possible filters are:

  • find: for xml files, if this filter is applied, it would take xpath expression and return the resultant xml text. Can be combined with capture to save results.

Example:

{% capture clusPF %}{{ statsFile | find: "/StatisticsAmplicon/OverallSamples/SummarizedSampleStatisics[SampleID='10002 - R1']/NumberOfClustersPF" }}{% endcapture %}

In the above example, the find filter is applied within the capture statement in Liquid, and is looking for a particular file.

  • stringify: serializes the liquid object into json string. Does not work with all objects, please post on the google groups if there is an object you would like supported with stringify that is not already. Enables you to parse JSON exclusively within your reports.

Example:

<script type="text/javascript">
                          var globals = {};
                          globals.sample = {{ sample | stringify }};
                          globals.sample.chromosomes = {{ sample.chromosomes | stringify }};
                          globals.sample.statsByChromosome = {{ statsFile.parse.StatisticsResequencing.Samples.SampleStatistics | stringify }};
           </script>

The above statement will take the results returned from the javascript and stringify the sample.chromosomes field which would likely be a list of chromosomes for that Sample. This object can now be parsed like normal JSON.

Dictionary Filters and Operators

The following are a suite of operators that allow filtering of the dictionary of files exposed in the Report Builder tool from your analysis' results. These can be applied on the name or directory of the files available.

Dictionary Filters/Operators:

  • where.starts_with - returns the values where the dictionary key starts with provided string
  • where.ends_with - returns the values where the dictionary key ends with provided string
  • where.contains - returns the values where the dictionary key contains the provided string
  • first - returns the first value, will throw an exceiption if nothing is there
  • first_or_default - returns the first or default value (usually null)

Example 1:

The following example will return the first file in the result that starts_with datafile1.

{% assign_object datafile1 = result.files.where.starts_with["datafile1"].first %}

Example 2:

The following example will return the first_or_default file that ends_with .xml in the result.

{% assign_object anyXml = result.files.where.ends_with[".xml"].first_or_default %}

Example 3:

The following example will return the first file in the result that contains file3 anywhere in its name.

{% assign_object datafile3 = result.files.where.contains["file3"].first %}

Break and Continue Tags

The break and continue Liquid tags provide the ability to continue or break any loop in Liquid within the Report Builder.

For example:

{% for file in files %}
    {% if file.href == null %}
        {% continue %}
    {% endif %}

    {% if file.href == 'http://special.com' %}
        {% assign specialHref = file.href %}
        {% break %}
    {% endif %}
{% endfor %}

The above statement would do the following:

  • If there were no files in the result (first part of the for loop), the logic would break one iteration through the for loop if the specified condition (file.href == null) occurred, then it would continue with the next iteration of the loop.
  • If the condition for the break statement is reached (if the file's href is http://special.com), the break statement will exit this for loop entirely and continue with the rest of the code outside of this loop if there is any

Interrupt Exception

If the condition in the interrupt_exception statement is met, then BaseSpace will stop all execition of this report template and throw an error to the user.

Example:

The following example will throw an exception on the report if any file that ends with .csv has a size that is less than 1 byte

{% if result.files.where.ends_with[".csv"] | size < 1 %}
    ERROR: no csv found for this result. Halting execution of template
    {{ errors.interrupt_exception }}
{% endif %}

AppResult

Built-in Variables:

result.id

To return Id of the AppResult

{{ result.id }}

result.name

To return Name of the AppResult

{{ result.name }}

result.session

To return Information about the AppSession that this AppResult is linked to

{{ result.session }}

result.samples

To return all of the Samples related to this AppResult

{{ result.samples }}

result.files

To return a list of all Files in an AppResult

{{ result.files }}

AppSession

session.id

To return the Id of the AppSession

{{ session.id }}

session.name

The Name of the AppSession

{{ session.name }}

session.results

A list of all of the AppResults tied to this AppSession

{{ session.results }}

Samples

Collection of Samples

If Samples were specified as the input for an AppResult via the AppSession, the following variables are available:

result.samples[index]

The number of Samples that were used as input:

{{ result.samples[index] }}

where index is a 0-based integer

result.samples["sample_id"]

A list of Samples tied to the AppResult. sample_id is the Sample Id as it appears on the Samplesheet:

{{ result.samples["sample_id"] }}

Accessing Sample Objects

samples["index"].id

The Id of the Sample at the specified index that was tied to the AppResult

To return the Id of the first Sample in the list:

{{ samples[0].id }}

samples["index"].name

The Name of the Sample at the specified index

To return the Name of the first Sample in the list:

{{ samples[0].name }}

samples["index"].sample_id

The Samplesheet Sample Id of the Sample at the specified index

To return the Samplesheet Sample Name of the first Sample in the list:

{{ samples[0].sample_id }}

samples["index"].status

The Status of the Sample

To return the Status of the first Sample in the list:

{{ samples[0].status }}

samples["index"].date_created

The date on which the Sample was created in UTC

To return the Date on which the first Sample in the list was created:

{{ samples[0].date_created }}

samples["index"].experiment_name

The name of the originating Run that created this Sample

To return the originating Run name of the first Sample in the list:

{{ samples[0].experiment_name }}

samples["index"].ispairedend

Whether or not this Sample has paired-end reads

To determine whether the first Sample in the list is paired end:

{{ samples[0].is_paired_end }}

samples["index"].read1

Number of read cycles for the first read

To return the number of read cycles for the first read of the first Sample in the list:

{{ samples[0].read1 }}

samples["index"].read2

Number of read cycles for the second read

To return the number of read cycles for the second read of the first Sample in the list:

{{ samples[0].read2 }}

samples["index"].numreadsraw

Number of bases that were read

To determine the number of bases read in the first Sample in the list:

{{ samples[0].num_reads_raw }}

samples["index"].numreadspf

Number of bases that were read that passed filter

To determine the number of bases read that passed filter in the first Sample in the list:

{{ samples[0].num_reads_pf }}

samples["index"].href

The URI to this Sample in BaseSpace

To return the Href of the first Sample in the list:

{{ samples[0].href }}

If a genome was provided for a sample, the following variables are available:

samples["index"].genome.name

The Name of this Sample's Genome, if provided

To return the Genome name of the first Sample in the list:

{{ samples[0].genome.name }}

samples["index"].genome.display_name

The Display Name for this Sample's Genome

To return the Genome display name of the first Sample in the list:

{{ samples[0].genome.display_name }}

samples["index"].genome.path

The Path of this Sample's Genome

To return the Genome's path in BaseSpace of the first Sample in the list:

{{ samples[0].genome.path }}

samples["index"].genome.source

The Source of this Sample's Genome

To return the Genome source of the first Sample in the list:

{{ samples[0].genome.source }}

samples["index"].genome.species

The Species of this Sample's Genome

To return the Genome species of the first Sample in the list:

{{ samples[0].genome.species }}

samples["index"].genome.build

The Build of this Sample's Genome

To return the Genome build of the first Sample in the list:

{{ samples[0].genome.build }}

Files

result.files[index]

The index of the files in the AppResult. This will essentially show how many files are in the AppResult

{{ result.files["index"] }}

result.files[path]

The Path to the File in this AppResult in BaseSpace

{{ result.files["path"] }}

result.files[“myfile”].href

Get the URL to a file with .href like this:

{{ result.files["myfile"].href }}

result.files[“myfile”].content

Get the content of a file with .content like this:

{{ result.files["myfile"].content }}

result.files[“myfile”].parse

To access the data structure of a file, it must first be explicitly parsed like this:

{{ result.files["myfile"].parse }}

This is auto-determined by file extension:

.json file extension will be read in as a json file
.xml read in as xml file
.csv read in as csv file
.tab, .tsv read in as tab separated file

Before parsing, various options can be added to the file. These options can be chained in any order before parse. Options:

Noheader
skiplines
Json
Xml
Csv
Tsv

Noheader

Noheader must be used when a csv or tsv file has no header. Ex:

{{ result.files[“myfile.csv”].noheader.parse }}

Skiplines

Skiplines is used for files that have informational text at the beginning of the file which can cause parsing to fail. This will ignore the first n lines of the file for parsing.

{{ result.files[“myfile.csv”].skiplines[5].parse }}

Json, Xml, Csv, and Tsv

For cases where the extension doesn’t match the file type, like in .txt files, use:

For json: {{ result.files[“myfile”].json.parse }}
For xml: {{ result.files[“myfile”].xml.parse }}
For csv: {{ result.files[“myfile”].csv.parse }}
For tab separated: {{ result.files[“myfile”].tsv.parse }}

All of these options can be chained in any order before the parse:

{{ result.files[“myfile”].noheader.skiplines[2].csv.parse }}
{{ result.files[“myfile”].noheader.csv.skiplines[2].parse }}
{{ result.files[“myfile”].skiplines[2].csv.noheader.parse }}

Select Columns (csv only)

The select statement allows the selection of specific columns in a .csv file by the column name or index.

The take statement determines how many rows from the selected column(s) should be returned.

Example 1:

The following is an example of parsing the columns labelled 0, 1, and 2 to return the first value from each column

{% assign grid2 = result.files[key].select['0,1,2'].take[1].parse %}

Example 2:

The following is an example of parsing the columns labelled LastName, City, and Phone to return the first 5 values from each column

{% assign grid3 = result.files[key].select['LastName','City','Phone'].take[5].parse %}

To Array (csv only)

The to_array statement allows the output of data from a .csv file to a 2-dimensional data array

Example:

The following example will take the files that match the specified key value and parse them into an array that is stringified into JSON

{{ result.files[key].parse.to_array | stringify }}

The output would look like JSON created from a .csv file.

Take (csv only)

The take statement provides the ability to take a subset of rows from a .csv file

Example:

The following example will return the values from the first row in the first three columns:

{% assign grid2 = result.files[key].select['0,1,2'].take[1].parse %}

Example 1 Parsing an XML file

Assume that there is only one file named “testresultfolder/foo.xml” in the result with the following content:

<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
</book>
</catalog>

With Liquid template:

{% for filename in result.files %}
File path name: {{ filename }}
    {% for book in result.files[filename].parse.catalog.book %}
            Book Id: {{ book['@id'] }}
            Author: {{ book.author }}
    {% endfor %}
{% endfor %}

Note that result.files is a dictionary, with filename as key. Enumerating the dictionary returns the keys. Note that catalog in result.files[filename].catalog is an array, as can be seen in the XML Note that the book id is accessed like this: book[“@id”]. That is because it is an attribute as can be seen in the XML.

The output should be:

File path name: testresultfolder/foo.xml
Book Id: bk101
Author: Gambardella, Matthew
Book Id: bk102
Author: Ralls, Kim

This will also generate the same result:

{% for book in result.files[“testresultfolder/foo.xml”].parse.catalog.book %}
Book Id: {{ book['@id'] }}
Author: {{ book.author }}
{% endfor %}

If the foo.xml has a different extension, like foo.txt, this will also generate the same result:

{% for book in result.files[“testresultfolder/foo.txt”].xml.parse.catalog.book %}
Book Id: {{ book['@id'] }}
Author: {{ book.author }}
{% endfor %}

Example 2 csv files

Assume that there is only one file named “testresultfolder/bar.csv” in the result with the following content:

Title,Author    
XML Developer's Guide, Matt
Midnight Rain, Kim

The following template:

{% for column_name in result.files[“testresultfolder/bar.csv”].parse.column_names %}
{{column_name}}
{% endfor %}

{% for row in result.files[“testresultfolder/bar.csv”].parse.rows %}
Title: {{ row[“Title”] }}
Author: {{ row[“Author”] }}
{% endfor %}

Will produce the following output:

Title
Author
XML Developer's Guide
Matt
Midnight Rain
Kim

The following template will generate the same output using column indexers:

{% for column_name in result.files[“testresultfolder/bar.csv”].parse.column_names %}
{{column_name}}
{% endfor %}

{% for row in result.files[“testresultfolder/bar.csv”].parse.rows %}
Title: {{ row[0] }}
Author: {{ row[1] }}
{% endfor %}

Example 3 csv files with informational text at the beginning of the file and txt extension

Assume that there is only one file named “testresultfolder/wibble.txt” in the result with the following content:

This file is generated by Book Tracker Extreme Pro Edition v14.5 gamma 3 build 92646
Title,Author
XML Developer's Guide, Matt
Midnight Rain, Kim

The following template:

{% for column_name in result.files[“testresultfolder/wibble.txt”].skiplines[1].csv.parse.column_names %}
{{column_name}}
{% endfor %}

{% for row in result.files[“testresultfolder/wibble.txt”].csv.skiplines[1].parse.rows %}
Title: {{ row[“Title”] }}
Author: {{ row[“Author”] }}
{% endfor %}

Will produce the following output:

Title
Author
XML Developer's Guide
Matt
Midnight Rain
Kim

Example 4 csv files with no header

Assume that there is only one file named “testresultfolder/baz.csv” in the result with the following content:

XML Developer's Guide, Matt
Midnight Rain, Kim

The following template:

{% for column_name in result.files[“testresultfolder/baz.csv”].noheader.parse.column_names %}
{{column_name}}
{% endfor %}

{% for row in result.files[“testresultfolder/baz.csv”].noheader.parse.rows %}
Title: {{ row[“column0”] }}
Author: {{ row[“column1”] }}
{% endfor %}

Will produce the following output:

column0
column1
XML Developer's Guide
Matt
Midnight Rain
Kim

The following template will generate the same output using column indexers:

{% for column_name in result.files[“testresultfolder/baz.csv”].noheader.parse.column_names %}
{{column_name}}
{% endfor %}

{% for row in result.files[“testresultfolder/bar.csv”].parse.rows %}
Title: {{ row[0] }}
Author: {{ row[1] }}
{% endfor %}

Example 5 JSON files

Assume that there is only one file in the output for the analysis called statisticsFile.json and the contents of this file are the following:

{ 
    SampleName:"104050_13",
    MLST:{ 
        SequenceType:"147",
        AlleleType:{ 
            gapA:"3",
            infB:"4",
            mdh:"6",
            pgi:"1",
            phoE:"7",
            rpoB:"4",
            tonB:"38"
        },
        Mismatches:"0",
        Uncertainty:"-",
        Depth:"35.7074285714",
        MaxMAF:"0.0909090909091"
    }
}

To return all of the keys for the AlleleType object in the JSON above, simply use the following:

{% for key in result.files[statisticsFile].Json.parse.MLST.AlleleType %}
    <p>{{ key }}</p>
{% endfor %}

Which would return the following:

<p>gapA</p>
<p>infB</p>
<p>mdh</p>
<p>pgi</p>
<p>phoE</p>  
<p>rpoB</p>
<p>tonB</p>

Then, to return the values for each of these keys we would use the following:

{% for key in result.files[statisticsFile].Json.parse.MLST.AlleleType %}
    <p>{{ result.files[statisticsFile].Json.parse.MLST.AlleleType[key] }}</p>
{% endfor %}

This would return the following:

<p>3</p>
<p>4</p>
<p>6</p>
<p>1</p>
<p>7</p>  
<p>4</p>
<p>38</p>

BaseSpace Charts

Ever wondered how we were able to create the charts that you see in BaseSpace? If you have not had a chance to view this already, check out the charts for the BacillusCereus Public Dataset in BaseSpace.

We use the d3.js (Data Driven Documents) Javascript charting library to dynamically create these charts from data in BaseSpace. D3 charts can take as input data from a file in the output AppResult or from another Web service.

For more information on D3 Charts, there is extensive documentation available on their website, which can be found here.

There are also some guides and tutorials available, here is an example of one that we recommend: Dashing D3.js.

Example Reports

Isaac v2

This example report is the report for the Isaac native application in BaseSpace. The css files in the example are example css files, so the formatting may look different when previewing in your own report builder. Please considering using a public css library or creating your own.

Report UI

Code

Summary Report

This report does not contain a Summary report. Normally, a summary report would be shown above the Result report, but if it is empty it will not display.

Result Report

The Result report for this app is what is shown in the screenshot above, here is the code for the report:

<!-- begin header subtemplate -->
<!doctype html>
<html>
<head>
    <!-- css includes -->
    <link href="master.css" rel="stylesheet" />

    <link href="slave.css" rel="stylesheet" />

    <!-- JS Libraries -->

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
    <script src="http://placeholder.url.com/jshashtable-2.1.js"></script>
    <script src="http://placeholder.url.com/jquery.numberformatter-1.2.3.min.js"></script>
    <script src="http://placeholder.url.com/underscore-min.js"></script>
    <title>Report</title>

    <style>
        #coverage-histogram-container {
            height: 320px;
        }
    </style>
</head>

<body>
<div class="container">
<!-- end header subtemplate -->
    <div class="row-fluid">
        <h1 class="col-span-12">
            {% for sampleKey in result.samples %}
            <a href="{{ result.samples[sampleKey].href }}"> {{ result.samples[sampleKey].name }}</a></h1>
            {% endfor %}

        </h1>
    </div>

    {% for key in result.files %}
    {% if key contains 'ResequencingRunStatistics.xml' %}

    {% assign totalReads = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['NumberOfClustersPF'] %}
    {% assign clustersAlignedR1 = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['ClustersAlignedR1'] %}
    {% assign clustersAlignedR2 = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['ClustersAlignedR2'] %}
    {% assign weightedCoverage = result.files[key].parse.StatisticsResequencing.RunStats.['OverallCoverage'] %}
    {% assign fragmentLengthMedian = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['FragmentLengthMedian']%}
    {% assign fragmentLengthSD = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['FragmentLengthSD'] %}
    {% assign errorRateR1 = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['ErrorRateR1'] %}
    {% assign errorRateR2 = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics['ErrorRateR2'] %}

    {% assign sNPsNumPassingVariants = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.SNPs['NumPassingVariants'] %}
    {% assign sNPHeterozygoteToHomozygoteRatio = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.SNPs['HeterozygoteToHomozygoteRatio'] %}
    {% assign sNPTransitionTransversionRatio = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants['TransitionTransversionRatio'] %}

    {% assign variantsNumPassingVariants = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.Insertions['NumPassingVariants'] %}
    {% assign variantsHeterozygoteToHomozygoteRatio = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.Insertions['HeterozygoteToHomozygoteRatio'] %}

    {% assign deletionsNumPassingVariants = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.Deletions['NumPassingVariants'] %}
    {% assign deletionsHeterozygoteToHomozygoteRatio = result.files[key].parse.StatisticsResequencing.OverallSamples.SummarizedSampleStatisics.Variants.Deletions['HeterozygoteToHomozygoteRatio'] %}

    {% if totalReads != 0 %}
        {% assign percentAlignedR1 = clustersAlignedR1 | divided_by: totalReads | times: 100 %}
        {% assign percentAlignedR2 = clustersAlignedR2 | divided_by: totalReads | times: 100 %}
    {% else %}
        {% assign percentAlignedR1 = "N/A" %}
        {% assign percentAlignedR2 = "N/A" %}
    {% endif %}
    <div class="row-fluid">
        <span class="col-span-6">
            <section class="bs-panel">
                <hgroup>
                    <h5>Alignment Statistics</h5>
                </hgroup>
                <table class="table bs-table bs-table-justify">
                    <tbody>
                        <tr>
                            <th>Number of reads</th>
                            <td>{{ totalReads }}</td>
                        </tr>
                        <tr>
                            <th>Coverage</th>
                            <td>{{ weightedCoverage }}</td>
                        </tr>
                        {% if result.samples[0].is_paired_end %}
                        <tr>
                            <th>Fragment Length Median</th>
                            <td>{{ fragmentLengthMedian }}</td>
                        </tr>
                        <tr>
                            <th>Fragment Length Standard Deviation</th>
                            <td>{{ fragmentLengthSD }}</td>
                        </tr>
                        {% endif %}
                    </tbody>
                </table>
                <table class="table bs-table bs-table-justify">
                    <thead>
                        <tr><th colspan="2">Read 1</th></tr>
                    </thead>
                    <tbody>
                        <tr>
                            <th>Aligned (%)</th>
                            <td>{{percentAlignedR1}}</td>
                        </tr>
                        <tr>
                            <th>Mismatch (%)</th>
                            <td>{{errorRateR1}}</td>
                        </tr>
                    </tbody>
                </table>
                {% if result.samples[0].is_paired_end %}
                <table class="table bs-table bs-table-justify">
                    <thead>
                        <tr><th colspan="2">Read 2</th></tr>
                    </thead>
                    <tbody>
                        <tr>
                            <th>Aligned (%)</th>
                            <td>{{percentAlignedR2}}</td>
                        </tr>
                        <tr>
                            <th>Mismatch (%)</th>
                            <td>{{errorRateR2}}</td>
                        </tr>
                    </tbody>
                </table>
                {% endif %}
            </section>
        </span>

        <span class="col-span-6">
            <section class="bs-panel">
                <hgroup>
                    <h5>Variant Statistics</h5>
                </hgroup>
                    <table class="table bs-table bs-table-justify">
                        <thead>
                            <tr>
                                <th class="table-header" colspan="2">SNVs</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <th>Total number</th>
                                <td>{{sNPsNumPassingVariants}}</td>
                            </tr>
                            <tr>
                                <th>Het/Hom Ratio</th>
                                <td>{{sNPHeterozygoteToHomozygoteRatio}}</td>
                            </tr>
                            <tr>
                                <th>Transitions / Transversions</th>
                                <td>{{sNPTransitionTransversionRatio}}</td>
                            </tr>
                        </tbody>
                    </table>
                    <table class="table bs-table bs-table-justify">
                        <thead>
                            <tr>
                                <th class="table-header" colspan="2">Insertions</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <th>Total number</th>
                                <td>{{variantsNumPassingVariants}}</td>
                            </tr>
                            <tr>
                                <th>Het/Hom Ratio</th>
                                <td>{{variantsHeterozygoteToHomozygoteRatio}}</td>
                            </tr>
                        </tbody>
                    </table>
                    <table class="table bs-table bs-table-justify" >
                        <thead>
                            <tr>
                                <th class="table-header" colspan="2">Deletions</th>
                            </tr>
                        </thead>
                        <tbody>
                            <tr>
                                <th>Total number</th>
                                <td>{{deletionsNumPassingVariants}}</td>
                            </tr>
                            <tr>
                                <th>Het/Hom Ratio</th>
                                <td>{{deletionsHeterozygoteToHomozygoteRatio}}</td>
                            </tr>
                        </tbody>
                    </table>
            </section>
        </span>
    </div>
    {% endif %}
    {% endfor %}

    {% for key in result.files %}
    {% if key contains 'CoverageHistogram.txt' %}
    <br/>
    <hr class="clearfix" />
    <div class="row">
        <span class="col-span-12">
            <section class="bs-panel">
                <hgroup>
                    <h5>Coverage Histogram</h5>
                    <div class="bs-toolbar">
                        <a href="{{ result.files[key].href }}" target="_blank"><label>Export (TSV)</label></a>
                    </div>
                </hgroup>

                <div class="padded clearfix">
                    <span class="col-span-2">
                        <select id="chr-select"></select>
                    </span>
                    <span class="col-span-2">
                        <input id="fix-scale" type="checkbox" /> <label>Fix Y Scale</label>
                    </span>
                </div>


                <div id="coverage-histogram-container">
                    <!-- d3 chart goes here -->
                </div>

                <div id="coverage-histogram-data" style="display:none">{{ result.files[key].content }}</div>

                <script>
                    $(document).ready(function() {
                        parseCoverage("#coverage-histogram-container", $("#coverage-histogram-data").text());
                    });
                </script>
            </section>
        </span>
    </div>

    <script>
    <!-- d3 coverage chart -->
    </script>

    {% endif %}
    {% endfor %}

    <script>
        $(document).ready(function() {
            $(".bs-table td").each(function() {
                var floatRegex = /\d+\./;
                if ($(this).text().match(floatRegex)) {
                    $(this).formatNumber({format:"#,##0.00", locale:"us"});
                }
            });
        });
    </script>
    </div>
</body>