Skip to content

Commit 2387676

Browse files
committed
Merge branch 'main' into 1727-sca-integration-ort
2 parents 1112a82 + 3d8700a commit 2387676

24 files changed

+1045
-165
lines changed

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ v35.4.0 (unreleased)
2020
- Add new ``benchmark_purls`` pipeline.
2121
https://github.com/aboutcode-org/scancode.io/issues/1804
2222

23+
- Add a Resources tree view.
24+
https://github.com/aboutcode-org/scancode.io/issues/1682
25+
26+
- Improve CycloneDX SBOM support.
27+
* Upgrade the cyclonedx-python-lib to 11.0.0
28+
* Fix the validate_document following library upgrade.
29+
* Add support when the "components" entry is missing.
30+
https://github.com/aboutcode-org/scancode.io/issues/1727
31+
2332
v35.3.0 (2025-08-20)
2433
--------------------
2534

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ dependencies = [
8282
# Profiling
8383
"pyinstrument==5.1.1",
8484
# CycloneDX
85-
"cyclonedx-python-lib==10.2.0",
86-
"jsonschema==4.24.0",
85+
"cyclonedx-python-lib==11.0.0",
86+
"jsonschema==4.25.1",
8787
# MatchCode-toolkit
8888
"matchcode-toolkit==7.2.2",
8989
# Univers

scanpipe/models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from django.db import transaction
4747
from django.db.models import Case
4848
from django.db.models import Count
49+
from django.db.models import Exists
4950
from django.db.models import IntegerField
5051
from django.db.models import OuterRef
5152
from django.db.models import Prefetch
@@ -2432,6 +2433,17 @@ def macho_binaries(self):
24322433
def executable_binaries(self):
24332434
return self.union(self.win_exes(), self.macho_binaries(), self.elfs())
24342435

2436+
def with_has_children(self):
2437+
"""
2438+
Annotate the QuerySet with has_children field based on whether
2439+
each resource has any children (subdirectories/files).
2440+
"""
2441+
children_qs = CodebaseResource.objects.filter(
2442+
parent_path=OuterRef("path"),
2443+
)
2444+
2445+
return self.annotate(has_children=Exists(children_qs))
2446+
24352447

24362448
class ScanFieldsModelMixin(models.Model):
24372449
"""Fields returned by the ScanCode-toolkit scans."""

scanpipe/pipes/benchmark.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,18 @@ def get_expected_purls(project):
4949
return sorted(set(expected_purls))
5050

5151

52+
def get_unique_project_purls(project):
53+
"""
54+
Return the sorted list of unique Package URLs (PURLs) discovered in the project.
55+
56+
Extracts the ``purl`` field from all discovered packages, removes duplicates,
57+
and sorts the result to provide a deterministic list of project PURLs.
58+
"""
59+
project_packages = project.discoveredpackages.only_package_url_fields()
60+
sorted_unique_purls = sorted({package.purl for package in project_packages})
61+
return sorted_unique_purls
62+
63+
5264
def compare_purls(project, expected_purls):
5365
"""
5466
Compare discovered project PURLs against the expected PURLs.
@@ -57,10 +69,10 @@ def compare_purls(project, expected_purls):
5769
- Lines starting with '-' are missing from the project.
5870
- Lines starting with '+' are unexpected in the project.
5971
"""
60-
project_packages = project.discoveredpackages.only_package_url_fields()
61-
sorted_unique_purls = sorted({package.purl for package in project_packages})
72+
sorted_project_purls = get_unique_project_purls(project)
73+
print(sorted_project_purls)
6274

63-
diff_result = difflib.ndiff(sorted_unique_purls, expected_purls)
75+
diff_result = difflib.ndiff(sorted_project_purls, expected_purls)
6476

6577
# Keep only lines that are diffs (- or +)
6678
filtered_diff = [line for line in diff_result if line.startswith(("-", "+"))]

scanpipe/pipes/cyclonedx.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ def validate_document(document):
123123
The validator is loaded from the document specVersion property.
124124
"""
125125
if isinstance(document, str):
126+
document_str = document
126127
document = json.loads(document)
128+
else:
129+
document_str = json.dumps(document)
127130

128131
spec_version = document.get("specVersion")
129132
if not spec_version:
@@ -132,7 +135,8 @@ def validate_document(document):
132135
schema_version = SchemaVersion.from_version(spec_version)
133136

134137
json_validator = JsonStrictValidator(schema_version)
135-
return json_validator._validata_data(document)
138+
json_validation_errors = json_validator.validate_str(document_str)
139+
return json_validation_errors
136140

137141

138142
def is_cyclonedx_bom(input_location):
@@ -281,7 +285,7 @@ def is_empty(value):
281285
elif isinstance(value, list) and not any(value):
282286
return True
283287

284-
for component in cyclonedx_document_json["components"]:
288+
for component in cyclonedx_document_json.get("components", []):
285289
for property_name, property_value in component.items():
286290
if is_empty(property_value) or property_name in ignored_properties:
287291
entries_to_delete.append((component, property_name))

scanpipe/templates/scanpipe/includes/project_summary_level.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@
6969
<a href="{{ project_resources_url }}">
7070
{{ project.resource_count|intcomma }}
7171
</a>
72+
{% if project.resource_count > 1 %}
73+
<a href="{% url 'codebase_resource_tree' project.slug %}" class="ml-2">
74+
<span class="icon">
75+
<i class="fa-solid fa-folder-tree is-size-6"></i>
76+
</span>
77+
</a>
78+
{% endif %}
7279
{% if project.resource_compliance_alert_count %}
7380
<a href="{% url 'project_resources' project.slug %}?compliance_alert=error" class="has-text-danger is-size-5 ml-2">
7481
{{ project.resource_compliance_alert_count|intcomma }}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<ul>
2+
{% for node in children %}
3+
<li>
4+
{% if node.is_dir %}
5+
<div class="tree-node is-flex is-align-items-center has-text-weight-semibold px-1" data-folder data-path="{{ node.path }}"{% if node.has_children %} data-target="{{ node.path|slugify }}" data-url="{% url 'codebase_resource_tree' slug=project.slug %}?path={{ node.path }}"{% endif %}>
6+
<span class="icon is-small chevron mr-1{% if not node.has_children %} is-invisible{% endif %} is-clickable is-flex is-align-items-center" data-chevron>
7+
<i class="fas fa-chevron-right"></i>
8+
</span>
9+
<span class="is-flex is-align-items-center folder-meta is-clickable" data-folder-click hx-get="{% url 'codebase_resource_table' project.slug %}?path={{ node.path }}" hx-target="#right-pane">
10+
<span class="icon is-small mr-2">
11+
<i class="fas fa-folder"></i>
12+
</span>
13+
<span>{{ node.name }}</span>
14+
</span>
15+
</div>
16+
{% if node.has_children %}
17+
<div id="dir-{{ node.path|slugify }}" class="ml-4 is-hidden" data-loaded="false"></div>
18+
{% endif %}
19+
{% else %}
20+
<div class="tree-node-file is-flex is-align-items-center pl-5 is-clickable is-file" data-file data-path="{{ node.path }}" hx-get="{% url 'codebase_resource_table' project.slug %}?path={{ node.path }}" hx-target="#right-pane">
21+
<span class="icon is-small mr-2">
22+
<i class="far fa-file"></i>
23+
</span>
24+
<span>{{ node.name }}</span>
25+
</div>
26+
{% endif %}
27+
</li>
28+
{% endfor %}
29+
</ul>

scanpipe/templates/scanpipe/panels/license_clarity_panel.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<article id="license-clarity-panel" class="panel is-info">
1+
<article id="license-clarity-panel" class="panel is-dark">
22
<div class="panel-heading is-flex is-justify-content-space-between">
33
License clarity
44
{% include "scanpipe/dropdowns/help_dropdown_tooltip.html" with content="License clarity is a set of metrics that indicate how clearly, comprehensively and accurately a software project has defined and communicated the licensing that applies to the software." only %}

scanpipe/templates/scanpipe/panels/license_detections_summary.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{% load humanize %}
22
{% if license_detection_summary %}
33
<div class="column is-half">
4-
<article id="license-detection-summary-panel" class="panel is-info">
4+
<article id="license-detection-summary-panel" class="panel is-dark">
55
<div class="panel-heading is-flex is-justify-content-space-between">
66
Unique license detections
77
{% include "scanpipe/dropdowns/help_dropdown_tooltip.html" with content="All unique license detections in the codebase identified by matched license text and other license match characteristics. Also contains other license clues." only %}
88
</div>
9-
<div class="panel is-info">
10-
<nav class="panel is-info">
9+
<div class="panel is-dark">
10+
<nav class="panel is-dark">
1111
{% for license_expression, count in license_detection_summary.items %}
1212
<a class="panel-block is-align-items-flex-start break-word is-flex is-align-items-center" href="{{ project_licenses_url }}?license_expression={{ license_expression|default:'_EMPTY_' }}" target="_blank">
1313
{{ license_expression|default:'<i>No licenses</i>' }}

scanpipe/templates/scanpipe/panels/project_codebase.html

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
<nav id="codebase-navigation" class="panel is-dark">
2-
<p class="panel-heading">
3-
Codebase
4-
{% if current_dir and current_dir != "." %}
5-
<span class="tag ml-2">
6-
{% for dir_name, full_path in codebase_breadcrumbs.items %}
7-
{% if not forloop.last %}
8-
<a href="#" hx-target="#codebase-navigation" hx-swap="outerHTML" hx-get="{{ project_details_url }}codebase/?current_dir={{ full_path }}">
9-
{{ dir_name }}
10-
</a>
11-
<span class="mr-1">/</span>
12-
{% else %}
13-
{{ dir_name }}/
14-
{% endif %}
15-
{% endfor %}
16-
</span>
17-
{% endif %}
18-
</p>
2+
<div class="panel-heading is-flex is-justify-content-space-between is-align-items-center">
3+
<div>
4+
<span>Codebase</span>
5+
{% if current_dir and current_dir != "." %}
6+
<span class="tag ml-2">
7+
{% for dir_name, full_path in codebase_breadcrumbs.items %}
8+
{% if not forloop.last %}
9+
<a href="#" hx-target="#codebase-navigation" hx-swap="outerHTML" hx-get="{{ project_details_url }}codebase/?current_dir={{ full_path }}">
10+
{{ dir_name }}
11+
</a>
12+
<span class="mr-1">/</span>
13+
{% else %}
14+
{{ dir_name }}/
15+
{% endif %}
16+
{% endfor %}
17+
</span>
18+
{% endif %}
19+
</div>
20+
<a href="{% url 'codebase_resource_tree' project.slug %}" class="ml-2 has-text-white has-text-decoration-none">
21+
<i class="fa-solid fa-folder-tree mr-1"></i>Tree view
22+
</a>
23+
</div>
1924
{% for node in codebase_tree %}
2025
{% if node.is_dir %}
2126
<a class="panel-block" href="#" hx-target="#codebase-navigation" hx-swap="outerHTML" hx-get="{{ project_details_url }}codebase/?current_dir={{ node.location }}">

0 commit comments

Comments
 (0)