From 5fa4506728e1af961197560a1f73c587dd639a33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 14 Nov 2022 21:07:47 +0100
Subject: [PATCH] applications: sort elements on manifest page (#63882)

---
 .../templates/hobo/applications/manifest.html | 23 +++----
 hobo/applications/views.py                    |  8 ++-
 tests/test_application.py                     | 60 +++++++++++++++++++
 3 files changed, 74 insertions(+), 17 deletions(-)

diff --git a/hobo/applications/templates/hobo/applications/manifest.html b/hobo/applications/templates/hobo/applications/manifest.html
index 5302301..3402db6 100644
--- a/hobo/applications/templates/hobo/applications/manifest.html
+++ b/hobo/applications/templates/hobo/applications/manifest.html
@@ -28,22 +28,13 @@
 
   <ul class="objects-list single-links application-content">
     {% for relation in relations %}
-      {% if not relation.auto_dependency %}
-        <li>
-          <a {% if relation.element.get_redirect_url %}href="{{ relation.element.get_redirect_url }}"{% endif %}>
-            {% if relation.element.error %}<span class="tag tag-error">{{ relation.element.get_error_status_display }}</span>{% endif %}
-            {{ relation.element.name }} <span class="extra-info">- {{ relation.element.type_label }}</span>
-          </a>
-          {% if app.editable %}<a rel="popup" class="delete" href="{% url 'application-delete-element' app_slug=app.slug pk=relation.id %}">{% trans "remove" %}</a>{% endif %}
-        </li>
-      {% endif %}
-    {% endfor %}
-    {% for relation in relations %}
-      {% if relation.auto_dependency %}
-        <li class="auto-dependency">
-          <a {% if relation.element.get_redirect_url %}href="{{ relation.element.get_redirect_url }}"{% endif %}>{{ relation.element.name }} <span class="extra-info">- {{ relation.element.type_label }}</span></a>
-        </li>
-      {% endif %}
+      <li {% if relation.auto_dependency %}class="auto-dependency"{% endif %}>
+        <a {% if relation.element.get_redirect_url %}href="{{ relation.element.get_redirect_url }}"{% endif %}>
+          {% if relation.element.error %}<span class="tag tag-error">{{ relation.element.get_error_status_display }}</span>{% endif %}
+          {{ relation.element.name }} <span class="extra-info">- {{ relation.element.type_label }}</span>
+        </a>
+        {% if app.editable and not relation.auto_dependency %}<a rel="popup" class="delete" href="{% url 'application-delete-element' app_slug=app.slug pk=relation.id %}">{% trans "remove" %}</a>{% endif %}
+      </li>
     {% endfor %}
   </ul>
 
diff --git a/hobo/applications/views.py b/hobo/applications/views.py
index 6f74751..d25a5c7 100644
--- a/hobo/applications/views.py
+++ b/hobo/applications/views.py
@@ -99,7 +99,9 @@ class ManifestView(TemplateView):
         context['types_by_service'] = {}
 
         type_labels = {}
-        for object_type in get_object_types():
+        object_types = get_object_types()
+        types = [o['id'] for o in object_types]
+        for object_type in object_types:
             type_labels[object_type['id']] = object_type['singular']
             if object_type.get('minor'):
                 continue
@@ -110,6 +112,10 @@ class ManifestView(TemplateView):
 
         for relation in context['relations']:
             relation.element.type_label = type_labels.get(relation.element.type)
+        context['relations'] = sorted(
+            context['relations'],
+            key=lambda a: (a.auto_dependency, types.index(a.element.type), simplify(a.element.name)),
+        )
 
         return context
 
diff --git a/tests/test_application.py b/tests/test_application.py
index f4f2a0c..7672db4 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,6 +1,7 @@
 import base64
 import io
 import json
+import random
 import re
 import tarfile
 
@@ -379,6 +380,65 @@ def test_create_application(app, admin_user, settings, analyze):
         app.get('/applications/manifest/test/delete/%s/' % application.relation_set.first().pk, status=404)
 
 
+def test_manifest_ordering(app, admin_user, settings):
+    Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
+
+    settings.KNOWN_SERVICES = {
+        'wcs': {
+            'foobar': {
+                'title': 'Foobar',
+                'url': 'https://wcs.example.invalid/',
+                'orig': 'example.org',
+                'secret': 'xxx',
+            }
+        }
+    }
+
+    application = Application.objects.create(name='Test', slug='test')
+    objects = [
+        # type, slug, auto_dependency
+        ('forms', 'bar', True),
+        ('forms', 'foo', True),
+        ('cards', 'bar', True),
+        ('cards', 'foo', True),
+        ('workflows', 'bar', True),
+        ('workflows', 'foo', True),
+        ('forms', 'baaaar', False),
+        ('forms', 'foooo', False),
+        ('cards', 'baaaar', False),
+        ('cards', 'foooo', False),
+        ('workflows', 'baaaar', False),
+        ('workflows', 'foooo', False),
+    ]
+    random.shuffle(objects)
+    for _type, slug, auto_dependency in objects:
+        element = Element.objects.create(
+            type=_type,
+            slug=slug,
+            name=slug.title(),
+            cache={},
+        )
+        Relation.objects.create(application=application, element=element, auto_dependency=auto_dependency)
+
+    login(app)
+    with HTTMock(mocked_http):
+        resp = app.get('/applications/manifest/test/')
+        assert resp.pyquery('.application-content li a').text() == (
+            'Baaaar - Form remove '
+            'Foooo - Form remove '
+            'Baaaar - Card Model remove '
+            'Foooo - Card Model remove '
+            'Baaaar - Workflow remove '
+            'Foooo - Workflow remove '
+            'Bar - Form '
+            'Foo - Form '
+            'Bar - Card Model '
+            'Foo - Card Model '
+            'Bar - Workflow '
+            'Foo - Workflow'
+        )
+
+
 def test_scandeps_on_unknown_element(app, admin_user, settings):
     Wcs.objects.create(base_url='https://wcs.example.invalid', slug='foobar', title='Foobar')
 
-- 
2.35.1

