# 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?**:
  - **Оператор superset-tools** — настраивает и запускает задачи (переводы, бэкапы, деплои, миграции). Может быть несколько операторов одновременно.
  - **Администратор** — мониторит состояние системы, просматривает дашборды, управляет окружениями.
  - **API-клиент** — внешние системы, вызывающие API superset-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(…)` |
