Журнал: стартовая часть первой, задача в обеих выборках не теряет историю (#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
|
return got
|
||||||
|
|
||||||
def refresh_history(self, p: dict, cmd: dict, fetched: List[dict]) -> Optional[dict]:
|
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;
|
Ответ не 2xx Jira или хаба — текст в error (since, отправка) или backfill_error (jql), refresh остаётся done;
|
||||||
обрыв связи — как во всём refresh."""
|
обрыв связи — как во всём refresh."""
|
||||||
h = p.get("history")
|
h = p.get("history")
|
||||||
@ -1263,10 +1264,16 @@ class Executor:
|
|||||||
for raw in raws:
|
for raw in raws:
|
||||||
if raw["key"] in read:
|
if raw["key"] in read:
|
||||||
continue
|
continue
|
||||||
read.add(raw["key"])
|
|
||||||
items.extend(self.changelog_items(raw["key"], self.full_changelog(raw, out["incomplete"]), since,
|
items.extend(self.changelog_items(raw["key"], self.full_changelog(raw, out["incomplete"]), since,
|
||||||
field_id_of))
|
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"):
|
if h.get("since"):
|
||||||
since = _jira_time(h["since"])
|
since = _jira_time(h["since"])
|
||||||
try:
|
try:
|
||||||
@ -1275,8 +1282,9 @@ class Executor:
|
|||||||
changed = []
|
changed = []
|
||||||
for r in fetched:
|
for r in fetched:
|
||||||
upd = _jira_time(((r.get("fields") or {}).get("updated")))
|
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.append(r["key"])
|
||||||
|
changed = list(dict.fromkeys(changed))
|
||||||
for i in range(0, len(changed), 100):
|
for i in range(0, len(changed), 100):
|
||||||
collect(self.search_changelog("key in (%s)" % ",".join(changed[i:i + 100])), since)
|
collect(self.search_changelog("key in (%s)" % ",".join(changed[i:i + 100])), since)
|
||||||
except HttpError as e:
|
except HttpError as e:
|
||||||
@ -1285,12 +1293,6 @@ class Executor:
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
out["error"] = str(e)
|
out["error"] = str(e)
|
||||||
log("refresh: %s" % 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()
|
sent_keys = set()
|
||||||
for i in range(0, len(items), self.HISTORY_BATCH):
|
for i in range(0, len(items), self.HISTORY_BATCH):
|
||||||
chunk = items[i:i + 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(sorted(c["key"] for c in self.sent()), ["X", "Y"])
|
||||||
self.assertEqual(out["result"]["history"]["changes"], 2)
|
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
|
def test_neighbor_changed_included(self): # Review Focus 5
|
||||||
self.fake.add("X")
|
self.fake.add("X")
|
||||||
self.fake.add("N", in_base=False, updated=CHANGED)
|
self.fake.add("N", in_base=False, updated=CHANGED)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user