feat: sed-мутации датасетов, правки миграции и фиксы

- Dataset viewer: sed-замена (find→replace) по всем/выбранным/текущим датасетам
  с обязательным визуальным предпросмотром и целями (sql/metrics/yaml); сохранение
  и выбор именованных правил (sedRules store + SedRuleEditor).
- Migration: literal-замена в dataset YAML при переносе; рескан ID чартов/датасетов
  перед миграцией + метрика средней длительности синка (GET /migration/sync-stats);
  логическая группировка опций (маппинги БД, сервер изменений, rescan) под галочками.
- Fix: дашборды хаба скрывались дефолтным профиль-фильтром show_only_slug_dashboards=true
  (теперь false); мастер миграции не грузил дашборды предзаполненного источника;
  потеря терминального task_status из-за утечки pending-корутин в /ws/logs (панель
  результата не появлялась); горизонтальный скролл в MappingTable; чистая per-env
  ошибка в mapping coverage.
- Backend: record_sync_duration + alembic-миграция sync-duration; literal_replace модуль.
This commit is contained in:
2026-08-19 10:29:28 +03:00
parent a5764eb008
commit 81ba94684f
53 changed files with 2224 additions and 188 deletions

View File

@@ -996,10 +996,17 @@ async def websocket_endpoint(websocket: WebSocket, task_id: str, source: str = N
while True:
log_task = asyncio.create_task(log_queue.get())
status_task = asyncio.create_task(status_queue.get())
done, _ = await asyncio.wait(
done, pending = await asyncio.wait(
[log_task, status_task],
return_when=asyncio.FIRST_COMPLETED,
)
# @INVARIANT Cancel the not-yet-completed waiter BEFORE the next iteration.
# Without this, each loop leaks a pending queue.get() coroutine that
# consumes the NEXT item (e.g. the terminal task_status event), so the
# completion broadcast is silently swallowed and the client never sees
# the structured result — the result panel stays hidden after a task ends.
for pending_task in pending:
pending_task.cancel()
for coro in done:
result = coro.result()