Refactor category sorting in docker_cards_grouped lookup plugin, restructure Shopware task sequence, and extend menu categories (Commerce, Storage). Added unit tests for lookup plugin.

Conversation reference: https://chatgpt.com/share/6908642f-29cc-800f-89ec-fd6de9892b44
This commit is contained in:
2025-11-03 09:14:15 +01:00
parent 48557b06e3
commit df8390f386
11 changed files with 164 additions and 33 deletions

View File

@@ -4,11 +4,13 @@ __metaclass__ = type
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleError
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
"""
Group the given cards into categorized and uncategorized lists
based on the tags from menu_categories.
Categories are sorted alphabetically before returning.
"""
if len(terms) < 2:
raise AnsibleError("Missing required arguments")
@@ -19,6 +21,7 @@ class LookupModule(LookupBase):
categorized = {}
uncategorized = []
# Categorize cards
for card in cards:
found = False
for category, data in menu_categories.items():
@@ -29,10 +32,14 @@ class LookupModule(LookupBase):
if not found:
uncategorized.append(card)
# Sort categories alphabetically
sorted_categorized = {
k: categorized[k] for k in sorted(categorized.keys(), key=str.lower)
}
return [
{
'categorized': categorized,
'categorized': sorted_categorized,
'uncategorized': uncategorized,
}
]

View File

@@ -25,7 +25,6 @@ portfolio_menu_categories:
- ollama
- openwebui
- flowise
- minio
- qdrant
- litellm
@@ -102,14 +101,12 @@ portfolio_menu_categories:
- fusiondirectory
- user-management
Customer Relationship Management:
description: "Tools for managing customer relationships, sales pipelines, marketing, and support activities."
Customer Relationship:
description: "Customer Relationship Management (CRM) software for managing customer relationships, sales pipelines, marketing, and support activities."
icon: "fa-solid fa-address-book"
tags:
- crm
- customer
- relationship
- sales
- marketing
- support
- espocrm
@@ -222,7 +219,7 @@ portfolio_menu_categories:
- snipe-it
Content Management:
description: "CMS and web publishing platforms"
description: "Content Management Systems (CMS) and web publishing platforms"
icon: "fa-solid fa-file-alt"
tags:
- cms
@@ -231,4 +228,27 @@ portfolio_menu_categories:
- website
- joomla
- wordpress
- blog
- blog
Commerce:
description: "Platforms for building and managing online shops, product catalogs, and digital sales channels — including payment, inventory, and customer features."
icon: "fa-solid fa-cart-shopping"
tags:
- commerce
- ecommerce
- shopware
- shop
- sales
- store
- magento
- pretix
Storage:
description: "High-performance, self-hosted storage solutions for managing, scaling, and accessing unstructured data — including object storage compatible with Amazon S3 APIs."
icon: "fa-solid fa-database"
tags:
- storage
- object-storage
- s3
- minio
- datasets

View File

@@ -7,7 +7,9 @@ features:
logout: true
server:
csp:
flags: {}
flags:
script-src-elem:
unsafe-inline: true
whitelist: {}
domains:
aliases: []

View File

@@ -1,7 +1,7 @@
---
galaxy_info:
author: "Kevin Veen-Birkenbach"
description: "Open-Source Commerce (PHP/Symfony) with optional OIDC/LDAP, Redis & OpenSearch — containerized & automated."
description: "Shopware is a modern open-source eCommerce platform built on PHP and Symfony. It enables businesses to create scalable online stores with flexible product management, intuitive administration, customizable storefronts, and powerful APIs for headless and omnichannel commerce."
license: "Infinito.Nexus NonCommercial License"
license_url: "https://s.infinito.nexus/license"
company: |
@@ -11,10 +11,6 @@ galaxy_info:
galaxy_tags:
- shopware
- ecommerce
- docker
- symfony
- oidc
- ldap
repository: https://s.infinito.nexus/code
issue_tracker_url: https://s.infinito.nexus/issues
documentation: "https://docs.infinito.nexus/"

View File

@@ -25,22 +25,11 @@
timeout: 300
- name: "Ensure admin user exists with correct password"
include_tasks: 03_admin.yml
include_tasks: 01_admin.yml
- name: "Warm up caches and index"
shell: |
docker exec -i --user {{ SHOPWARE_USER }} {{ SHOPWARE_WEB_CONTAINER }} bash -lc '
cd {{ SHOPWARE_ROOT }}
php bin/console messenger:consume --time-limit=60 --limit=100 || true
php bin/console dal:refresh:index || true
php bin/console cache:clear
'
args:
chdir: "{{ docker_compose.directories.instance }}"
- name: Execute setup routines (OIDC/LDAP)
include_tasks: 01_setup.yml
- name: Execute cleanup routines
include_tasks: 02_cleanup.yml
when: MODE_CLEANUP
#- name: Execute setup routines (OIDC/LDAP)
# include_tasks: 02_setup.yml
#
#- name: Execute cleanup routines
# include_tasks: 03_cleanup.yml
# when: MODE_CLEANUP

View File

View File

@@ -0,0 +1,117 @@
import os
import sys
import unittest
from importlib import import_module
# Compute repo root (…/tests/unit/roles/web-app-desktop/lookup_plugins/docker_cards_grouped.py -> repo root)
_THIS_DIR = os.path.dirname(__file__)
_REPO_ROOT = os.path.abspath(os.path.join(_THIS_DIR, "../../../../.."))
# Add the lookup_plugins directory to sys.path so we can import the plugin as a plain module
_LOOKUP_DIR = os.path.join(_REPO_ROOT, "roles", "web-app-desktop", "lookup_plugins")
if _LOOKUP_DIR not in sys.path:
sys.path.insert(0, _LOOKUP_DIR)
# Import the plugin module
plugin = import_module("docker_cards_grouped")
LookupModule = plugin.LookupModule
try:
from ansible.errors import AnsibleError
except Exception: # Fallback for environments without full Ansible
class AnsibleError(Exception):
pass
class TestDockerCardsGroupedLookup(unittest.TestCase):
def setUp(self):
self.lookup = LookupModule()
# Menu categories with mixed-case names to verify case-insensitive sort
self.menu_categories = {
"B-Group": {"tags": ["b", "beta"]},
"a-Group": {"tags": ["a", "alpha"]},
"Zeta": {"tags": ["z"]},
}
# Cards with tags; one should end up uncategorized
self.cards = [
{"title": "Alpha Tool", "tags": ["a"]},
{"title": "Beta Widget", "tags": ["beta"]},
{"title": "Zed App", "tags": ["z"]},
{"title": "Unmatched Thing", "tags": ["x"]},
]
def _run(self, cards=None, menu_categories=None):
result = self.lookup.run(
[cards or self.cards, menu_categories or self.menu_categories]
)
# Plugin returns a single-element list containing the result dict
self.assertIsInstance(result, list)
self.assertEqual(len(result), 1)
self.assertIsInstance(result[0], dict)
return result[0]
def test_categorization_and_uncategorized(self):
data = self._run()
self.assertIn("categorized", data)
self.assertIn("uncategorized", data)
categorized = data["categorized"]
uncategorized = data["uncategorized"]
# Each matching card is placed into the proper category
self.assertIn("a-Group", categorized)
self.assertIn("B-Group", categorized)
self.assertIn("Zeta", categorized)
titles_in_a = [c["title"] for c in categorized["a-Group"]]
titles_in_b = [c["title"] for c in categorized["B-Group"]]
titles_in_z = [c["title"] for c in categorized["Zeta"]]
self.assertEqual(titles_in_a, ["Alpha Tool"])
self.assertEqual(titles_in_b, ["Beta Widget"])
self.assertEqual(titles_in_z, ["Zed App"])
# Unmatched card should be in 'uncategorized'
self.assertEqual(len(uncategorized), 1)
self.assertEqual(uncategorized[0]["title"], "Unmatched Thing")
def test_categories_sorted_alphabetically_case_insensitive(self):
data = self._run()
categorized = data["categorized"]
# Verify order is alphabetical by key, case-insensitive
keys = list(categorized.keys())
self.assertEqual(keys, ["a-Group", "B-Group", "Zeta"])
def test_multiple_tags_match_first_category_encountered(self):
# A card that matches multiple categories should be placed
# into the first matching category based on menu_categories iteration order.
# Here "Dual Match" has both 'a' and 'b' tags; since "a-Group" is alphabetically
# before "B-Group" only after sorting happens at RETURN time, we need to ensure the
# assignment is based on menu_categories order (insertion order).
menu_categories = {
"B-Group": {"tags": ["b"]},
"a-Group": {"tags": ["a"]},
}
cards = [{"title": "Dual Match", "tags": ["a", "b"]}]
# The plugin iterates menu_categories in insertion order and breaks on first match,
# so this card should end up in "B-Group".
data = self._run(cards=cards, menu_categories=menu_categories)
categorized = data["categorized"]
self.assertIn("B-Group", categorized)
self.assertEqual([c["title"] for c in categorized["B-Group"]], ["Dual Match"])
self.assertNotIn("a-Group", categorized) # no card added there
def test_missing_arguments_raises(self):
with self.assertRaises(AnsibleError):
self.lookup.run([]) # no args
with self.assertRaises(AnsibleError):
self.lookup.run([[]]) # only one arg
if __name__ == "__main__":
unittest.main()