Multi-Ticketsystem-Unterstützung: Pluggable Ticket-Quellen pro Projekt #14

Open
opened 2026-03-30 19:19:00 +00:00 by David · 0 comments
Collaborator

Beschreibung

TicketPilot soll mehrere Ticketsysteme als Quelle unterstützen — nicht nur Odoo und AppSignal.
Pro Projekt/Repo können ein oder mehrere Ticketsysteme konfiguriert werden.
Die Ticket-Synchronisation soll auch ohne Claude-Pipeline möglich sein (reiner Sync-Modus).
Unbekannte Ticketsysteme können dynamisch hinzugefügt werden (URL + Token),
wobei Claude Code den Adapter automatisch generiert.

Hintergrund

Aktuell sind Ticket-Quellen fest verdrahtet (Odoo via JSON-RPC in odoo_poller.py,
AppSignal via GraphQL in appsignal_poller.py). Die Konfiguration läuft global über .env.
Das schränkt die Flexibilität ein:

  • Verschiedene Projekte können unterschiedliche Ticketsysteme nutzen
  • Neue Ticketsysteme erfordern manuelle Code-Änderungen
  • Es gibt keinen "Nur-Sync"-Modus ohne Pipeline

Konzept

1. Architektur: Pluggable Ticket-Source-System

TicketSource (Abstract Base)
├── OdooTicketSource        (bestehend, refactored)
├── AppSignalTicketSource   (bestehend, refactored)
├── JiraTicketSource        (built-in)
├── GitHubTicketSource      (built-in)
├── FreshDeskTicketSource   (built-in)
└── CustomTicketSource      (dynamisch, Claude-generiert)

Abstrakte Basis-Klasse (backend/services/ticket_sources/base.py):

  • authenticate(token, url) → bool
  • fetch_tickets(since: datetime) → list[RawTicket]
  • post_comment(ticket_id, message) → bool
  • get_ticket_url(ticket_id) → str
  • test_connection() → ConnectionResult

2. Neues DB-Modell: TicketSourceConfig

class TicketSourceConfig(Base):
    __tablename__ = "ticket_source_configs"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)              # z.B. "Firmen-Jira"
    source_type = Column(String, nullable=False)       # "odoo"|"jira"|"github"|"custom"
    base_url = Column(String, nullable=False)          # API-URL
    auth_token = Column(String, nullable=False)        # Verschlüsselt gespeichert
    auth_config = Column(JSON)                         # Zusätzliche Auth-Felder (user, db, etc.)
    poll_interval_minutes = Column(Integer, default=5)
    sync_only = Column(Boolean, default=False)         # Nur Sync, keine Pipeline
    enabled = Column(Boolean, default=True)
    filter_config = Column(JSON)                       # Source-spezifische Filter
    created_at = Column(DateTime)
    last_synced_at = Column(DateTime)
    last_sync_error = Column(Text)

# Many-to-Many: Welche Repos nutzen welche Ticket-Quellen
class RepoTicketSource(Base):
    __tablename__ = "repo_ticket_sources"

    repo_id = Column(Integer, ForeignKey("repos.id"))
    source_id = Column(Integer, ForeignKey("ticket_source_configs.id"))

3. Erweiterung Ticket-Modell

Bestehendes Feld source erweitern:

  • source → Referenz auf ticket_source_configs.id statt hardcoded String
  • source_config_id → FK auf TicketSourceConfig
  • external_ticket_id → Generisch statt odoo_id / appsignal_incident_id

4. Sync-Modus ohne Pipeline

  • sync_only=True in TicketSourceConfig
  • Tickets werden abgeholt und in DB gespeichert
  • Kein Scoring, keine Pipeline, kein Claude
  • Dashboard zeigt sie als "Nur Sync" an
  • Nützlich für: Ticket-Übersicht, Reporting, manuelle Bearbeitung

5. Dynamische Adapter-Generierung (Custom Ticketsystem)

Wenn ein unbekanntes Ticketsystem konfiguriert wird:

  1. User gibt ein: Name, URL, Token, API-Dokumentation (optional)
  2. System ruft Claude Code auf mit Prompt:
    • "Erstelle einen TicketSource-Adapter für [System] mit URL [url]"
    • "Implementiere die abstrakte Basis-Klasse"
    • "Teste die Verbindung mit dem Token"
  3. Claude generiert backend/services/ticket_sources/custom_{name}.py
  4. Adapter wird dynamisch geladen (importlib)
  5. Connection-Test → Bei Erfolg: Aktivieren

6. Frontend: Ticket-Source-Verwaltung

Neue Seite TicketSources.tsx:

  • Liste aller konfigurierten Ticket-Quellen mit Status
  • "Neue Quelle hinzufügen" Dialog:
    • Dropdown: Odoo / Jira / GitHub / FreshDesk / Andere...
    • Bei bekanntem System: Spezifische Felder (URL, Token, DB, User)
    • Bei "Andere": URL + Token + optionale API-Docs
    • Checkbox "Nur synchronisieren (ohne Pipeline)"
    • "Verbindung testen" Button
  • Zuordnung zu Repos (Multi-Select)
  • Sync-Status und letzte Fehler anzeigen
  • Manueller Sync-Trigger pro Quelle

7. Betroffene Dateien

Neu:

  • backend/services/ticket_sources/base.py — Abstrakte Basis-Klasse
  • backend/services/ticket_sources/odoo.py — Refactored aus odoo_poller.py
  • backend/services/ticket_sources/appsignal.py — Refactored aus appsignal_poller.py
  • backend/services/ticket_sources/jira.py — Jira-Adapter
  • backend/services/ticket_sources/github.py — GitHub-Adapter
  • backend/services/ticket_sources/custom_loader.py — Dynamischer Adapter-Loader
  • backend/models/ticket_source.py — Neues DB-Modell
  • backend/api/ticket_sources.py — Neue REST-Endpoints
  • frontend/src/pages/TicketSources.tsx — Neue UI-Seite

Refactored:

  • backend/services/odoo_poller.py → Logik nach ticket_sources/odoo.py
  • backend/services/appsignal_poller.py → Logik nach ticket_sources/appsignal.py
  • backend/models/ticket.py — Generische source-Felder
  • backend/services/pipeline.py — Sync-only Modus
  • backend/main.py — Dynamische Poller-Registrierung
  • backend/config.py — Ticket-Source-Config statt globale Odoo/AppSignal-Vars
  • frontend/src/App.tsx — Neue Route
  • frontend/src/api/client.ts — Neue API-Calls

Migration:

  • Alembic-Migration für neue Tabellen + Ticket-Feld-Umbau

Akzeptanzkriterien

  • Abstrakte TicketSource-Klasse mit definierten Interface-Methoden existiert
  • Odoo-Poller nutzt die neue Abstraktion (Rückwärtskompatibel)
  • AppSignal-Poller nutzt die neue Abstraktion
  • TicketSourceConfig DB-Modell mit CRUD-Endpoints funktioniert
  • Ticket-Quellen können pro Repo zugeordnet werden (M:N)
  • "Verbindung testen" funktioniert für Odoo und AppSignal
  • Sync-Only-Modus: Tickets werden geholt aber keine Pipeline gestartet
  • Frontend: Ticket-Quellen können konfiguriert und getestet werden
  • Mindestens ein weiterer Adapter (Jira oder GitHub) ist implementiert
  • Custom-Adapter-Generierung via Claude Code funktioniert (PoC)
  • Bestehende Odoo/AppSignal-Konfiguration migriert automatisch
  • Auth-Tokens werden verschlüsselt in DB gespeichert (nicht plaintext)
  • Tests für alle neuen Services (Adapter-Interface, Config-CRUD, Sync-Modus)

Technische Hinweise

  • Betroffene Dateien: siehe oben (9 neue, 8 refactored)
  • Ansatz: Plugin-Architektur mit Abstract Base Class + Registry-Pattern
  • Migration nötig: ja (neue Tabellen + Ticket-Model-Änderung)
  • Token-Verschlüsselung: cryptography.fernet mit Key aus .env
  • Dynamisches Laden: importlib.import_module() für Custom-Adapter

Aufwand: XL

Neuer Service-Layer, DB-Migration, Frontend-Seite, Refactoring bestehender Poller.
Empfehlung: In 4 Sub-Issues aufteilen:

  1. Abstract Base + Refactoring Odoo/AppSignal (L)
  2. DB-Modell + API + Migration (M)
  3. Frontend TicketSources-Seite (M)
  4. Custom-Adapter-Generator + weitere Adapter (L)
## Beschreibung TicketPilot soll mehrere Ticketsysteme als Quelle unterstützen — nicht nur Odoo und AppSignal. Pro Projekt/Repo können ein oder mehrere Ticketsysteme konfiguriert werden. Die Ticket-Synchronisation soll auch ohne Claude-Pipeline möglich sein (reiner Sync-Modus). Unbekannte Ticketsysteme können dynamisch hinzugefügt werden (URL + Token), wobei Claude Code den Adapter automatisch generiert. ## Hintergrund Aktuell sind Ticket-Quellen fest verdrahtet (Odoo via JSON-RPC in `odoo_poller.py`, AppSignal via GraphQL in `appsignal_poller.py`). Die Konfiguration läuft global über `.env`. Das schränkt die Flexibilität ein: - Verschiedene Projekte können unterschiedliche Ticketsysteme nutzen - Neue Ticketsysteme erfordern manuelle Code-Änderungen - Es gibt keinen "Nur-Sync"-Modus ohne Pipeline ## Konzept ### 1. Architektur: Pluggable Ticket-Source-System ``` TicketSource (Abstract Base) ├── OdooTicketSource (bestehend, refactored) ├── AppSignalTicketSource (bestehend, refactored) ├── JiraTicketSource (built-in) ├── GitHubTicketSource (built-in) ├── FreshDeskTicketSource (built-in) └── CustomTicketSource (dynamisch, Claude-generiert) ``` **Abstrakte Basis-Klasse** (`backend/services/ticket_sources/base.py`): - `authenticate(token, url) → bool` - `fetch_tickets(since: datetime) → list[RawTicket]` - `post_comment(ticket_id, message) → bool` - `get_ticket_url(ticket_id) → str` - `test_connection() → ConnectionResult` ### 2. Neues DB-Modell: `TicketSourceConfig` ```python class TicketSourceConfig(Base): __tablename__ = "ticket_source_configs" id = Column(Integer, primary_key=True) name = Column(String, nullable=False) # z.B. "Firmen-Jira" source_type = Column(String, nullable=False) # "odoo"|"jira"|"github"|"custom" base_url = Column(String, nullable=False) # API-URL auth_token = Column(String, nullable=False) # Verschlüsselt gespeichert auth_config = Column(JSON) # Zusätzliche Auth-Felder (user, db, etc.) poll_interval_minutes = Column(Integer, default=5) sync_only = Column(Boolean, default=False) # Nur Sync, keine Pipeline enabled = Column(Boolean, default=True) filter_config = Column(JSON) # Source-spezifische Filter created_at = Column(DateTime) last_synced_at = Column(DateTime) last_sync_error = Column(Text) # Many-to-Many: Welche Repos nutzen welche Ticket-Quellen class RepoTicketSource(Base): __tablename__ = "repo_ticket_sources" repo_id = Column(Integer, ForeignKey("repos.id")) source_id = Column(Integer, ForeignKey("ticket_source_configs.id")) ``` ### 3. Erweiterung Ticket-Modell Bestehendes Feld `source` erweitern: - `source` → Referenz auf `ticket_source_configs.id` statt hardcoded String - `source_config_id` → FK auf TicketSourceConfig - `external_ticket_id` → Generisch statt `odoo_id` / `appsignal_incident_id` ### 4. Sync-Modus ohne Pipeline - `sync_only=True` in TicketSourceConfig - Tickets werden abgeholt und in DB gespeichert - Kein Scoring, keine Pipeline, kein Claude - Dashboard zeigt sie als "Nur Sync" an - Nützlich für: Ticket-Übersicht, Reporting, manuelle Bearbeitung ### 5. Dynamische Adapter-Generierung (Custom Ticketsystem) Wenn ein unbekanntes Ticketsystem konfiguriert wird: 1. User gibt ein: Name, URL, Token, API-Dokumentation (optional) 2. System ruft Claude Code auf mit Prompt: - "Erstelle einen TicketSource-Adapter für [System] mit URL [url]" - "Implementiere die abstrakte Basis-Klasse" - "Teste die Verbindung mit dem Token" 3. Claude generiert `backend/services/ticket_sources/custom_{name}.py` 4. Adapter wird dynamisch geladen (importlib) 5. Connection-Test → Bei Erfolg: Aktivieren ### 6. Frontend: Ticket-Source-Verwaltung Neue Seite `TicketSources.tsx`: - Liste aller konfigurierten Ticket-Quellen mit Status - "Neue Quelle hinzufügen" Dialog: - Dropdown: Odoo / Jira / GitHub / FreshDesk / Andere... - Bei bekanntem System: Spezifische Felder (URL, Token, DB, User) - Bei "Andere": URL + Token + optionale API-Docs - Checkbox "Nur synchronisieren (ohne Pipeline)" - "Verbindung testen" Button - Zuordnung zu Repos (Multi-Select) - Sync-Status und letzte Fehler anzeigen - Manueller Sync-Trigger pro Quelle ### 7. Betroffene Dateien **Neu:** - `backend/services/ticket_sources/base.py` — Abstrakte Basis-Klasse - `backend/services/ticket_sources/odoo.py` — Refactored aus odoo_poller.py - `backend/services/ticket_sources/appsignal.py` — Refactored aus appsignal_poller.py - `backend/services/ticket_sources/jira.py` — Jira-Adapter - `backend/services/ticket_sources/github.py` — GitHub-Adapter - `backend/services/ticket_sources/custom_loader.py` — Dynamischer Adapter-Loader - `backend/models/ticket_source.py` — Neues DB-Modell - `backend/api/ticket_sources.py` — Neue REST-Endpoints - `frontend/src/pages/TicketSources.tsx` — Neue UI-Seite **Refactored:** - `backend/services/odoo_poller.py` → Logik nach ticket_sources/odoo.py - `backend/services/appsignal_poller.py` → Logik nach ticket_sources/appsignal.py - `backend/models/ticket.py` — Generische source-Felder - `backend/services/pipeline.py` — Sync-only Modus - `backend/main.py` — Dynamische Poller-Registrierung - `backend/config.py` — Ticket-Source-Config statt globale Odoo/AppSignal-Vars - `frontend/src/App.tsx` — Neue Route - `frontend/src/api/client.ts` — Neue API-Calls **Migration:** - Alembic-Migration für neue Tabellen + Ticket-Feld-Umbau ## Akzeptanzkriterien - [ ] Abstrakte TicketSource-Klasse mit definierten Interface-Methoden existiert - [ ] Odoo-Poller nutzt die neue Abstraktion (Rückwärtskompatibel) - [ ] AppSignal-Poller nutzt die neue Abstraktion - [ ] TicketSourceConfig DB-Modell mit CRUD-Endpoints funktioniert - [ ] Ticket-Quellen können pro Repo zugeordnet werden (M:N) - [ ] "Verbindung testen" funktioniert für Odoo und AppSignal - [ ] Sync-Only-Modus: Tickets werden geholt aber keine Pipeline gestartet - [ ] Frontend: Ticket-Quellen können konfiguriert und getestet werden - [ ] Mindestens ein weiterer Adapter (Jira oder GitHub) ist implementiert - [ ] Custom-Adapter-Generierung via Claude Code funktioniert (PoC) - [ ] Bestehende Odoo/AppSignal-Konfiguration migriert automatisch - [ ] Auth-Tokens werden verschlüsselt in DB gespeichert (nicht plaintext) - [ ] Tests für alle neuen Services (Adapter-Interface, Config-CRUD, Sync-Modus) ## Technische Hinweise - Betroffene Dateien: siehe oben (9 neue, 8 refactored) - Ansatz: Plugin-Architektur mit Abstract Base Class + Registry-Pattern - Migration nötig: ja (neue Tabellen + Ticket-Model-Änderung) - Token-Verschlüsselung: `cryptography.fernet` mit Key aus .env - Dynamisches Laden: `importlib.import_module()` für Custom-Adapter ## Aufwand: XL Neuer Service-Layer, DB-Migration, Frontend-Seite, Refactoring bestehender Poller. Empfehlung: In 4 Sub-Issues aufteilen: 1. Abstract Base + Refactoring Odoo/AppSignal (L) 2. DB-Modell + API + Migration (M) 3. Frontend TicketSources-Seite (M) 4. Custom-Adapter-Generator + weitere Adapter (L)
Sign in to join this conversation.
No description provided.