Skip to content

Commit

Permalink
Merge pull request #913 from aanil/revamp_reports
Browse files Browse the repository at this point in the history
Add Yggdrasil reports to project page
  • Loading branch information
aanil authored Jan 20, 2025
2 parents b029e9d + 07ba459 commit 4fcd8f7
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 76 deletions.
31 changes: 21 additions & 10 deletions run_dir/design/project_samples_old.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,34 @@ <h4>User project description
<li class="nav-item"><a class="nav-link" href="#tab_running_notes_content" role="tab" data-toggle="tab">Running Notes</a></li>
<li class="nav-item"><a class="nav-link" href="#tab_com_content" role="tab" data-toggle="tab" id="tab_communication">User communication</a></li>
<li class="nav-item"><a class="nav-link" href="#tab_links_content" role="tab" data-toggle="tab">Links</a></li>
{% if not multiqc %}
<li class="nav-item" data-toggle="tooltip" data-html="true" data-placement="bottom" title="No MultiQC report available" ><a class="nav-link disabled">Open MultiQC reports</a></li>
{% else %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">Open MultiQC reports</a>
<div class="dropdown-menu">
{% for key in multiqc %}
<li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" id="reports_dropdown">Reports</a>
<div class="dropdown-menu" aria-labelledby="reports_dropdown">
{% if not reports%}
<a class="dropdown-item" href="#" disabled>No reports to show</a>
{% end %}
{% if 'multiqc' in reports%}
<h6 class="dropdown-header">MultiQC Reports</h6>
{% for key in reports['multiqc'] %}
{% if 'qc' in key %}
{% set rep_name = ' QC' %}
{% else %}
{% set rep_name = ' '+key.strip('_').capitalize() %}
{% end %}
<a class="dropdown-item" href="/multiqc_report/{{ project }}?type={{ key }}" target="_blank">Open{{ rep_name }} MultiQC report</a>
{% end %}
</div>
</li>
{% end %}
{% end %}
{% if 'project_summary' in reports %}
<h6 class="dropdown-header">Project Summary Report</h6>
<a class="dropdown-item" href="/proj_summary_report/{{ project }}" target="_blank">Open Project Summary report</a>
{% end %}
{% if 'sample_summary_reports' in reports %}
<h6 class="dropdown-header">Single Cell Sample Summary Reports</h6>
{% for sample in reports['sample_summary_reports'] %}
<a class="dropdown-item" href="/singlecell_sample_summary_report/{{ project }}/{{ sample }}" target="_blank"> {{ sample}} </a>
{% end %}
{% end %}
</div>
</li>
<li class="nav-item"><a class="nav-link" href="/bioinfo/{{ project }}" target="_blank">Open bioinfo tab</a></li>
<li class="nav-item"><a class="nav-link" href="#tab_agreements_content" role="tab" data-toggle="tab" id="tab_agreements">Agreements</a></li>
</ul>
Expand Down
5 changes: 3 additions & 2 deletions status/bioinfo_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import dateutil

from status.reports import MultiQCReportHandler
from status.util import SafeHandler


Expand Down Expand Up @@ -282,8 +283,8 @@ def get(self, project_id):
if application in app_classes[key]:
application = key
break
# to check if multiqc report exists (get_multiqc() is defined in util.BaseHandler)
multiqc = self.get_multiqc(project_id) or ""

multiqc = MultiQCReportHandler.get_multiqc(self.application, project_id) or ""
self.write(
t.generate(
gs_globals=self.application.gs_globals,
Expand Down
6 changes: 4 additions & 2 deletions status/flowcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ def __init__(self, application, request, **kwargs):
super(SafeHandler, self).__init__(application, request, **kwargs)

def get(self, name):
reports_dir = self.application.minknow_reports_path
reports_dir = os.path.join(self.application.reports_path, "minknow_reports")
report_path = os.path.join(reports_dir, f"report_{name}.html")

self.write(open(report_path).read())
Expand All @@ -483,7 +483,9 @@ def __init__(self, application, request, **kwargs):
super(SafeHandler, self).__init__(application, request, **kwargs)

def get(self, name):
reports_dir = self.application.toulligqc_reports_path
reports_dir = os.path.join(
self.application.reports_path, "other_reports", "toulligqc_reports"
)
report_path = os.path.join(reports_dir, f"report_{name}.html")

self.write(open(report_path).read())
Expand Down
20 changes: 0 additions & 20 deletions status/multiqc_report.py

This file was deleted.

33 changes: 29 additions & 4 deletions status/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
from ibm_cloud_sdk_core.api_exception import ApiException
from zenpy import ZenpyException

from status.reports import (
MultiQCReportHandler,
ProjectSummaryReportHandler,
SingleCellSampleSummaryReportHandler,
)
from status.util import SafeHandler, dthandler

lims = lims.Lims(BASEURI, USERNAME, PASSWORD)
Expand Down Expand Up @@ -689,7 +694,9 @@ def project_info(self, project, view_with_sources=False):
"_qc_": "QC MultiQC",
"_pipeline_": "Pipeline MultiQC",
}
for report_type in self.get_multiqc(project, read_file=False).keys():
for report_type in MultiQCReportHandler.get_multiqc(
self.application, project, read_file=False
).keys():
# Attempt to assign a name of the report type, otherwise default to the type itself
report_name = type_to_name.get(report_type, report_type)
reports[report_name] = f"/multiqc_report/{project}?type={report_type}"
Expand Down Expand Up @@ -946,8 +953,26 @@ def get(self, project):
worksets_view = self.application.worksets_db.view(
"project/ws_name", descending=True
)
# to check if multiqc report exists (get_multiqc() is defined in util.BaseHandler)
multiqc = list(self.get_multiqc(project).keys())

reports = {}
multiqc = list(
MultiQCReportHandler.get_multiqc(
self.application, project, read_file=False
).keys()
)
if multiqc:
reports["multiqc"] = multiqc
if ProjectSummaryReportHandler.get_summary_report(
self.application, project, read_file=False
):
reports["project_summary"] = True
sample_summary_reports = (
SingleCellSampleSummaryReportHandler.get_sample_summary_report(
self.application, project
)
)
if sample_summary_reports:
reports["sample_summary_reports"] = sample_summary_reports
self.write(
t.generate(
gs_globals=self.application.gs_globals,
Expand All @@ -958,7 +983,7 @@ def get(self, project):
lims_dashboard_url=self.application.settings["lims_dashboard_url"],
prettify=prettify_css_names,
worksets=worksets_view[project],
multiqc=multiqc,
reports=reports,
lims_uri=BASEURI,
)
)
Expand Down
166 changes: 166 additions & 0 deletions status/reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import os
from typing import Any, Optional, Union

from status.util import SafeHandler


class MultiQCReportHandler(SafeHandler):
def get(self, project: str) -> None:
report_type = self.get_argument("type")
multiqc_report = self.get_multiqc(self.application, project)
if multiqc_report:
self.write(multiqc_report[report_type])
else:
t = self.application.loader.load("error_page.html")
self.write(
t.generate(
gs_globals=self.application.gs_globals,
status="404",
reason="MultiQC Report Not Found",
user=self.get_current_user(),
)
)

@staticmethod
def get_multiqc(
app: Any, project_id: str, read_file: bool = True
) -> Union[str, dict, None]:
"""
Getting multiqc reports for requested project from the filesystem
Returns a string containing html if report exists, otherwise None
If read_file is false, the value of the dictionary will be the path to the file
"""

project_name = ""
multiqc_reports = {}
query_res = app.cloudant.post_view(
db="projects", ddoc="projects", view="id_to_name", key=project_id
).get_result()
if query_res["rows"]:
project_name = query_res["rows"][0]["value"]

if project_name:
multiqc_path = os.path.join(app.reports_path, "mqc_reports") or ""
for report_type in ["_", "_qc_", "_pipeline_"]:
multiqc_name = f"{project_name}{report_type}multiqc_report.html"
multiqc_file_path = os.path.join(multiqc_path, multiqc_name)
if os.path.exists(multiqc_file_path):
if read_file:
with open(multiqc_file_path, encoding="utf-8") as multiqc_file:
html = multiqc_file.read()
multiqc_reports[report_type] = html
else:
multiqc_reports[report_type] = multiqc_file_path
return multiqc_reports


class ProjectSummaryReportHandler(SafeHandler):
"""Handler for project summary reports generated using yggdrasil"""

def get(self, project_id: str) -> None:
report = self.get_summary_report(self.application, project_id)
if report:
self.write(report)
else:
t = self.application.loader.load("error_page.html")
self.write(
t.generate(
gs_globals=self.application.gs_globals,
status="404",
reason="Project Summary Report Not Found",
user=self.get_current_user(),
)
)

@staticmethod
def get_summary_report(
app: Any, project_id: str, read_file: bool = True
) -> Union[str, bool, None]:
"""If read_file is false, the function will return True if the file exists, otherwise None
If read_file is True, it returns a string containing the report in html if it exists"""
project_name = ""

query_res = app.cloudant.post_view(
db="projects", ddoc="projects", view="id_to_name", key=project_id
).get_result()

if query_res["rows"]:
project_name = query_res["rows"][0]["value"]

if project_name:
report_path = os.path.join(
app.reports_path,
"yggdrasil",
project_id,
f"{project_name}_project_summary.html",
)
if os.path.exists(report_path):
if read_file:
with open(report_path, encoding="utf-8") as report_file:
return report_file.read()
else:
return True
else:
return None


class SingleCellSampleSummaryReportHandler(SafeHandler):
"""Handler for Single Cell sample summary reports generated using yggdrasil"""

def get(self, project_id: str, sample_id: str) -> None:
report = self.get_sample_summary_report(
self.application, project_id, sample_id=sample_id
)
if report:
self.set_header("Content-Type", "application/pdf")
self.set_header(
"Content-Disposition",
f"inline; filename={sample_id}_single_cell_sample_summary_report.pdf",
)
self.write(report)
else:
t = self.application.loader.load("error_page.html")
self.write(
t.generate(
gs_globals=self.application.gs_globals,
status="404",
reason="Single Cell Sample Summary Report Not Found",
user=self.get_current_user(),
)
)

@staticmethod
def get_sample_summary_report(
app: Any, project_id: str, sample_id: Optional[str] = None
) -> Union[bytes, list[str], None]:
"""Returns a list of sample summary reports for the requested project if sample_id is None,
otherwise returns the report for the requested sample"""

sample_summary_reports_path = os.path.join(
app.reports_path, "yggdrasil", project_id
)
if sample_id:
report_path = os.path.join(
sample_summary_reports_path, sample_id, f"{sample_id}_report.pdf"
)
if os.path.exists(report_path):
with open(report_path, "rb") as report_file:
return report_file.read()
else:
return None

else:
reports = []
if os.path.exists(sample_summary_reports_path):
for item in os.listdir(sample_summary_reports_path):
if os.path.isdir(
os.path.join(sample_summary_reports_path, item)
) and item.startswith(f"{project_id}_"):
if os.path.exists(
os.path.join(
sample_summary_reports_path, item, f"{item}_report.pdf"
)
):
reports.append(item)

return reports
29 changes: 0 additions & 29 deletions status/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,35 +159,6 @@ def write_error(self, status_code, **kwargs):
)
)

def get_multiqc(self, project_id, read_file=True):
"""
Getting multiqc reports for requested project from the filesystem
Returns a string containing html if report exists, otherwise None
If read_file is false, the value of the dictionary will be the path to the file
"""
view = self.application.projects_db.view("project/id_name_dates")
rows = view[project_id].rows
project_name = ""
multiqc_reports = {}
# get only the first one
for row in rows:
project_name = row.value.get("project_name", "")
break

if project_name:
multiqc_path = self.application.multiqc_path or ""
for type in ["_", "_qc_", "_pipeline_"]:
multiqc_name = f"{project_name}{type}multiqc_report.html"
multiqc_file_path = os.path.join(multiqc_path, multiqc_name)
if os.path.exists(multiqc_file_path):
if read_file:
with open(multiqc_file_path, encoding="utf-8") as multiqc_file:
html = multiqc_file.read()
multiqc_reports[type] = html
else:
multiqc_reports[type] = multiqc_file_path
return multiqc_reports

@staticmethod
def get_user_details(app, user_email):
user_details = {}
Expand Down
Loading

0 comments on commit 4fcd8f7

Please sign in to comment.