Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions protzilla/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ def current_plots(self) -> Plots | None:
@property
def current_outputs(self) -> Output:
return self.steps.current_step.output

@property
def current_filtered_data(self) -> dict:
return self.steps.current_step.filtered_datatable

@property
def current_step(self) -> Step | None:
Expand Down
1 change: 1 addition & 0 deletions protzilla/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, instance_identifier: str | None = None):
self.form_inputs: dict = {}
self.inputs: dict = {}
self.output: Output = Output()
self.filtered_datatable: dict = {}
self.plots: Plots = Plots()
self.messages: Messages = Messages([])
self.instance_identifier = instance_identifier
Expand Down
81 changes: 37 additions & 44 deletions ui/runs/templates/runs/tables.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,65 +9,58 @@
{% block js %}
<script type="text/javascript" src="{% static 'js/jquery.js' %}"></script>
<script type="text/javascript" src="{% static 'js/datatables-1.13.4.min.js' %}"></script>

<script type="text/javascript" src="{% static 'js/datatables-controls.js' %}"></script>
<script>
$(document).ready(function () {
$.ajax({
url: "{% url 'runs:tables_content' run_name index key %}",
data: "{{ clean_ids }}",
type: "GET",
success: function (response) {
let tr = $("<tr>");
response.columns.forEach(element => {
tr.append($("<th>").text(element))
});
$("#datatable").html($("<thead>").append(tr))
.DataTable({
data: response.data,
columnDefs: [{
targets: "_all",
render: function (data, type, row) {
if (type !== 'display') {
return data;
}
if (typeof data === 'string' && data.startsWith("https://")) {
return data.split(' ').map(url => `<a href="${url}" target="_blank">${url.split('/').pop()}</a>`).join(' ')
}
if (typeof data === 'number' && (!Number.isInteger(data) || data >= 1e4)) {
return data.toPrecision(4);
}
return `<div style="word-break:break-all">${data}</div>`;
},
}],
});
}
});
$('#tables_dropdown').on("change", function () {
window.location.href = $(this).val()
})
});
const RUNS_TABLES_CONTENT_URL = "{% url 'runs:tables_content' run_name index key %}";
const CLEAN_IDS = "{{ clean_ids }}";
</script>
{% endblock %}

{% block content %}
<div class="p-3">
<p>
Index {{ index|add:1 }}, Section {{ section }}, step {{ step }}, method {{ method }}
<br>
</p>

<div class="d-flex justify-content-between">
<div class="input-group mb-3 w-50">
<span class="input-group-text">Choose table</span>
{% include 'runs/field_select_with_label.html' with key="tables_dropdown" categories=options only %}
</div>
<div>
{% if clean_ids %}
<a class="mb-3 btn btn-grey" href="{% url 'runs:tables' run_name index key %}">Enable Isoforms</a>
{% else %}
<a class="mb-3 btn btn-grey" href="?clean-ids">Disable Isoforms</a>
{% endif %}
<a class="mb-3 btn btn-grey" href="{% url 'runs:download_table' run_name index key %}">Download Table</a>
{% if clean_ids %}
<a class="mb-3 btn btn-grey" href="{% url 'runs:tables' run_name index key %}">Enable Isoforms</a>
{% else %}
<a class="mb-3 btn btn-grey" href="?clean-ids">Disable Isoforms</a>
{% endif %}
<a class="mb-3 btn btn-grey" href="{% url 'runs:download_table' run_name index key %}">Download Table</a>
</div>
</div>

<div class="input-group mb-3 w-50">
<input type="text" id="searchInput" class="form-control" placeholder="Type search query here..." >
<button class="btn btn-grey ms-1" id="search-btn">Search</button>
</div>

<div style="overflow-x: auto;">
<table id="datatable" class="display"></table>
</div>

<div class="d-flex justify-content-between">
<div class="d-flex justify-content-between">
<div class="input-group mt-3 w-auto">
<span class="input-group-text">Rows per page</span>
<select id="rowsPerPage" class="form-select form-select-sm">
<option value="10" selected>10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<div id="page-info" class="mt-3 ms-3 d-flex align-items-center"></div>
</div>
<div id="pagination" class="pagination-buttons d-flex justify-content-end mt-3"></div>
</div>
<table id="datatable" class="display"></table>
</div>
</div>
{% endblock %}
69 changes: 56 additions & 13 deletions ui/runs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
)
from django.shortcuts import render
from django.urls import reverse
from django.conf import settings
from django.http import JsonResponse


from protzilla.constants.paths import WORKFLOWS_PATH
from protzilla.run import Run, get_available_run_names
Expand All @@ -39,7 +42,8 @@
make_name_field,
make_sidebar,
)
from ui.runs.views_helper import display_message, display_messages

from ui.runs.views_helper import display_message, display_messages, parameters_from_post, get_filtered_data, set_filtered_data

from .form_mapping import (
get_filled_form_by_method,
Expand Down Expand Up @@ -495,29 +499,68 @@ def navigate(request, run_name: str):
run.step_goto(index, section_name)
return HttpResponseRedirect(reverse("runs:detail", args=(run_name,)))


def tables_content(request, run_name, index, key):
"""
Handles the content of a table during a run, including filtering, searching, sorting, and pagination.

:param request: the request object
:param run_name: the name of the run
:param index: the index of the current step
:param key: the key of the datatable

:return: a JSON response containing the table data
"""

if run_name not in active_runs:
active_runs[run_name] = Run(run_name)
run = active_runs[run_name]
# TODO this will change with df_mode implementation
if index < len(run.steps.previous_steps):
outputs = run.steps.previous_steps[index].output[key]
else:
outputs = run.current_outputs[key]
out = outputs.replace(np.nan, None)

filtered_data = get_filtered_data(run, index, key)

if request.GET.get("is_new_search", "false").lower() == "true":
filtered_data = get_filtered_data(run, index, key, reset=True)
search_query = request.GET.get("search_query", "").lower()
if search_query:
mask = filtered_data.astype(str).stack().str.contains(search_query, case=False, na=False).unstack()
filtered_data = filtered_data [mask.any(axis=1)]
set_filtered_data(run, index, key, filtered_data )

if request.GET.get("is_new_sorting", "false").lower() == "true":
sorting_column_idx = int(request.GET.get("sorting_column_index", "0"))
is_sort_ascending = request.GET.get("is_sort_ascending", "true").lower() == "true"
primary_column = filtered_data.columns[sorting_column_idx]
secondary_column = filtered_data.columns[0]
filtered_data = filtered_data.sort_values(by=[primary_column, secondary_column], ascending=[is_sort_ascending, True])
set_filtered_data(run, index, key, filtered_data )

if "clean-ids" in request.GET:
for column in out.columns:
for column in filtered_data.columns:
if "protein" in column.lower():
out[column] = out[column].map(
filtered_data[column] = filtered_data[column].map(
lambda group: ";".join(
unique_justseen(map(clean_uniprot_id, group.split(";")))
)
)
return JsonResponse(
dict(columns=out.to_dict("split")["columns"], data=out.to_dict("split")["data"])
)

current_page = int(request.GET.get("current_page", 1))
rows_per_page = int(request.GET.get("rows_per_page", 10))

total_items = len(filtered_data )
total_pages = (total_items + rows_per_page - 1) // rows_per_page
start_id_y = (current_page - 1) * rows_per_page
end_id_y = start_id_y + rows_per_page
paginated_data = filtered_data .iloc[start_id_y:end_id_y]

response_data = {
"columns": paginated_data.to_dict("split")["columns"],
"data": paginated_data.to_dict("split")["data"],
"page": current_page,
"total_pages": total_pages,
"total_items": total_items,
"start_item": start_id_y + 1,
"end_item": min(end_id_y, total_items)
}
return JsonResponse(response_data)


def change_method(request, run_name):
Expand Down
47 changes: 47 additions & 0 deletions ui/runs/views_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import re

from django.contrib import messages
import numpy as np

import ui.runs.form_mapping as form_map
from protzilla.steps import StepManager
Expand Down Expand Up @@ -118,3 +119,49 @@ def clear_messages(request):
for message in messages.get_messages(request):
pass
storage.used = True


def get_filtered_data(run, index, key, reset=False):
"""
Retrieves the corresponding output data and creates a copy for the filtered data in the data table

:param run: the corresponding run
:param index: the index of the current step
:param key: the key of the datatable
:param reset: the option to reload the real output data

:return: a dict with the filtered data for the table
"""
if index < len(run.steps.previous_steps):
if key not in run.steps.previous_steps[index].datatable_filtered_output or reset:
outputs = run.steps.previous_steps[index].output[key]
filtered_data = outputs.copy()
filtered_data = filtered_data.replace(np.nan, None)
run.steps.previous_steps[index].datatable_filtered_output[key] = filtered_data
else:
filtered_data = run.steps.previous_steps[index].datatable_filtered_output[key]

else:
if key not in run.current_filtered_data or reset:
outputs = run.current_outputs[key]
filtered_data = outputs.copy()
filtered_data = filtered_data.replace(np.nan, None)
run.current_filtered_data[key] = filtered_data
else:
filtered_data = run.current_filtered_data[key]

return filtered_data

def set_filtered_data(run, index, key, filtered_data):
"""
Saves the filtered data from the table

:param run: the corresponding run
:param index: the index of the current step
:param key: the key of the datatable
:param filtered_data: the filtered data from the table
"""
if index < len(run.steps.previous_steps):
run.steps.previous_steps[index].datatable_filtered_output[key] = filtered_data
else:
run.current_filtered_data[key] = filtered_data
Loading
Loading