Доска проекта в ensure_fields со story_points (#113)
Хаб дописывает board_id доски проекта задачи в ensure_fields со story_points — как в ensure_sprint; свои тексты 422 для канбана и задачи вне проектов; повтор берёт доску заново. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
49ee8c11b8
commit
fd26fb36e5
@ -52,33 +52,41 @@ def check_setup(conn, cmds, files_dir) -> None:
|
||||
|
||||
|
||||
def _assign_boards(conn, cmds) -> None:
|
||||
"""Доска проекта задачи в params ensure_sprint (design #7): метки временного ключа — из его ensure_issue в пакете,
|
||||
иначе из зеркала; 422 — задача вне проектов, разные доски, нет доски, канбан, тип ещё не известен."""
|
||||
"""Доска проекта задачи в params ensure_sprint (design #7) и ensure_fields со story_points (оценка через доску,
|
||||
sp-estimation-board): метки временного ключа — из его ensure_issue в пакете, иначе из зеркала; 422 — задача вне
|
||||
проектов, разные доски, нет доски, канбан, тип ещё не известен."""
|
||||
temp_labels = {c["params"]["temp_key"]: c["params"].get("labels") or [] for c in cmds if c["type"] == "ensure_issue"}
|
||||
found = jira_projects.light_list(conn)
|
||||
for i, c in enumerate(cmds, 1):
|
||||
if c["type"] != "ensure_sprint":
|
||||
points = c["type"] == "ensure_fields" and "story_points" in c["params"]
|
||||
if c["type"] != "ensure_sprint" and not points:
|
||||
continue
|
||||
goal, kanban = ("оценки", KANBAN_POINTS) if points else ("спринта", KANBAN_SPRINT)
|
||||
key = c["params"]["key"]
|
||||
labels = temp_labels[key] if key in temp_labels else (mirror.labels_of(conn, key) or [])
|
||||
own = [p for p in found if p["label"] in labels]
|
||||
if not own:
|
||||
raise CommandError(f"команда {i}: {key} не входит ни в один проект — доску для спринта не определить")
|
||||
raise CommandError(f"команда {i}: {key} не входит ни в один проект — доску для {goal} не определить")
|
||||
if len({p["board_id"] for p in own}) > 1:
|
||||
raise CommandError(f"команда {i}: {key} в проектах {', '.join(p['label'] for p in own)} с разными досками")
|
||||
p = own[0]
|
||||
err = _board_error(p["label"], p["board_id"], p["board_type"])
|
||||
err = _board_error(p["label"], p["board_id"], p["board_type"], kanban)
|
||||
if err:
|
||||
raise CommandError(f"команда {i}: {err}")
|
||||
c["params"] = {**c["params"], "board_id": p["board_id"]}
|
||||
|
||||
|
||||
def _board_error(label, board_id, board_type) -> str | None:
|
||||
"""Общие тексты 422 для ensure_sprint и команд спринта: доски нет, канбан, тип ещё не известен; None — доска годится."""
|
||||
KANBAN_SPRINT = "спринтов нет"
|
||||
KANBAN_POINTS = "оценка — только на scrum-доске"
|
||||
|
||||
|
||||
def _board_error(label, board_id, board_type, kanban=KANBAN_SPRINT) -> str | None:
|
||||
"""Общие тексты 422 для ensure_sprint, команд спринта и оценки: доски нет, канбан (хвост текста — kanban), тип ещё
|
||||
не известен; None — доска годится."""
|
||||
if board_id is None:
|
||||
return f"у проекта {label} нет доски Jira"
|
||||
if board_type == "kanban":
|
||||
return f"у проекта {label} канбан-доска, спринтов нет"
|
||||
return f"у проекта {label} канбан-доска, {kanban}"
|
||||
if board_type is None:
|
||||
return f"тип доски проекта {label} ещё не известен — дождитесь обновления из Jira"
|
||||
return None
|
||||
@ -282,8 +290,8 @@ def check_confirmed(conn, cmd) -> tuple[bool, dict | None]:
|
||||
|
||||
|
||||
def retry_params(cmd) -> dict:
|
||||
"""Повтор ensure_sprint и команд спринта берёт доску заново (она в params ставится при постановке)."""
|
||||
drop = cmd["type"] in ("ensure_sprint", *commands.SPRINT_TYPES)
|
||||
"""Повтор ensure_sprint, команд спринта и ensure_fields берёт доску заново (она в params ставится при постановке)."""
|
||||
drop = cmd["type"] in ("ensure_sprint", "ensure_fields", *commands.SPRINT_TYPES)
|
||||
return {k: v for k, v in cmd["params"].items() if not (drop and k == "board_id")}
|
||||
|
||||
|
||||
|
||||
@ -895,6 +895,82 @@ def test_retry_premigration_sprint_without_board(conn, add_project): # Review
|
||||
assert queue.batch_view(conn, r["id"])["commands"][0]["params"] == {"key": "A-1", "sprint": "Спринт 2", "board_id": 4216}
|
||||
|
||||
|
||||
|
||||
def fields_sp(key, sp=1):
|
||||
return {"type": "ensure_fields", "params": {"key": key, "story_points": sp}}
|
||||
|
||||
|
||||
def test_story_points_gets_project_board(conn, add_project): # сценарий «Оценка задачи проекта»
|
||||
add_project(conn)
|
||||
mirror.apply_state(conn, [{"key": "AURORA-1692", "labels": ["AWPCA"]}], [])
|
||||
b = queue.create_batch(conn, "x", "claude", [fields_sp("AURORA-1692")])
|
||||
assert queue.batch_view(conn, b["id"])["commands"][0]["params"] == {"key": "AURORA-1692", "story_points": 1, "board_id": 4216}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("setup, key, msg", [
|
||||
(dict(board_id=5120, board_type="kanban"), "AURORA-1",
|
||||
"команда 1: у проекта STOCKS_DRRK канбан-доска, оценка — только на scrum-доске"),
|
||||
(dict(board_id=5120, board_type=None), "AURORA-1", "тип доски проекта STOCKS_DRRK ещё не известен"),
|
||||
(dict(board_id=None, board_type=None), "AURORA-1", "у проекта STOCKS_DRRK нет доски Jira"),
|
||||
(dict(board_id=5120, board_type="scrum"), "AURORA-77",
|
||||
"команда 1: AURORA-77 не входит ни в один проект — доску для оценки не определить"),
|
||||
(dict(board_id=5120, board_type="scrum"), "AURORA-12", "с разными досками"),
|
||||
])
|
||||
def test_story_points_board_rejected(conn, add_project, setup, key, msg): # сценарии «Оценка вне проектов», «на канбане»
|
||||
add_project(conn)
|
||||
add_project(conn, "STOCKS_DRRK", **setup)
|
||||
mirror.apply_state(conn, [{"key": "AURORA-1", "labels": ["STOCKS_DRRK"]}, {"key": "AURORA-77", "labels": ["kind-dev"]},
|
||||
{"key": "AURORA-12", "labels": ["AWPCA", "STOCKS_DRRK"]}], [])
|
||||
with pytest.raises(CommandError, match=msg):
|
||||
queue.create_batch(conn, "x", "claude", [fields_sp(key)])
|
||||
assert conn.execute("SELECT COUNT(*) AS n FROM batch").fetchone()["n"] == 0
|
||||
|
||||
|
||||
def test_fields_without_points_no_board(conn, add_project): # сценарий «Поля без оценки — без доски»
|
||||
add_project(conn)
|
||||
mirror.apply_state(conn, [{"key": "AURORA-77", "labels": ["kind-dev"]}], [])
|
||||
b = queue.create_batch(conn, "x", "claude", [{"type": "ensure_fields", "params": {"key": "AURORA-77", "duedate": "2026-10-30"}}])
|
||||
assert "board_id" not in queue.batch_view(conn, b["id"])["commands"][0]["params"]
|
||||
|
||||
|
||||
def test_story_points_for_new_issue_uses_batch_labels(conn, add_project):
|
||||
add_project(conn)
|
||||
b = queue.create_batch(conn, "x", "claude", [
|
||||
{"type": "ensure_issue", "params": {"temp_key": "new:1", "issue_type": "task", "summary": "Н"}}, fields_sp("new:1")],
|
||||
project="AWPCA")
|
||||
assert queue.batch_view(conn, b["id"])["commands"][1]["params"]["board_id"] == 4216
|
||||
|
||||
|
||||
def test_fields_and_sprint_same_issue(conn, add_project):
|
||||
add_project(conn)
|
||||
mirror.apply_state(conn, [{"key": "A-1", "labels": ["AWPCA"]}], [])
|
||||
b = queue.create_batch(conn, "x", "claude", [fields_sp("A-1"), sprint("A-1")])
|
||||
assert [c["params"]["board_id"] for c in queue.batch_view(conn, b["id"])["commands"][:2]] == [4216, 4216]
|
||||
|
||||
|
||||
def test_client_board_id_in_fields_rejected(conn, add_project):
|
||||
add_project(conn)
|
||||
with pytest.raises(CommandError, match="лишние параметры board_id"):
|
||||
queue.create_batch(conn, "x", "claude", [{"type": "ensure_fields", "params": {"key": "A-1", "story_points": 1, "board_id": 1}}])
|
||||
|
||||
|
||||
def test_retry_story_points_recomputes_board(conn, add_project):
|
||||
add_project(conn)
|
||||
mirror.apply_state(conn, [{"key": "AURORA-1692", "labels": ["AWPCA"]}], [])
|
||||
b = queue.create_batch(conn, "x", "claude", [fields_sp("AURORA-1692")])
|
||||
cid = queue.batch_view(conn, b["id"])["commands"][0]["id"]
|
||||
conn.execute("UPDATE jira_project SET board_id = 5120, board_type = 'scrum'")
|
||||
r = queue.retry_command(conn, cid, "claude")
|
||||
assert queue.batch_view(conn, r["id"])["commands"][0]["params"]["board_id"] == 5120
|
||||
|
||||
|
||||
def test_retry_fields_without_points_keeps_params(conn, add_project):
|
||||
add_project(conn)
|
||||
p = {"key": "AURORA-77", "duedate": "2026-10-30"}
|
||||
b = queue.create_batch(conn, "x", "claude", [{"type": "ensure_fields", "params": dict(p)}])
|
||||
r = queue.retry_command(conn, queue.batch_view(conn, b["id"])["commands"][0]["id"], "claude")
|
||||
assert queue.batch_view(conn, r["id"])["commands"][0]["params"] == p
|
||||
|
||||
def meta(name="AWPCA_Sprint_4"):
|
||||
return {"type": "ensure_sprint_meta", "params": {"sprint": name, "start_date": "2026-10-28", "end_date": "2026-11-11"}}
|
||||
def close(name="AWPCA_Sprint_3_Corrections", to="AWPCA_Sprint_4"):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user