diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2be142b..173519d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,6 +3,10 @@ Release notes
### Version 5.1.1-dev
+- Add visual indicator in hierarchy views, when an object on the far left or far right
+ also belong or have a hierarchy (relathionship tree).
+ https://github.com/nexB/dejacode/issues/70
+
### Version 5.1.0
- Upgrade Python version to 3.12 and Django to 5.0.x
diff --git a/component_catalog/templates/component_catalog/component_details.html b/component_catalog/templates/component_catalog/component_details.html
index 092b918..9464026 100644
--- a/component_catalog/templates/component_catalog/component_details.html
+++ b/component_catalog/templates/component_catalog/component_details.html
@@ -58,7 +58,7 @@
{% block javascripts %}
{{ block.super }}
-
+
{% include 'component_catalog/includes/component_hierarchy.js.html' with related_parents=tabsets.Hierarchy.fields.0.1.related_parents related_children=tabsets.Hierarchy.fields.0.1.related_children productcomponents=tabsets.Hierarchy.fields.0.1.productcomponents %}
{% if tabsets.Owner.extra %}
{% include 'organization/includes/owner_hierarchy.js.html' with current_owner=object.owner parents=tabsets.Owner.extra.context.owner_parents children=tabsets.Owner.extra.context.owner_children tab_name="tab_owner" %}
diff --git a/component_catalog/templates/component_catalog/includes/component_hierarchy.js.html b/component_catalog/templates/component_catalog/includes/component_hierarchy.js.html
index 4947825..c37e010 100644
--- a/component_catalog/templates/component_catalog/includes/component_hierarchy.js.html
+++ b/component_catalog/templates/component_catalog/includes/component_hierarchy.js.html
@@ -1,84 +1,66 @@
-
\ No newline at end of file
+ {% if related_child.child_count > 0 %}
+ var linkUrl = '{{ related_child.child.get_absolute_url }}#hierarchy';
+ addEndpointWithLink(jsPlumbHierarchy, source_id, 'RightMiddle', linkUrl);
+ {% endif %}
+ {% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/component_catalog/templates/component_catalog/tabs/tab_subcomponents.html b/component_catalog/templates/component_catalog/tabs/tab_subcomponents.html
index dc97a86..d98f485 100644
--- a/component_catalog/templates/component_catalog/tabs/tab_subcomponents.html
+++ b/component_catalog/templates/component_catalog/tabs/tab_subcomponents.html
@@ -34,7 +34,7 @@
{% endif %}
{{ data.child.version }} |
- {{ data.child.owner }} |
+ {{ data.child.owner|default_if_none:"" }} |
{{ data.subcomponent.purpose }} |
{% if data.subcomponent.license_expression %}
@@ -50,7 +50,7 @@
| {{ data.subcomponent.is_modified|as_icon }} |
{% if component.is_active %}
-
+
-
diff --git a/component_catalog/tests/test_views.py b/component_catalog/tests/test_views.py
index fbd77d4..351f875 100644
--- a/component_catalog/tests/test_views.py
+++ b/component_catalog/tests/test_views.py
@@ -444,8 +444,8 @@ def test_component_catalog_hierarchy_tab(self):
expected1 = f''
expected2 = f' '
- expected3 = f"source: 'component_{self.component1.id}'"
- expected4 = f"target: 'component_{self.component2.id}'"
+ expected3 = f"var source_id = 'component_{self.component1.id}'"
+ expected4 = f"var target_id = 'component_{self.component2.id}'"
self.assertContains(response, expected1)
self.assertContains(response, expected2)
@@ -598,7 +598,6 @@ def test_component_catalog_detail_view_owner_tab_hierarchy_availability(self):
self.client.login(username="nexb_user", password="t3st")
url = self.component1.get_absolute_url()
response = self.client.get(url)
- self.assertNotContains(response, "jsPlumbOwnerHierarchy")
self.assertNotContains(response, "Selected Owner")
self.assertNotContains(response, "Child Owners")
@@ -608,7 +607,6 @@ def test_component_catalog_detail_view_owner_tab_hierarchy_availability(self):
)
response = self.client.get(url)
- self.assertContains(response, "jsPlumb")
self.assertContains(response, "Selected Owner")
self.assertContains(response, "Child Owners")
self.assertContains(response, child_owner.name)
diff --git a/component_catalog/views.py b/component_catalog/views.py
index abb89fe..943eb8c 100644
--- a/component_catalog/views.py
+++ b/component_catalog/views.py
@@ -21,6 +21,7 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core import signing
from django.core.validators import EMPTY_VALUES
+from django.db.models import Count
from django.db.models import Prefetch
from django.http import FileResponse
from django.http import Http404
@@ -630,18 +631,38 @@ def get_queryset(self):
"licenses",
)
- related_children_qs = Subcomponent.objects.select_related(
- "usage_policy",
- ).prefetch_related(
- "licenses",
- Prefetch("child", queryset=component_prefetch_qs),
+ related_children_qs = (
+ Subcomponent.objects.select_related(
+ "usage_policy",
+ )
+ .prefetch_related(
+ "licenses",
+ Prefetch("child", queryset=component_prefetch_qs),
+ )
+ .annotate(
+ child_count=Count("child__children"),
+ )
+ .order_by(
+ "child__name",
+ "child__version",
+ )
)
- related_parents_qs = Subcomponent.objects.select_related(
- "usage_policy",
- ).prefetch_related(
- "licenses",
- Prefetch("parent", queryset=component_prefetch_qs),
+ related_parents_qs = (
+ Subcomponent.objects.select_related(
+ "usage_policy",
+ )
+ .prefetch_related(
+ "licenses",
+ Prefetch("parent", queryset=component_prefetch_qs),
+ )
+ .annotate(
+ parent_count=Count("parent__related_parents"),
+ )
+ .order_by(
+ "parent__name",
+ "parent__version",
+ )
)
return (
diff --git a/dje/templates/hierarchy_base.js.html b/dje/templates/hierarchy_base.js.html
new file mode 100644
index 0000000..c74e979
--- /dev/null
+++ b/dje/templates/hierarchy_base.js.html
@@ -0,0 +1,58 @@
+
\ No newline at end of file
diff --git a/license_library/tests/test_views.py b/license_library/tests/test_views.py
index b6d0155..00b6725 100644
--- a/license_library/tests/test_views.py
+++ b/license_library/tests/test_views.py
@@ -598,7 +598,7 @@ def test_license_library_detail_view_owner_tab_hierarchy_availability(self):
)
response = self.client.get(url)
- self.assertContains(response, "jsPlumbOwnerHierarchy")
+ self.assertContains(response, "jsPlumbHierarchy")
self.assertContains(response, "Selected Owner")
self.assertContains(response, "Child Owners")
self.assertContains(response, child_owner.name)
diff --git a/organization/templates/organization/includes/owner_hierarchy.js.html b/organization/templates/organization/includes/owner_hierarchy.js.html
index 7c24e2c..a376c00 100644
--- a/organization/templates/organization/includes/owner_hierarchy.js.html
+++ b/organization/templates/organization/includes/owner_hierarchy.js.html
@@ -5,7 +5,7 @@
}
jsPlumb.ready(function() {
- var jsPlumbOwnerHierarchy = jsPlumb.getInstance({
+ var jsPlumbHierarchy = jsPlumb.getInstance({
Connector: ['Straight'],
PaintStyle: {strokeStyle: 'gray', lineWidth: 1},
EndpointStyle: {radius: 3, fillStyle: 'gray'},
@@ -14,29 +14,30 @@
});
// Do not draw right away as the tab may be hidden
- jsPlumbOwnerHierarchy.setSuspendDrawing(true);
+ jsPlumbHierarchy.setSuspendDrawing(true);
+
{% for parent in parents %}
- jsPlumbOwnerHierarchy.connect({source: 'owner_{{ current_owner.pk }}', target: 'owner_{{ parent.pk }}'});
+ jsPlumbHierarchy.connect({source: 'owner_{{ current_owner.pk }}', target: 'owner_{{ parent.pk }}'});
{% endfor %}
{% for child in children %}
- jsPlumbOwnerHierarchy.connect({source: 'owner_{{ child.pk }}', target: 'owner_{{ current_owner.pk }}'});
+ jsPlumbHierarchy.connect({source: 'owner_{{ child.pk }}', target: 'owner_{{ current_owner.pk }}'});
{% endfor %}
// Draw if the related tab is active
if (is_active_tab("{{ tab_name }}"))
- jsPlumbOwnerHierarchy.setSuspendDrawing(false, true);
+ jsPlumbHierarchy.setSuspendDrawing(false, true);
// Repaint on opening the tab, as when the tab content is hidden
// the connectors are not painted properly
$('button[data-bs-target="#{{ tab_name }}"]').on('shown.bs.tab', function (e) {
// Second argument instructs jsPlumb to perform a full repaint.
- jsPlumbOwnerHierarchy.setSuspendDrawing(false, true);
+ jsPlumbHierarchy.setSuspendDrawing(false, true);
});
// Repaint on resizing the browser window if the related tab is active
$(window).resize(function(){
if (is_active_tab("{{ tab_name }}"))
- jsPlumbOwnerHierarchy.repaintEverything();
+ jsPlumbHierarchy.repaintEverything();
});
});
\ No newline at end of file
diff --git a/organization/templates/organization/owner_details.html b/organization/templates/organization/owner_details.html
index 550d437..87bb109 100644
--- a/organization/templates/organization/owner_details.html
+++ b/organization/templates/organization/owner_details.html
@@ -2,7 +2,7 @@
{% load static %}
{% block javascripts %}
{{ block.super }}
-
+
{% if tabsets.Hierarchy.extra %}
{% include 'organization/includes/owner_hierarchy.js.html' with current_owner=tabsets.Hierarchy.extra.context.owner parents=tabsets.Hierarchy.extra.context.owner_parents children=tabsets.Hierarchy.extra.context.owner_children tab_name="tab_hierarchy" %}
{% endif %}
diff --git a/product_portfolio/templates/product_portfolio/includes/product_hierarchy.js.html b/product_portfolio/templates/product_portfolio/includes/product_hierarchy.js.html
index eeba994..31e6926 100644
--- a/product_portfolio/templates/product_portfolio/includes/product_hierarchy.js.html
+++ b/product_portfolio/templates/product_portfolio/includes/product_hierarchy.js.html
@@ -1,51 +1,24 @@
-
\ No newline at end of file
+ {% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/product_portfolio/templates/product_portfolio/product_details.html b/product_portfolio/templates/product_portfolio/product_details.html
index bf37593..585e1f4 100644
--- a/product_portfolio/templates/product_portfolio/product_details.html
+++ b/product_portfolio/templates/product_portfolio/product_details.html
@@ -111,7 +111,7 @@
{% if has_edit_productpackage or has_edit_productcomponent %}
{% endif %}
-
+
{% include 'product_portfolio/includes/product_hierarchy.js.html' with relations_feature_grouped=tabsets.Hierarchy.fields.0.1.relations_feature_grouped %}
{% if tabsets.Owner.extra %}
{% include 'organization/includes/owner_hierarchy.js.html' with current_owner=object.owner parents=tabsets.Owner.extra.context.owner_parents children=tabsets.Owner.extra.context.owner_children tab_name="tab_owner" %}
diff --git a/product_portfolio/tests/test_views.py b/product_portfolio/tests/test_views.py
index 26e9a05..4d0aa84 100644
--- a/product_portfolio/tests/test_views.py
+++ b/product_portfolio/tests/test_views.py
@@ -713,9 +713,9 @@ def test_product_portfolio_detail_view_hierarchy_tab(self):
f' ',
f' ',
f' ',
- f"target: 'product_{self.product1.id}'",
- f"source: 'component_{pc1.id}'",
- f"source: 'package_{pp1.id}'",
+ f"var target_id = 'product_{self.product1.id}'",
+ f"var source_id = 'component_{pc1.id}'",
+ f"var source_id = 'package_{pp1.id}'",
f' {pc1.feature}',
f'{pp1.feature}',
'',
diff --git a/product_portfolio/views.py b/product_portfolio/views.py
index 54e945e..f4e5d74 100644
--- a/product_portfolio/views.py
+++ b/product_portfolio/views.py
@@ -389,6 +389,9 @@ def tab_hierarchy(self):
.prefetch_related(
"component__licenses",
)
+ .annotate(
+ children_count=Count("component__children"),
+ )
.order_by(
"feature",
"component",
|