# Copyright (c) 2026 Patrick Motsch # All rights reserved. """Workflow action: list Redmine tickets from the mirror with filters.""" import logging from typing import Any, Dict, List, Optional from modules.datamodels.datamodelChat import ActionResult from modules.features.redmine.serviceRedmine import listTickets from ._shared import resolveInstanceContext, ticketToDict logger = logging.getLogger(__name__) def _normalizeTrackerIds(value: Any) -> Optional[List[int]]: if value is None or value == "": return None if isinstance(value, int): return [value] if isinstance(value, str): value = [v.strip() for v in value.split(",") if v.strip()] if isinstance(value, list): ids: List[int] = [] for v in value: try: ids.append(int(v)) except (TypeError, ValueError): continue return ids or None return None async def listTicketsAction(self, parameters: Dict[str, Any]) -> ActionResult: """List Redmine tickets from the local mirror.""" try: user, mandateId, featureInstanceId = resolveInstanceContext(self.services, parameters) except ValueError as exc: return ActionResult.isFailure(error=str(exc)) trackerIds = _normalizeTrackerIds(parameters.get("trackerIds")) statusFilter = (parameters.get("status") or "*").lower() if statusFilter not in {"*", "open", "closed"}: statusFilter = "*" updatedFrom = parameters.get("dateFrom") or None updatedTo = parameters.get("dateTo") or None assignedToId: Optional[int] = None if parameters.get("assignedToId") not in (None, ""): try: assignedToId = int(parameters["assignedToId"]) except (TypeError, ValueError): return ActionResult.isFailure(error="assignedToId must be an int") try: tickets = listTickets( user, mandateId, featureInstanceId, trackerIds=trackerIds, statusFilter=statusFilter, updatedOnFrom=updatedFrom, updatedOnTo=updatedTo, assignedToId=assignedToId, ) except Exception as exc: logger.exception("redmine.listTickets failed") return ActionResult.isFailure(error=f"List tickets failed: {exc}") limit = 100 try: limit = max(1, min(500, int(parameters.get("limit") or 100))) except (TypeError, ValueError): limit = 100 offset = 0 try: offset = max(0, int(parameters.get("offset") or 0)) except (TypeError, ValueError): offset = 0 page = tickets[offset:offset + limit] return ActionResult.isSuccess(data={ "count": len(page), "totalMatched": len(tickets), "offset": offset, "hasMore": (offset + limit) < len(tickets), "tickets": [ticketToDict(t) for t in page], })