Skip to content

Commit 8666e9c

Browse files
authored
Keep picks in the page address and add a copyable release post (#13)
1 parent 32910a8 commit 8666e9c

2 files changed

Lines changed: 62 additions & 3 deletions

File tree

‎README.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,21 @@ commits, Jira keys, environments, and failed jobs; rows out of scope are dimmed.
115115
picking it highlights the rows it ships and closes the line again. A `--jira` report starts with each service's
116116
release tag picked. Services without a production deployment are left out of the page.
117117

118-
**Release** at the bottom turns the picked tags into three lists, each with a copy button and a text box to copy from
119-
by hand, since browsers allow the copy button only over HTTPS:
118+
The address keeps the picks after `#`, as `team/api=2.4.0&team/web=5.12.0`, so sharing or reloading the page keeps
119+
them. An address with picks replaces the release tags of a `--jira` report; picks that are not in the report, such as
120+
a tag that a later run no longer lists, are skipped with a warning. Clearing every pick empties the address, so a
121+
reload starts from the release tags again.
122+
123+
**Release** at the bottom turns the picked tags into three lists and a release post, each with a copy button and a
124+
text box to copy from by hand, since browsers allow the copy button only over HTTPS:
120125

121126
- **Jira tasks**: the keys of every in-scope row from each picked tag down to production, without duplicates, with
122127
summary and status, flagging issues that are not done. Copy them one per line or as a JQL `key in (...)` clause,
123128
each in its own box.
124129
- **Tag pipelines**: the pipeline of each picked tag, as a Markdown list.
125130
- **Compare**: a GitLab compare link per service from production to the picked tag, as a Markdown list.
131+
- **Release post**: one Markdown text with a line per picked tag, holding its compare link and pipeline, followed by
132+
the Jira tasks with their summaries.
126133

127134
## GitLab Pages
128135

‎release_scope/_static/index.html‎

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
.warn { color: var(--warn); }
4444
.scroll { overflow-x: auto; }
4545
.outputs { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 12px; margin-top: 16px; }
46+
.outputs .wide { grid-column: 1 / -1; }
4647
.out-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
4748
tr.out-of-scope td { opacity: 0.55; }
4849
tr.shipped td { background: color-mix(in srgb, var(--accent) 12%, transparent); }
@@ -84,7 +85,8 @@ <h1 x-text="report.jira_scope.length ? 'Release scope: ' + report.jira_scope.joi
8485
</template>
8586

8687
<h2>Services</h2>
87-
<p x-show="pickable.length" class="muted">Open a service and pick the tag to release: it ships every merge request and commit from its row down to production.</p>
88+
<p x-show="pickable.length" class="muted">Open a service and pick the tag to release: it ships every merge request and commit from its row down to production. The address keeps the picks, so share it to share them.</p>
89+
<p x-show="ignored" class="warn" x-text="ignored"></p>
8890
<template x-for="service in deployed" :key="service.project">
8991
<details class="service" name="services">
9092
<summary>
@@ -242,6 +244,13 @@ <h3>Compare</h3>
242244
</ul>
243245
<textarea readonly x-text="compareMarkdown"></textarea>
244246
</section>
247+
<section class="wide">
248+
<div class="out-head">
249+
<h3>Release post</h3>
250+
<button type="button" @click="copy(releasePost)">Copy</button>
251+
</div>
252+
<textarea readonly rows="12" x-text="releasePost"></textarea>
253+
</section>
245254
</div>
246255

247256
<p class="muted">Legend: ✅ success · ❌ failed · 🔄 running · ⏭ canceled or skipped · ⚠️ no pipeline</p>
@@ -259,6 +268,7 @@ <h3>Compare</h3>
259268
report: null,
260269
failure: "",
261270
notice: "",
271+
ignored: "",
262272
picks: {},
263273

264274
async init() {
@@ -271,6 +281,8 @@ <h3>Compare</h3>
271281
this.picks[service.project] = service.candidates.some((item) => item.tag.name === tag) ? tag : "";
272282
}
273283
this.report = report;
284+
this.readHash();
285+
window.addEventListener("hashchange", () => this.readHash());
274286
} catch (error) {
275287
this.failure = `Cannot load the report: ${error.message}.`;
276288
}
@@ -321,6 +333,16 @@ <h3>Compare</h3>
321333
})
322334
.join("\n");
323335
},
336+
get releasePost() {
337+
const services = this.picked.map((item) => {
338+
const pipeline = item.candidate.tag.pipeline;
339+
const build = pipeline ? `[pipeline #${pipeline.id}](${pipeline.url}) ${pipeline.status}` : "no pipeline";
340+
const compare = `[${this.compareLabel(item)}](${item.candidate.compare_url})`;
341+
return `- ${item.service.project} ${item.candidate.tag.name}: ${compare} · ${build}`;
342+
});
343+
const tasks = this.tasks.map((task) => `- [${task.key}](${task.url})${task.issue ? " " + task.issue.summary : ""}`);
344+
return ["**Services**", ...services, "", `**Jira tasks (${tasks.length})**`, ...(tasks.length ? tasks : ["- none"])].join("\n");
345+
},
324346
get compareMarkdown() {
325347
return this.picked
326348
.map((item) => `- ${item.service.project}: [${this.compareLabel(item)}](${item.candidate.compare_url})`)
@@ -330,6 +352,36 @@ <h3>Compare</h3>
330352
pick(service, tag, element) {
331353
this.picks[service.project] = tag;
332354
element.closest("details").open = false;
355+
this.writeHash();
356+
},
357+
readHash() {
358+
const decode = (value) => {
359+
try {
360+
return decodeURIComponent(value);
361+
} catch {
362+
return value;
363+
}
364+
};
365+
const wanted = new Map(
366+
location.hash.slice(1).split("&").filter(Boolean)
367+
.map((part) => part.split("=", 2).map(decode)),
368+
);
369+
if (wanted.size) {
370+
for (const service of this.pickable) {
371+
const tag = wanted.get(service.project) || "";
372+
const known = !tag || service.candidates.some((item) => item.tag.name === tag);
373+
this.picks[service.project] = known ? tag : "";
374+
if (known) wanted.delete(service.project);
375+
}
376+
}
377+
const unknown = [...wanted].map(([project, tag]) => (tag ? `${project} ${tag}` : project));
378+
this.ignored = unknown.length ? `Ignored picks from the address that are not in this report: ${unknown.join(", ")}.` : "";
379+
this.writeHash();
380+
},
381+
writeHash() {
382+
const encode = (value) => encodeURIComponent(value).replaceAll("%2F", "/");
383+
const hash = this.picked.map(({ service, candidate }) => `${encode(service.project)}=${encode(candidate.tag.name)}`).join("&");
384+
history.replaceState(null, "", hash ? `#${hash}` : location.pathname + location.search);
333385
},
334386
pickOf(service) {
335387
return service.candidates.find((item) => item.tag.name === this.picks[service.project]);

0 commit comments

Comments
 (0)