wiki/c-work/4-done/2026-04-redmine-feature.md
2026-04-21 23:49:43 +02:00

32 KiB
Raw Blame History

Redmine Feature -- Ticket-Topologie, Browser, AI-Tools, Workflow-Nodes

Beschreibung und Kontext

Wir nutzen in mehreren Software-Projekten Redmine als Ticketsystem (Userstories, Acceptance Criteria, Features, plus deren Beziehungen). Aus dem MARS/SSS-Pilot (pamocreate/projects/valueon/sss/project_mars/redmine-sync/) wissen wir, dass die Redmine-REST-API trägt: lesen, anlegen, ändern, Beziehungen verwalten. Bisher passierte das per Skript. Wir wollen Redmine als erstklassiges Feature in PORTA betreiben, sodass User die Tickets direkt im Workspace browsen, editieren, automatisieren und durch AI-Agents/Workflows verarbeiten können.

Business-Treiber: Verkürzte Schleife zwischen Konzeption (KI-gestützt im Workspace), Anforderungs-Pflege (Userstories/Acc.Criteria/Features) und Backlog. Mehrere Projekte (myABI/SSS, weitere LO-Mandanten) sollen gleichzeitig an unterschiedliche Redmine-Instanzen/-Projekte angebunden werden -- daher Konfiguration pro Feature-Instanz.

Risiko bei Nicht-Umsetzung: Wir bleiben beim Sync-via-Markdown-Skript-Workflow; jede neue Auswertung (z. B. Topologie, Beziehungs-Filter) braucht ein Einzelskript; Agents haben keinen Lese-/Schreibzugriff auf Tickets.

Pilot-Referenz: pamocreate/projects/valueon/sss/project_mars/redmine-sync/code/_redmineClient.py (REST-Wrapper, idempotente Reads/Writes), inventoryFeatures.py (Beziehungs-Parsing), README.md (Trackers, Custom Fields, Sprint-Limits).

Fokus und kritische Details

  • Userstory = Wurzel, Orphans separat. Im Browser ist die Wurzel jedes Trees eine User Story; alle anderen Tracker (Feature, Acc.Criteria, Bug, Task) erscheinen als Nachfahren entlang aktiver Relation-Typen (richtungsunabhängig). Tickets, die an keiner US hängen, werden unter einem virtuellen Orphan-Wurzelknoten gesammelt (= "Tickets ohne Verbindung zu einer User Story") -- so bleiben sie auffindbar und sind als KPI ausweisbar.
  • Wurzel-Tracker explizit aus der Config. Kein Heuristik-Fallback. Im RedmineInstanceConfig steht rootTrackerName (Default Userstory); beim Schema-Fetch wird dieser Name gegen die Tracker-Liste des Projekts case-insensitive aufgelöst. Falls der Tracker nicht existiert, ist rootTrackerId=None und das UI zeigt einen Konfig-Fehler.
  • Lokaler Mirror für 20k+ Tickets. Tickets und Relations werden in poweron_redmine gespiegelt (RedmineTicketMirror, RedmineRelationMirror, beide PowerOnModel mit Auto-DDL). Stats und Browser lesen ausschliesslich aus dem Mirror. Schreibops gehen an Redmine und upserten danach sofort den betroffenen Ticket-Datensatz im Mirror. Eigener Service serviceRedmineSync mit per-Instanz asyncio.Lock, Routes POST /api/redmine/{instanceId}/sync?force= und GET /sync/status -- gestartet manuell aus der Settings-Page (Button), später optional zeitgesteuert.
  • Statistik = erste Seite des Features. Eigene Page mit KPI-Tiles (Total / Offen / Geschlossen in Periode / Neu in Periode / Orphan-Count) und Diagrammen via FormGeneratorReport (siehe Abschnitt "Statistik-Sektionen").
  • Zeitraum-Auswahl: ausschliesslich PeriodPicker (components/PeriodPicker). Keine Custom-Dropdowns. PeriodValue ({preset, fromDate, toDate}) ist die einzige Quelle der Wahrheit; gesendet wird via dateFrom/dateTo an das Backend (analog gateway/modules/shared/dateRange.py). Im Stats-Page wird PeriodPicker direkt vom FormGeneratorReport.dateRangeSelector gerendert; im Browser-Page-Filter-Bar wird er als Standalone-Komponente eingesetzt. Default-Preset für Stats: last12Months; für Browser: lastMonth.
  • Pro Feature-Instanz eine Verbindung: redmineBaseUrl, apiKey (encrypted), projectId, optional Whitelist-Tracker / Default-Custom-Fields. Mehrere Instanzen pro Mandat möglich.
  • API-Key sicher speichern: Wie andere Connector-Secrets (apiTokenConfigKey-Pattern bei Jira) -- verschlüsselt im Mandat-Secret-Store, nie ins Frontend zurückgeben.
  • Idempotenz beim Schreiben: Vor jedem PUT/POST aktuellen Stand lesen, nur Diff schreiben (siehe applyPlan.py-Pattern).
  • Backlogs-Plugin (Sprint). Redmine-Standard kennt keine Sprint-Zuweisung via REST; das Backlogs-Plugin speichert separat. Read-Only im V1, Hinweis bei Schreibversuchen.
  • Custom Fields pro Projekt unterschiedlich. Wir lesen /projects/{id}/custom_fields beim Connect und cachen das Schema pro Instanz; Edit-Form rendert dynamisch (analog FormGenerator).
  • Workflow-Limits. DELETE /issues ist mit unseren Keys typischerweise verboten (HTTP 403); Closed + resolution_type=invalid als Soft-Delete. Statuswechsel ausserhalb des erlaubten Workflows liefern 204 ohne Effekt -- nach jedem PUT validieren.
  • Rate-Limit / Throttle. Connector mit asyncio.Semaphore-basierter Drosselung (Default 5 parallel, 150ms Throttle wie im Pilot).
  • Tests im Porta-UI, nicht via pytest-Sandbox. Verbindungs- und Sync-Tests laufen über die Buttons "Verbindung testen" und "Sync starten" in RedmineSettingsView. Keine externen Pytest-Live-Tests, keine ENV-Variablen-Choreographie. Pure Aggregations-/Cache-/Orphan-Logik bleibt offline-testbar mit Snapshot-Fixture (tests/fixtures/redmineSnapshot.json).
  • i18n: Alle UI-Texte über t('Deutscher Klartext'); Backend-Felder (Statusnamen, Trackernamen, CF-Labels) kommen direkt aus Redmine -- nicht durch t() jagen.

Statistik-Sektionen (V1) -- alle als FormGeneratorReport-Sections

Section type Inhalt Datenquelle
KPI-Tiles kpiGrid Total · Offen (% Anteil) · Geschlossen in Periode · Neu in Periode · Orphan-Count aggregierte Counts
Status pro Tracker horizontalBar (stacked) Pro Tracker je ein Balken, Segmente nach Status Group-by Tracker × Status, Snapshot
Throughput lineChart (2 Serien: erstellt, geschlossen) Pro Bucket (Tag/Woche/Monat) Group-by Bucket auf created_on und auf updated_on (closed)
Top Bearbeiter (offen) horizontalBar Top 6 Personen mit den meisten offenen Tickets Group-by assigned_to_id, isClosed=false
Beziehungs-Verteilung pieChart (donut) Anteile der Beziehungstypen Group-by relation_type über sichtbare Tickets
Backlog-Aging barChart 5 Buckets (<7, 7-30, 30-90, 90-180, >180 Tage) auf now - updated_on offene Tickets nach Alter

Backend liefert für die Stats-Page einen Endpoint GET /api/redmine/{instanceId}/stats?dateFrom=&dateTo=&bucket=&trackers= und gibt eine RedmineStatsDto zurück (alle 6 Sektions-Daten in einem Round-Trip; spart 5 Calls).

Optionale spätere Sektionen (Phase 2+)

MTTR pro Tracker · Sprint-Burnup je fixedVersion · Re-Open-Rate · Cumulative Flow Diagram · Bug-Inflow vs. Feature-Inflow.

Ziel und Nicht-Ziele

Ziel V1:

  • Feature-Modul redmine mit Konfig pro Instanz, RBAC-Katalog, Navigation-Views.
  • Backend-Connector connectorTicketsRedmine.py (read/edit/write/relations) + serviceRedmineStats (Aggregationen für die Statistik-Page).
  • 3 Frontend-Seiten: Statistik (Default), Browser, Config.
  • AI-Tools (Toolbox redmine, always-on innerhalb der Feature-Instanz): readRedmineTicket, searchRedmineTickets, updateRedmineTicket, createRedmineTicket, addRedmineRelation, listRedmineMeta, getRedmineStats.
  • Workflow-Method methodRedmine mit denselben Aktionen für Graph-Editor / Automation.
  • HTML-Pilot (Mock-JSON, kein Backend) zur UI-Validierung vor dem Backend-Bau -- erledigt (local/notes/redmine-pilot/).

Explizit NICHT (V1):

  • Kein Sprint-Management (Backlogs-Plugin schreiben). Nur Anzeige + Hinweis.
  • Kein Anhang-Upload/-Download (Phase 2).
  • Kein Watcher-Management, keine Time-Entries.
  • Kein Bulk-Edit-UI (CLI/Workflow ja, UI später).
  • Keine Webhook-Subscriptions (Polling-only V1).

Betroffene Module

  • Gateway:
    • modules/connectors/connectorTicketsRedmine.py (NEU) -- aiohttp-basiert, analog connectorTicketsJira.py.
    • modules/features/redmine/ (NEU): mainRedmine.py (Feature-Code, RBAC-Katalog), routeFeatureRedmine.py (REST-API), serviceRedmine.py (Geschäftslogik, Idempotenz, Caching), serviceRedmineStats.py (Aggregationen für Stats-Page, gemeinsam genutzt von Route + AI-Tool), interfaceFeatureRedmine.py (Instance-Config-Persistenz, Schema-Cache), datamodelRedmine.py (Pydantic: RedmineInstanceConfig, RedmineTicketDto, RedmineRelationDto, RedmineFieldSchemaDto, RedmineStatsDto).
    • modules/workflows/methods/methodRedmine/ (NEU): methodRedmine.py + actions/ (connectRedmine, readTicket, searchTickets, updateTicket, createTicket, addRelation, getStats).
    • modules/serviceCenter/services/serviceAgent/coreTools/_redmineTools.py (NEU) bzw. Erweiterung in registerCore.py (Toolbox redmine, always-on bei vorhandener Feature-Instanz).
    • modules/system/registry.py -- Feature-Discovery erweitert (kein Code-Change wenn Auto-Discover greift, sonst Eintrag).
    • modules/shared/dateRange.py -- bereits vorhanden, wiederverwenden (kein Neubau).
  • Frontend:
    • pages/views/redmine/RedmineConfigPage.tsx (NEU)
    • pages/views/redmine/RedmineStatsPage.tsx (NEU) -- ersetzt RedmineTopologyPage; rendert primär <FormGeneratorReport> mit aktiviertem dateRangeSelector (= PeriodPicker), filters (Tracker-Multiselect) und periodSelector (Granularität day/week/month).
    • pages/views/redmine/RedmineBrowserPage.tsx (NEU)
    • components/RedmineTicketTree/ (NEU, im Browser; rendert User-Story-Roots + virtuelle Orphan-Wurzel)
    • components/RedmineTicketEditor/ (NEU, dynamische Felder via FormGenerator)
    • api/redmineApi.ts (NEU) -- getStats({dateFrom, dateTo, bucket, trackers}), listTickets({dateFrom, dateTo, ...}), getTicket, updateTicket, createTicket, addRelation, getMeta, testConfig
    • config/pageRegistry.tsx -- 3 Seiten registrieren (Default-Page = Stats)
    • PeriodPicker: components/PeriodPicker -- ohne Änderung wiederverwendet (in Stats indirekt via FormGeneratorReport.dateRangeSelector, im Browser direkt).
  • DB-Migration:
    • Ja, eine Tabelle RedmineInstanceConfig (FK auf FeatureInstance) -- speichert Base-URL, projectId, encrypted apiKey, Default-Filter (incl. defaultPeriodPreset als JSON-Snapshot des PeriodValue), rootTrackerId (default Userstory), Schema-Cache-TTL. Schema-Daten (Trackers, Statuses, CFs) liegen in einer separaten JSON-Spalte als Cache.
  • Andere:
    • wiki/b-reference/gateway/features/redmine.md (NEU, beim Done)
    • wiki/TOPICS.md Eintrag (beim Done)

Entscheidungen

Datum Entscheidung Begründung
2026-04-21 Konfig pro Feature-Instanz (nicht pro User/Mandat) Mehrere Redmine-Projekte pro Mandat müssen parallel gehen; Connector-Secrets pro Instanz analog Trustee/CommCoach.
2026-04-21 User Story = fixe Tree-Wurzel (Tracker konfigurierbar), Orphans unter virtuellem Wurzelknoten User-Vorgabe nach Pilot-Review: Userstory-zentrierte Sicht; Orphans dürfen nicht "verschwinden", müssen als KPI sichtbar sein.
2026-04-21 Topologie-Page gestrichen, Sicht im Browser integriert Doppelte Tree-Sicht war redundant; Browser kann beides.
2026-04-21 Statistik-Page als erste Seite des Features User-Vorgabe nach Pilot-Review; soll bei Öffnen des Features ein "State of the Backlog" liefern.
2026-04-21 Diagramme via FormGeneratorReport -- KEINE Custom-Charts Bestehender Standard, Recharts-basiert, in 6+ Pages produktiv (Billing, Trustee, Audit).
2026-04-21 Zeitraum-Auswahl nur via PeriodPicker -- KEINE Custom-Dropdowns User-Vorgabe; einheitliche UX, Presets (ytd, lastMonth, last12Months, lastN, custom) und Round-trip-Stabilität sind in der Komponente bereits gelöst.
2026-04-21 AI-Tools always-on innerhalb der Feature-Instanz (kein Connection-Gating) User-Entscheid; analog Trustee-Tools. Authentifizierung läuft via Instance-Config nicht via UserConnection.
2026-04-21 HTML-Pilot mit Mock-JSON aus tickets-redmine.csv Schnellster UI-Feedback-Loop; keine API-Kopplung beim UI-Design.
2026-04-21 Backlogs-Sprint nur Read-Only V1 REST-API liefert kein verlässliches Schreiben (siehe Pilot README §5).
2026-04-21 DELETE-Operation als "Close + resolution=invalid" API-Restriktion (HTTP 403); konsistent mit Pilot-Verhalten.
2026-04-21 RedmineStatsDto als Roh-Buckets, Section-Mapping im Frontend Backend bleibt unabhängig vom FormGeneratorReport-Schema; saubere Trennung.
2026-04-21 Multi-Instanz analog Trustee-Pattern (Pfad /redmine/{instanceId}, Instance-Context-Resolver) Standard für Mandat-aktivierbare Features; bookmarkbar, RBAC-Scoping einheitlich.
2026-04-21 Tests direkt gegen SSS-Sandbox (redmine.logobject.ch, Projekt myABI/SSS) -- keine Mocks/VCR Realistisches Verhalten inkl. Custom-Field-Set, Workflow-Restriktionen, Beziehungslimits. CI markiert pytest.mark.live zum Skippen ohne Netz.
2026-04-21 Stats-Cache TTL 60s + Write-Invalidation ab V1 "Saubere Lösung von Anfang an"; vermeidet wiederholte Aggregations-Latenz auf grossen Projekten und ist trivial später zu tunen.

Umsetzungs-Checkliste

Phase 0 -- HTML-Pilot (UI-Validierung, ohne Backend) -- erledigt

  • Mock-JSON aus tickets-redmine.csv extrahieren (~19 Tickets, alle Tracker + Beziehungstypen + 4 Orphans)
  • local/notes/redmine-pilot/index.html -- Tabs: Statistik (Default), Browser, Config
  • Statistik-View: KPI-Tiles + 5 SVG-Charts (Status-pro-Tracker, Throughput, Top-Bearbeiter, Beziehungs-Donut, Backlog-Aging) als Vorlage für FormGeneratorReport
  • Browser-View: Tree links (Userstory-Wurzeln + virtueller Orphan-Wurzelknoten, kollabierbar, Multi-Filter), rechts Edit-Form
  • Config-View: Form für Base-URL, API-Key, Project-ID, Default-Filter, Wurzel-Tracker
  • User-Review/Feedback-Schleife → UI-Konzept eingefroren

Phase 1 -- Backend Connector + Feature

  • datamodelRedmine.py -- RedmineInstanceConfig, RedmineTicketDto, RedmineRelationDto, RedmineFieldSchemaDto, RedmineStatsDto (mit Substruktur pro Section)
  • connectorTicketsRedmine.py: getIssue, listIssues (paginated, updated_on>= filter), updateIssue, createIssue, addRelation, listRelations, getProjectMeta (Trackers/Statuses/CFs), whoAmI
  • DB-Migration: redmine_instance_config Tabelle, FK auf feature_instance. Spalten: base_url, project_id, api_key_secret_id, root_tracker_id, default_period_value (JSONB, PeriodValue-Snapshot), schema_cache (JSONB), schema_cached_at
  • interfaceFeatureRedmine.py -- Config CRUD (apiKey verschlüsselt), Schema-Cache (Trackers/Statuses/CFs mit TTL, default 24h)
  • serviceRedmine.py -- Idempotenz-Layer (Read-before-Write), Filter/Pagination, Throttle (Semaphore default 5/150ms)
  • serviceRedmineStats.py -- Aggregation in 6 Sektionen (statusByTracker, throughput, topAssignees, relationDistribution, backlogAging, kpis); akzeptiert dateFrom/dateTo (via dateRange.parseIsoDateRange) + bucket (day|week|month) + optional trackerIds. Berechnet Orphan-Count = Tickets nicht erreichbar von einer User-Story-Wurzel via Relationen jeden Typs. Liefert Roh-Buckets im RedmineStatsDto (kein FormGeneratorReport-Schema).
  • serviceRedmineStatsCache.py -- TTL-Cache (default 60s) keyed auf (instanceId, dateFrom, dateTo, bucket, sortedTrackerIds). Invalidierung pro Instance bei update/create/addRelation-Calls in serviceRedmine.
  • mainRedmine.py -- FEATURE_CODE="redmine", UI_OBJECTS=["redmineStats", "redmineBrowser", "redmineConfig"], DATA_OBJECTS, TEMPLATE_ROLES
  • routeFeatureRedmine.py -- /api/redmine/{instanceId}/...:
    • GET stats?dateFrom=&dateTo=&bucket=&trackers=RedmineStatsDto
    • GET tickets?dateFrom=&dateTo=&trackers=&status=&search=list[RedmineTicketDto]
    • GET tickets/{id}, PUT tickets/{id}, POST tickets, POST tickets/{id}/relations
    • GET meta, POST config/test
  • RBAC-Permissions: redmine.read, redmine.write, redmine.config
  • Audit-Log Events (category="redmine", actions: read, update, create, addRelation, configChange)

Phase 2 -- Frontend

  • RedmineConfigPage -- Form, Test-Button (POST config/test), Schema-Refresh-Button, Wurzel-Tracker-Dropdown, Default-PeriodPicker (Snapshot wird in default_period_value gespeichert)
  • RedmineStatsPage -- nutzt <FormGeneratorReport> mit:
    • dateRangeSelector={ enabled: true, defaultPresetKind: 'last12Months', direction: 'past', enabledPresets: ['lastMonth', 'thisQuarter', 'lastQuarter', 'last12Months', 'ytd', 'lastN', 'custom'] }
    • periodSelector={ periods: ['day','week','month'], defaultPeriod: 'week' } (Bucket-Granularität)
    • filters=[{ key: 'trackers', label: t('Tracker'), type: 'multiselect', options: meta.trackers }]
    • onFilterChangegetStats(...)setSections(...) (KPI + 5 Charts wie unter "Statistik-Sektionen" definiert)
  • RedmineBrowserPage -- Split-Layout: TicketTree links + TicketEditor rechts; Save → re-fetch + Re-render. Filter-Bar enthält <PeriodPicker> (standalone) für Bearbeitungsperiode.
  • RedmineTicketTree -- collapsible, Multi-Filter (Tracker, Relation-Type, Assignee), Userstory-Wurzeln + virtueller Orphan-Wurzelknoten (gestrichelte Pille, zeigt Anzahl Top-Level-Orphans, lässt sich auf-/zuklappen)
  • RedmineTicketEditor -- dynamisches Form aus meta.customFields (FormGenerator), Status-Dropdown, Subject, Description, Assignee, Tracker, Beziehungs-Liste mit Klick-Navigation
  • redmineApi.ts -- useApi-Wrapper für alle Endpoints; getStats({dateFrom, dateTo, bucket, trackers}) mit Debounce 300ms
  • pageRegistry.tsx -- 3 Seiten lazy-registriert mit objectKey, Default-Page = redmineStats
  • i18n-Keys gepflegt (t('Speichern'), t('Beziehung hinzufügen'), t('Tickets ohne Verbindung zu einer User Story'), ...)
  • Confirm-Dialog beim Verlassen mit ungespeicherten Änderungen

Phase 3 -- AI-Tools + Workflow-Method

  • _redmineTools.py -- Tools registriert in Toolbox redmine (immer aktiv bei vorhandener Feature-Instanz):
    • readRedmineTicket(issueId, includeRelations) -- readOnly
    • searchRedmineTickets(query, tracker, status, dateFrom, dateTo) -- readOnly
    • listRedmineMeta() -- readOnly (Trackers, Statuses, CFs)
    • getRedmineStats(dateFrom, dateTo, bucket, trackers) -- readOnly, gleiche Aggregation wie UI
    • updateRedmineTicket(issueId, fields, notes) -- write
    • createRedmineTicket(subject, description, tracker, customFields) -- write
    • addRedmineRelation(fromId, toId, relationType) -- write
  • toolboxRegistry.py -- Toolbox-Definition redmine mit featureCode="redmine"
  • methodRedmine + Actions analog methodJira -- actionId="redmine.<action>", dynamicMode=True, FrontendType-Annotationen
  • FlowEditor: Nodes erscheinen automatisch via ActionToolAdapter -- visuell prüfen
  • Tests: Unit-Tests für Connector (Mock-Server), Integration-Tests gegen einen lokalen Redmine-Sandbox (oder VCR-Cassette)

Phase 4 -- Doku & Cleanup

  • wiki/b-reference/gateway/features/redmine.md neu anlegen (mit Sektion "Statistik-Aggregationen")
  • wiki/TOPICS.md Eintrag
  • Pilot-Skript-Doku (redmine-sync) ergänzen: "→ produktiv via PORTA-Feature 'redmine'"
  • Plan-Dokument nach c-work/4-done/ verschieben

Akzeptanzkriterien

# Kriterium (Given-When-Then) Prio
1 Given gültige Redmine-Config in Feature-Instanz, When User die Config-Seite öffnet und auf "Verbindung testen" klickt, Then sieht er Username, Project-Name, Anzahl gelisteter Trackers/CFs must
2 Given Tickets mit relates/parent/blocks-Beziehungen und einer User Story als Wurzel, When User den Browser öffnet, Then sind alle Tickets entweder unter der passenden User Story eingerückt oder unter dem virtuellen Orphan-Wurzelknoten gelistet -- kein Ticket geht verloren must
3 Given ein Ticket im Browser angeklickt, When User die Description ändert und auf Speichern klickt, Then wird PUT /issues/{id} ausgeführt UND nach Re-Fetch die geänderte Description im UI sichtbar must
4 Given die Statistik-Page geöffnet, When User im PeriodPicker den Preset "letzter Monat" wählt, Then aktualisieren sich KPIs UND alle 5 Diagramme; im Throughput-Chart sind nur Buckets innerhalb dieser Periode sichtbar must
5 Given AI-Agent im Workspace mit aktiver Redmine-Feature-Instanz, When User "Lies Ticket #99526 und fasse es zusammen" eingibt, Then ruft Agent readRedmineTicket auf und liefert die Zusammenfassung must
6 Given AI-Agent, When User "Setze Status von #99526 auf In Progress" sagt, Then ruft Agent updateRedmineTicket mit {status_id: <In Progress>} auf, und Redmine bestätigt must
7 Given GraphEditor mit redmine.searchTicketsredmine.updateTicket-Workflow, When der Workflow ausgeführt wird, Then werden gefundene Tickets erwartungsgemäss aktualisiert should
8 Given Versuch DELETE /issues/{id} aus dem UI, When ausgelöst, Then führt das Backend stattdessen Close + resolution_type=invalid aus und meldet das im Toast should
9 Given fehlerhafte Redmine-Antwort (HTTP 422), When ein Update fehlschlägt, Then sieht User die fehlerhaften Felder farblich markiert mit Original-Errormeldung must
10 Given zwei Mandate mit unterschiedlichen Redmine-Configs, When ein User der App auf Tickets von Mandant B zugreift, Then bekommt er nur Tickets aus dessen Project-ID (RBAC + Instance-Scoping) must
11 Given Stats-Page mit Tracker-Filter "nur Bug+Task", When neu geladen, Then enthält der getStats-Call den Parameter trackers=1,3, Backend filtert serverseitig, alle 5 Charts zeigen ausschliesslich diese Tracker must
12 Given AI-Agent, When User "Wieviele Bugs sind in den letzten 30 Tagen entstanden?" fragt, Then ruft Agent getRedmineStats(dateFrom=now-30d, dateTo=now, trackers=[Bug]) auf und liefert die kpis.createdInPeriod-Zahl should
13 Given Tickets ohne assigned_to_id, When auf Statistik-Page geladen, Then erscheint im "Top Bearbeiter"-Chart eine Zeile "(nicht zugewiesen)" mit korrekter Anzahl should

Testplan

Alle Connector-/Route-Tests laufen live gegen redmine.logobject.ch, Projekt myABI/SSS (Test-Tickets in einem Sprint-Bereich, der ausschliesslich für CI verwendet wird). Marker pytest.mark.live ermöglicht skippen, wenn REDMINE_API_KEY-Env nicht gesetzt ist. Service-/Stats-Tests laufen ohne Netz auf Fixture-Daten (Snapshot der Live-Tickets als JSON, einmalig erzeugt via Helper-Skript).

ID AC Art Automatisiert Repo-Pfad Status
T1 1 live-integration ja (mark.live) gateway/tests/test_connector_redmine_live.py (whoAmI, getProjectMeta, getIssue, listIssues paginated) pending
T2 1 api ja gateway/tests/test_route_redmine_config.py pending
T3 2 unit ja gateway/tests/test_service_redmine_orphans.py (Tree-Builder + Orphan-Detection auf Fixture-JSON) pending
T4 3 live-integration ja (mark.live) gateway/tests/test_route_redmine_update_live.py (Read-modify-write Round-trip) pending
T5 4,11 unit ja gateway/tests/test_service_redmine_stats.py (Period-Filter, Tracker-Filter, Bucket-Aggregation auf Fixture-JSON) pending
T6 5,6,12 integration ja gateway/tests/test_redmine_tools.py (mark.live für Write-Tools) pending
T7 7 integration ja gateway/tests/test_method_redmine.py pending
T8 8 api ja gateway/tests/test_route_redmine_delete.py (Soft-Delete-Mapping + Toast-Message) pending
T9 9 ui manuell -- pending
T10 10 api ja gateway/tests/test_route_redmine_rbac.py pending
T11 4 ui manuell -- (PeriodPicker-Round-trip in Stats) pending
T12 13 unit ja gateway/tests/test_service_redmine_stats.py::test_unassigned_bucket pending
T13 -- unit ja gateway/tests/test_service_redmine_stats_cache.py (TTL, Hit/Miss, Invalidierung bei Write) pending
  • Pilot-Code: pamocreate/projects/valueon/sss/project_mars/redmine-sync/code/
  • Pilot-README: pamocreate/projects/valueon/sss/project_mars/redmine-sync/code/README.md
  • HTML-Pilot (Phase 0, erledigt): poweron/local/notes/redmine-pilot/index.html
  • Pilot-Doku: poweron/local/notes/redmine-pilot/README.md
  • Redmine REST-API: https://www.redmine.org/projects/redmine/wiki/Rest_api
  • Vergleichs-Connector: gateway/modules/connectors/connectorTicketsJira.py
  • Vergleichs-Method: gateway/modules/workflows/methods/methodJira/methodJira.py
  • Vergleichs-Feature: gateway/modules/features/trustee/ (Config pro Instanz, Tools)
  • Bestehende Komponenten -- wiederverwenden, nicht neubauen:
    • frontend_nyla/src/components/PeriodPicker/ (Zeitraum-Auswahl mit Presets)
    • frontend_nyla/src/components/FormGenerator/FormGeneratorReport/ (Stats-Page-Renderer mit Charts via Recharts; akzeptiert PeriodPicker via dateRangeSelector)
    • gateway/modules/shared/dateRange.py (parseIsoDateRange, isoDateRangeToLocalEpoch)
  • Stats-Vorbild im Code (Pattern für serviceRedmineStats): gateway/modules/workflows/methods/methodTrustee/actions/queryData.py

Kritischer Review (Pre-Execution)

Dieser Abschnitt fasst Risiken, blinde Flecken und ungeklärte Punkte zusammen, die vor Phase 1 entweder akzeptiert, mitigiert oder eskaliert werden müssen.

A. Stark / wahrscheinlich kein Problem

  1. HTML-Pilot deckt UI-Konzept ab: 3 Seiten visuell validiert, User-Feedback eingebaut (Userstory-Wurzel, Orphans, Stats-First-Page). Risiko UI-Rework in Phase 2 ist minimal.
  2. Patterns vorhanden: connectorTicketsJira.py, methodJira, feature/trustee (Config pro Instanz), FormGeneratorReport, PeriodPicker, dateRange.py -- keine Greenfield-Entscheidung mehr nötig, nur Anwenden.
  3. Pilot-Code für REST-Calls bewährt (_redmineClient.py): Endpoint-Liste, Pagination, idempotente Update-Logik in produktivem Sync seit Wochen stabil.
  4. AI-Tool-Pattern (Toolbox + ActionToolAdapter) ist in CommCoach/Trustee/Jira mehrfach umgesetzt -- Phase 3 ist Copy-Adapt, kein Pionierwerk.

B. Mittlere Risiken / Frühzeitig prüfen

  1. Schema-Cache-TTL (default 24h): Custom-Field-Änderungen in Redmine landen erst nach Refresh im UI. Mitigation: "Schema neu laden"-Button auf Config-Seite (im Pilot bereits), zusätzlich Auto-Refresh bei 400 unknown_custom_field-Antwort.
  2. Throughput-Aggregation auf grossen Projekten: 5'000+ Tickets würden bei bucket=day × 12 Monate = 365 Buckets × 5 Charts den Browser belasten. Mitigation: Backend liefert nur die aggregierten Sektions-Daten (nicht Roh-Tickets); zusätzlich bucket-Auto-Downgrade (Backend wechselt auf week/month, wenn >180 Buckets).
  3. PeriodPicker-Round-trip: FormGeneratorReport synthetisiert dateRange aus periodValue. Bei Browser-Reload muss der Preset (z. B. last12Months) erhalten bleiben, sonst rebrechnet sich lastN jedes Mal anders. Bereits in Komponente gelöst (siehe FormGeneratorReportTypes.ts-Doku), aber AC11/T11 explizit testen.
  4. Orphan-Detection vs. Tracker-Filter: Wenn der User im Browser z. B. nur "Bug" filtert, dann kann ein zur Userstory verknüpftes Bug fälschlich als "Orphan" wirken (weil die US wegen Filter unsichtbar ist). Entscheid: Orphan-Berechnung läuft IMMER über alle Tracker (US-Wurzeln werden für die Reachability nie ausgefiltert); nur die Anzeige folgt dem Filter. Diese Regel ist im Pilot bereits so umgesetzt -- in serviceRedmineStats.orphanIdsFor() als Kommentar dokumentieren.
  5. AI-Tool getRedmineStats vs. UI-Endpoint: Beide nutzen serviceRedmineStats -- aber Tool-Output muss kompakter sein (Token-Budget). Lösung: Tool gibt nur kpis + Top-3 pro Section zurück, mit Hinweis "vollständige Daten unter UI-Statistik-Page".

C. Entschieden (User 2026-04-21)

  1. DTO-Format: ✓ Roh-Buckets im RedmineStatsDto, Mapping auf ReportSection[] im Frontend (RedmineStatsPage.tsx). Sauberer Layer, Backend bleibt unabhängig vom Frontend-Render-Schema.
  2. Multi-Instanz-Routing: ✓ Standard-Pattern wie Trustee (Pfad-Parameter /api/redmine/{instanceId}/..., Multi-Instance pro Mandat, Instance-Resolution analog featureInstanceContext). Kein Sonderweg.
  3. Default-Periode pro Instanz: ✓ default_period_value-Spalte als optionaler PeriodValue-Snapshot. Wird nur beim Erst-Öffnen verwendet; URL-Params ?dateFrom=&dateTo= überschreiben.
  4. Connector-Tests: ✓ Direkt gegen den SSS-Case (redmine.logobject.ch, Projekt myABI/SSS) -- keine Mock-HTTP-Server, keine VCR-Cassetten. Tests laufen als Live-Integration; bei CI-Lauf ohne Netzzugriff sauber pytest.mark.live skippen lassen.
  5. Stats-Caching: ✓ Sauberes Caching von Anfang an -- TTL-basierter In-Memory-Cache pro (instanceId, dateFrom, dateTo, bucket, trackerIds)-Key, default 60s, mit Invalidierung auf update/create/addRelation für die betroffene Instance.

D. Schwächen / Lücken im aktuellen Plan

  1. Keine Migration-Strategie für bestehende default_period_value-Werte: Wenn wir später Presets ändern, brechen wir gespeicherte Snapshots. Mitigation: PeriodValue-Snapshot enthält fromDate/toDate als Fallback -- alte Presets werden im schlechtesten Fall als custom re-rehydriert, kein Datenverlust.
  2. Kein Hinweis im Plan auf Error-States im Stats-Chart: Wenn getStats 500 wirft, was zeigt die Page? Ergänzung in Phase 2: <FormGeneratorReport noDataMessage> und Error-Toast.
  3. Keine Bulk-Operation auf Stats-KPIs (z. B. "alle Orphans als 'archived' markieren"): bewusst V2.
  4. Webhook-Subscription ist bewusst NICHT V1 -- d. h. Ticket-Änderungen in Redmine werden erst beim nächsten User-Reload sichtbar. Im Plan unter "Nicht-Ziele" festgehalten -- für Stakeholder transparent.
  5. i18n der Stats-Sektions-Titel: User-facing strings ("Top Bearbeiter", "Throughput", ...) in Page-Komponente, nicht im Backend (Backend liefert nur Daten). Bereits Konvention -- explizit im Phase-2-Checklist erwähnen.

E. Reihenfolge-Empfehlung der Ausführung

  1. Phase 1 startet mit datamodelRedmine.py + connectorTicketsRedmine.py + Connector-Tests (T1) -- damit alle Folge-Module typisiert arbeiten.
  2. Erst dann DB-Migration + interfaceFeatureRedmine + serviceRedmine (read-only).
  3. Davor schon, parallel zu (1)-(2): serviceRedmineStats + Tests (T5) -- läuft unit-isoliert auf Mock-Tickets.
  4. Erst wenn Backend GET-Pfade stehen, beginnen Frontend-Pages -- Mock-Mode in redmineApi.ts als Übergang nicht nötig (Pilot deckt das ab).
  5. Phase 3 (AI-Tools / Workflow) ganz am Schluss -- braucht stabile Service-Layer.

F. Definition of "Ready to Execute"

  • HTML-Pilot vom User abgenommen
  • Plan v2 (mit PeriodPicker, Stats-Page, Orphans, Decisions, AC) geschrieben
  • Bestehende Komponenten/Patterns identifiziert
  • Offene Fragen C1--C5 entschieden (User-Antworten 2026-04-21)
  • Sandbox-Redmine bestätigt: redmine.logobject.ch, Projekt myABI/SSS -- Tests laufen direkt dort

Plan ist executionsbereit. Phase 1 kann starten.

G. Phase-1 Start-Sequenz (operative Reihenfolge)

  1. gateway/modules/features/redmine/datamodelRedmine.py -- Pydantic-Modelle, inkl. RedmineStatsDto mit Roh-Bucket-Substrukturen
  2. gateway/modules/connectors/connectorTicketsRedmine.py -- Read-Methoden zuerst (whoAmI, getProjectMeta, getIssue, listIssues, listRelations), Write-Methoden danach
  3. T1 (Live-Connector-Tests) parallel mit (2) -- Tests treiben Connector-API-Form
  4. Helper-Skript gateway/tests/fixtures/captureRedmineSnapshot.py -- ruft listIssues einmalig live, schreibt JSON-Fixture für Service-/Stats-Unit-Tests
  5. serviceRedmine.py (Idempotenz, Throttle) + serviceRedmineStats.py (Aggregationen) + serviceRedmineStatsCache.py
  6. T3, T5, T12, T13 (Unit auf Fixture)
  7. DB-Migration + interfaceFeatureRedmine.py (Config-Persistenz, Schema-Cache)
  8. mainRedmine.py + routeFeatureRedmine.py + RBAC + Audit-Log
  9. T2, T4, T8, T10 (API-Tests)
  10. → Phase 2 (Frontend) startet erst, wenn Phase 1 grün ist.

Abschluss

  • wiki/b-reference/gateway/features/redmine.md aktualisiert (NEU anlegen)
  • wiki/TOPICS.md aktualisiert (Eintrag "Redmine Feature")
  • Dieses Dokument → z-archive/ verschoben