70 lines
7.0 KiB
Markdown
70 lines
7.0 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: Журнал изменений
|
||
Если в параметрах `refresh` есть `history`, агент (с v13) SHALL после отправки всех пачек состояния прочитать журнал
|
||
изменений (changelog) Jira и отправить его хабу. `history` может содержать `since`, `jql` или оба:
|
||
- `since` (регулярная часть) — из задач, выгруженных этим `refresh` (база и соседи), агент SHALL отобрать те, у которых
|
||
Jira `updated` не раньше `since` (сравнение моментов времени с учётом смещения), и прочитать их журнал запросом
|
||
`search` `jql = key in (...)`, `fields = key`, `expand = changelog`, по 100 ключей; из журнала каждой задачи взять
|
||
записи с `created` не раньше `since`. Таких задач нет — вызовов Jira на этом шаге нет.
|
||
- `jql` (стартовая загрузка) — агент SHALL выполнить `search` по `history.jql` с `fields = key`, `expand = changelog`
|
||
(постранично, `maxResults = 100`) и взять журнал найденных задач целиком. Задача, уже прочитанная регулярной частью,
|
||
повторно не читается.
|
||
|
||
Если у задачи `changelog.total` больше числа записей в ответе, агент SHALL дочитать журнал
|
||
`GET /rest/api/2/issue/{key}?fields=key&expand=changelog`; если и он неполный — передать что есть, записать в лог ключ
|
||
и «получено N из M» и добавить ключ в `history.incomplete` результата.
|
||
|
||
Элемент для хаба — одно поле записи журнала: `{history_id, seq, key, created, author, field_id, field_name, from,
|
||
from_string, to, to_string}`: `history_id` — id записи строкой; `seq` — номер элемента в `items` записи, с 0; `created` —
|
||
время записи как есть; `author` — отображаемое имя автора, иначе логин, иначе `null`; `field_name` — `field` элемента;
|
||
`field_id` — `fieldId` элемента, а если его нет: для `fieldtype = custom` — id поля, найденный по имени в
|
||
`GET /rest/api/2/field` (не больше одного такого вызова за `refresh`), для прочих — `field`; не найден — `null`; `from`,
|
||
`fromString`, `to`, `toString` — как есть, нестроковое значение — строкой. Запись без `id` или с пустым `created` и
|
||
элемент с пустым `field` агент SHALL отбросить с записью в лог (ключ задачи). Элементы SHALL отправляться
|
||
`POST /agent/history` пачками до 500; повторы при отправке — как у пачек состояния.
|
||
|
||
Результат `refresh` SHALL содержать `history = {issues: <задач с хотя бы одним элементом>, changes: <элементов
|
||
отправлено>, incomplete: [<ключи>]}`. Ответ не 2xx (`HttpError`) от Jira или хаба MUST NOT переводить `refresh` в
|
||
`failed` и MUST NOT вызывать повтор команды: агент SHALL записать ошибку в лог и вернуть `done`, добавив в `history`
|
||
текст ошибки (у ошибки хаба — префикс «хаб: ») — `error` при ошибке регулярной части или отправки элементов,
|
||
`backfill_error` при ошибке `search` стартовой загрузки (регулярная часть при этом выполняется). Обрыв связи с Jira или
|
||
хабом (`ConnectionError`) на шаге журнала обрабатывается, как в остальном `refresh`. Без `history` в параметрах агент
|
||
журнал не читает, ключа `history` в результате нет.
|
||
|
||
#### Scenario: Ничего не менялось
|
||
- **WHEN** `refresh {jql: base, history: {since: "2026-10-22T08:45:00Z"}}`, у всех 180 задач `updated` раньше `since`
|
||
- **THEN** вызовов `search` с `expand = changelog` нет; результат `history = {issues: 0, changes: 0, incomplete: []}`
|
||
|
||
#### Scenario: Две задачи изменились
|
||
- **WHEN** тот же `refresh`, у X и Y `updated` позже `since`; у X в журнале записи от вчера и от 09:10 UTC (смена статуса и
|
||
резолюции), у Y — от 09:30 UTC (правка описания)
|
||
- **THEN** один `search` `key in (X,Y)` с `expand = changelog`; хабу отправлены 3 элемента (у X — `seq` 0 и 1 одной записи,
|
||
вчерашняя запись не отправлена); результат `history = {issues: 2, changes: 3, incomplete: []}`
|
||
|
||
#### Scenario: Стартовая загрузка
|
||
- **WHEN** `refresh {jql: base, history: {jql: "(base) AND sprint in openSprints() AND statusCategory != Done"}}`, по ней
|
||
12 задач
|
||
- **THEN** `search` по `history.jql` с `expand = changelog`; журнал 12 задач отправлен целиком
|
||
|
||
#### Scenario: Плановая дата без fieldId
|
||
- **WHEN** элемент журнала `{field: "Плановое окончание", fieldtype: "custom"}` без `fieldId`; в `GET /field` у поля с этим
|
||
именем id `customfield_10201`
|
||
- **THEN** у элемента `field_id = "customfield_10201"`; `GET /field` вызван один раз за `refresh`
|
||
|
||
#### Scenario: Ошибка Jira при чтении журнала
|
||
- **WHEN** `search` с `expand = changelog` для регулярной части ответил 500
|
||
- **THEN** состояние в хабе обновлено; `refresh` — `done` с `history.error`, повтора команды нет; в логе — ошибка
|
||
|
||
#### Scenario: Ошибка стартовой загрузки
|
||
- **WHEN** `history = {since: …, jql: …}`, `search` по `history.jql` ответил 400, у X `updated` позже `since`
|
||
- **THEN** журнал X отправлен; `refresh` — `done`, в `history` — `backfill_error` с текстом Jira, `error` нет
|
||
|
||
#### Scenario: Неполный журнал
|
||
- **WHEN** у X `changelog.total = 250`, и `search`, и `GET /issue/X?expand=changelog` вернули 100 записей
|
||
- **THEN** отправлены 100 записей X; в логе «X: получено 100 из 250»; `history.incomplete = ["X"]`
|
||
|
||
#### Scenario: Хаб без журнала
|
||
- **WHEN** `refresh {jql: base}` без `history`
|
||
- **THEN** журнал не читается; в результате нет ключа `history`
|