Skip to content

Commit 42db8e3

Browse files
snomiaoclaudeCopilot
authored
feat: add release notes to frontend Slack notifications (#72)
* feat: add release notes to frontend Slack notifications - Include GitHub release body content in Slack messages - Format release notes with proper Slack markdown - Truncate long release notes (>500 chars) with "Read more" link - Update tests to cover release notes functionality This enhancement provides better context in Slack notifications by including the actual release notes from GitHub, making it easier for team members to understand what changed in each release. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> * Update app/tasks/gh-frontend-release-notification/index.ts Co-authored-by: Copilot <[email protected]> * feat: add release notes to frontend Slack notifications - Integrated release notes fetching for frontend releases - Enhanced Slack message formatting with release details - Updated message structure for better readability --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 63b3f50 commit 42db8e3

File tree

4 files changed

+105
-11
lines changed

4 files changed

+105
-11
lines changed

app/tasks/gh-desktop-release-notification/upsertSlackMessage.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
#!/usr/bin/env bun --hot
12
import { slack } from "@/src/slack";
23
import { getSlackChannel } from "@/src/slack/channels";
34
import KeyvSqlite from "@keyv/sqlite";
45
import DIE from "@snomiao/die";
56
import Keyv from "keyv";
67
import { slackMessageUrlParse, slackMessageUrlStringify } from "../gh-design/gh-design";
78
import { COMFY_PR_CACHE_DIR } from "./COMFY_PR_CACHE_DIR";
9+
import chalk from "chalk";
810

911
const SlackChannelIdsCache = new Keyv<string>({
1012
store: new KeyvSqlite("sqlite://" + COMFY_PR_CACHE_DIR + "/slackChannelIdCache.sqlite"),
@@ -49,7 +51,11 @@ export async function upsertSlackMessage({
4951
if (!channel) DIE(`No slack channel specified`);
5052

5153
if (!url) {
52-
if (process.env.DRY_RUN) throw new Error("sending slack message: " + JSON.stringify({ text, channel }));
54+
if (process.env.DRY_RUN) {
55+
console.error("DRY RUN MODE");
56+
console.error("sending text:", text);
57+
throw new Error(chalk.red("Sending slack message to: " + JSON.stringify({ channel })))
58+
};
5359
const thread_ts = !replyUrl ? undefined : slackMessageUrlParse(replyUrl).ts;
5460
const msg = !thread_ts
5561
? await slack.chat.postMessage({ text, channel })
@@ -58,7 +64,11 @@ export async function upsertSlackMessage({
5864
const url = slackMessageUrlStringify({ channel, ts: msg.ts! });
5965
return { ...msg, url, text, channel };
6066
}
61-
if (process.env.DRY_RUN) throw new Error("updating slack message: " + JSON.stringify({ text, channel, url }));
67+
if (process.env.DRY_RUN) {
68+
console.error("DRY RUN MODE");
69+
console.error("sending text:", text);
70+
throw new Error(chalk.red("Updating slack message to: " + JSON.stringify({ channel, url })))
71+
};
6272
const ts = slackMessageUrlParse(url).ts;
6373
const msg = await slack.chat.update({ text, channel, ts });
6474
return { ...msg, url, text, channel };

app/tasks/gh-frontend-release-notification/index.spec.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ describe("GithubFrontendReleaseNotificationTask", () => {
7777
version: mockRelease.tag_name,
7878
status: "stable",
7979
isStable: true,
80+
releaseNotes: mockRelease.body,
8081
createdAt: new Date(mockRelease.created_at),
8182
releasedAt: new Date(mockRelease.published_at),
8283
});
@@ -87,10 +88,11 @@ describe("GithubFrontendReleaseNotificationTask", () => {
8788
version: mockRelease.tag_name,
8889
status: "stable",
8990
isStable: true,
91+
releaseNotes: mockRelease.body,
9092
createdAt: new Date(mockRelease.created_at),
9193
releasedAt: new Date(mockRelease.published_at),
9294
slackMessage: {
93-
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.0> is stable!",
95+
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.0> is stable!\n\n*Release Notes:*\nRelease notes",
9496
channel: "test-channel-id",
9597
url: "https://slack.com/message/123",
9698
},
@@ -135,10 +137,11 @@ describe("GithubFrontendReleaseNotificationTask", () => {
135137
version: mockRelease.tag_name,
136138
status: "stable",
137139
isStable: true,
140+
releaseNotes: mockRelease.body,
138141
createdAt: new Date(mockRelease.created_at),
139142
releasedAt: new Date(mockRelease.published_at),
140143
slackMessage: {
141-
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.0> is stable!",
144+
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.0> is stable!\n\n*Release Notes:*\nRelease notes",
142145
channel: "test-channel-id",
143146
url: "https://slack.com/message/123",
144147
},
@@ -173,6 +176,7 @@ describe("GithubFrontendReleaseNotificationTask", () => {
173176
version: mockPrerelease.tag_name,
174177
status: "prerelease",
175178
isStable: false,
179+
releaseNotes: mockPrerelease.body,
176180
createdAt: new Date(mockPrerelease.created_at),
177181
releasedAt: new Date(mockPrerelease.published_at),
178182
});
@@ -183,10 +187,11 @@ describe("GithubFrontendReleaseNotificationTask", () => {
183187
version: mockPrerelease.tag_name,
184188
status: "prerelease",
185189
isStable: false,
190+
releaseNotes: mockPrerelease.body,
186191
createdAt: new Date(mockPrerelease.created_at),
187192
releasedAt: new Date(mockPrerelease.published_at),
188193
slackMessageDrafting: {
189-
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0-beta.1|Release v1.0.0-beta.1> is prerelease!",
194+
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0-beta.1|Release v1.0.0-beta.1> is prerelease!\n\n*Release Notes:*\nBeta release notes",
190195
channel: "test-channel-id",
191196
url: "https://slack.com/message/456",
192197
},
@@ -224,6 +229,7 @@ describe("GithubFrontendReleaseNotificationTask", () => {
224229
version: mockDraft.tag_name,
225230
status: "draft",
226231
isStable: false,
232+
releaseNotes: mockDraft.body,
227233
createdAt: new Date(mockDraft.created_at),
228234
releasedAt: undefined,
229235
});
@@ -265,6 +271,7 @@ describe("GithubFrontendReleaseNotificationTask", () => {
265271
version: oldRelease.tag_name,
266272
status: "stable",
267273
isStable: true,
274+
releaseNotes: oldRelease.body,
268275
createdAt: new Date(oldRelease.created_at),
269276
releasedAt: new Date(oldRelease.published_at),
270277
});
@@ -299,6 +306,7 @@ describe("GithubFrontendReleaseNotificationTask", () => {
299306
version: mockRelease.tag_name,
300307
status: "stable",
301308
isStable: true,
309+
releaseNotes: mockRelease.body,
302310
createdAt: new Date(mockRelease.created_at),
303311
releasedAt: new Date(mockRelease.published_at),
304312
slackMessage: {
@@ -314,8 +322,9 @@ describe("GithubFrontendReleaseNotificationTask", () => {
314322
version: mockRelease.tag_name,
315323
status: "stable",
316324
isStable: true,
325+
releaseNotes: mockRelease.body,
317326
slackMessage: {
318-
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.1> is stable!",
327+
text: "🎨 ComfyUI_frontend <https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0|Release v1.0.1> is stable!\n\n*Release Notes:*\nUpdated release notes",
319328
channel: "test-channel-id",
320329
url: "https://slack.com/message/123",
321330
},
@@ -331,6 +340,59 @@ describe("GithubFrontendReleaseNotificationTask", () => {
331340
}),
332341
);
333342
});
343+
344+
it("should truncate long release notes in Slack message", async () => {
345+
const longReleaseNotes = "x".repeat(600); // Create a 600 character string
346+
const mockRelease = {
347+
html_url: "https://github.com/Comfy-Org/ComfyUI_frontend/releases/tag/v1.0.0",
348+
tag_name: "v1.0.0",
349+
draft: false,
350+
prerelease: false,
351+
created_at: new Date().toISOString(),
352+
published_at: new Date().toISOString(),
353+
body: longReleaseNotes,
354+
};
355+
356+
mockGh.repos = {
357+
listReleases: jest.fn().mockResolvedValue({
358+
data: [mockRelease],
359+
}),
360+
} as any;
361+
362+
// First call - save initial data
363+
collection.findOneAndUpdate.mockResolvedValueOnce({
364+
url: mockRelease.html_url,
365+
version: mockRelease.tag_name,
366+
status: "stable",
367+
isStable: true,
368+
releaseNotes: longReleaseNotes,
369+
createdAt: new Date(mockRelease.created_at),
370+
releasedAt: new Date(mockRelease.published_at),
371+
});
372+
373+
// Second call - save with truncated message
374+
collection.findOneAndUpdate.mockResolvedValueOnce({
375+
url: mockRelease.html_url,
376+
version: mockRelease.tag_name,
377+
status: "stable",
378+
isStable: true,
379+
releaseNotes: longReleaseNotes,
380+
slackMessage: {
381+
text: expect.stringContaining("... <"),
382+
channel: "test-channel-id",
383+
url: "https://slack.com/message/123",
384+
},
385+
});
386+
387+
await runGithubFrontendReleaseNotificationTask();
388+
389+
// Check that the message was truncated and includes a "Read more" link
390+
expect(upsertSlackMessage).toHaveBeenCalledWith(
391+
expect.objectContaining({
392+
text: expect.stringMatching(/\.\.\. <.*\|Read more>/),
393+
}),
394+
);
395+
});
334396
});
335397

336398
describe("Database Index", () => {

app/tasks/gh-frontend-release-notification/index.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#!/usr/bin/env bun --hot
12
import { db } from "@/src/db";
23
import { gh } from "@/src/gh";
34
import { parseGithubRepoUrl } from "@/src/parseOwnerRepo";
@@ -31,6 +32,7 @@ export type GithubFrontendReleaseNotificationTask = {
3132
releasedAt?: Date;
3233
isStable?: boolean;
3334
status: "draft" | "prerelease" | "stable";
35+
releaseNotes?: string;
3436

3537
slackMessageDrafting?: {
3638
text: string;
@@ -81,23 +83,35 @@ async function runGithubFrontendReleaseNotificationTask() {
8183
const url = release.html_url;
8284
const status = release.draft ? "draft" : release.prerelease ? "prerelease" : "stable";
8385

86+
// Extract release notes from body
87+
const releaseNotes = release.body ?? "";
88+
8489
let task = await save({
8590
url,
8691
status: status,
8792
isStable: status == "stable",
8893
version: release.tag_name,
94+
releaseNotes: releaseNotes,
8995
createdAt: new Date(release.created_at || DIE("no created_at in release, " + JSON.stringify(release))),
9096
releasedAt: !release.published_at ? undefined : new Date(release.published_at),
9197
});
9298

9399
if (+task.createdAt! < +new Date(config.sendSince)) return task;
94100

101+
// Format release notes for Slack (not truncate, slack will fold automatically)
102+
const formattedReleaseNotes = task.isStable ? releaseNotes || "" : "";
103+
95104
const newSlackMessageText = config.slackMessage
96105
.replace("{url}", task.url)
97-
.replace("{repo}", parseGithubUrl(task.url)?.repo || DIE(`unable parse REPO from URL ${task.url}`))
98-
.replace("{version}", task.version || DIE(`unable to parse version from task ${JSON.stringify(task)}`))
99-
.replace("{status}", task.status);
100-
106+
.replace("{repo}", parseGithubUrl(task.url)?.repo || DIE(`Unable to parse REPO from URL ${task.url}`))
107+
.replace("{version}", task.version || DIE(`Unable to parse version from task ${JSON.stringify(task)}`))
108+
.replace("{status}", task.status)
109+
.replace(/$/, "\n" + formattedReleaseNotes)
110+
.replace(/(.*) in (https:\/\/\S*)$/gm, "<$2|$1>") // linkify URLs at the end of lines;
111+
.replace(/^([\s\S]{1800}.*)\r?\n[\s\S]*?(.*[\s\S]{1800})$/, "$1\n...TRUNCATED...\n$2") // truncate to 4000 characters, slack limit is 40000 but be safe
112+
.replace('**Full Changelog**', 'Full Changelog');
113+
114+
console.log(newSlackMessageText);
101115
const shouldSendDraftingMessage = !task.isStable;
102116
const draftingTextChanged =
103117
!task.slackMessageDrafting?.text || task.slackMessageDrafting.text.trim() !== newSlackMessageText.trim();
@@ -108,6 +122,9 @@ async function runGithubFrontendReleaseNotificationTask() {
108122
channelName: config.slackChannelName,
109123
text: newSlackMessageText,
110124
url: task.slackMessageDrafting?.url,
125+
}).catch((e) => {
126+
console.error("Failed to send draft slack message for release", task.url, e);
127+
throw e;
111128
}),
112129
});
113130
}
@@ -123,10 +140,12 @@ async function runGithubFrontendReleaseNotificationTask() {
123140
text: newSlackMessageText,
124141
url: task.slackMessage?.url,
125142
replyUrl: task.slackMessageDrafting?.url,
143+
}).catch((e) => {
144+
console.error("Failed to send slack message for release", task.url, JSON.stringify(e));
145+
throw e;
126146
}),
127147
});
128148
}
129-
130149
return task;
131150
})
132151
.log()

src/updateSlackMessages.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import pMap from "p-map";
44
import { postSlackMessage } from "./postSlackMessage";
55
import { SlackMsgs } from "./slack/SlackMsgs";
66

7+
/**
8+
* @deprecated use upsertSlackMessage instead
9+
*/
710
export async function updateSlackMessages() {
811
// send
912
return await pMap(

0 commit comments

Comments
 (0)