Журнал: стартовая часть первой, задача в обеих выборках не теряет историю (#39, I-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
05e3f2c114
commit
faed8b9c7a
20
src/agent.py
20
src/agent.py
@ -1241,7 +1241,8 @@ class Executor:
|
||||
return got
|
||||
|
||||
def refresh_history(self, p: dict, cmd: dict, fetched: List[dict]) -> Optional[dict]:
|
||||
"""Журнал после состояния: since — задачи этого refresh с updated не раньше since, jql — стартовая загрузка.
|
||||
"""Журнал после состояния: jql — стартовая загрузка (первой, журнал целиком), since — задачи этого refresh с
|
||||
updated не раньше since, ещё не прочитанные стартовой.
|
||||
Ответ не 2xx Jira или хаба — текст в error (since, отправка) или backfill_error (jql), refresh остаётся done;
|
||||
обрыв связи — как во всём refresh."""
|
||||
h = p.get("history")
|
||||
@ -1263,10 +1264,16 @@ class Executor:
|
||||
for raw in raws:
|
||||
if raw["key"] in read:
|
||||
continue
|
||||
read.add(raw["key"])
|
||||
items.extend(self.changelog_items(raw["key"], self.full_changelog(raw, out["incomplete"]), since,
|
||||
field_id_of))
|
||||
read.add(raw["key"]) # только прочитанную: упавшую на ней стартовую часть дочитает since
|
||||
|
||||
if h.get("jql"): # первой: журнал задач стартовой выборки — целиком (I-1)
|
||||
try:
|
||||
collect(self.search_changelog(h["jql"]), None)
|
||||
except HttpError as e:
|
||||
out["backfill_error"] = "HTTP %d %s" % (e.code, e.text[:500])
|
||||
log("refresh: стартовая загрузка журнала не прочитана: %s" % out["backfill_error"])
|
||||
if h.get("since"):
|
||||
since = _jira_time(h["since"])
|
||||
try:
|
||||
@ -1275,8 +1282,9 @@ class Executor:
|
||||
changed = []
|
||||
for r in fetched:
|
||||
upd = _jira_time(((r.get("fields") or {}).get("updated")))
|
||||
if upd is not None and upd >= since and r["key"] not in changed:
|
||||
if upd is not None and upd >= since and r["key"] not in read:
|
||||
changed.append(r["key"])
|
||||
changed = list(dict.fromkeys(changed))
|
||||
for i in range(0, len(changed), 100):
|
||||
collect(self.search_changelog("key in (%s)" % ",".join(changed[i:i + 100])), since)
|
||||
except HttpError as e:
|
||||
@ -1285,12 +1293,6 @@ class Executor:
|
||||
except ValueError as e:
|
||||
out["error"] = str(e)
|
||||
log("refresh: %s" % e)
|
||||
if h.get("jql"):
|
||||
try:
|
||||
collect(self.search_changelog(h["jql"]), None)
|
||||
except HttpError as e:
|
||||
out["backfill_error"] = "HTTP %d %s" % (e.code, e.text[:500])
|
||||
log("refresh: стартовая загрузка журнала не прочитана: %s" % out["backfill_error"])
|
||||
sent_keys = set()
|
||||
for i in range(0, len(items), self.HISTORY_BATCH):
|
||||
chunk = items[i:i + self.HISTORY_BATCH]
|
||||
|
||||
@ -174,6 +174,37 @@ class RefreshHistoryTest(Base):
|
||||
self.assertEqual(sorted(c["key"] for c in self.sent()), ["X", "Y"])
|
||||
self.assertEqual(out["result"]["history"]["changes"], 2)
|
||||
|
||||
def test_since_and_jql_keeps_old_history(self): # I-1: задача в обеих выборках — журнал целиком
|
||||
jql = "(%s) AND sprint in openSprints() AND statusCategory != Done" % BASE
|
||||
self.fake.add("X", updated=CHANGED)
|
||||
self.fake.add_history("X", "2020-01-01T00:00:00.000+0300", [_st("status", "a", "b")])
|
||||
self.fake.add_history("X", "2026-10-22T12:10:00.000+0300", [_st("status", "b", "c")])
|
||||
self.fake.jql_results[jql] = ["X"]
|
||||
out = self.refresh({"since": SINCE, "jql": jql})
|
||||
self.assertEqual(sorted(c["created"][:4] for c in self.sent()), ["2020", "2026"])
|
||||
self.assertEqual(out["result"]["history"]["changes"], 2)
|
||||
self.assertEqual(len(self.changelog_queries()), 1) # X уже прочитана стартовой — второго search нет
|
||||
|
||||
def test_backfill_failure_on_issue_leaves_it_to_since(self): # I-1: упавшая на задаче стартовая часть её не «съедает»
|
||||
jql = "(%s) AND sprint in openSprints() AND statusCategory != Done" % BASE
|
||||
self.fake.add("X", updated=CHANGED)
|
||||
self.fake.add_history("X", "2020-01-01T00:00:00.000+0300", [_st("status", "a", "b")])
|
||||
self.fake.add_history("X", "2026-10-22T12:10:00.000+0300", [_st("status", "b", "c")])
|
||||
self.fake.jql_results[jql] = ["X"]
|
||||
self.fake.changelog_cap = 1 # search обрезает — дочитывание GET /issue отдаёт обе записи
|
||||
self.fake.issue_changelog_cap = 2
|
||||
orig, failed = self.fake.route, []
|
||||
|
||||
def route(method, path, q, body):
|
||||
if path == "/rest/api/2/issue/X" and q.get("expand") == "changelog" and not failed:
|
||||
failed.append(1)
|
||||
return 500, {"errorMessages": ["сбой"]}
|
||||
return orig(method, path, q, body)
|
||||
self.fake.route = route
|
||||
out = self.refresh({"since": SINCE, "jql": jql})
|
||||
self.assertTrue(out["result"]["history"]["backfill_error"].startswith("HTTP 500"))
|
||||
self.assertEqual([c["created"][:4] for c in self.sent()], ["2026"])
|
||||
|
||||
def test_neighbor_changed_included(self): # Review Focus 5
|
||||
self.fake.add("X")
|
||||
self.fake.add("N", in_base=False, updated=CHANGED)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user