# UX Reference: Async Backend Refactoring **Feature Branch**: `032-translate-requests-httpx` **Created**: 2026-06-04 **Status**: Draft ## 1. User Persona & Context - **Who is the user?**: - **Оператор ss-tools** — настраивает и запускает задачи (переводы, бэкапы, деплои, миграции). Может быть несколько операторов одновременно. - **Администратор** — мониторит состояние системы, просматривает дашборды, управляет окружениями. - **API-клиент** — внешние системы, вызывающие API ss-tools (например, CI/CD pipeline для деплоя). - **What is their goal?**: Выполнять несколько операций параллельно без блокировок. Видеть отзывчивый интерфейс независимо от того, какие фоновые задачи выполняются. - **Context**: Работа через веб-интерфейс SvelteKit SPA или напрямую через REST API. Один экземпляр сервера обслуживает нескольких пользователей. ## 2. The «Happy Path» Narratives ### Narrative A: Translate Setup (основная жалоба) Оператор открывает переводческую задачу. На вкладке Preview нажимает «Run Preview» (sample_size=10). Видит спиннер. **Не дожидаясь**, переключается на Target Config → жмёт «Check Target Schema». Через 2 секунды — зелёная галочка. Возвращается на Preview — переводы уже загрузились. Всё заняло ~8 секунд параллельно вместо 11 последовательно. ### Narrative B: Multi-Operator (командная работа) Оператор А запускает бэкап 100 дашбордов из продакшен-Superset. Оператор Б в это же время настраивает переводческую задачу — нажимает предпросмотр, проверяет схему. Администратор открывает список джоб. Все три пользователя работают без задержек — их запросы не стоят в очереди за бэкапом. ### Narrative C: CI/CD Deploy + Monitoring (API-клиент) CI/CD пайплайн дёргает `POST /git/deploy` для деплоя дашборда в стейджинг. Одновременно администратор через UI проверяет статус дашбордов в LLM Analysis. Деплой (15 секунд на экспорт/импорт + Git-операции) и анализ (скриншоты + LLM) выполняются параллельно. ## 3. Interface Mockups ### API Interaction Flow — Multi-Plugin Concurrency ``` Клиент A (Backup) Сервер (1 worker) Клиент B (Translate) Клиент C (Dashboard) │ │ │ │ │ POST /backup/run ───────►│ │ │ │ │ ┌─ Superset export ──┐ │ │ │ │ │ get_dashboards() │ │ │ │ │ │ (async, не блок.) │ │ │ │ │ └─────────────────────┘ │ │ │ │ │ │ │ │◄── POST /preview ────────│ │ │ │ │ │ │ │ ┌─ Superset chart data ┐│ │ │ │ │ LLM call (async) ││ │ │ │ └──────────────────────┘│ │ │ │ │ │ │ │ │◄── GET /dashboards ─────│ │ │ │ │ │ │ ┌─ Все три выполняются ┐│ │ │ │ │ конкурентно, ни один ││ │ │ │ │ не ждёт другой ││ │ │ │ └──────────────────────┘│ │ │ │ │ │ │◄── backup result ────────│ │ │ │ (100 dashboards OK) │ │ │ │ │◄── preview result ───────│ │ │ │ (10 rows + cost) │ │ │ │ │◄── dashboard list ──────│ ``` ### До vs После (тайминг при 1 worker) ``` ДО рефакторинга (sync, 1 worker): ┌────────────┬──────────────────┬───────────────┬──────────┐ │ Backup 45s │ Preview 8s │ SchemaCheck 3s│ GET /jobs│ = 56.5s total └────────────┴──────────────────┴───────────────┴──────────┘ ПОСЛЕ рефакторинга (async, 1 worker): ┌──────────────────────────────────────────────────────────┐ │ Backup 45s (экспорт дашбордов, файловый I/O) │ │ ┌──────────────────┐ │ │ │ Preview 8s │ │ │ │ ┌──────────────┐ │ │ │ │SchemaCheck 3s│ │ │ │ └──────────────┘ │ │ └──────────────────┘ │ │ ┌── GET /jobs 50ms│ └────────────────────────────────────────┴─────────────────┘ = 45s total ``` ### Существующие UI-компоненты (без изменений) Контракты компонентов не меняются. Меняется только временная характеристика — компоненты больше не ждут друг друга: | Компонент | Файл | Состояния (без изменений) | Что изменилось | |-----------|------|---------------------------|----------------| | TranslationPreview | `TranslationPreview.svelte` | idle → loading → preview_loaded / error | Может быть в loading одновременно с TargetSchemaHint в checking | | TargetSchemaHint | `TargetSchemaHint.svelte` | idle → checking → complete / error | Не ждёт окончания preview | | DashboardGrid | `DashboardGrid.svelte` | Загрузка списка | Не ждёт backup/git/deploy | | BackupConfig | `BackupConfig.svelte` | Запуск бэкапа | Не блокирует другие вкладки | | GitDeployPanel | Компонент деплоя | Деплой дашборда | Не блокирует просмотр дашбордов | | StorageUpload | Загрузка файлов | Upload progress | Не блокирует фоновые задачи | ## 4. The «Error» Experience ### Scenario A: Таймаут Superset API при бэкапе - **User Action**: Запущен бэкап 100 дашбордов. Superset начинает отвечать медленно. - **System Response**: - Конкретный вызов `export_dashboard(id)` получает таймаут (30 секунд). - В отчёте бэкапа этот дашборд помечается как FAILED с причиной «timeout». - Бэкап продолжается со следующим дашбордом. - **Другие запросы не затронуты** — админ всё ещё может смотреть список дашбордов. - **Recovery**: Повторный запуск бэкапа только для упавших дашбордов. ### Scenario B: Отмена запроса (браузер закрыт) - **User Action**: Оператор запустил предпросмотр, закрыл вкладку. - **System Response**: - FastAPI обнаруживает `asyncio.CancelledError`. - Активный HTTP-вызов к LLM отменяется (`httpx.AsyncClient` закрывает соединение). - Поллинг SQL Lab прекращается. - Если БД-транзакция не закоммичена — откатывается. - **Recovery**: Ноль утечек. При следующем открытии страницы — состояние «idle». ### Scenario C: Сетевая ошибка LLM-провайдера - **User Action**: Запущен предпросмотр, LLM-провайдер недоступен (DNS failure). - **System Response**: - Исключение только в запросе предпросмотра. - UI показывает `preview_error`: «Preview failed: connection to LLM provider failed». - Параллельный запрос проверки схемы (к Superset) выполняется нормально. - **Recovery**: Кнопка «Retry Preview». ### Scenario D: Конкурентный deploy + backup одного дашборда - **User Action**: CI/CD запустил деплой дашборда X. Оператор запустил бэкап всех дашбордов (включая X). - **System Response**: Бэкап и деплой работают с разными версиями дашборда. Бэкап экспортирует текущую версию из Superset, деплой импортирует новую. Конфликта данных не возникает — каждый работает со своим снимком. - **Recovery**: Не требуется. Операции идемпотентны. ### Scenario E: SMTP-сервер недоступен при отправке отчёта - **User Action**: Задача завершилась, scheduler пытается отправить email-отчёт. - **System Response**: - `aiosmtplib` получает connection timeout (10 секунд). - Уведомление не отправлено, ошибка залогирована. - Основной API продолжает работать — пользователи не замечают проблему. - **Recovery**: Отчёт доступен в UI. Администратор проверяет логи, чинит SMTP-сервер. ## 5. Tone & Voice - **Style**: Технический, лаконичный. Аудитория — DevOps-инженеры и операторы данных. - **Terminology**: «Предпросмотр» (Preview), «Бэкап» (Backup), «Деплой» (Deploy), «Поллинг» (Polling), «Event loop», «Конкурентность» (Concurrency), «Таймаут» (Timeout). ## 6. Backward Compatibility Contract **Все публичные API-эндпоинты сохраняют обратную совместимость.** Меняется только транспортный слой и временные характеристики. | Модуль | Endpoint | Method | Тело запроса | Тело ответа | Коды | |--------|----------|--------|-------------|-------------|------| | Translate | `/jobs/{id}/preview` | POST | Без изменений | Без изменений | 201, 400, 502 | | Translate | `/check-target-schema` | POST | Без изменений | Без изменений | 200, 502, 503, 504 | | Notifications | (scheduler, no public API) | — | Без изменений | Без изменений | — | **Error Mapping (единый для всех эндпоинтов, взаимодействующих с Superset):** | Условие | HTTP Status | |---------|-------------| | Pool acquire timeout (семафор исчерпан) | 503 Service Unavailable | | Superset network unreachable / connection error | 502 Bad Gateway | | Superset request timeout (upstream) | 504 Gateway Timeout | | Superset auth failure после retry | 502 Bad Gateway | | Superset rate-limit (429 от Superset) | 502 Bad Gateway (не раскрываем upstream 429) | Единственное изменение — **временные характеристики**: лёгкие запросы больше не ждут тяжёлых. ## 7. Файловые операции (File I/O Contract) Файловые операции, вызываемые из `async def`-ручек, должны быть неблокирующими: | Операция | До рефакторинга | После рефакторинга | |----------|-----------------|---------------------| | Экспорт дашборда (ZIP до 50MB) | `shutil.copy` + `open(wb)` | `asyncio.to_thread(…)` или `aiofiles` | | Сохранение загруженного файла | `shutil.copyfileobj` | `asyncio.to_thread(…)` или `aiofiles` | | Чтение дашборда с диска | `path.read_bytes()` | `asyncio.to_thread(…)` | | Git-клонирование репозитория | GitPython (sync) | `asyncio.to_thread(…)` | | Удаление директории | `shutil.rmtree` | `asyncio.to_thread(…)` |