mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-12-15 21:33:05 +00:00
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:
@@ -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,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,7 +7,9 @@ features:
|
||||
logout: true
|
||||
server:
|
||||
csp:
|
||||
flags: {}
|
||||
flags:
|
||||
script-src-elem:
|
||||
unsafe-inline: true
|
||||
whitelist: {}
|
||||
domains:
|
||||
aliases: []
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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
|
||||
|
||||
0
tests/unit/web-app-desktop/__init__.py
Normal file
0
tests/unit/web-app-desktop/__init__.py
Normal file
117
tests/unit/web-app-desktop/lookup_plugins/__init__.py
Normal file
117
tests/unit/web-app-desktop/lookup_plugins/__init__.py
Normal 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()
|
||||
Reference in New Issue
Block a user