Skip to content

Commit

Permalink
Add a dependency tree view based on d3.js #1145
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Jul 29, 2024
1 parent 4218eb0 commit 584cc23
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 0 deletions.
6 changes: 6 additions & 0 deletions scanpipe/templates/scanpipe/dependency_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
</div>

<div class="container is-fluid mb-3">
<a href="{% url 'project_dependency_tree' project.slug %}" class="is-pulled-right">
<span class="icon">
<i class="fa-solid fa-sitemap"></i>
</span>
<span>View the dependency tree</span>
</a>
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
{% include 'scanpipe/includes/list_view_thead.html' %}
<tbody>
Expand Down
44 changes: 44 additions & 0 deletions scanpipe/templates/scanpipe/project_dependency_tree.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{% extends "scanpipe/base.html" %}

{% block title %}ScanCode.io: {{ project.name }} - Dependency tree{% endblock %}

{% block content %}
<div id="content-header" class="container is-max-widescreen mb-3">
{% include 'scanpipe/includes/navbar_header.html' %}
<section class="mx-5">
<div class="is-flex is-justify-content-space-between">
{% include 'scanpipe/includes/breadcrumb.html' with linked_project=True current="Dependency tree" %}
</div>
</section>
</div>

<div class="container is-fluid mb-3">
<div id="tree"></div>
</div>
{% endblock %}

{% block scripts %}
<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6"></script>
{{ dependency_tree|json_script:"dependency_tree" }}
<script>
const data = JSON.parse(document.getElementById("dependency_tree").textContent);

const plot = Plot.plot({
axis: null,
margin: 10,
marginLeft: 40,
marginRight: 160,
width: 928 * 2,
height: 1800 * 2,
marks: [
Plot.tree(d3.hierarchy(data).leaves(), {
path: (node) => node.ancestors().reverse().map(({ data: { name } }) => name).join("|"),
delimiter: "|"
})
]
});

document.getElementById("tree").appendChild(plot);
</script>
{% endblock %}
5 changes: 5 additions & 0 deletions scanpipe/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@
views.DiscoveredDependencyListView.as_view(),
name="project_dependencies",
),
path(
"project/<slug:slug>/dependency_tree/",
views.ProjectDependencyTreeView.as_view(),
name="project_dependency_tree",
),
path(
"project/<slug:slug>/relations/",
views.CodebaseRelationListView.as_view(),
Expand Down
31 changes: 31 additions & 0 deletions scanpipe/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2281,3 +2281,34 @@ def get_object(self, queryset=None):
return licenses[key]
except KeyError:
raise Http404(f"License {key} not found.")


class ProjectDependencyTreeView(ConditionalLoginRequired, generic.DetailView):
model = Project
template_name = "scanpipe/project_dependency_tree.html"

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["dependency_tree"] = self.get_dependency_tree(project=self.object)
return context

def get_dependency_tree(self, project):
root_packages = project.discoveredpackages.root_packages()
project_children = [self.get_node(package) for package in root_packages]

project_tree = {
"name": project.name,
"children": project_children,
}

return project_tree

def get_node(self, package):
node = {"name": str(package)}
children = [
self.get_node(child_package)
for child_package in package.children_packages.all()
]
if children:
node["children"] = children
return node

0 comments on commit 584cc23

Please sign in to comment.