feat(030): Dataset Lifecycle Workspace — Stats Bar, split-view, inline-edit, bulk actions
Backend: - Add MetricItem, StatsCounts, ColumnDescriptionUpdate, MetricDescriptionUpdate DTOs - Add metric_count to DatasetItem and DatasetDetailResponse - Add server-side filtering via ?filter=unmapped|mapped|linked|all - Add PUT endpoints for column/metric description inline-edit - Add HTML stripping validation (max 2000 chars, plain text) - Add WebSocket dataset.updated event on task completion - RBAC: plugin:migration:WRITE for mutations, READ for reads - 14 pytest tests (stats, filter, metrics, inline-edit, validation, 502/503) Frontend: - Rewrite +page.svelte as split-view orchestrator (387 lines) - StatsBar.svelte — 4 metric tiles with aria-pressed, server-side filter dispatch - DatasetList.svelte — card grid with progress bars, checkboxes, search - DatasetPreview.svelte — presentational detail panel (container fetches) - ColumnsTable.svelte — inline-edit with type chips, bold/italic fallback - MetricsTable.svelte — inline-edit with expression hints - 52 vitest tests across 7 test files - i18n: 11 EN + 11 RU keys (with_mapping/с_маппингом) Spec: - 15 issues resolved (semantic label, filtering, endpoints, conflict, metric_count, RBAC, ID types, ports, i18n, validation, a11y, propagation, resize, perf, ownership) Status: 14/14 backend tests pass, 52/52 frontend tests pass, build succeeds Risk: Low — T051 browser validation pending (non-blocking)
This commit is contained in:
@@ -393,6 +393,33 @@ async def websocket_endpoint(
|
||||
extra={"task_id": task_id},
|
||||
)
|
||||
# #endregion websocket_endpoint
|
||||
# #region dataset_websocket_endpoint [C:4] [TYPE Function]
|
||||
# @BRIEF WebSocket endpoint for dataset.updated events — auto-refresh on task completion.
|
||||
# @RELATION CALLS -> [TaskManagerPackage]
|
||||
# @PRE env_id must reference a known environment.
|
||||
# @POST WebSocket streams dataset.updated events until disconnect.
|
||||
# @SIDE_EFFECT Subscribes to dataset event queue in task manager lifecycle.
|
||||
@app.websocket("/ws/datasets/{env_id}")
|
||||
async def dataset_websocket_endpoint(websocket: WebSocket, env_id: str):
|
||||
seed_trace_id()
|
||||
with belief_scope("dataset_websocket_endpoint", f"env_id={env_id}"):
|
||||
await websocket.accept()
|
||||
logger.reason("Accepted dataset event WebSocket", extra={"env_id": env_id})
|
||||
task_manager = get_task_manager()
|
||||
queue = await task_manager.subscribe_dataset_events(env_id)
|
||||
try:
|
||||
while True:
|
||||
event = await queue.get()
|
||||
await websocket.send_json(event)
|
||||
logger.reflect("Forwarded dataset.updated event to client", extra={"env_id": env_id})
|
||||
except WebSocketDisconnect:
|
||||
logger.reason("WebSocket client disconnected from dataset events", extra={"env_id": env_id})
|
||||
except Exception as exc:
|
||||
logger.explore("WebSocket dataset streaming failed", extra={"env_id": env_id, "error": str(exc)})
|
||||
finally:
|
||||
task_manager.unsubscribe_dataset_events(env_id, queue)
|
||||
logger.reflect("Released dataset event subscription", extra={"env_id": env_id})
|
||||
# #endregion dataset_websocket_endpoint
|
||||
# #region StaticFiles [C:1] [TYPE Mount] [SEMANTICS static, frontend, spa]
|
||||
# @BRIEF Mounts the frontend build directory to serve static assets.
|
||||
frontend_path = project_root / "frontend" / "build"
|
||||
|
||||
Reference in New Issue
Block a user