Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions demo/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class DemoIssue:
issue_type: str = "Story"
links: tuple[str, ...] = ()
blocked_by: tuple[str, ...] = ()
blocks: tuple[str, ...] = ()
parent: str | None = None
subtasks: tuple[str, ...] = ()


SERVICES: typing.Final = (
Expand Down Expand Up @@ -199,6 +202,7 @@ class DemoIssue:
category="done",
issue_type="Bug",
links=(f"{ENDPOINT}/shop/worker/-/merge_requests/31",),
blocks=("SHOP-40",),
),
DemoIssue(
key="SHOP-36",
Expand All @@ -207,6 +211,7 @@ class DemoIssue:
category="indeterminate",
links=(f"{ENDPOINT}/shop/api/-/merge_requests/40",),
blocked_by=("SHOP-45",),
subtasks=("SHOP-46",),
),
DemoIssue(
key="SHOP-38",
Expand Down Expand Up @@ -242,6 +247,7 @@ class DemoIssue:
links=(f"{ENDPOINT}/shop/admin/-/merge_requests/15",),
),
DemoIssue(key="SHOP-45", summary="Pick the dark palette with design", status="To Do", category="new"),
DemoIssue(key="SHOP-46", summary="Dark mode in emails", status="To Do", category="new", parent="SHOP-36"),
)


Expand Down Expand Up @@ -392,6 +398,9 @@ def _mock_service(router: respx.Router, service: DemoService, now: dt.datetime)
router.get(f"{api}/pipelines", params={"source": "push"}).respond(json=push)


_BLOCKS: typing.Final = {"name": "Blocks", "inward": "is blocked by", "outward": "blocks"}


def _issue(issue: DemoIssue) -> dict[str, typing.Any]:
return {
"key": issue.key,
Expand All @@ -400,12 +409,11 @@ def _issue(issue: DemoIssue) -> dict[str, typing.Any]:
"status": {"name": issue.status, "statusCategory": {"key": issue.category}},
"issuetype": {"name": issue.issue_type},
"issuelinks": [
{
"type": {"name": "Blocks", "inward": "is blocked by", "outward": "blocks"},
"inwardIssue": {"key": key},
}
for key in issue.blocked_by
*({"type": _BLOCKS, "inwardIssue": {"key": key}} for key in issue.blocked_by),
*({"type": _BLOCKS, "outwardIssue": {"key": key}} for key in issue.blocks),
],
"parent": {"key": issue.parent} if issue.parent else None,
"subtasks": [{"key": key} for key in issue.subtasks],
},
}

Expand Down
11 changes: 6 additions & 5 deletions docs/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,15 @@ screen. While a service is open, the column folds into a strip with the number o
get the full width. Clicking the strip closes the service.

Above the tabs, **Left behind by these picks** lists what the picked tags leave out for every Jira task they ship,
and for every issue related to one:
and for every issue a task depends on: one that blocks it through Jira's `Blocks` link type, and its subtasks.

- a service where the task or related issue has changes that no pick covers, with the tag it needs or why there is
none: `no tag yet`, `not merged`, `merged into <branch>`, `in prod or not merged`, `not in this report`, or
- a service where the task or an issue it depends on has changes that no pick covers, with the tag it needs or why
there is none: `no tag yet`, `not merged`, `merged into <branch>`, `in prod or not merged`, `not in this report`, or
`failed to collect`;
- a related issue with no changes at all that is not done in Jira, with its status.
- an issue the task depends on with no changes at all that is not done in Jira, with its status.

Changes already in production and closed merge requests are not listed.
Changes already in production and closed merge requests are not listed. Other relations, such as `blocks`,
`relates to`, or a parent, do not hold a release back; they show only when you hover over the issue's key.

It turns the picked tags into four tabs. Each has a copy button and a text box to copy from by hand, since browsers
allow the copy button only over HTTPS.
Expand Down
13 changes: 8 additions & 5 deletions release_scope/_static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -675,10 +675,10 @@ <h2 x-text="t('release')"></h2>
viaJira: "через Jira",
viaJiraTitle: "{change} не упоминает задачу. {key} ссылается на это изменение в Jira.",
taskFrom: "из {items}",
gapsTitle: "Не войдёт с этими тегами:",
gapNeeds: "нужен {tag}",
relationParent: "родитель",
relationSubtask: "подзадача",
gapsTitle: "Не войдёт в релиз с выбранными тегами:",
gapNeeds: "нужен тег {tag}",
relationParent: "входит в",
relationSubtask: "включает подзадачу",
},
};

Expand Down Expand Up @@ -744,6 +744,9 @@ <h2 x-text="t('release')"></h2>

const byKey = (a, b) => a.localeCompare(b, undefined, { numeric: true });

const dependsOn = (relation) =>
relation.kind === "subtask" || (relation.kind === "link" && relation.type === "Blocks" && relation.direction === "inward");

const OUT_OF_RANGE = new Set(["production", "older", "open", "branch", "closed", "elsewhere"]);

const outOfRange = (service, links) => {
Expand Down Expand Up @@ -937,7 +940,7 @@ <h2 x-text="t('release')"></h2>
};
for (const task of this.tasks) {
check(task.key, null, task.key);
for (const relation of task.issue ? task.issue.relations : []) check(task.key, relation, relation.key);
for (const relation of task.issue ? task.issue.relations.filter(dependsOn) : []) check(task.key, relation, relation.key);
}
return [...gaps];
},
Expand Down
24 changes: 22 additions & 2 deletions tests/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def test_demo_lists_its_services_and_picks_tags_for_an_issue(page: Page, demo_si
names = page.locator("details.service .name")
expect(names).to_have_text(["admin", "api", "web", "worker"])
expect(names.first).to_have_attribute("title", "shop/admin")
expect(page.locator(".issue-row code")).to_have_count(8)
expect(page.locator(".issue-row code")).to_have_count(9)
_issue(page, "SHOP-38").check()
expect(page).to_have_url(re.compile(r"#issues=SHOP-38&shop/api=2\.5\.0&shop/web=1\.9\.0$"))
assert errors == []
Expand Down Expand Up @@ -355,6 +355,7 @@ def test_demo_a_hand_pick_lists_the_changes_it_leaves_behind(page: Page, demo_si
"SHOP-38 is blocked by SHOP-44 → admin: not merged",
"SHOP-35 → worker: needs 0.15.0",
"SHOP-36 is blocked by SHOP-45: To Do",
"SHOP-36 has subtask SHOP-46: To Do",
]
)
assert errors == []
Expand All @@ -378,6 +379,25 @@ def test_demo_related_issues_have_their_own_group_and_relations_show_on_hover(
) -> None:
page.goto(demo_site)

expect(page.locator(".issue-group")).to_have_text(["Done · 3", "Not done · 3", "Related · 2"])
expect(page.locator(".issue-group")).to_have_text(["Done · 3", "Not done · 3", "Related · 3"])
expect(page.locator(".issue-row code", has_text="SHOP-38")).to_have_attribute("title", "is blocked by SHOP-44")
assert errors == []


def test_demo_left_behind_list_reads_in_russian(page: Page, demo_site: str, errors: list[str]) -> None:
page.goto(demo_site)
for project, tag in (("shop/api", "2.5.0"), ("shop/web", "1.9.0")):
service = _demo_service(page, project)
service.locator("summary").click()
service.locator(".tag", has_text=tag).get_by_role("button").click()

page.get_by_role("combobox").first.select_option("ru")

expect(page.locator(".gaps p")).to_have_text("Не войдёт в релиз с выбранными тегами:") # noqa: RUF001
expect(page.locator(".gaps li")).to_contain_text(
[
"SHOP-35 → worker: нужен тег 0.15.0",
"SHOP-36 включает подзадачу SHOP-46: To Do",
]
)
assert errors == []
Loading