|
34 | 34 | "Legend: ✅ success · ❌ failed · 🔄 running · ⏭ canceled or skipped · ⚠️ warning or allowed failure" |
35 | 35 | ) |
36 | 36 | _FAILED, _SKIPPED, _PENDING = 0, 1, 2 |
| 37 | +_UNRELEASED: typing.Final = {"not_merged": "⏳ not merged", "not_found": "⚠️ linked change not found"} |
| 38 | +_LINKED: typing.Final = "🎯" |
37 | 39 |
|
38 | 40 |
|
39 | 41 | def _inline(value: str) -> str: |
@@ -79,6 +81,8 @@ def _state(service: Service) -> int | None: |
79 | 81 | return _FAILED |
80 | 82 | if service.rows: |
81 | 83 | return _PENDING |
| 84 | + if service.release and service.release.state in _UNRELEASED: |
| 85 | + return _SKIPPED |
82 | 86 | return _SKIPPED if service.warnings else None |
83 | 87 |
|
84 | 88 |
|
@@ -109,12 +113,16 @@ def _failure_counts(service: Service) -> str: |
109 | 113 |
|
110 | 114 | def _pending(service: Service) -> str: |
111 | 115 | state: typing.Final = _state(service) |
| 116 | + release: typing.Final = service.release |
112 | 117 | if state == _FAILED: |
113 | 118 | return "❌ failed to collect" |
114 | 119 | if state == _SKIPPED: |
115 | | - return "⚠️ see below" |
116 | | - untagged: typing.Final = "" if service.rows[0].tags else " · untagged head" |
117 | | - return f"{_plural(len(service.rows), 'change')}{untagged}" |
| 120 | + return _UNRELEASED.get(release.state, "⚠️ see below") if release else "⚠️ see below" |
| 121 | + if release: |
| 122 | + suffix = f" · release {_tag(release.tag)}" if release.tag else " · needs a new tag" |
| 123 | + else: |
| 124 | + suffix = "" if service.rows[0].tags else " · untagged head" |
| 125 | + return f"{_plural(len(service.rows), 'change')}{suffix}" |
118 | 126 |
|
119 | 127 |
|
120 | 128 | def _not_done(service: Service, issues: dict[str, JiraIssue]) -> str: |
@@ -199,7 +207,7 @@ def _row(row: Row, project: str, jira: JiraState | None) -> list[str]: |
199 | 207 | issues: typing.Final = jira.issues if jira else {} |
200 | 208 | return [ |
201 | 209 | "<br>".join(_tag(tag) for tag in row.tags), |
202 | | - _merge_requests_or_commits(row), |
| 210 | + f"{_LINKED} {_merge_requests_or_commits(row)}" if row.linked else _merge_requests_or_commits(row), |
203 | 211 | "<br>".join(_jira_key(key, issues) for key in row.jira_keys), |
204 | 212 | *([_related_services(row, project, issues)] if jira else []), |
205 | 213 | ", ".join(_cell(name) for name in row.environments), |
@@ -238,26 +246,44 @@ def _service_section(service: Service, production: str, jira: JiraState | None) |
238 | 246 | if service.error: |
239 | 247 | lines.extend([f"❌ {_inline(service.error)}", ""]) |
240 | 248 | lines.extend(line for warning in service.warnings for line in (f"⚠️ {_inline(warning)}", "")) |
| 249 | + release: typing.Final = service.release |
| 250 | + if release and release.pending_merge_requests: |
| 251 | + waiting: typing.Final = ", ".join( |
| 252 | + _reference(f"!{item.iid}", item.url, item.title, f"@{item.author}" if item.author else None) |
| 253 | + for item in release.pending_merge_requests |
| 254 | + ) |
| 255 | + lines.extend([f"⏳ Not merged: {waiting}", ""]) |
| 256 | + if release and release.state == "not_found": |
| 257 | + lines.extend(["⚠️ No linked change was found between production and the head of the default branch.", ""]) |
241 | 258 | if service.rows: |
242 | 259 | lines.extend(_rows_section(service, production, jira)) |
243 | 260 | return lines |
244 | 261 |
|
245 | 262 |
|
| 263 | +def _scope_issue(key: str, jira: JiraState | None) -> str: |
| 264 | + issue: typing.Final = jira.issues.get(key) if jira else None |
| 265 | + if issue is None: |
| 266 | + return f"- {_inline(key)} · not found in Jira" |
| 267 | + return f"- {_link(_inline(key), issue.url)} {_cell(issue.summary)} · {_cell(issue.status)}" |
| 268 | + |
| 269 | + |
246 | 270 | def render_markdown(report: Report) -> str: |
247 | 271 | production: typing.Final = report.production_environment |
248 | 272 | collected: typing.Final = report.collected_at.astimezone(datetime.UTC).strftime("%Y-%m-%d %H:%M") |
| 273 | + jira: typing.Final = report.jira |
| 274 | + scope: typing.Final = report.jira_scope |
249 | 275 | lines: typing.Final = [ |
250 | | - "# Release scope", |
| 276 | + f"# Release scope: {', '.join(_inline(key) for key in scope)}" if scope else "# Release scope", |
251 | 277 | "", |
252 | 278 | ( |
253 | 279 | f"Collected {collected} UTC. Changes run from the commit on `{_inline(production)}` " |
254 | | - "to the head of the default branch." |
| 280 | + + ("to the latest change linked to the issues." if scope else "to the head of the default branch.") |
255 | 281 | ), |
256 | 282 | "", |
257 | | - _LEGEND, |
258 | | - "", |
259 | 283 | ] |
260 | | - jira: typing.Final = report.jira |
| 284 | + if scope: |
| 285 | + lines.extend([*(_scope_issue(key, jira) for key in scope), ""]) |
| 286 | + lines.extend([f"{_LEGEND} · {_LINKED} linked to the issues" if scope else _LEGEND, ""]) |
261 | 287 | if jira and jira.error: |
262 | 288 | lines.extend([f"❌ {_inline(jira.error)}", ""]) |
263 | 289 | if not report.services: |
|
0 commit comments