Skip to content

Commit 44bd599

Browse files
authored
Merge pull request #52 from PennChopMicrobiomeProgram/48-add-submissions-view-for-each-project-page
Allow bad metadata with warnings and add submissions view on new submission form
2 parents f77a486 + a57afbb commit 44bd599

File tree

7 files changed

+125
-47
lines changed

7 files changed

+125
-47
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,8 @@ jobs:
3636
strategy:
3737
fail-fast: false
3838
matrix:
39-
python-version: ['3.12']
40-
os: [ubuntu-latest, macos-latest]
41-
include:
42-
- python-version: '3.11'
43-
os: ubuntu-latest
44-
- python-version: '3.10'
45-
os: ubuntu-latest
46-
- python-version: '3.9'
47-
os: ubuntu-latest
48-
runs-on: ${{ matrix.os }}
39+
python-version: ['3.12', '3.11', '3.10', '3.9']
40+
runs-on: ubuntu-latest
4941

5042
steps:
5143
- uses: actions/checkout@v4

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A Flask site for validating metadata sheets per the standards of the PCMP and an
99

1010
## Development
1111

12-
To start with local development:
12+
This is a Flask app leveraging with bootstrap, jquery, and datatables (via CDN). To start with local development:
1313

1414
```
1515
git clone https://github.com/PennChopMicrobiomeProgram/CHOP_metadata_checker.git

app/app.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import csv
22
import os
3-
from .src.utils import export_table, import_table, run_checks, table_from_file
3+
from .src.utils import (
4+
export_table,
5+
import_table,
6+
is_importable,
7+
run_checks,
8+
table_from_file,
9+
)
410
from .metadatalib.src.metadatalib import SQLALCHEMY_DATABASE_URI
511
from .metadatalib.src.metadatalib.consts import ALLOWED_EXTENSIONS
612
from .metadatalib.src.metadatalib.models import (
@@ -153,21 +159,18 @@ def download(submission_id):
153159
return response
154160

155161

156-
@app.route("/review/<ticket_code>", methods=["GET", "POST"])
157-
def review(ticket_code):
158-
if request.method == "POST":
159-
table_data = session.pop("table_data", {"cols": [], "rows": []})
160-
t = Table(table_data["cols"], table_data["rows"])
161-
import_table(t, db, ticket_code, request.form["comment"])
162-
163-
return render_template("final.html", ticket_code=ticket_code)
164-
165-
166162
@app.route("/submit/<ticket_code>", methods=["GET", "POST"])
167163
def submit(ticket_code):
168164
project = (
169165
db.session.query(Project).filter(Project.ticket_code == ticket_code).first()
170166
)
167+
recent_submissions = (
168+
db.session.query(Submission)
169+
.filter(Submission.project_id == project.project_id)
170+
.order_by(Submission.time_submitted.desc())
171+
.limit(3)
172+
.all()
173+
)
171174

172175
if not project:
173176
return render_template("dne.html", ticket_code=ticket_code)
@@ -177,6 +180,7 @@ def submit(ticket_code):
177180
"submit.html",
178181
filename="Select file ...",
179182
project=project,
183+
submissions=recent_submissions,
180184
)
181185
elif request.method == "POST":
182186
# Check if post request has a file
@@ -208,13 +212,30 @@ def submit(ticket_code):
208212
"submit.html",
209213
filename=file_fp.filename,
210214
project=project,
215+
submissions=recent_submissions,
211216
table=t,
212217
checks=checks,
218+
is_importable=is_importable(t),
213219
)
214220

215221
return redirect(url_for("submit", ticket_code=project.ticket_code))
216222

217223

224+
@app.route("/review/<ticket_code>", methods=["GET", "POST"])
225+
def review(ticket_code):
226+
if request.method == "POST":
227+
table_data = session.pop("table_data", {"cols": [], "rows": []})
228+
if table_data == {"cols": [], "rows": []}:
229+
flash(
230+
"Something went wrong! There's no metadata in the system to submit. Please try again and be sure not to reload or go back in your browser."
231+
)
232+
return redirect(url_for("submit", ticket_code=ticket_code))
233+
t = Table(table_data["cols"], table_data["rows"])
234+
import_table(t, db, ticket_code, request.form["comment"])
235+
236+
return render_template("final.html", ticket_code=ticket_code)
237+
238+
218239
@app.route("/summary")
219240
def summary():
220241
return render_template(

app/src/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from flask_sqlalchemy import SQLAlchemy
88
from tablemusthave import Table
99
from app.metadatalib.src.metadatalib.consts import (
10+
CHOP_MANDATORY_TUBE,
1011
DEFAULT_SAMPLE_FIELDS,
1112
REGEX_TRANSLATE,
1213
)
@@ -99,6 +100,10 @@ def import_table(
99100
return submission
100101

101102

103+
def is_importable(t: Table) -> bool:
104+
return all(col in t.colnames() for col in CHOP_MANDATORY_TUBE)
105+
106+
102107
def export_table(db: SQLAlchemy, submission_id: int) -> Table:
103108
samples = (
104109
db.session.query(Sample).filter(Sample.submission_id == submission_id).all()

app/templates/index.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,13 @@ <h5 class="m-3">Please submit your project id below to get to the metadata submi
1919
<button type="submit" class="btn btn-success m-3">GO</button>
2020
</form>
2121
</div>
22+
23+
<div class="p-4">
24+
<h3> Welcome to the CHOP Microbiome Metadata Checker! </h3>
25+
<p class="w-50">This website is used to validate your metadata spreadsheet used in bioinformatics analyses for the PennCHOP Microbiome Center. It will parse your spreadsheet to ensure it fulfills certain specifications and display error messages for columns and cells that do not meet these specifications.</p>
26+
27+
<p class="w-50">To get started, enter your project code above or go directly to the URL you were received. Once there, you can Upload Metadata (csv, tsv, or txt files only; if you have an Excel sheet you'll have to export it to one of these formats) and the checker will determine if there are any issues. If there are none, you can Submit Metadata, provide an optional comment, and then you'll be done! If there are issues but they won't break anything immediately, you can proceed but with caution. If there are breaking issues, you will have to fix them and reupload your metadata.</p>
28+
29+
<p class="w-50">To view previous submissions for your project, enter your project code and Go. On the metadata submission page you will see a list of your most recent submissions.</p>
30+
</div>
2231
{% endblock %}

app/templates/project.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ <h3 class="m-3">{{project.contact_name}} :-: {{project.contact_email}}</h3>
2525
</tr>
2626
{% endfor %}
2727
</tbody>
28-
</table>
28+
</table>
2929

30-
<script type="text/javascript">
30+
<script type="text/javascript">
3131
$(document).ready(function () {
3232
/* Initialize the DataTable */
3333
oTable = $('#submissions').dataTable({
@@ -49,5 +49,5 @@ <h3 class="m-3">{{project.contact_name}} :-: {{project.contact_email}}</h3>
4949
/* Redraw manually */
5050
oTable.fnDraw();
5151
});
52-
</script>
52+
</script>
5353
{% endblock %}

app/templates/submit.html

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,89 @@
11
{% extends 'base.html' %}
22
{% import 'macros.jinja' as macros %}
33

4-
{% block head %}
5-
{% endblock %}
6-
74
{% block body %}
85

96
<body>
10-
<div class="card m-3">
11-
<h2 class="m-3">{{project.project_name}}</h2>
12-
<h3 class="m-3">{{project.contact_name}} :-: {{project.contact_email}}</h3>
13-
<div class="card-body">
14-
<form action="{{ '/submit/' + project.ticket_code }}" method="POST" enctype="multipart/form-data">
15-
<div class="form-group">
16-
<label>Select metadata file</label>
17-
<div class="row">
18-
<div class="col-sm">
19-
<div class="custom-file">
7+
<div class="flex card m-3">
8+
<div class="container-fluid p-4 card-body">
9+
<div class="row">
10+
<div class="col-lg">
11+
<h2 class="m-3">{{project.project_name}}</h2>
12+
<h3 class="m-3">{{project.contact_name}} :-: {{project.contact_email}}</h3>
13+
<form action="{{ '/submit/' + project.ticket_code }}" method="POST" enctype="multipart/form-data">
14+
<div class="form-group">
15+
<label>Select metadata file</label>
16+
<div class="custom-file py-2">
2017
<input type="file" class="custom-file-input" name="metadata_upload" id="metadata_upload">
2118
<label class="custom-file-label" for="metadata_upload" id="filename">{{ filename }}</label>
2219
</div>
20+
<button type="submit" class="btn btn-success">Upload Metadata</button>
2321
</div>
24-
<div class="col-sm">
25-
<button type="submit" class="btn btn-success">Upload metadata</button>
26-
</div>
22+
</form>
23+
24+
<div class="py-4">
25+
{% if checks is defined %}
26+
{% if checks['passed'] %}
27+
<a href="{{ url_for('review', ticket_code=project.ticket_code) }}" class="btn btn-success">Submit Metadata</a>
28+
{% elif is_importable %}
29+
<a href="{{ url_for('review', ticket_code=project.ticket_code) }}" class="btn btn-warning" onclick="return confirm('There are still issues in your metadata. Are you sure you want to submit with these issues?')">Submit Metadata</a>
30+
<i class="max-w-prose">There are still issues flagged by the checker but you can submit if you want.</i>
31+
{% else %}
32+
<i class="max-w-prose">There are schema-breaking issues flagged by the checker. You must resolve these issues before you can submit.</i>
33+
{% endif %}
34+
{% endif %}
2735
</div>
2836
</div>
29-
</form>
30-
31-
{% if checks is defined %}
32-
{% if checks['passed'] %}
33-
<a href="{{ url_for('review', ticket_code=project.ticket_code) }}" class="btn btn-success">Submit Metadata</a>
34-
{% endif %}
3537

38+
<div class="col-md">
39+
<h4>Recent Submissions</h4>
40+
<table class="display" id="submissions">
41+
<thead>
42+
<tr>
43+
<th>Submission ID</th>
44+
<th>Version</th>
45+
<th>Time Submitted</th>
46+
<th>Comment</th>
47+
</tr>
48+
</thead>
49+
<tbody>
50+
{% for submission in submissions %}
51+
<tr>
52+
<td><a href="{{ url_for('show_submission', submission_id=submission.submission_id) }}">{{ submission.submission_id }}</a></td>
53+
<td>{{ submission.version }}</td>
54+
<td>{{ submission.time_submitted }}</td>
55+
<td>{{ submission.comment }}</td>
56+
</tr>
57+
{% endfor %}
58+
</tbody>
59+
</table>
60+
61+
<script type="text/javascript">
62+
$(document).ready(function () {
63+
/* Initialize the DataTable without the search bar and pagination */
64+
oTable = $('#submissions').dataTable({
65+
"bSort": false,
66+
"iDisplayLength": 20,
67+
"bLengthChange": false,
68+
"bPaginate": false, // Disable pagination
69+
"bStateSave": true,
70+
"bFilter": false, // Disable the search bar
71+
});
72+
73+
/* Snap pagination info to grid */
74+
$("#submissions_info").addClass("span-12");
75+
76+
/* Redraw manually */
77+
oTable.fnDraw();
78+
});
79+
</script>
80+
</div>
81+
</div>
82+
</div>
83+
</div>
84+
85+
<div>
86+
{% if checks is defined and table is defined %}
3687
{{ macros.table(table, checks) }}
3788
{% endif %}
3889
</div>

0 commit comments

Comments
 (0)