Skip to content

Commit 97bb941

Browse files
authored
Package details view #164 #464 (#481)
* Add simple package details view including all model fields #164 Signed-off-by: Thomas Druez <[email protected]> * Display only 5 Resources per Packages in list view #464 Signed-off-by: Thomas Druez <[email protected]> * Add Resources in Package details using new tabs system #164 Signed-off-by: Thomas Druez <[email protected]> * Add CHANGELOG entry #164 #464 Signed-off-by: Thomas Druez <[email protected]>
1 parent 11c29c0 commit 97bb941

File tree

11 files changed

+177
-22
lines changed

11 files changed

+177
-22
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,17 @@ v31.0.0 (next)
5656

5757
https://github.com/nexB/scancode.io/issues/413
5858

59-
- Correctly extract symlinks in docker images. We now usse the latest
59+
- Correctly extract symlinks in docker images. We now use the latest
6060
container-inspector to fix symlinks extraction in docker image tarballs.
61-
In particular broken synlinks are not treated as an error anymore
61+
In particular broken symlinks are not treated as an error anymore
6262
and symlinks are extracted correctly.
6363
https://github.com/nexB/scancode.io/issues/471
6464
https://github.com/nexB/scancode.io/issues/407
6565

66+
- Add a Package details view including all model fields and resources.
67+
Display only 5 resources per package in the list view.
68+
https://github.com/nexB/scancode.io/issues/164
69+
https://github.com/nexB/scancode.io/issues/464
6670

6771
v30.2.0 (2021-12-17)
6872
--------------------

scancodeio/static/main.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,26 @@ function setupCloseModalButtons() {
5858
}
5959
}
6060

61+
// Tabs
62+
63+
function setupTabs() {
64+
const $tabLinks = getAll('.tabs a');
65+
66+
$tabLinks.forEach(function ($el) {
67+
$el.addEventListener('click', function (event) {
68+
const activeLink = document.querySelector('.tabs .is-active');
69+
const activeTabContent = document.querySelector('.tab-content.is-active');
70+
const target_id = $el.dataset.target;
71+
const targetTabContent = document.getElementById(target_id);
72+
73+
activeLink.classList.remove('is-active');
74+
$el.parentNode.classList.add('is-active');
75+
activeTabContent.classList.remove('is-active');
76+
targetTabContent.classList.add('is-active');
77+
});
78+
});
79+
}
80+
6181
// Utils, available globally
6282

6383
function getAll(selector) {
@@ -122,6 +142,7 @@ document.addEventListener('DOMContentLoaded', function () {
122142

123143
setupOpenModalButtons();
124144
setupCloseModalButtons();
145+
setupTabs();
125146

126147
// Close modals and dropdowns on pressing "escape" key
127148
document.addEventListener('keydown', function (event) {

scanpipe/filters.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
from django.apps import apps
2424
from django.db import models
25-
from django.utils.translation import gettext_lazy as _
2625

2726
import django_filters
2827
from django_filters.widgets import LinkWidget
@@ -117,9 +116,11 @@ class BulmaDropdownWidget(BulmaLinkWidget):
117116

118117

119118
class ProjectFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
120-
search = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
119+
search = django_filters.CharFilter(
120+
label="Search", field_name="name", lookup_expr="icontains"
121+
)
121122
sort = django_filters.OrderingFilter(
122-
label=_("Sort"),
123+
label="Sort",
123124
fields=["created_date", "name"],
124125
empty_label="Newest",
125126
choices=(
@@ -130,7 +131,7 @@ class ProjectFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
130131
widget=BulmaDropdownWidget,
131132
)
132133
pipeline = django_filters.ChoiceFilter(
133-
label=_("Pipeline"),
134+
label="Pipeline",
134135
field_name="runs__pipeline_name",
135136
choices=scanpipe_app.get_pipeline_choices(include_blank=False),
136137
widget=BulmaDropdownWidget,
@@ -177,8 +178,8 @@ def filter(self, qs, value):
177178
class InPackageFilter(django_filters.ChoiceFilter):
178179
def __init__(self, *args, **kwargs):
179180
kwargs["choices"] = (
180-
("true", _("Yes")),
181-
("false", _("No")),
181+
("true", "Yes"),
182+
("false", "No"),
182183
)
183184
super().__init__(*args, **kwargs)
184185

@@ -191,7 +192,9 @@ def filter(self, qs, value):
191192

192193

193194
class ResourceFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
194-
search = django_filters.CharFilter(field_name="path", lookup_expr="icontains")
195+
search = django_filters.CharFilter(
196+
label="Search", field_name="path", lookup_expr="icontains"
197+
)
195198
in_package = InPackageFilter(label="In a Package")
196199

197200
class Meta:
@@ -236,8 +239,10 @@ def filter_for_lookup(cls, field, lookup_type):
236239

237240

238241
class PackageFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
239-
search = django_filters.CharFilter(field_name="name", lookup_expr="icontains")
240-
purl = PackageURLFilter()
242+
search = django_filters.CharFilter(
243+
label="Search", field_name="name", lookup_expr="icontains"
244+
)
245+
purl = PackageURLFilter(label="Package URL")
241246

242247
class Meta:
243248
model = DiscoveredPackage
@@ -271,7 +276,9 @@ class Meta:
271276

272277

273278
class ErrorFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
274-
search = django_filters.CharFilter(field_name="message", lookup_expr="icontains")
279+
search = django_filters.CharFilter(
280+
label="Search", field_name="message", lookup_expr="icontains"
281+
)
275282

276283
class Meta:
277284
model = ProjectError

scanpipe/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,6 +1756,16 @@ class Meta:
17561756
def __str__(self):
17571757
return self.package_url or str(self.uuid)
17581758

1759+
def get_absolute_url(self):
1760+
return reverse("package_detail", args=[self.project_id, self.pk])
1761+
1762+
@cached_property
1763+
def resources(self):
1764+
"""
1765+
Returns the assigned codebase_resources QuerySet as a list.
1766+
"""
1767+
return list(self.codebase_resources.all())
1768+
17591769
@property
17601770
def purl(self):
17611771
"""

scanpipe/templates/scanpipe/base.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
button.as-link {height: auto; line-height: initial; font-size: inherit;}
5050
.file.is-boxed.has-name .file-cta {border-width: medium; border-style: dashed;}
5151
progress.file-upload::before {content: 'Files upload: 'attr(value)'%'; position: absolute; top: -14%; left: 43%; color: black; font-weight: 400;}
52+
.is-clipped-list ul {--snippet-spacing: 0.95rem; height: calc(8 * var(--snippet-spacing)); overflow: hidden;}
53+
.tab-content {display: none;}
54+
.tab-content.is-active {display: revert !important;}
5255
</style>
5356
{% block extrahead %}{% endblock %}
5457
</head>

scanpipe/templates/scanpipe/error_list.html

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,17 @@
3030
<td style="max-width: 130px;">
3131
<a href="?model={{ error.model }}" class="is-black-link">{{ error.model }}</a>
3232
</td>
33-
<td style="max-width: 150px;">
34-
<a href="?message={{ error.message }}" class="is-black-link">{{ error.message }}</a>
33+
<td class="break-word" style="max-width: 250px;">
34+
<div style="max-height: 200px; overflow-y: scroll;">
35+
{% if error.message|length < 100 %}
36+
<a href="?message={{ error.message }}" class="is-black-link">{{ error.message }}</a>
37+
{% else %}
38+
{{ error.message }}
39+
{% endif %}
40+
</div>
3541
</td>
3642
<td class="break-all" style="max-width: 450px;">
37-
<div style="max-height: 150px; overflow-y: scroll;">
43+
<div style="max-height: 200px; overflow-y: scroll;">
3844
{% if error.details.codebase_resource_pk and error.details.codebase_resource_path %}
3945
<div>
4046
<strong>Codebase resource</strong>:
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{% extends "scanpipe/base.html" %}
2+
{% load static humanize %}
3+
4+
{% block title %}ScanCode.io: {{ project.name }} - {{ object.name }}{% endblock %}
5+
6+
{% block content %}
7+
<div class="container is-max-desktop">
8+
{% include 'scanpipe/includes/navbar_header.html' %}
9+
<div class="mx-5 mb-2">{% include 'scanpipe/includes/messages.html' %}</div>
10+
11+
<section class="mx-5 mb-3">
12+
<nav class="breadcrumb is-medium mb-1" aria-label="breadcrumbs">
13+
<ul>
14+
<li><a href="{% url 'project_list' %}">Projects</a></li>
15+
<li><a href="{{ project.get_absolute_url }}">{{ project.name }}</a></li>
16+
<li><a href="{% url 'project_packages' project.uuid %}">Discovered Packages</a></li>
17+
</ul>
18+
</nav>
19+
<div class="tags has-addons my-3">
20+
<span class="tag is-dark">Package URL</span>
21+
<span class="tag is-info">{{ object }}</span>
22+
</div>
23+
</section>
24+
25+
<div class="tabs is-boxed mx-5">
26+
<ul>
27+
<li class="is-active">
28+
<a data-target="tab-details">
29+
<span class="icon is-small"><i class="fas fa-info-circle"></i></span>
30+
<span>Details</span>
31+
</a>
32+
</li>
33+
<li>
34+
<a data-target="tab-resources">
35+
<span class="icon is-small"><i class="fas fa-file-alt"></i></span>
36+
<span>Resources</span>
37+
</a>
38+
</li>
39+
</ul>
40+
</div>
41+
42+
<section id="tab-details" class="tab-content is-active mx-5">
43+
<dl>
44+
{% for field, value in package_data.items %}
45+
<dt class="has-text-weight-semibold">
46+
{{ field }}
47+
</dt>
48+
<dd class="mb-4">
49+
<pre>{{ value|default_if_none:'' }}</pre>
50+
</dd>
51+
{% endfor %}
52+
</dl>
53+
</section>
54+
55+
<section id="tab-resources" class="tab-content mx-5">
56+
<ul>
57+
{% for resource in object.resources %}
58+
<li>
59+
<a href="{{ resource.get_absolute_url }}">{{ resource }}</a>
60+
</li>
61+
{% endfor %}
62+
</ul>
63+
</section>
64+
65+
</div>
66+
{% endblock %}

scanpipe/templates/scanpipe/package_list.html

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,25 @@
2828
{% for package in object_list %}
2929
<tr class="break-word">
3030
<td style="min-width: 500px;" title="{{ package.package_uid }}">
31-
{{ package.package_url }}
31+
<a href="{{ package.get_absolute_url }}">{{ package.package_url }}</a>
3232
</td>
3333
<td style="min-width: 300px; max-width: 400px;">
3434
{{ package.license_expression|linebreaksbr }}
3535
</td>
3636
<td title="{{ package.copyright }}">
3737
{{ package.copyright|truncatechars:150|linebreaksbr }}
3838
</td>
39-
<td>
39+
<td class="is-clipped-list">
4040
<ul>
41-
{% for resource in package.codebase_resources.all %}
41+
{% for resource in package.resources %}
4242
<li>
43-
<a href="{{ resource.get_absolute_url }}">{{ resource }}</a>
43+
<a href="{{ resource.get_absolute_url }}" title="{{ resource.path }}">{{ resource.name }}</a>
4444
</li>
4545
{% endfor %}
4646
</ul>
47+
{% if package.resources|length > 5 %}
48+
<button class="button is-small is-fullwidth show-clipped">Show {{ package.resources|length }} resources</button>
49+
{% endif %}
4750
</td>
4851
</tr>
4952
{% endfor %}
@@ -55,4 +58,21 @@
5558
{% include 'scanpipe/includes/pagination.html' with page_obj=page_obj %}
5659
{% endif %}
5760
</div>
61+
{% endblock %}
62+
63+
{% block scripts %}
64+
<script>
65+
let $showClippedButton = getAll("button.show-clipped");
66+
67+
$showClippedButton.forEach(function ($el) {
68+
$el.addEventListener("click", function () {
69+
$el.parentNode.classList.toggle("is-clipped-list");
70+
let text = $el.textContent;
71+
if (text.startsWith("Show"))
72+
$el.textContent = text.toggle("Show", "Hide");
73+
else
74+
$el.textContent = text.replace("Hide", "Show");
75+
});
76+
});
77+
</script>
5878
{% endblock %}

scanpipe/templates/scanpipe/resource_detail.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
<section class="mx-5 mb-3">
1212
<nav class="breadcrumb is-medium mb-1" aria-label="breadcrumbs">
1313
<ul>
14-
<li><a href="{% url 'project_list' %}">Projects</a></li>
15-
<li><a href="{{ project.get_absolute_url }}">{{ project.name }}</a></li>
16-
<li><a href="{% url 'project_resources' project.uuid %}">Codebase Resources</a></li>
14+
<li><a href="{% url 'project_list' %}">Projects</a></li>
15+
<li><a href="{{ project.get_absolute_url }}">{{ project.name }}</a></li>
16+
<li><a href="{% url 'project_resources' project.uuid %}">Codebase Resources</a></li>
1717
</ul>
1818
</nav>
1919
</section>

scanpipe/urls.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
views.CodebaseResourceDetailsView.as_view(),
3737
name="resource_detail",
3838
),
39+
path(
40+
"project/<uuid:uuid>/packages/<int:pk>/",
41+
views.DiscoveredPackageDetailsView.as_view(),
42+
name="package_detail",
43+
),
3944
path(
4045
"project/<uuid:uuid>/resources/",
4146
views.CodebaseResourceListView.as_view(),

scanpipe/views.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343

4444
from scancodeio.auth import ConditionalLoginRequired
4545
from scancodeio.auth import conditional_login_required
46+
from scanpipe.api.serializers import DiscoveredPackageSerializer
4647
from scanpipe.filters import ErrorFilterSet
4748
from scanpipe.filters import PackageFilterSet
4849
from scanpipe.filters import ProjectFilterSet
@@ -664,6 +665,18 @@ def get_context_data(self, **kwargs):
664665
return context
665666

666667

668+
class DiscoveredPackageDetailsView(
669+
ConditionalLoginRequired, ProjectRelatedViewMixin, generic.DetailView
670+
):
671+
model = DiscoveredPackage
672+
template_name = "scanpipe/package_detail.html"
673+
674+
def get_context_data(self, **kwargs):
675+
context = super().get_context_data(**kwargs)
676+
context["package_data"] = DiscoveredPackageSerializer(self.object).data
677+
return context
678+
679+
667680
@conditional_login_required
668681
def run_detail_view(request, uuid):
669682
template = "scanpipe/includes/run_modal_content.html"

0 commit comments

Comments
 (0)