Fix task API stability and Playwright runtime in Docker
This commit is contained in:
@@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
COPY backend/requirements.txt /app/backend/requirements.txt
|
||||
RUN pip install --no-cache-dir -r /app/backend/requirements.txt
|
||||
RUN python -m playwright install --with-deps chromium
|
||||
|
||||
COPY backend/ /app/backend/
|
||||
COPY --from=frontend-build /app/frontend/build /app/frontend/build
|
||||
|
||||
@@ -54,3 +54,4 @@ email-validator
|
||||
openai
|
||||
playwright
|
||||
tenacity
|
||||
Pillow
|
||||
|
||||
@@ -11,7 +11,7 @@ import asyncio
|
||||
import threading
|
||||
import inspect
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from .models import Task, TaskStatus, LogEntry, LogFilter, LogStats
|
||||
@@ -329,8 +329,18 @@ class TaskManager:
|
||||
tasks = [t for t in tasks if t.plugin_id in plugin_id_set]
|
||||
if completed_only:
|
||||
tasks = [t for t in tasks if t.status in [TaskStatus.SUCCESS, TaskStatus.FAILED]]
|
||||
# Sort by start_time descending (most recent first)
|
||||
tasks.sort(key=lambda t: t.started_at or datetime.min, reverse=True)
|
||||
# Sort by started_at descending with tolerant handling of mixed tz-aware/naive values.
|
||||
def sort_key(task: Task) -> float:
|
||||
started_at = task.started_at
|
||||
if started_at is None:
|
||||
return float("-inf")
|
||||
if not isinstance(started_at, datetime):
|
||||
return float("-inf")
|
||||
if started_at.tzinfo is None:
|
||||
return started_at.replace(tzinfo=timezone.utc).timestamp()
|
||||
return started_at.timestamp()
|
||||
|
||||
tasks.sort(key=sort_key, reverse=True)
|
||||
return tasks[offset:offset + limit]
|
||||
# [/DEF:get_tasks:Function]
|
||||
|
||||
|
||||
@@ -109,7 +109,8 @@ class Task(BaseModel):
|
||||
params: Dict[str, Any] = Field(default_factory=dict)
|
||||
input_required: bool = False
|
||||
input_request: Optional[Dict[str, Any]] = None
|
||||
result: Optional[Dict[str, Any]] = None
|
||||
# Result payload can be dict/list/scalar depending on plugin and legacy records.
|
||||
result: Optional[Any] = None
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the Task model and validates input_request for AWAITING_INPUT status.
|
||||
|
||||
@@ -12,6 +12,7 @@ import json
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from ...models.task import TaskRecord, TaskLogRecord
|
||||
from ...models.mapping import Environment
|
||||
from ..database import TasksSessionLocal
|
||||
from .models import Task, TaskStatus, LogEntry, TaskLog, LogFilter, LogStats
|
||||
from ..logger import logger, belief_scope
|
||||
@@ -21,6 +22,40 @@ from ..logger import logger, belief_scope
|
||||
# @SEMANTICS: persistence, service, database, sqlalchemy
|
||||
# @PURPOSE: Provides methods to save and load tasks from the tasks.db database using SQLAlchemy.
|
||||
class TaskPersistenceService:
|
||||
@staticmethod
|
||||
def _json_load_if_needed(value):
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, (dict, list)):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
stripped = value.strip()
|
||||
if stripped == "" or stripped.lower() == "null":
|
||||
return None
|
||||
try:
|
||||
return json.loads(stripped)
|
||||
except json.JSONDecodeError:
|
||||
return value
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _parse_datetime(value):
|
||||
if value is None or isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
return datetime.fromisoformat(value)
|
||||
except ValueError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _resolve_environment_id(session: Session, env_id: Optional[str]) -> Optional[str]:
|
||||
if not env_id:
|
||||
return None
|
||||
exists = session.query(Environment.id).filter(Environment.id == env_id).first()
|
||||
return env_id if exists else None
|
||||
|
||||
# [DEF:__init__:Function]
|
||||
# @PURPOSE: Initializes the persistence service.
|
||||
# @PRE: None.
|
||||
@@ -48,7 +83,8 @@ class TaskPersistenceService:
|
||||
|
||||
record.type = task.plugin_id
|
||||
record.status = task.status.value
|
||||
record.environment_id = task.params.get("environment_id") or task.params.get("source_env_id")
|
||||
raw_env_id = task.params.get("environment_id") or task.params.get("source_env_id")
|
||||
record.environment_id = self._resolve_environment_id(session, raw_env_id)
|
||||
record.started_at = task.started_at
|
||||
record.finished_at = task.finished_at
|
||||
|
||||
@@ -123,21 +159,28 @@ class TaskPersistenceService:
|
||||
for record in records:
|
||||
try:
|
||||
logs = []
|
||||
if record.logs:
|
||||
for log_data in record.logs:
|
||||
# Handle timestamp conversion if it's a string
|
||||
if isinstance(log_data.get('timestamp'), str):
|
||||
log_data['timestamp'] = datetime.fromisoformat(log_data['timestamp'])
|
||||
logs_payload = self._json_load_if_needed(record.logs)
|
||||
if isinstance(logs_payload, list):
|
||||
for log_data in logs_payload:
|
||||
if not isinstance(log_data, dict):
|
||||
continue
|
||||
log_data = dict(log_data)
|
||||
log_data['timestamp'] = self._parse_datetime(log_data.get('timestamp')) or datetime.utcnow()
|
||||
logs.append(LogEntry(**log_data))
|
||||
|
||||
started_at = self._parse_datetime(record.started_at)
|
||||
finished_at = self._parse_datetime(record.finished_at)
|
||||
params = self._json_load_if_needed(record.params)
|
||||
result = self._json_load_if_needed(record.result)
|
||||
|
||||
task = Task(
|
||||
id=record.id,
|
||||
plugin_id=record.type,
|
||||
status=TaskStatus(record.status),
|
||||
started_at=record.started_at,
|
||||
finished_at=record.finished_at,
|
||||
params=record.params or {},
|
||||
result=record.result,
|
||||
started_at=started_at,
|
||||
finished_at=finished_at,
|
||||
params=params if isinstance(params, dict) else {},
|
||||
result=result,
|
||||
logs=logs
|
||||
)
|
||||
loaded_tasks.append(task)
|
||||
|
||||
@@ -74,7 +74,8 @@ class DashboardValidationPlugin(PluginBase):
|
||||
|
||||
log.info(f"Executing {self.name} with params: {params}")
|
||||
|
||||
dashboard_id = params.get("dashboard_id")
|
||||
dashboard_id_raw = params.get("dashboard_id")
|
||||
dashboard_id = str(dashboard_id_raw) if dashboard_id_raw is not None else None
|
||||
env_id = params.get("environment_id")
|
||||
provider_id = params.get("provider_id")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user