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

290 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- status: plan -->
<!-- started: 2026-04-21 -->
<!-- component: gateway, frontend-nyla -->
# 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**
- [x] Mock-JSON aus `tickets-redmine.csv` extrahieren (~19 Tickets, alle Tracker + Beziehungstypen + 4 Orphans)
- [x] `local/notes/redmine-pilot/index.html` -- Tabs: Statistik (Default), Browser, Config
- [x] Statistik-View: KPI-Tiles + 5 SVG-Charts (Status-pro-Tracker, Throughput, Top-Bearbeiter, Beziehungs-Donut, Backlog-Aging) als Vorlage für `FormGeneratorReport`
- [x] Browser-View: Tree links (Userstory-Wurzeln + virtueller Orphan-Wurzelknoten, kollabierbar, Multi-Filter), rechts Edit-Form
- [x] Config-View: Form für Base-URL, API-Key, Project-ID, Default-Filter, Wurzel-Tracker
- [x] 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 }]`
- `onFilterChange``getStats(...)``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.searchTickets``redmine.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 |
## Links
- 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"
- [x] HTML-Pilot vom User abgenommen
- [x] Plan v2 (mit PeriodPicker, Stats-Page, Orphans, Decisions, AC) geschrieben
- [x] Bestehende Komponenten/Patterns identifiziert
- [x] Offene Fragen C1--C5 entschieden (User-Antworten 2026-04-21)
- [x] 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