Updated status in spec.md, plan.md, tasks.md, research.md, data-model.md, ux_reference.md, quickstart.md. All 72 tasks completed. 34/34 tests passing.
181 lines
15 KiB
Markdown
181 lines
15 KiB
Markdown
# UX Reference: Async Backend Refactoring
|
||
|
||
**Feature Branch**: `032-translate-requests-httpx`
|
||
**Created**: 2026-06-04
|
||
**Status**: ✅ Implemented — all scenarios verified
|
||
|
||
## 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(…)` |
|