Files
ss-tools/specs/030-dataset-lifecycle-workspace/spec.md
2026-05-19 18:36:15 +03:00

201 lines
25 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Feature Specification: Dataset Lifecycle Workspace
**Feature Branch**: `030-dataset-lifecycle-workspace`
**Created**: 2026-05-19
**Status**: Draft
**Input**: Feature 029 — Dataset Lifecycle Workspace (variant C)
## Clarifications
### Session 2026-05-19
- Q: Stats Bar фильтрует клиентски или серверно? → A: Клиентская фильтрация по данным из API. Бэкенд в ответе `GET /api/datasets` возвращает 4 stats-числа (`total`, `unmapped_count`, `mapped_count`, `linked_count`). Клиенты фильтруют карточки без дополнительного запроса. Пагинация остаётся серверной. При фильтре пагинация переключает страницы отфильтрованного подмножества.
- Q: Каким запросом сохранять inline-edit описания колонок/метрик? → A: Два новых ss-tools эндпоинта: `PUT /api/datasets/{id}/columns/{col_id}/description` и `PUT /api/datasets/{id}/metrics/{metric_id}/description` (body: `{description: "text"}`). Бэкенд выполняет GET датасета из Superset, модифицирует одну колонку/метрику, PUT обратно полный объект. В Superset нет PATCH — только PUT с полным датасетом.
- Q: Как обрабатывать конфликт одновременного редактирования? → A: Last-write-wins. Бэкенд всегда выполняет GET→modify→PUT без проверки версии. Последний сохранивший перезаписывает. Для описаний колонок/метрик это приемлемый риск. ETag/блокировки — out of scope.
- Q: Параметр `override_columns` при PUT в Superset? → A: Всегда `override_columns=false`. Бэкенд получает полный датасет из GET, модифицирует только description нужной колонки, PUT обратно с `override_columns=false`. Superset сохраняет все колонки, не участвовавшие в payload.
- Q: Как Stats Bar узнаёт об изменении counts после асинхронных задач? → A: WebSocket push. При завершении mapping/docs задачи сервер отправляет событие `dataset.updated` через существующий WebSocket. Фронтенд по событию перезапрашивает `GET /api/datasets` и обновляет Stats Bar + карточки автоматически.
## User Scenarios & Testing
### User Story 1 — Stats-Driven Overview & Filtering (Priority: P1)
Пользователь заходит на страницу датасетов и видит 4 ключевые метрики в Stats Bar вверху. Каждая метрика кликабельна как фильтр списка. Метрики дают мгновенный обзор состояния датасетов и позволяют быстро отфильтровать те, которые требуют внимания.
**Why this priority**: Stats bar — первое, что видит пользователь. Он заменяет текущую пустую область над таблицей и даёт моментальный actionable insight. Без него страница неотличима от текущей реализации.
**Independent Test**: Перейти на `/datasets`, проверить что каждая из 4 метрик отображается и клик по ней фильтрует список.
**Acceptance Scenarios**:
1. **Given** пользователь видит страницу датасетов с загруженными данными, **When** страница загружена, **Then** Stats Bar показывает 4 метрики: Все (total), Без маппинга, С описанием, С дашбордами
2. **Given** Stats Bar отображается, **When** пользователь кликает на "Без маппинга", **Then** список фильтруется до датасетов с `mapped_fields.mapped === 0`, метрика подсвечена
3. **Given** Stats Bar отображается, **When** пользователь кликает на уже активный фильтр, **Then** фильтр сбрасывается и показываются все датасеты
4. **Given** API возвращает ошибку при загрузке, **When** страница рендерится, **Then** Stats Bar показывает 0 для всех метрик или состояние ошибки
---
### User Story 2 — Split-View с карточками и детальной панелью (Priority: P1)
Пользователь видит split-view: слева (40%) — список датасетов в виде карточек с прогресс-баром маппинга, справа (60%) — детальный просмотр выбранного датасета без перезагрузки страницы. На мобильных (< 768px) список занимает весь экран, а детали открываются как slide-over.
**Why this priority**: Split-view ядро workspace. Без него нет смысла в Stats Bar и inline-редактировании. Это главное UX-изменение относительно текущей таблицы + отдельной страницы деталей.
**Independent Test**: Выбрать датасет в левой панели, убедиться что правая панель показывает его детали без перезагрузки страницы.
**Acceptance Scenarios**:
1. **Given** страница загружена, **When** список датасетов отрендерен, **Then** карточки отображаются в левой панели (40%), правая панель показывает placeholder "Выберите датасет"
2. **Given** список загружен, **When** пользователь кликает на карточку датасета, **Then** правая панель загружает детали выбранного датасета (имя, схема, колонки, метрики, дашборды)
3. **Given** датасет выбран в правой панели, **When** пользователь кликает на другую карточку, **Then** правая панель обновляется без полной перезагрузки страницы
4. **Given** ширина экрана < 768px, **When** пользователь кликает на карточку, **Then** правая панель открывается как slide-over поверх списка
5. **Given** slide-over открыт на мобильном, **When** пользователь кликает "Назад" или вне slide-over, **Then** slide-over закрывается, возвращаясь к списку
---
### User Story 3 — Карточки датасетов с прогресс-баром (Priority: P2)
Каждая карточка в левой панели содержит: имя таблицы, схему, количество колонок, количество метрик, прогресс-бар маппинга (mapped/total в процентах), кнопки быстрых действий (Маппинг, Доки). Прогресс-бар визуально показывает состояние маппинга.
**Why this priority**: Карточки основной навигационный элемент. Без них split-view бесполезен, но технически карточки зависят от структуры split-view (US2).
**Independent Test**: Проверить что карточка содержит все обязательные поля и прогресс-бар правильно отражает `mapped/total`.
**Acceptance Scenarios**:
1. **Given** список датасетов загружен, **When** карточки отрендерены, **Then** каждая карточка показывает: имя таблицы, схему, кол-во колонок, кол-во метрик, прогресс-бар
2. **Given** датасет имеет `mapped_fields: {total: 10, mapped: 7}`, **When** карточка рендерится, **Then** прогресс-бар заполнен на 70% зелёным/жёлтым/синим в зависимости от процента
3. **Given** датасет без метрик (metric_count = 0), **When** карточка рендерится, **Then** кол-во метрик показывает "0" или скрыто
4. **Given** карточка отображается, **When** пользователь нажимает кнопку "Маппинг", **Then** открывается модалка маппинга колонок для этого датасета
---
### User Story 4 — Детальная панель с колонками и метриками (Priority: P2)
Правая панель показывает: хедер датасета (полное имя, схема, кол-во колонок/метрик, связанные дашборды, дата создания), секцию "Колонки" (таблица с column_name, verbose_name, type, description, кнопка inline-редактирования), секцию "Метрики" (таблица с metric_name, expression, verbose_name, description, кнопка inline-редактирования), связанные дашборды.
**Why this priority**: Детальная панель вторая половина split-view. Без неё нет рабочего пространства, но технически зависит от US2.
**Independent Test**: Выбрать датасет с метриками и колонками, проверить что обе секции отображаются корректно.
**Acceptance Scenarios**:
1. **Given** датасет выбран, **When** правая панель загружена, **Then** хедер показывает: `table_name`, `schema`, `column_count`, `metric_count`, `linked_dashboard_count`, `created_on`
2. **Given** у колонки есть `verbose_name`, **When** секция колонок рендерится, **Then** `verbose_name` показывается жирным, `column_name` не показывается
3. **Given** у колонки нет `verbose_name`, **When** секция колонок рендерится, **Then** `column_name` показывается курсивом как fallback
4. **Given** у колонки/метрики нет `description`, **When** строка рендерится, **Then** показывается "✏ Добавить описание" как action prompt
5. **Given** у метрики нет `description`, **When** строка метрики рендерится, **Then** под именем показывается `expression` как подсказка
---
### User Story 5 — Inline-редактирование описаний (Priority: P3)
Пользователь может редактировать description колонок и метрик прямо в детальной панели. Клик по на строке открывает textarea, пользователь вводит текст, сохраняет через API. Изменения применяются без перезагрузки страницы.
**Why this priority**: Inline-редактирование ключевая фича workspace, но она менее критична чем базовая навигация и просмотр. Зависит от US4 (детальная панель) и бэкенд-эндпоинта.
**Independent Test**: Кликнуть на колонке, ввести описание, сохранить проверить что описание обновилось и отображается.
**Acceptance Scenarios**:
1. **Given** строка колонки без описания, **When** пользователь кликает "✏ Добавить описание", **Then** появляется textarea с кнопками "Сохранить" и "Отмена"
2. **Given** textarea открыта, **When** пользователь вводит текст и нажимает "Сохранить", **Then** описание сохраняется через PUT `/api/datasets/{id}`, textarea закрывается, описание отображается
3. **Given** textarea открыта, **When** пользователь нажимает "Отмена", **Then** textarea закрывается без изменений
4. **Given** сохранение не удалось (ошибка API), **When** пользователь нажимает "Сохранить", **Then** показывается сообщение об ошибке, textarea остаётся открытой для повторной попытки
5. **Given** строка колонки с существующим описанием, **When** пользователь кликает , **Then** textarea открывается с предзаполненным текстом описания
---
### User Story 6 — Bulk-режим с нижней панелью (Priority: P3)
Чекбоксы на карточках позволяют выбрать несколько датасетов. При выборе 2+ датасетов появляется нижняя панель с bulk-действиями: "Маппинг колонок" и "Генерация документации". Модалки bulk-маппинга и генерации доков переиспользуются из текущей реализации.
**Why this priority**: Bulk-режим уже существует в текущей реализации, его нужно адаптировать под карточки в split-view. Менее приоритетно чем базовый просмотр.
**Independent Test**: Выбрать 3 датасета через чекбоксы, проверить что нижняя панель появляется и кнопки работают.
**Acceptance Scenarios**:
1. **Given** карточки отображаются с чекбоксами, **When** пользователь отмечает 2+ датасета, **Then** нижняя панель появляется с кнопками "Маппинг колонок" и "Генерация документации"
2. **Given** нижняя панель видна, **When** пользователь снимает выделение со всех датасетов, **Then** панель исчезает
3. **Given** нижняя панель видна, **When** пользователь нажимает "Маппинг колонок", **Then** открывается модалка маппинга (переиспользованная из текущей реализации)
4. **Given** выбраны датасеты с разных страниц пагинации, **When** нижняя панель отображается, **Then** счётчик показывает общее количество выбранных датасетов
---
### User Story 7 — Отображение метрик в API и UI (Priority: P2)
Бэкенд API возвращает метрики в `DatasetDetailResponse`. Фронтенд отображает их в секции "Метрики" детальной панели.
**Why this priority**: Метрики обязательная часть детальной панели (US4), но требуют изменений на бэкенде. Без бэкенд-поддержки UI нечего показывать.
**Independent Test**: Вызвать `GET /api/datasets/{id}`, проверить наличие полей `metrics` и `metric_count` в ответе, и что `MetricItem` содержит все обязательные поля.
**Acceptance Scenarios**:
1. **Given** датасет имеет метрики в Superset API, **When** бэкенд получает `GET /api/datasets/{id}`, **Then** ответ содержит `metrics: [...]` и `metric_count`
2. **Given** у метрики есть все поля Superset API, **When** бэкенд трансформирует ответ, **Then** `MetricItem` содержит: `id`, `metric_name`, `expression`, `verbose_name`, `description`, `metric_type`
3. **Given** Superset API возвращает датасет без метрик, **When** бэкенд обрабатывает ответ, **Then** `metrics: []` и `metric_count: 0`
---
### Edge Cases
- Что происходит когда API датасетов возвращает 503 (Superset недоступен)?
- Как обрабатывается случай когда `detail` API для выбранного датасета падает, но список загружен?
- Как ведёт себя split-view при ресайзе окна между десктопным и мобильным layout?
- Что показывать в Stats Bar когда данные ещё не загружены (loading state)?
- Как обрабатывать случай когда у датасета 0 колонок и 0 метрик?
- Что происходит при сохранении inline-edit когда несколько пользователей редактируют один датасет (конфликт)? **Решено**: last-write-wins. Последний сохранивший перезаписывает. ETag/блокировки out of scope.
- Как Stats Bar обновляет числа при переключении окружения запрашивает новые counts?
## Requirements
### Functional Requirements
- **FR-001**: Система MUST отображать Stats Bar с 4 метриками (Все, Без маппинга, С описанием, С дашбордами) вверху страницы `/datasets`
- **FR-002**: Каждая метрика в Stats Bar MUST быть кликабельной и действовать как фильтр списка датасетов
- **FR-003**: Система MUST отображать split-view layout: левая панель (40%) со списком карточек, правая панель (60%) с деталями выбранного датасета
- **FR-004**: При ширине экрана < 768px правая панель MUST открываться как slide-over поверх списка
- **FR-005**: Карточка датасета MUST содержать: имя таблицы, схему, количество колонок, количество метрик, прогресс-бар маппинга, кнопки быстрых действий
- **FR-006**: Прогресс-бар MUST визуально отображать процент маппинга (mapped/total) с цветовой кодировкой: зелёный (100%), жёлтый (≥50%), синий (<50%)
- **FR-007**: Правая панель MUST показывать хедер датасета (полное имя, схема, кол-во колонок/метрик, связанные дашборды, дата создания)
- **FR-008**: Правая панель MUST содержать секцию "Колонки" с таблицей (column_name, verbose_name, type, description, inline edit)
- **FR-009**: Правая панель MUST содержать секцию "Метрики" с таблицей (metric_name, expression, verbose_name, description, inline edit)
- **FR-010**: При наличии `verbose_name` система MUST показывать его жирным; при отсутствии показывать `column_name`/`metric_name` курсивом
- **FR-011**: При отсутствии `description` система MUST показывать "✏ Добавить описание" как action prompt
- **FR-012**: Для метрик без описания система MUST показывать `expression` как подсказку под именем
- **FR-013**: Inline-редактирование MUST поддерживать открытие textarea с кнопками "Сохранить"/"Отмена", с предзаполнением существующего описания
- **FR-014**: Сохранение описания колонки MUST выполняться через `PUT /api/datasets/{id}/columns/{col_id}/description` (body: `{description: "text"}`). Сохранение описания метрики `PUT /api/datasets/{id}/metrics/{metric_id}/description`. Бэкенд выполняет GETmodifyPUT в Superset.
- **FR-015**: Карточки MUST содержать чекбоксы для bulk-выбора, при 2+ выбранных MUST появляться нижняя панель с bulk-действиями
- **FR-016**: Bulk-действия "Маппинг колонок" и "Генерация документации" MUST переиспользовать существующие модалки
- **FR-017**: `DatasetDetailResponse` на бэкенде MUST включать `metrics: list[MetricItem]` и `metric_count: int`
- **FR-018**: `MetricItem` Pydantic DTO MUST содержать поля: `id`, `metric_name`, `expression`, `verbose_name`, `description`, `metric_type`
- **FR-019**: `SupersetClient.get_dataset_detail` MUST извлекать `dataset.get("metrics", [])` из ответа Superset API
- **FR-020**: Система MUST поддерживать i18n ключи (EN/RU) для: all, without_mapping, with_description, linked_to_dashboards, metrics, metric_count, no_selection, save_description, add_description
- **FR-021**: Breadcrumb для `/datasets` MUST отображать "Датасеты / Datasets" (EN/RU), а не "Маппер колонок"
- **FR-022**: Ответ `GET /api/datasets` MUST включать поля stats: `total` (int), `unmapped_count` (int), `mapped_count` (int), `linked_count` (int) для отрисовки Stats Bar без дополнительных запросов
- **FR-023**: PUT в Superset при сохранении inline-edit MUST выполняться с `override_columns=false` (query parameter) предотвращает случайное удаление колонок, не упомянутых в payload
- **FR-024**: По WebSocket-событию `dataset.updated` (отправляемому при завершении mapping/docs задачи) фронтенд MUST перезапрашивать `GET /api/datasets` и обновлять Stats Bar counts + карточки без ручного «Обновить»
### Key Entities
- **StatsBarMetric**: Метрика в Stats Bar label, count, icon, filter_key, is_active
- **DatasetCard**: Карточка в левой панели id, table_name, schema, column_count, metric_count, mapped_progress (%), quick_actions[]
- **DatasetDetailPanel**: Детальная панель header_info, columns_table, metrics_table, linked_dashboards
- **MetricItem** (новый): id, metric_name, expression, verbose_name, description, metric_type
- **ColumnRow**: Строка в таблице колонок column_name, verbose_name, type, description, is_editing (UI state)
- **MetricRow**: Строка в таблице метрик metric_name, expression, verbose_name, description, is_editing (UI state)
- **BulkActionPanel**: Нижняя панель selected_count, available_actions[], is_visible
## Success Criteria
### Measurable Outcomes
- **SC-001**: Пользователь может за 1 клик перейти от Stats Bar к отфильтрованному списку датасетов
- **SC-002**: Переключение между датасетами в split-view происходит < 500ms (без полной перезагрузки страницы)
- **SC-003**: Пользователь может отредактировать и сохранить описание колонки/метрики < 3 кликов
- **SC-004**: Stats Bar корректно обновляет значения при переключении окружения
- **SC-005**: Все существующие bulk-действия (маппинг, генерация доков) продолжают работать в новом UI
- **SC-006**: Страница корректно адаптируется между десктопным split-view и мобильным slide-over без потери функциональности
- **SC-007**: Все новые строки интерфейса имеют переводы на EN и RU