[layerindex-web][PATCH] Show layer dependencies recursively

Piotr Buliński <[email protected]>
Newsgroups org.yoctoproject.lists.yocto-patches
Message-ID <[email protected]>
Implements: https://bugzilla.yoctoproject.org/show_bug.cgi?id=12599

(without the debugging console.log)

Signed-off-by: Piotr Buliński <[email protected]>
---
 layerindex/static/css/additional.css |   4 ++
 templates/layerindex/detail.html     | 104 ++++++++++++++++++++++++++-
 2 files changed, 106 insertions(+), 2 deletions(-)

diff --git a/layerindex/static/css/additional.css b/layerindex/static/css/additional.css
index ea4e555..a3c0a3a 100644
--- a/layerindex/static/css/additional.css
+++ b/layerindex/static/css/additional.css
@@ -52,6 +52,10 @@ textarea {
     font-size: 1.2em;
 }
 
+#dependencies_list ul {
+    font-size: 1em;
+}
+
 .description li {
     margin-bottom: 0.5em;
 }
diff --git a/templates/layerindex/detail.html b/templates/layerindex/detail.html
index 11c7df7..9ab6bf6 100644
--- a/templates/layerindex/detail.html
+++ b/templates/layerindex/detail.html
@@ -164,11 +164,18 @@
                             {% if layerbranch.get_required.count > 0 %}
                                 <h3>Dependencies </h3>
                                 <p>The {{ layeritem.name }} layer depends upon:</p>
-                                <ul>
+                                <ul id="dependencies_list">
                                     {% for dep in layerbranch.get_required %}
-                                        <li><a href="{% url 'layer_item' url_branch dep.dependency.name %}">{{ dep.dependency.name }}</a></li>
+                                        <li data-layerbranch="{{ dep.dependency.id }}">
+                                            <a href="{% url 'layer_item' url_branch dep.dependency.name %}">{{ dep.dependency.name }}</a>
+                                        </li>
                                     {% endfor %}
                                 </ul>
+                                <div class="text-right">
+                                    <button type="button" class="btn btn-default btn-xs" id="expand_dependencies">
+                                        Expand
+                                    </button>
+                                </div>
                             {% endif %} <!-- end of layerbranch.get_required.count -->
                             {% if layerbranch.get_recommends.count > 0 %}
                                 <h3>Recommends </h3>
@@ -415,6 +422,82 @@
             $(".recipestable > tbody > tr").show();
         }
 
+        // api abstracts access to the REST API endpoints and adds caching
+        // to avoid making multiple requests for the same data.
+        var api = {
+            // each calls the given callback for each item returned by the API
+            // endpoint with the given filter.
+            // If no data is returned, callback is never called.
+            each: function(endpoint, filter, callback) {
+                var cacheKey = endpoint + filter;
+                if (!(cacheKey in this)) {
+                    var url = "{% url 'api-root' %}" + endpoint + "/";
+                    this[cacheKey] = $.getJSON(url, {filter: filter});
+                }
+
+                this[cacheKey].done(function(data) {
+                    $.each(data, function(_, item) {
+                        callback(item);
+                    });
+                });
+            }
+        }
+
+        // layerUrl returns the URL for the layer detail page for the given layer name.
+        function layerUrl(layerName) {
+            return "{% url 'layer_item' url_branch '__layername__' %}".replace(
+                '__layername__', layerName
+            );
+        }
+
+        // expandLayerDependencies expands the dependencies of the layer
+        // referenced by parentListItem from the API and append them as a nested list,
+        // recursing to build the full tree.
+        // The 'seen' array tracks the current path to break dependency cycles.
+        function expandLayerDependencies(layerBranchId, seen) {
+            var branchId = "{{ layerbranch.branch_id }}";
+
+            if (!layerBranchId) {
+                return;
+            }
+
+            // keep track of seen layer branch IDs to avoid infinite recursion
+            // in case of circular dependencies
+            seen = seen.concat([layerBranchId]);
+
+            var nestedList = $('<ul></ul>');
+
+            var filter = "layerbranch:" + layerBranchId;
+            api.each("layerDependencies", filter, function(dependency) {
+                if (!dependency.required) {
+                    return;
+                }
+
+                var filter = "id:" + dependency.dependency;
+                api.each("layerItems", filter, function(layerItem) {
+                    var filter = "branch!layer:" + branchId + "!" + layerItem.id;
+                    api.each("layerBranches", filter, function(layerBranch) {
+                        var listItem = $('<li></li>').
+                            attr('data-layerbranch', layerBranch.id).
+                            append($('<a></a>').
+                                attr('href', layerUrl(layerItem.name)).
+                                text(layerItem.name));
+
+                        // detect circular dependencies to avoid infinite recursion
+                        if (seen.includes(layerBranch.id)) {
+                            listItem.append(' <span class="text-muted">(circular)</span>');
+                        } else {
+                            listItem.append(expandLayerDependencies(layerBranch.id, seen));
+                        }
+
+                        nestedList.append(listItem);
+                    });
+                });
+            });
+
+            return nestedList;
+        }
+
         $(document).ready(function() {
 
             $(function() {
@@ -444,6 +527,23 @@
                 $('.label-inverse').tooltip();
             });
 
+            // When the "Expand" button is clicked, hide the button and start
+            // expanding dependencies for each top-level dependency.
+            $("#expand_dependencies").click(function() {
+                $(this).hide();
+
+                $("#dependencies_list > li").each(function() {
+                    if ($(this).children('ul').length > 0) {
+                        // already expanded, skip
+                        return;
+                    }
+
+                    var layerBranchId = $(this).data('layerbranch');
+
+                    $(this).append(expandLayerDependencies(layerBranchId, []));
+                });
+            });
+
             clearRecipeSearch();
         });
     </script>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.