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:
@@ -71,6 +71,7 @@ class JobLifecycle:
|
||||
self.persistence_service = persistence_service
|
||||
self.executor = executor
|
||||
self.loop = loop
|
||||
self._dataset_subscribers: dict[str, list[asyncio.Queue]] = {}
|
||||
# #endregion __init__
|
||||
|
||||
# #region create_task [C:4] [TYPE Function] [SEMANTICS task,create,persist,schedule]
|
||||
@@ -190,6 +191,12 @@ class JobLifecycle:
|
||||
"Task lifecycle reached persisted terminal state",
|
||||
extra={"task_id": task_id, "status": str(task.status)},
|
||||
)
|
||||
# Broadcast dataset.updated for mapping and documentation tasks (FR-024, R4)
|
||||
if task.plugin_id in ("dataset-mapper", "llm_documentation"):
|
||||
dataset_ids = task.params.get("dataset_ids") or [task.params.get("dataset_id")]
|
||||
env_id = task.params.get("env") or task.params.get("environment_id", "")
|
||||
if dataset_ids and env_id:
|
||||
self._broadcast_dataset_updated(env_id, dataset_ids)
|
||||
# #endregion _run_task
|
||||
|
||||
# #region resolve_task [C:3] [TYPE Function] [SEMANTICS task,resolve,mapping,resume]
|
||||
@@ -309,5 +316,37 @@ class JobLifecycle:
|
||||
)
|
||||
self.graph.resolve_future(task_id, True)
|
||||
# #endregion resume_task_with_password
|
||||
|
||||
# #region subscribe_dataset_events [C:2] [TYPE Function] [SEMANTICS dataset,subscribe,events,websocket]
|
||||
# @BRIEF Subscribe to dataset.updated events for a specific environment.
|
||||
async def subscribe_dataset_events(self, env_id: str) -> asyncio.Queue:
|
||||
queue = asyncio.Queue()
|
||||
if env_id not in self._dataset_subscribers:
|
||||
self._dataset_subscribers[env_id] = []
|
||||
self._dataset_subscribers[env_id].append(queue)
|
||||
return queue
|
||||
# #endregion subscribe_dataset_events
|
||||
|
||||
# #region unsubscribe_dataset_events [C:2] [TYPE Function] [SEMANTICS dataset,unsubscribe,events]
|
||||
# @BRIEF Unsubscribe from dataset.updated events for a specific environment.
|
||||
def unsubscribe_dataset_events(self, env_id: str, queue: asyncio.Queue):
|
||||
if env_id in self._dataset_subscribers:
|
||||
if queue in self._dataset_subscribers[env_id]:
|
||||
self._dataset_subscribers[env_id].remove(queue)
|
||||
if not self._dataset_subscribers[env_id]:
|
||||
del self._dataset_subscribers[env_id]
|
||||
# #endregion unsubscribe_dataset_events
|
||||
|
||||
# #region _broadcast_dataset_updated [C:3] [TYPE Function] [SEMANTICS dataset,broadcast,event,websocket]
|
||||
# @BRIEF Broadcast dataset.updated event to all subscribers for a given environment.
|
||||
def _broadcast_dataset_updated(self, env_id: str, dataset_ids: list[int]):
|
||||
event = {"type": "dataset.updated", "payload": {"env_id": env_id, "dataset_ids": dataset_ids}}
|
||||
if env_id in self._dataset_subscribers:
|
||||
for queue in self._dataset_subscribers[env_id]:
|
||||
try:
|
||||
self.loop.call_soon_threadsafe(queue.put_nowait, event)
|
||||
except Exception:
|
||||
pass
|
||||
# #endregion _broadcast_dataset_updated
|
||||
# #endregion JobLifecycle
|
||||
# #endregion JobLifecycleModule
|
||||
|
||||
Reference in New Issue
Block a user