Skip to content

Commit 925d404

Browse files
authored
Add the ability to delete a Scan from Product inventory #222 (#281)
Signed-off-by: tdruez <[email protected]>
1 parent 31a41c3 commit 925d404

27 files changed

+326
-75
lines changed

CHANGELOG.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,9 @@ Release notes
161161
- Fix the validity of SPDX outputs.
162162
https://github.com/aboutcode-org/dejacode/issues/180
163163

164+
- Add ability to start and delete package scans from the Product inventory tab.
165+
https://github.com/aboutcode-org/dejacode/pull/281
166+
164167
### Version 5.2.0
165168

166169
- Add visual indicator in hierarchy views, when an object on the far left or far right

component_catalog/templates/component_catalog/includes/scan_delete_modal.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ <h5 class="modal-title">Delete {% trans 'Scan' %}</h5>
1010
<p><strong>Are you sure you want to delete this Scan?</strong></p>
1111
<p>
1212
All data and results related to the Scan will be deleted.<br>
13-
If the Scan is in progress, it will be cancel.
13+
If the Scan is in progress, it will be canceled.
1414
</p>
1515
</div>
1616
<div class="modal-footer">

component_catalog/templates/component_catalog/includes/scan_package_modal.html

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,38 @@ <h5 class="modal-title">Scan Package</h5>
1616
</div>
1717
<div class="modal-footer">
1818
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
19-
<a id="submit-scan-request" href="{% url 'component_catalog:package_scan' user.dataspace object.uuid %}" class="btn btn-success">Submit Scan Request</a>
19+
{% if is_xhr %}
20+
<button hx-swap="innerHTML"
21+
hx-on--before-request="
22+
let target = document.querySelector(this.getAttribute('hx-target'));
23+
if (target) {
24+
const spinner = document.createElement('div');
25+
spinner.classList.add('spinner-border', 'spinner-border-md');
26+
// Create the span for accessibility
27+
const span = document.createElement('span');
28+
span.classList.add('visually-hidden');
29+
span.textContent = 'Submitting Scan Request...';
30+
// Append the span to the spinner div
31+
spinner.appendChild(span);
32+
// Replace the inner content of the target with the spinner
33+
target.innerHTML = ''; // Clear any existing content
34+
target.appendChild(spinner);
35+
}
36+
"
37+
hx-on--error="
38+
let target = document.querySelector(this.getAttribute('hx-target'));
39+
if (target) target.innerHTML = 'Error!';
40+
"
41+
class="btn btn-success scan-confirm"
42+
data-bs-dismiss="modal"
43+
>
44+
Submit Scan Request
45+
</button>
46+
{% else %}
47+
<a id="submit-scan-request" class="btn btn-success" href="{% url 'component_catalog:package_scan' user.dataspace object.uuid %}">
48+
Submit Scan Request
49+
</a>
50+
{% endif %}
2051
</div>
2152
</div>
2253
</div>
Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,11 @@
11
<strong class="ms-1">{{ scan.runs.0.status|title }}</strong>
22
{% include 'component_catalog/includes/scan_status.html' with status=scan.runs.0.status only %}
33

4-
{% if compact_mode %}
5-
<div class="mt-1">
6-
{% if view_url %}
7-
<a class="text-dark" href="{{ view_url }}#scan" target="_blank" data-bs-toggle="tooltip" data-bs-title="View Scan results"><i class="far fa-file-alt"></i></a>&nbsp;
8-
{% endif %}
9-
{% if scan.download_result_url %}
10-
<a class="text-dark" href="{{ scan.download_result_url }}" target="_blank" data-bs-toggle="tooltip" data-bs-title="Download Scan results"><i class="fas fa-download"></i></a>
11-
{% endif %}
12-
</div>
13-
{% else %}
14-
<div class="text-center">
15-
{% if scan.download_result_url %}
16-
<a class="btn btn-outline-dark btn-sm mt-2" href="{{ scan.download_result_url }}" target="_blank"><i class="fas fa-download"></i> Results</a>
17-
{% endif %}
18-
{% if scan.delete_url %}
19-
<a class="btn btn-outline-danger btn-sm mt-2 scan_delete_link" href="#scan-delete-modal" role="button" data-delete-url="{{ scan.delete_url }}" title="Delete" data-bs-toggle="modal"><i class="far fa-trash-alt"></i></a>
20-
{% endif %}
21-
</div>
22-
{% endif %}
4+
<div class="text-center">
5+
{% if scan.download_result_url %}
6+
<a class="btn btn-outline-dark btn-sm mt-2" href="{{ scan.download_result_url }}" target="_blank"><i class="fas fa-download"></i> Results</a>
7+
{% endif %}
8+
{% if scan.delete_url %}
9+
<a class="btn btn-outline-danger btn-sm mt-2 scan_delete_link" href="#scan-delete-modal" role="button" data-delete-url="{{ scan.delete_url }}" title="Delete" data-bs-toggle="modal"><i class="far fa-trash-alt"></i></a>
10+
{% endif %}
11+
</div>
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<div class="progress" style="width: 100%; margin-bottom: 0; height: .5rem;">
22
{% if status == 'success' %}
3-
<div class="progress-bar {% if has_errors %}bg-warning{% else %}bg-success{% endif %}" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
3+
<div class="progress-bar {% if has_errors %}bg-warning{% else %}bg-success{% endif %}" title="{{ status|title }}" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
44
{% elif status == 'failure' or status == "stopped" or status == "stale" %}
5-
<div class="progress-bar bg-danger" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
5+
<div class="progress-bar bg-danger" title="{{ status|title }}" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
66
{% elif status == 'warning' %}
7-
<div class="progress-bar bg-warning" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
7+
<div class="progress-bar bg-warning" title="{{ status|title }}" role="progressbar" style="width: 100%" aria-label="Scan progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
88
{% elif status == 'running' %}
9-
<div class="progress-bar" role="progressbar" style="width: 40%" aria-label="Scan progress" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>
9+
<div class="progress-bar" title="{{ status|title }}" role="progressbar" style="width: 40%" aria-label="Scan progress" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>
1010
{% elif status == 'not_started' or status == 'queued' %}
11-
<div class="progress-bar" role="progressbar" style="width: 0" aria-label="Scan progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
11+
<div class="progress-bar" title="{{ status|title }}" role="progressbar" style="width: 0" aria-label="Scan progress" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
1212
{% endif %}
1313
</div>

component_catalog/templates/component_catalog/scan_list.html

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
{{ block.super }}
3737
<script>
3838
document.addEventListener('DOMContentLoaded', function () {
39-
$('.scan_delete_link').on('click', function() {
40-
let delete_url = $(this).data('delete-url');
41-
$('#scan-delete-modal a.delete-confirm').attr('href', delete_url);
39+
document.querySelectorAll('.scan_delete_link').forEach(link => {
40+
link.addEventListener('click', function() {
41+
let deleteUrl = this.getAttribute('data-delete-url');
42+
document.querySelector('#scan-delete-modal a.delete-confirm').setAttribute('href', deleteUrl);
43+
});
4244
});
4345
});
4446
</script>

component_catalog/urls.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from component_catalog.views import ScanListView
2424
from component_catalog.views import component_create_ajax_view
2525
from component_catalog.views import delete_scan_view
26+
from component_catalog.views import get_scan_progress_htmx_view
2627
from component_catalog.views import package_create_ajax_view
2728
from component_catalog.views import package_scan_view
2829
from component_catalog.views import send_scan_data_as_file_view
@@ -98,6 +99,11 @@
9899
package_scan_view,
99100
name="package_scan",
100101
),
102+
path(
103+
"packages/<str:dataspace>/<uuid:uuid>/scan_progress_htmx/",
104+
get_scan_progress_htmx_view,
105+
name="scan_progress_htmx",
106+
),
101107
path(
102108
"packages/<str:dataspace>/<uuid:uuid>/tab_scan/",
103109
PackageTabScanView.as_view(),

component_catalog/views.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from django.http import JsonResponse
3030
from django.shortcuts import get_object_or_404
3131
from django.shortcuts import redirect
32+
from django.shortcuts import render
3233
from django.urls import reverse
3334
from django.urls import reverse_lazy
3435
from django.utils.dateparse import parse_datetime
@@ -39,6 +40,7 @@
3940
from django.utils.translation import gettext_lazy as _
4041
from django.views.decorators.csrf import csrf_exempt
4142
from django.views.decorators.csrf import csrf_protect
43+
from django.views.decorators.http import require_GET
4244
from django.views.decorators.http import require_POST
4345
from django.views.generic import FormView
4446
from django.views.generic.edit import BaseFormView
@@ -85,6 +87,7 @@
8587
from dje.utils import get_help_text as ght
8688
from dje.utils import get_preserved_filters
8789
from dje.utils import is_available
90+
from dje.utils import is_hx_request
8891
from dje.utils import is_uuid4
8992
from dje.utils import localized_datetime
9093
from dje.utils import remove_empty_values
@@ -1456,6 +1459,7 @@ def package_scan_view(request, dataspace, uuid):
14561459
dataspace = user.dataspace
14571460
package = get_object_or_404(Package, uuid=uuid, dataspace=dataspace)
14581461
download_url = package.download_url
1462+
is_hxr = is_hx_request(request)
14591463

14601464
scancodeio = ScanCodeIO(dataspace)
14611465
if scancodeio.is_configured() and dataspace.enable_package_scanning:
@@ -1466,15 +1470,73 @@ def package_scan_view(request, dataspace, uuid):
14661470
user_uuid=user.uuid,
14671471
dataspace_uuid=dataspace.uuid,
14681472
)
1473+
1474+
if is_hxr:
1475+
template = "product_portfolio/tables/scan_progress_cell.html"
1476+
scan = scancodeio.get_scan_results(
1477+
download_url=download_url,
1478+
dataspace=dataspace,
1479+
)
1480+
scan["download_result_url"] = get_scan_results_as_file_url(scan)
1481+
1482+
status = scancodeio.get_status_from_scan_results(scan)
1483+
needs_refresh = False
1484+
if status in ["running", "not_started", "queued"]:
1485+
needs_refresh = True
1486+
1487+
context = {
1488+
"package": package,
1489+
"scan": scan,
1490+
"view_url": package.get_absolute_url(),
1491+
"needs_refresh": needs_refresh,
1492+
}
1493+
return render(request, template, context)
1494+
14691495
scancode_msg = "The Package URL was submitted to ScanCode.io for scanning."
14701496
messages.success(request, scancode_msg)
14711497
else:
1498+
if is_hxr:
1499+
return HttpResponse("URL is not reachable.")
1500+
14721501
scancode_msg = f"The URL {download_url} is not reachable."
14731502
messages.error(request, scancode_msg)
14741503

1504+
if is_hxr:
1505+
return Http404
1506+
14751507
return redirect(f"{package.details_url}#scan")
14761508

14771509

1510+
@login_required
1511+
@require_GET
1512+
def get_scan_progress_htmx_view(request, dataspace, uuid):
1513+
template = "product_portfolio/tables/scan_progress_cell.html"
1514+
dataspace = request.user.dataspace
1515+
package = get_object_or_404(Package, uuid=uuid, dataspace=dataspace)
1516+
scancodeio = ScanCodeIO(dataspace)
1517+
1518+
scan = scancodeio.get_scan_results(
1519+
download_url=package.download_url,
1520+
dataspace=dataspace,
1521+
)
1522+
if not scan:
1523+
raise Http404("Scan not found.")
1524+
1525+
status = scancodeio.get_status_from_scan_results(scan)
1526+
needs_refresh = False
1527+
if status in ["running", "not_started", "queued"]:
1528+
needs_refresh = True
1529+
1530+
scan["download_result_url"] = get_scan_results_as_file_url(scan)
1531+
context = {
1532+
"package": package,
1533+
"scan": scan,
1534+
"view_url": package.get_absolute_url(),
1535+
"needs_refresh": needs_refresh,
1536+
}
1537+
return render(request, template, context)
1538+
1539+
14781540
@login_required
14791541
@require_POST
14801542
def package_create_ajax_view(request):

dejacode/static/css/dejacode_bootstrap.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ table.text-break thead {
9292
.bg-warning-orange {
9393
background-color: var(--bs-orange);
9494
}
95+
.spinner-border-md {
96+
--bs-spinner-width: 1.5rem;
97+
--bs-spinner-height: 1.5rem;
98+
--bs-spinner-border-width: 0.2em;
99+
}
95100

96101
/* -- Dark there fixes -- */
97102
[data-bs-theme=dark] .btn-outline-dark {

dejacode_toolkit/scancodeio.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ def delete_scan(self, detail_url):
156156
logger.debug(f"{self.label} [Exception] {exception}")
157157
return False
158158

159+
@staticmethod
160+
def get_status_from_scan_results(scan_results):
161+
status = ""
162+
if runs := scan_results.get("runs"):
163+
status = runs[0].get("status", "")
164+
return status
165+
159166
def update_from_scan(self, package, user):
160167
"""
161168
Update the provided `package` instance using values from Scan results.

0 commit comments

Comments
 (0)