New page /analytics/compare and API GET /admin/analytics/compare (+ /lists/{group}):
the administrator picks an old (A) and a new (B) run; the report shows the new
addresses (only in B), the ones that left (only in A) and the common ones whose
membership in the seven indicators (pass, partial, fail, egress https any/all,
ingress ssh any/all) differs, with a "what changed" summary per address; the
dynamics of each indicator (delta = new - left + entered - exited) and a verdict
transition matrix. Every number opens a list with CSV. Cancelled addresses are not
part of a run. The list dialog moved to a shared analytics-dialog.js and template;
/analytics got a "compare with another run" button.
Docs, plan and summary in docs/changes/.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
90 lines
15 KiB
Markdown
90 lines
15 KiB
Markdown
# План: сравнение двух запусков в разделе «Аналитика»
|
||
|
||
Статус: реализовано, см. [итог](2026-10-04_10-08_analytics-run-compare-summary.md).
|
||
|
||
## 1. Что нужно
|
||
|
||
Администратор выбирает два завершённых запуска и видит динамику между ними по семи индикаторам страницы «Аналитика»: `pass`, `partial`, `fail`, «Egress https: есть провалы», «Egress https: все провалены», «Ingress ssh: есть провалы», «Ingress ssh: все провалены». Отдельно выделяются три группы адресов:
|
||
|
||
1. **Новые**: есть в новом запуске, в старом не было. Нужны, чтобы понять, какие адреса прибыли в проект и подключились к анализу.
|
||
2. **Изменившиеся**: есть в обоих запусках, но состояние по проверкам разное. Для них показывается подробная сводка, что именно изменилось.
|
||
3. **Выбывшие**: были в старом запуске, в новом их нет. Нужны, чтобы понять, какие адреса вышли из состава проекта.
|
||
|
||
Адреса, которые есть в обоих запусках и не изменились, только считаются (список доступен, но не выделяется).
|
||
|
||
## 2. Основные решения
|
||
|
||
- **Отдельная страница `/analytics/compare`**, а не режим текущей: страница одного запуска остаётся как есть («данные других запусков на странице не участвуют»). На `/analytics` добавляется кнопка «Сравнить с другим запуском», она ведёт на сравнение с текущим запуском в роли нового.
|
||
- **Выбор запусков.** Два списка: «Запуск A (старый)» и «Запуск B (новый)» и кнопка «Поменять местами». По умолчанию B — последний завершённый запуск, A — предыдущий. Порядок не навязывается: «новые» всегда означает «есть в B, нет в A». Доступны только завершённые запуски с адресами (как в текущем выборе). Одинаковые A и B — ошибка `400`.
|
||
- **Адрес — это IP.** В запуске он присутствует, если у него есть итог, отличный от `cancelled`. Отменённые адреса из сравнения исключаются (в обоих запусках), их число показывается примечанием, чтобы они не выглядели как «новые» или «выбывшие».
|
||
- **Состояние адреса** — принадлежность к семи индикаторам по последнему циклу адреса в запуске (те же правила, что в отчёте запуска; один адрес может входить в несколько, вердикт — ровно в один из трёх). Адрес **изменился**, если принадлежность хотя бы к одному индикатору в A и B разная. Адрес с теми же индикаторами, но другим набором проваленных целей или площадок считается неизменившимся (см. риски).
|
||
- **Расчёт на control-api** по двум уже существующим `Analysis` (кэш завершённых запусков переиспользуется). Новых таблиц, миграций и новых данных нет.
|
||
|
||
## 3. Решение
|
||
|
||
### 3.1. control-api (`internal/analytics/compare.go`, новый)
|
||
|
||
- Таблица семи индикаторов (ключ, название, предикат по адресу): ключи совпадают с видами списков `verdict_pass`, `verdict_partial`, `verdict_fail`, `egress_https_any`, `egress_https_all`, `ingress_ssh_any`, `ingress_ssh_all`. Предикаты повторяют условия `Compute`/`List`; тест проверяет, что число адресов по каждому индикатору в каждом запуске равно соответствующему полю `summary`.
|
||
- `Compare(a, b *Analysis) *Comparison`:
|
||
- группы: `new`, `left`, `common`, `changed`, `same` (числа; `common = changed + same`);
|
||
- по каждому индикатору: `base`, `target`, `delta`, `new` (новые адреса в индикаторе), `left` (выбывшие, были в индикаторе), `entered` (общие адреса, вошедшие в индикатор), `exited` (общие адреса, вышедшие из индикатора). Инвариант: `delta = new − left + entered − exited` (проверяется тестом);
|
||
- матрица переходов вердикта для общих адресов (3×3), плюс строка «нет в A» (новые) и столбец «нет в B» (выбывшие);
|
||
- число отменённых адресов в каждом запуске.
|
||
- `(*Comparison) List(group, filter)`: таблица адресов. Группы: `new`, `left`, `changed`, `same`, `entered`, `exited`. Фильтры: `indicator` (для `new`/`left` — адрес входит в индикатор в своём запуске; для `changed`/`same` — принадлежность этому индикатору; для `entered`/`exited` обязателен), `from`+`to` (вердикт в A и в B, для ячейки матрицы). Порядок — по числовому адресу. Неверная группа или индикатор — ошибка (`404`).
|
||
- Столбцы списков:
|
||
- `new`: Адрес, Подсеть, Вердикт, Egress, Ingress, Индикаторы (в B);
|
||
- `left`: те же столбцы по запуску A;
|
||
- `changed`, `entered`, `exited`, `same`: Адрес, Подсеть, Вердикт (A → B), Egress (A → B), Ingress (A → B), **Что изменилось**. «Что изменилось» — текст по шагам: «вердикт partial → pass», «вошёл в: Egress https: все провалены», «вышел из: Ingress ssh: есть провалы», «https: провалены цели +a.test −b.test», «ssh: площадки −rxmsk», «валидатор v3 → v12». У `same` — «без изменений».
|
||
- Для текста нужны из `addr` уже имеющиеся поля (`https.failedTargets`, `ssh.sites`, `https.validator`, статистики egress/ingress); новых вычислений в `Compute` нет.
|
||
|
||
### 3.2. API (`internal/httpapi/handlers_analytics.go`)
|
||
|
||
- `GET /api/v1/admin/analytics/compare?base=A&target=B` → отчёт сравнения (`runs`: сведения об обоих запусках, `groups`, `indicators`, `transitions`, `cancelled`).
|
||
- `GET /api/v1/admin/analytics/compare/lists/{group}?base=A&target=B[&indicator=…][&from=…&to=…][&format=csv]` → таблица или CSV (UTF-8 с BOM; имя `compare_<group>[_<indicator>]_run<A>-<B>.csv`).
|
||
- Ошибки: `400` (нет или неверные id, `base = target`), `404` (запуска нет, неизвестная группа/индикатор), `409` (запуск ещё идёт).
|
||
- Загрузка `Analysis` по id выносится из `analysisFor` в общую функцию, чтобы сравнение и страница одного запуска пользовались одним кэшем.
|
||
|
||
### 3.3. Дашборд
|
||
|
||
- Страница `/analytics/compare?base=A&target=B` (`handlers_analytics.go`, `templates/analytics_compare.html`, `static/analytics-compare.js`), прокси списков и CSV: `/analytics/compare/lists/{group}` и `/analytics/compare/csv/{group}`. Состояние страницы целиком в адресе (можно отправить ссылку).
|
||
- Содержимое страницы сверху вниз:
|
||
1. Выбор A и B, «Поменять местами», «Сравнить»; под ним примечание: даты, тип и размер обоих запусков, число отменённых.
|
||
2. Карточки: «Новые», «Выбывшие», «Общие», «Изменились», «Без изменений». Кликабельны, открывают диалог со списком.
|
||
3. «Динамика по индикаторам»: строка на индикатор, столбцы A, B, Δ (рост `pass` и падение остальных — зелёным, обратное — красным), Новые, Выбывшие, Вошли, Вышли. Ненулевые числа кликабельны (список с соответствующими `group` и `indicator`).
|
||
4. «Переходы вердикта»: матрица 3×3 по общим адресам + «нет в A» / «нет в B»; ячейки кликабельны.
|
||
5. Диалог со списком, «Скачать CSV», «Копировать» — тот же, что на странице запуска.
|
||
- Код диалога (открытие, заполнение, CSV, копирование, подсказки) выносится из `analytics.js` в общий `analytics-dialog.js`, разметка диалога — в общий шаблон; страница одного запуска переходит на него без изменения поведения.
|
||
- На `/analytics` добавляется кнопка «Сравнить с другим запуском».
|
||
|
||
## 4. Файлы
|
||
|
||
- `internal/analytics/compare.go` (новый), `internal/analytics/compare_test.go` (новый).
|
||
- `internal/httpapi/handlers_analytics.go` (общий загрузчик, два новых обработчика), `routes`.
|
||
- `internal/dashboard`: `handlers_analytics.go` (страница и прокси), `client.go`, `routes.go`, `templates/analytics_compare.html`, `templates/analytics.html` (кнопка, общий диалог), `static/analytics.js`, `static/analytics-dialog.js` (новый), `static/analytics-compare.js` (новый), `static/analytics.css`.
|
||
- Документы: `docs/API.md`, `docs/USAGE.md` («Аналитика запусков»), `docs/DASHBOARD.md`, `README.md`, итог `docs/changes/…-summary.md`.
|
||
|
||
## 5. Тесты
|
||
|
||
- `analytics`: два запуска с новыми, выбывшими, изменившимися и неизменившимися адресами, с отменёнными; числа групп; инвариант `delta = new − left + entered − exited` по каждому индикатору; число адресов по индикатору равно `summary` каждого запуска; матрица переходов; списки каждой группы и фильтры (`indicator`, `from`/`to`); текст «Что изменилось» (вердикт, вход/выход из индикатора, цели, площадки, валидатор); порядок по адресу; пустые группы.
|
||
- `httpapi`: отчёт и списки (JSON, CSV с именем файла), `400` на одинаковые и неверные id, `404` на неизвестные запуск/группу/индикатор, `409` на незавершённый запуск.
|
||
- `dashboard`: страница с запуском по умолчанию, с явным `base`/`target`, предупреждение на неверные id, прокси списка и CSV; страница одного запуска после выноса диалога по-прежнему отдаёт данные и скрипты.
|
||
- `gofmt -l`, `go build ./... && go vet ./... && go test ./...`, проверка синтаксиса JS.
|
||
|
||
## 6. Выкладка и проверка
|
||
|
||
Меняются `control-api` и `admin-dashboard` (статика встроена); агенты, prober, миграции без изменений. Порядок: проверка пустой очереди, тег отката образов, пересборка, перезапуск. Проверка на стенде: сравнение запусков 1 и 2 через API (сумма групп равна числу адресов, инвариант по индикаторам), затем страница в браузере: выбор запусков, обмен местами, открытие диалогов из карточек, таблицы и матрицы, CSV, а также что страница одного запуска работает как раньше.
|
||
|
||
## 7. Риски
|
||
|
||
- **Что считается изменением.** Только принадлежность к семи индикаторам. Адрес, который остался в «Egress https: есть провалы», но у которого поменялись проваленные цели, попадает в «без изменений»; в списке `same` его сводка «без изменений» это не показывает. Если такие случаи важны, следующим шагом можно считать изменением и смену наборов целей и площадок.
|
||
- **Размер списков.** Группа новых или `same` может содержать тысячи строк (диалог показывает все, как и другие списки); CSV полный.
|
||
- **Разный состав запусков.** Если запуск 1 был частичным (например, перепроверка), то в сравнении будет много «новых» и «выбывших»: это отражает состав запусков, а не динамику. Размеры обоих запусков показаны в примечании над таблицами.
|
||
- Вердикт — оценка системы при агрегации, а не итог всех проверок (см. «Качество данных»); сравнение вердикта это не меняет, столбцы Egress и Ingress показывают фактические проверки.
|
||
- Вынос кода диалога затрагивает работающую страницу; закрывается тестами и ручной проверкой п. 6.
|
||
|
||
## 8. Не входит в доработку
|
||
|
||
Сравнение более чем двух запусков, графики динамики по времени, разбивка новых и выбывших по подсетям, сравнение по другим показателям (классы ошибок, площадки, валидаторы), автоматический выбор «интересных» пар запусков.
|
||
|
||
Открытых вопросов нет. Жду команды начать реализацию.
|