8000 Add visual indicator when an object also have tree in hierarchy view #70 by tdruez · Pull Request #126 · aboutcode-org/dejacode · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add visual indicator when an object also have tree in hierarchy view #70 #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@

{% block javascripts %}
{{ block.super }}
<script src="{% static "js/jquery.jsPlumb-1.7.2-min.js" %}" integrity="sha384-ITD4LUuh8ImLrJ5g55OIlG2QoiYVUuXLN9CStlO1e2SQZm0SyGfNkMiwPboMOv8D" crossorigin="anonymous"></script>
<script src="{% static 'js/jquery.jsPlumb-1.7.2-min.js' %}" integrity="sha384-ITD4LUuh8ImLrJ5g55OIlG2QoiYVUuXLN9CStlO1e2SQZm0SyGfNkMiwPboMOv8D" crossorigin="anonymous"></script>
{% 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" %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,84 +1,66 @@
<script>
function isTabActive(id) {
const activeTab = document.querySelector('.tab-pane.active');
if (activeTab) {
return activeTab.id === id;
}
return false;
}
{% extends "hierarchy_base.js.html" %}

jsPlumb.ready(function() {
var jsPlumbComponentHierarchy = jsPlumb.getInstance({
Connector: ['Straight'],
PaintStyle: {strokeStyle: 'gray', lineWidth: 1},
EndpointStyle: {radius: 3, fillStyle: 'gray'},
Anchors: ['LeftMiddle', 'RightMiddle'],
Container: $("#tab_hierarchy")
});
{% block herarchy_js_content %}
// Connect related_parents
{% for related_parent in related_parents %}
var source_id = 'component_{{ object.pk }}';
var target_id = 'component_{{ related_parent.parent.pk }}';
var connectionOptions = {
source: source_id,
target: target_id,
paintStyle: {strokeStyle: 'gray', lineWidth: 2}
};
{% if object.dataspace.show_usage_policy_in_user_views and related_parent.usage_policy %}
var fill_color = '{{ related_parent.get_usage_policy_color }}';
connectionOptions['endpointStyle'] = {fillStyle: fill_color, radius: 4};
connectionOptions['paintStyle'] = {strokeStyle: fill_color, lineWidth: 2};
{% endif %}
{% if not related_parent.is_deployed %}
connectionOptions['paintStyle']['dashstyle'] = '2 2';
{% endif %}
connectNodes(jsPlumbHierarchy, connectionOptions);

// Do not draw right away as the tab may be hidden
jsPlumbComponentHierarchy.setSuspendDrawing(true);
{% for related_parent in related_parents %}
var connection = {
source: 'component_{{ object.pk }}',
target: 'component_{{ related_parent.parent.pk }}',
paintStyle: {strokeStyle: 'gray', lineWidth: 2}
};
{% if object.dataspace.show_usage_policy_in_user_views and related_parent.usage_policy %}
var fill_color = '{{ related_parent.get_usage_policy_color }}';
connection['endpointStyle'] = {fillStyle: fill_color, radius: 4};
connection['paintStyle'] = {strokeStyle: fill_color, lineWidth: 2};
{% endif %}
{% if not related_parent.is_deployed %}
connection['paintStyle']['dashstyle'] = '2 2';
{% endif %}
jsPlumbComponentHierarchy.connect(connection);
{% endfor %}
{% if related_parent.parent_count > 0 %}
var linkUrl = '{{ related_parent.parent.get_absolute_url }}#hierarchy';
addEndpointWithLink(jsPlumbHierarchy, target_id, 'LeftMiddle', linkUrl);
{% endif %}
{% endfor %}

{% for productcomponent in productcomponents %}
var connection = {
source: 'component_{{ object.pk }}',
target: 'product_{{ productcomponent.product.pk }}',
paintStyle: {strokeStyle: 'grey', lineWidth: 2}
};
{% if not productcomponent.is_deployed %}
connection['paintStyle']['dashstyle'] = '2 2';
{% endif %}
jsPlumbComponentHierarchy.connect(connection);
{% endfor %}
// Connect products (productcomponents)
{% for productcomponent in productcomponents %}
var connectionOptions = {
source: 'component_{{ object.pk }}',
target: 'product_{{ productcomponent.product.pk }}',
paintStyle: {strokeStyle: 'grey', lineWidth: 2}
};
{% if not productcomponent.is_deployed %}
connectionOptions['paintStyle']['dashstyle'] = '2 2';
{% endif %}
connectNodes(jsPlumbHierarchy, connectionOptions);
{% endfor %}

{% for related_child in related_children %}
var connection = {
source: 'component_{{ related_child.child.pk }}',
target: 'component_{{ object.pk }}',
paintStyle: {strokeStyle: 'gray', lineWidth: 2}
};
{% if object.dataspace.show_usage_policy_in_user_views and related_child.usage_policy %}
var fill_color = '{{ related_child.get_usage_policy_color }}';
connection['endpointStyle'] = {fillStyle: fill_color, radius: 4};
connection['paintStyle'] = {strokeStyle: fill_color, lineWidth: 2};
{% endif %}
{% if not related_parent.is_deployed %}
connection['paintStyle']['dashstyle'] = '2 2';
{% endif %}
jsPlumbComponentHierarchy.connect(connection);
{% endfor %}
// Connect related_children
{% for related_child in related_children %}
var source_id = 'component_{{ related_child.child.pk }}';
var target_id = 'component_{{ object.pk }}';
var connectionOptions = {
source: source_id,
target: target_id,
paintStyle: {strokeStyle: 'gray', lineWidth: 2}
};
{% if object.dataspace.show_usage_policy_in_user_views and related_child.usage_policy %}
var fill_color = '{{ related_child.get_usage_policy_color }}';
connectionOptions['endpointStyle'] = {fillStyle: fill_color, radius: 4};
connectionOptions['paintStyle'] = {strokeStyle: fill_color, lineWidth: 2};
{% endif %}
{% if not related_child.is_deployed %}
connectionOptions['paintStyle']['dashstyle'] = '2 2';
{% endif %}
connectNodes(jsPlumbHierarchy, connectionOptions);

// Draw if the related tab is active
if (isTabActive("tab_hierarchy"))
jsPlumbComponentHierarchy.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_hierarchy"]').on('shown.bs.tab', function (e) {
// Second argument instructs jsPlumb to perform a full repaint.
jsPlumbComponentHierarchy.setSuspendDrawing(false, true);
});

// Repaint on resizing the browser window if the related tab is active
$(window).resize(function(){
if (isTabActive("tab_hierarchy"))
jsPlumbComponentHierarchy.repaintEverything();
});
});
</script>
{% if related_child.child_count > 0 %}
var linkUrl = '{{ related_child.child.get_absolute_url }}#hierarchy';
addEndpointWithLink(jsPlumbHierarchy, source_id, 'RightMiddle', linkUrl);
{% endif %}
{% endfor %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{% endif %}
</td>
<td>{{ data.child.version }}</td>
<td>{{ data.child.owner }}</td>
<td>{{ data.child.owner|default_if_none:"" }}</td>
<td>{{ data.subcomponent.purpose }}</td>
<td>
{% if data.subcomponent.license_expression %}
Expand All @@ -50,7 +50,7 @@
<td class="text-center">{{ data.subcomponent.is_modified|as_icon }}</td>
<td>
{% if component.is_active %}
<ul class="list-inline">
<ul class="list-inline mb-0">
<li class="list-inline-item">
<a href="{{ data.child.get_absolute_url }}#hierarchy" target="_blank" title="{% trans 'Hierarchy' %}"><i class="fas fa-sitemap"></i></a>
</li>
Expand Down
6 changes: 2 additions & 4 deletions component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ def test_component_catalog_hierarchy_tab(self):

expected1 = f'<div id="component_{self.component1.id}" class="card bg-body-tertiary mb-2">'
expected2 = f'<div id="component_{self.component2.id}" class="card bg-body-tertiary mb-2">'
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)
Expand Down Expand Up @@ -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")

Expand All @@ -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)
Expand Down
41 changes: 31 additions & 10 deletions component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand Down
58 changes: 58 additions & 0 deletions dje/templates/hierarchy_base.js.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script>
const tabId = "{{ tab_name|default:"tab_hierarchy" }}"

function isTabActive(id) {
const activeTab = document.querySelector('.tab-pane.active');
return activeTab ? activeTab.id === id : false;
}

function addEndpointWithLink(jsPlumbInstance, elementId, position, linkUrl) {
const endpointOptions = {
paintStyle: {fillStyle: '#2e73d0', radius: 5},
anchors: [position]
}

const endpoint = jsPlumbInstance.addEndpoint(elementId, endpointOptions);
wrapEndpointInLink(endpoint.canvas, linkUrl);
return endpoint;
}

function wrapEndpointInLink(svgElement, linkUrl) {
const linkElement = document.createElement('a');
linkElement.href = linkUrl;
svgElement.parentNode.insertBefore(linkElement, svgElement);
linkElement.appendChild(svgElement);
}

function connectNodes(jsPlumbHierarchy, connectionOptions) {
jsPlumbHierarchy.connect(connectionOptions);
}

jsPlumb.ready(function() {
const jsPlumbHierarchy = jsPlumb.getInstance({
Connector: ['Straight'],
PaintStyle: {strokeStyle: 'gray', lineWidth: 1},
EndpointStyle: {radius: 3, fillStyle: 'gray'},
Anchors: ['LeftMiddle', 'RightMiddle'],
Container: document.querySelector("#tab_hierarchy")
});

// Do not draw right away as the tab may be hidden
jsPlumbHierarchy.setSuspendDrawing(true);

// Draw if the hierarchy tab is active
if (isTabActive(tabId)) jsPlumbHierarchy.setSuspendDrawing(false, true);

document.querySelector('button[data-bs-target="#tab_hierarchy"]').addEventListener('shown.bs.tab', function (e) {
// Second argument instructs jsPlumb to perform a full repaint.
jsPlumbHierarchy.setSuspendDrawing(false, true);
});

// Repaint on resizing the browser window if the related tab is active
window.addEventListener('resize', function(){
if (isTabActive(tabId)) jsPlumbHierarchy.repaintEverything();
});

{% block herarchy_js_content %}{% endblock %}
});
</script>
2 changes: 1 addition & 1 deletion license_library/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
Expand All @@ -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();
});
});
</script>
2 changes: 1 addition & 1 deletion organization/templates/organization/owner_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% load static %}
{% block javascripts %}
{{ block.super }}
<script src="{% static "js/jquery.jsPlumb-1.7.2-min.js" %}" integrity="sha384-ITD4LUuh8ImLrJ5g55OIlG2QoiYVUuXLN9CStlO1e2SQZm0SyGfNkMiwPboMOv8D" crossorigin="anonymous"></script>
<script src="{% static 'js/jquery.jsPlumb-1.7.2-min.js' %}" integrity="sha384-ITD4LUuh8ImLrJ5g55OIlG2QoiYVUuXLN9CStlO1e2SQZm0SyGfNkMiwPboMOv8D" crossorigin="anonymous"></script>
{% 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 %}
Expand Down
Loading
0