Skip to content

Commit

Permalink
add irods request batch handling (#1340), add davrods link (#1339)
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon authored Jul 20, 2023
1 parent ca37751 commit 1903d79
Show file tree
Hide file tree
Showing 11 changed files with 1,148 additions and 375 deletions.
22 changes: 19 additions & 3 deletions docs_manual/source/app_samplesheets_irods_delete.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ requests in the project as a project owner or delegate, open the
iRODS delete request list

The list provides a button for copying the iRODS path into the clipboard, status
information for the requests as well as dropdowns allowing you to either update
or delete your requests. On the top of the page you can see a *Create Request*
link for manual creation.
information for the requests, WebDAV link as well as dropdowns allowing you to
either update or delete your requests. On the top of the page you can see a
*Request Operations* dropdown with *Create Request* link for manual creation of
delete requests.


Manual Request Creation
Expand Down Expand Up @@ -99,3 +100,18 @@ user will be informed of rejection.
Accepting delete requests will delete the associated file(s) from iRODS with
no possibility for undoing the action! Each request should be reviewed
carefully.


Accepting and Rejecting Multiple Requests
=========================================

In addition to accepting or rejecting requests one by one, you can also accept
or reject multiple requests at once. This is done by selecting the requests you
want to accept or reject by clicking the checkboxes on the leftmost column of
the request list. Once you have selected the requests, click the
:guilabel:`Request Operations` dropdown and select either
:guilabel:`Accept Selected` or :guilabel:`Reject Selected`.

.. warning::

The requests for entire collections must be accepted or rejected individually.
12 changes: 8 additions & 4 deletions samplesheets/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,14 @@ def clean(self):
class IrodsRequestAcceptForm(forms.Form):
"""Form accepting an iRODS delete request."""

confirm = forms.BooleanField(
label='I accept the iRODS delete request',
required=True,
)
confirm = forms.BooleanField(required=True)

def __init__(self, *args, **kwargs):
num_requests = kwargs.pop('num_requests', None)
super().__init__(*args, **kwargs)
self.fields['confirm'].label = 'I accept the iRODS delete request'
if num_requests > 1:
self.fields['confirm'].label += 's'


class SheetVersionEditForm(forms.ModelForm):
Expand Down
81 changes: 81 additions & 0 deletions samplesheets/static/samplesheets/js/irods_request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// JS for iRODS request page
$(document).ready(function () {
// Disable "Accept Selected" and "Reject Selected" options by default
$('#sodar-ss-accept-selected').addClass('disabled');
$('#sodar-ss-reject-selected').addClass('disabled');

// Enable "Accept Selected" and "Reject Selected" options if at least one
// checkbox is checked or if the "Select All" checkbox is checked
$('.sodar-ss-checkbox-item').change(function () {
var checkedCheckboxes = $('.sodar-ss-checkbox-item:checked');
if (checkedCheckboxes.length > 0) {
$('#sodar-ss-accept-selected').removeClass('disabled');
$('#sodar-ss-reject-selected').removeClass('disabled');
} else {
$('#sodar-ss-accept-selected').addClass('disabled');
$('#sodar-ss-reject-selected').addClass('disabled');
}
// Uncheck "Select All" if any checkbox is unchecked
if (checkedCheckboxes.length < $('.sodar-ss-checkbox-item').length) {
$('#sodar-ss-request-check-all').prop('checked', false);
} else {
$('#sodar-ss-request-check-all').prop('checked', true);
}
});
});

/*****************
Manage checkboxes
*****************/
function checkAll(elem) {
// Enable if unchecked and disable if checked and show/hide buttons
$('.sodar-ss-checkbox-item').not(':disabled').each(function () {
$(this).prop('checked', elem.checked);
});
if (elem.checked) {
$('#sodar-ss-accept-selected').removeClass('disabled');
$('#sodar-ss-reject-selected').removeClass('disabled');
}
else {
$('#sodar-ss-accept-selected').addClass('disabled');
$('#sodar-ss-reject-selected').addClass('disabled');
}
}

/*****************
* Accept or reject selected
*****************/
function sendRequest(url) {
var checkboxes = document.querySelectorAll('.sodar-checkbox');
var selectedRequests = [];

checkboxes.forEach(function(checkbox) {
if (checkbox.checked && checkbox.value !== 'on') {
selectedRequests.push(checkbox.value);
}
});

// Create a form to send the POST request
var form = document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', url);

// Create a CSRF token input field
var csrfToken = jQuery("[name=csrfmiddlewaretoken]").val();
var csrfField = document.createElement('input');
csrfField.setAttribute('type', 'hidden');
csrfField.setAttribute('name', 'csrfmiddlewaretoken');
csrfField.setAttribute('value', csrfToken);
form.appendChild(csrfField);

// Add the data to the form
var hiddenField = document.createElement('input');
hiddenField.setAttribute('type', 'hidden');
hiddenField.setAttribute('name', 'irods_requests');
hiddenField.setAttribute('value', selectedRequests.join(','));
form.appendChild(hiddenField);
document.body.appendChild(form);

// Submit the form
form.submit();
}
30 changes: 30 additions & 0 deletions samplesheets/templates/samplesheets/_list_buttons.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div class="btn-group ml-auto" id="sodar-ff-buttons-list">
{% csrf_token %}
<button class="btn btn-primary dropdown-toggle"
type="button" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">
Request Operations
</button>
<div class="dropdown-menu dropdown-menu-right">

{# Create Request #}
<a href="{% url 'samplesheets:irods_request_create' project=project.sodar_uuid %}"
class="dropdown-item" role="button">
<i class="iconify" data-icon="mdi:plus-thick"></i> Create Request
</a>

{# Accept Selected #}
<a class="dropdown-item" id="sodar-ss-accept-selected"
href="javascript:{}"
onclick="sendRequest('{% url 'samplesheets:irods_request_accept_batch' project=project.sodar_uuid %}');">
<i class="iconify" data-icon="mdi:check-bold"></i> Accept Selected
</a>

{# Reject Selected #}
<a class="dropdown-item" id="sodar-ss-reject-selected"
href="javascript:{}"
onclick="sendRequest('{% url 'samplesheets:irods_request_reject_batch' project=project.sodar_uuid %}');">
<i class="iconify" data-icon="mdi:close-thick"></i> Reject Selected
</a>
</div>
</div>
86 changes: 44 additions & 42 deletions samplesheets/templates/samplesheets/irods_request_accept_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,54 +13,56 @@


<div class="container-fluid sodar-subtitle-container">
<h3>Accept iRODS Delete Request</h3>
{% if request_objects|length == 1 %}
<h3>Accept iRODS Delete Request</h3>
{% else %}
<h3>Accept {{ request_objects|length }} iRODS Delete Requests</h3>
{% endif %}
</div>

<div class="container-fluid sodar-page-container">
<div class="alert alert-danger">
<strong>Warning:</strong> Accepting the request will delete the associated
{% if is_collection %}collection{% else %}data object{% endif %} from iRODS.
This can <strong>not</strong> be undone without administrator assistance!
</div>
<div class="alert alert-danger">
The following {% if is_collection %}collection{% else %}data object{% endif %} will be deleted: <br />
<code class="text-danger">{{ irods_request.path }}</code>
{% if affected_objects %}
<div class="mt-3 mb-0">
The collection to be deleted contains the following data objects:
<ul class="mb-0">
{% for o in affected_objects %}
<li><code class="text-danger">{{ o.path|trim_base_path:irods_request.path }}</code></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if affected_collections %}
<div class="mt-3 mb-0">
The collection to be deleted comprises the following subcollections:
<ul class="mb-0">
{% for o in affected_collections %}
<li><code class="text-danger">{{ o.path|trim_base_path:irods_request.path }}/</code></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form | crispy }}
{% if request_objects %}
<div class="alert alert-danger">
<strong>Warning:</strong> Accepting {% if request_objects|length == 1 %}a request{% else %}requests{% endif %}
will delete the associated data objects from iRODS.
This can <strong>not</strong> be undone without administrator assistance!
</div>
<div class="alert alert-danger">
<strong>Info:</strong> The following {% if affected_object_paths|length > 1 %}
{{ affected_object_paths|length }} data objects
{% else %}data object{% endif %} will be deleted:
<ul class="mb-0">
{% for path in affected_object_paths %}
<li><code class="text-danger">{{ path }}</code></li>
{% endfor %}
</ul>
</div>
<form id="irods_requests_form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form | crispy }}
{% if request_objects|length %}
<input type="hidden" name="irods_requests" id="irods_requests" value="{{ irods_request_uuids }}"/>
{% endif %}
</form>
{% else %}
<div class="row">
<div class="btn-group ml-auto">
<a role="button" class="btn btn-secondary"
href="{{ request.session.real_referer }}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Cancel
</a>
<button type="submit" class="btn btn-primary" id="sodar-ss-btn-delete-submit">
<div class="alert alert-danger">
<strong>Error:</strong> No iRODS delete request found with the given UUID.
</div>
</div>
{% endif %}
<div class="row">
<div class="ml-auto btn-group">
<a role="button" class="btn btn-secondary"
href="{% url 'samplesheets:irods_requests' project=project.sodar_uuid %}">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Cancel
</a>
{% if request_objects|length %}
<button form="irods_requests_form" type="submit" class="btn btn-danger" id="sodar-ss-btn-delete-submit">
<i class="iconify" data-icon="mdi:check-bold"></i> Accept
</button>
</div>
{% endif %}
</div>
</form>
</div>
</div>

{% endblock projectroles_extend %}
35 changes: 30 additions & 5 deletions samplesheets/templates/samplesheets/irods_requests.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ <h3>iRODS Delete Requests</h3>
class="btn btn-secondary mr-1" role="button">
<i class="iconify" data-icon="mdi:arrow-left-circle"></i> Project Sheets
</a>
<a href="{% url 'samplesheets:irods_request_create' project=project.sodar_uuid %}"
class="btn btn-primary" role="button">
<i class="iconify" data-icon="mdi:plus-thick"></i> Create Request
</a>
{% include 'samplesheets/_list_buttons.html' with project=project folder=folder up=False %}
</div>
</div>

Expand All @@ -91,7 +88,16 @@ <h3>iRODS Delete Requests</h3>
<th>User</th>
<th>Created</th>
<th>Status</th>
<th></th>
{% if irods_webdav_enabled %}
<th></th>
{% endif %}
<th id="sodar-ss-request-header-select">
{% if can_manage_request %}
<input class="sodar-checkbox" type="checkbox"
onchange="checkAll(this)" name="check_all"
id="sodar-ss-request-check-all" title="Check/uncheck all" />
{% endif %}
</th>
</tr>
</thead>
<tbody>
Expand All @@ -106,6 +112,15 @@ <h3>iRODS Delete Requests</h3>
{% get_info_link irods_request.description as info_link %}
{{ info_link | safe }}
{% endif %}
{% if irods_webdav_enabled %}
<a href="{{ irods_request.webdav_url }}" id="sodar-ss-davrods-link"
class="btn ml-1 btn-secondary sodar-list-btn pull-right"
target="_blank" role="button"
data-toggle="tooltip" data-placement="top"
title="Open collection/file in WebDAV">
<i class="iconify" data-icon="mdi:open-in-new"></i>
</a>
{% endif %}
<button
role="submit"
class="btn btn-secondary sodar-list-btn sodar-copy-btn pull-right"
Expand Down Expand Up @@ -153,6 +168,14 @@ <h3>iRODS Delete Requests</h3>
{% endif %}
</div>
</td>
<td>
{% if can_manage_request %}
<input class="sodar-checkbox sodar-ss-checkbox-item" type="checkbox"
id="sodar-ss-checkbox-item-{{ irods_request.sodar_uuid }}"
name="batch_item_{{ irods_request.sodar_uuid }}" value="{{ irods_request.sodar_uuid }}"
{% if irods_request.is_collection %}disabled{% endif %} />
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -178,6 +201,8 @@ <h3>iRODS Delete Requests</h3>
{% block javascript %}
{{ block.super }}

<script type="text/javascript" src="{% static 'samplesheets/js/irods_request.js' %}"></script>

{# Tour content #}
<script type="text/javascript">
tourEnabled = true;
Expand Down
11 changes: 11 additions & 0 deletions samplesheets/tests/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,17 @@ def test_render(self):
[self.user_contributor], self.url, 'sodar-ss-request-table', True
)

def test_render_davrods_button(self):
"""Test UI rendering of davrods link button"""
self.login_and_redirect(
user=self.user_contributor,
url=self.url,
wait_elem='sodar-ss-request-table',
)
self.assertIsNotNone(
self.selenium.find_element(By.ID, 'sodar-ss-davrods-link')
)


class TestSheetVersionCompareView(
SampleSheetIOMixin, SheetConfigMixin, TestUIBase
Expand Down
Loading

0 comments on commit 1903d79

Please sign in to comment.