171 lines
18 KiB
Markdown
171 lines
18 KiB
Markdown
# web-app Specification
|
||
|
||
## Purpose
|
||
Веб-приложение хаба (`web/`, React + TypeScript + Vite) под префиксом `/app/`: вход по клиентскому токену, доска связей (перенос `links.html`) и страницы задач со служебными заметками; сборка в `src/jira_hub/web/app/`.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Вход по токену
|
||
Приложение SHALL работать с API хаба того же хоста под клиентским токеном: хранить его в `localStorage` под ключом
|
||
`jira-hub-token` (и в памяти, если хранилище закрыто) и передавать в заголовке `Authorization: Bearer` каждого запроса.
|
||
Если токена нет или любой запрос ответил 401, приложение SHALL показывать экран входа вместо страниц: поле токена с
|
||
подсказкой `uv run jira-hub tokens`, без панелей страниц и без фоновых запросов, которые могли бы изменить поле ввода.
|
||
Введённый токен SHALL проверяться запросом `GET /settings` и сохраняться только при успехе. Действие «Сменить токен»
|
||
SHALL очищать токен и показывать экран входа.
|
||
|
||
#### Scenario: Первый вход
|
||
- **WHEN** приложение открыто впервые, токена нет
|
||
- **THEN** показан экран входа; после ввода верного токена открывается запрошенная страница, токен сохранён
|
||
|
||
#### Scenario: Устаревший токен
|
||
- **WHEN** сохранённый токен не подходит и запрос ответил 401
|
||
- **THEN** показан экран входа с пояснением, что токен не подошёл; панели доски и задач не видны
|
||
|
||
#### Scenario: Токен от прежней доски
|
||
- **WHEN** в `localStorage` есть `jira-hub-token`, сохранённый страницей `links.html`
|
||
- **THEN** приложение входит с ним без запроса токена
|
||
|
||
### Requirement: Оболочка и адреса
|
||
Приложение SHALL открываться под префиксом `/app/` и иметь страницы `/app/board` (доска связей), `/app/issues` (список
|
||
задач) и `/app/issues/{key}` (карточка задачи); `/app` и `/app/` SHALL вести на `/app/board`, неизвестный путь —
|
||
на страницу «не найдено» со ссылками на разделы. Верхняя панель SHALL содержать переходы «Доска», «Задачи» и действие
|
||
«Сменить токен». Неожиданная ошибка отрисовки страницы SHALL показываться сообщением с кнопкой «Перезагрузить», не
|
||
пустым экраном.
|
||
|
||
#### Scenario: Переход между разделами
|
||
- **WHEN** на доске выбран раздел «Задачи»
|
||
- **THEN** адрес — `/app/issues`, показан список задач; «назад» возвращает на доску
|
||
|
||
### Requirement: Ошибки хаба
|
||
Приложение SHALL различать ошибки запросов к хабу: 401 — экран входа; хаб не отвечает (сеть, 5xx) — сообщение
|
||
«хаб не отвечает» с возможностью повторить, ранее загруженные данные остаются на экране; прочие 4xx — текст `detail`
|
||
ответа рядом с действием, которое его вызвало.
|
||
|
||
#### Scenario: Хаб остановлен
|
||
- **WHEN** доска показана, хаб остановлен, нажато «Перечитать»
|
||
- **THEN** на доске прежние данные и сообщение «хаб не отвечает — показаны прежние данные»
|
||
|
||
### Requirement: Справочники из настроек хаба
|
||
Приложение SHALL брать перевод меток видов работ (`kind_labels`) и типов связей Jira (`link_types`) из `GET /settings`
|
||
и не содержать собственных копий этих словарей. Если нужного словаря в настройках нет, страница SHALL сообщать, какого
|
||
словаря не хватает, вместо пустой доски или списка без подписей.
|
||
|
||
#### Scenario: Новый тип связи в настройках
|
||
- **WHEN** в `link_types` настроек добавлен тип и в зеркале есть связь этого типа
|
||
- **THEN** после перезагрузки страницы доска показывает связь этого типа без изменения кода приложения
|
||
|
||
### Requirement: Доска связей
|
||
Страница `/app/board` SHALL воспроизводить поведение доски связей хаба (`links.html` ревизии 3931a88) без изменений,
|
||
кроме перечисленных в этом требовании и в требовании «Источник данных доски»: построение модели из `GET /board`, CSV
|
||
фильтра Jira и JSON доски (`arm-ka-links/1`); дорожки по эпикам со свёрткой и перестановкой, служебные дорожки внизу;
|
||
карточки с перетаскиванием, растяжкой, размером по содержимому и привязкой к сетке; стрелки связей с выравниванием;
|
||
фильтры по спринтам и исполнителям; легенда со скрытием видов работ и типов связей; «+ соседи по связям», «⇄ передачи»,
|
||
«скрыть без связей», «⚑ критический путь», «▥ по датам» с колонками дат и их растяжкой; ящик «Проблемы»; таблица
|
||
связей; экспорт и импорт JSON; тема; «Сбросить раскладку». Раскладка, дорожки, фильтры и тема SHALL храниться в
|
||
`localStorage` под теми же ключами `graph-<имя>…`, что у `links.html`, с той же миграцией старых ключей.
|
||
Результат построения модели, видимой выборки, критического пути и проблем SHALL совпадать с результатом `links.html`
|
||
на одних и тех же данных и фильтрах. У карточки задачи (не у заглушки) SHALL быть значок «Открыть карточку»,
|
||
ведущий на `/app/issues/{key}`; ключ карточки по-прежнему открывает задачу в Jira. Действия «Сменить токен» в меню
|
||
доски нет — оно в верхней панели приложения. Ещё два отличия от `links.html` (R-10, R-18): двойной щелчок по уголку
|
||
карточки меряет высоту по содержимому при новой ширине, а не при прежней; указатель при перетаскивании и растяжке
|
||
захватывается на нажатом элементе, а не на карточке, поэтому двойной щелчок по уголку сбрасывает размер. Загрузка файла CSV/JSON доступна только после входа, с экрана входа её нет.
|
||
|
||
#### Scenario: Раскладка переживает переход
|
||
- **WHEN** раскладка карточек сохранена страницей `links.html`, открыта `/app/board` с теми же данными
|
||
- **THEN** карточки стоят на тех же местах, дорожки в том же порядке и свёртке, фильтры те же
|
||
|
||
#### Scenario: Совпадение со старой доской
|
||
- **WHEN** на одной фикстуре `GET /board` и одном наборе фильтров построены модель, видимая выборка, критический путь
|
||
и проблемы старой страницей и приложением
|
||
- **THEN** результаты совпадают
|
||
|
||
#### Scenario: Открыть карточку
|
||
- **WHEN** на карточке AURORA-1620 нажат значок «Открыть карточку»
|
||
- **THEN** открыта `/app/issues/AURORA-1620` внутри приложения
|
||
|
||
### Requirement: Источник данных доски
|
||
Доска SHALL хранить явный источник данных — хаб или файл. Пока показан снимок из файла (CSV или JSON-снимок), ответы
|
||
хаба SHALL его не заменять; «Перечитать» SHALL возвращать источник «хаб» и загружать `GET /board` заново без обращения к
|
||
Jira. Импорт JSON в режиме хаба SHALL применять раскладку, дорожки и фильтры и перечитывать хаб. «Обновить из Jira»
|
||
SHALL проверять агента (`GET /agent`; офлайн — предупреждение, пакет всё равно ставится), ставить
|
||
`POST /refresh {full: true, author: "viewer"}`, опрашивать `GET /batches/{id}` каждые 3 с одним опросом до
|
||
терминального статуса пакета и затем перечитывать `GET /board`. Кнопка «Обновить из Jira» SHALL быть недоступна,
|
||
пока запрос `/refresh` в пути или пакет выполняется. Незавершённый пакет `refresh` с автором `viewer` SHALL
|
||
подхватываться при каждой успешной загрузке из хаба. Опрос пакета SHALL прекращаться при ответе 404 и после 5 ошибок
|
||
«хаб не отвечает» подряд, с сообщением и снова доступной кнопкой. Шапка доски SHALL показывать
|
||
источник данных (данные хаба на время последнего полного прогона; «из файла») и одно текущее событие (загрузка; пакет
|
||
выполняется / выполнен / ошибка с текстом; хаб не отвечает — прежние данные; агент офлайн — пакет поставлен; ошибка
|
||
разбора файла — «файл не разобран: <причина>»); новое событие заменяет предыдущее целиком.
|
||
|
||
#### Scenario: Поздний ответ хаба не затирает файл
|
||
- **WHEN** запрос `GET /board` в пути, и в это время загружен CSV
|
||
- **THEN** после ответа хаба на доске по-прежнему данные CSV с пометкой «из файла»
|
||
|
||
#### Scenario: Нет второго полного прогона
|
||
- **WHEN** нажата «Обновить из Jira» и, пока пакет выполняется, доска перерисована (фильтр, «Перечитать»)
|
||
- **THEN** кнопка остаётся недоступной, в хабе один пакет, его опрашивает один опрос
|
||
|
||
#### Scenario: Пакет пропал
|
||
- **WHEN** опрос пакета получил 404
|
||
- **THEN** опрос остановлен, показано сообщение, кнопка «Обновить из Jira» снова доступна
|
||
|
||
#### Scenario: Пакет после «Перечитать»
|
||
- **WHEN** пакет `viewer` поставлен с другой вкладки и не завершён, нажато «Перечитать»
|
||
- **THEN** доска показывает «пакет #N выполняется» и по завершении перечитывает данные
|
||
|
||
### Requirement: Список задач
|
||
Страница `/app/issues` SHALL показывать задачи зеркала из `GET /issues` таблицей: ключ (ссылка на карточку), тема,
|
||
тип, статус Jira, исполнитель, спринт, срок (`planned_end`, иначе `duedate`), эпик (имя эпика, иначе ключ), вид работы,
|
||
статус по факту, признак «сосед». Фильтры SHALL соответствовать параметрам `GET /issues` (спринт, исполнитель, вид
|
||
работы, статус по факту, статус Jira, метка, эпик, только соседи, поиск по ключу или теме), храниться в query-строке
|
||
адреса и применяться сервером; варианты выпадающих списков SHALL браться из снимка `GET /board`. Задачи SHALL
|
||
загружаться порциями по 50 строк; «Показать ещё» SHALL быть доступна, пока последняя порция полная.
|
||
|
||
#### Scenario: Фильтр в адресе
|
||
- **WHEN** выбран спринт «AWPCA_Спринт 2» и исполнитель
|
||
- **THEN** адрес содержит `sprint=` и `assignee=`, таблица — результат `GET /issues` с этими параметрами; открытие этого
|
||
адреса в новой вкладке показывает ту же выборку
|
||
|
||
#### Scenario: Конец списка
|
||
- **WHEN** вторая порция вернула 17 задач
|
||
- **THEN** в таблице 67 задач, «Показать ещё» не показана
|
||
|
||
### Requirement: Карточка задачи
|
||
Страница `/app/issues/{key}` SHALL показывать задачу из `GET /issues/{key}`: поля зеркала, ссылку на задачу в Jira,
|
||
описание как текст с сохранёнными переносами строк (без разбора разметки Jira), исходящие и входящие связи с подписью
|
||
типа (логический тип из `link_types`, иначе имя типа Jira) и ссылками на карточки связанных задач, 10 последних команд
|
||
по задаче (id, пакет, тип, статус, время завершения, ошибка). Если задачи нет в зеркале (404), страница SHALL сообщать
|
||
об этом со ссылкой на список.
|
||
|
||
#### Scenario: Связанная задача
|
||
- **WHEN** у AURORA-1620 исходящая связь `ASUPR_Блокер_Начала` на AURORA-1621
|
||
- **THEN** в карточке «блокирует начало → AURORA-1621», ключ ведёт на `/app/issues/AURORA-1621`
|
||
|
||
### Requirement: Служебные заметки
|
||
Карточка задачи SHALL давать править служебные заметки: «вид работы» — выбор из значений `kind_labels` настроек
|
||
(и текущего значения, если его там нет, и пустого), «статус по факту» — текст с подсказками из ключей `status_labels`,
|
||
«сделано», «осталось», «заметка владельца» — многострочный текст. Сохранение SHALL отправлять
|
||
`PUT /issues/{key}/note` только с изменёнными полями и `updated_by: "web"`; очищенное поле отправляется как `null`.
|
||
После успеха SHALL показываться сохранённая заметка с временем и автором; при 422 — текст ошибки у формы без потери
|
||
введённого. Уход со страницы с несохранёнными правками SHALL требовать подтверждения. Заметки пишутся только в хаб,
|
||
в Jira приложение ничего не отправляет.
|
||
|
||
#### Scenario: Только изменённые поля
|
||
- **WHEN** в заметке изменено только «осталось» и нажато «Сохранить»
|
||
- **THEN** тело `PUT /issues/{key}/note` — `{rest_text: "…", updated_by: "web"}`
|
||
|
||
#### Scenario: Уход с несохранёнными правками
|
||
- **WHEN** в форме заметки есть несохранённая правка и выбран раздел «Доска»
|
||
- **THEN** приложение спрашивает подтверждение; при отказе остаётся в карточке с введённым текстом
|
||
|
||
### Requirement: Сборка и разработка
|
||
Приложение SHALL быть npm-проектом в `web/` (React, TypeScript, Vite) с точными версиями зависимостей и
|
||
`package-lock.json`. `make web` SHALL устанавливать зависимости (`npm ci`) и собирать приложение в
|
||
`src/jira_hub/web/app/`; каталог сборки не хранится в git. `make check` SHALL запускать `uv run pytest`, проверку типов
|
||
и линтер фронта и тесты фронта; зелёным считается только весь набор. `npm run dev` SHALL поднимать сервер разработки с
|
||
прокси маршрутов API на хаб из `JIRA_HUB_URL` (по умолчанию тестовый `http://127.0.0.1:8791`).
|
||
|
||
#### Scenario: Сборка с нуля
|
||
- **WHEN** в чистом checkout выполнен `make web`
|
||
- **THEN** в `src/jira_hub/web/app/` есть `index.html` и `assets/`; `git status` не показывает файлов сборки
|