Skip to content

Commit 14efc85

Browse files
comfy-pr-botsnomiaoclaude
authored
refactor: improve gh-design task logging and configuration (#127)
- Update time logger to use space-based depth instead of slashes for better readability - Comment out unused SLACK_MSG_URL_TEMPLATE in default config - Refactor gh-design task to use inline constants instead of database config - Add shebang for bun watch mode support - Integrate upsertSlackMessage utility for Slack notifications - Switch to ghPaged helper for GitHub API pagination - Improve code documentation and structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: snomiao <snomiao@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent ecd80c9 commit 14efc85

3 files changed

Lines changed: 80 additions & 90 deletions

File tree

app/tasks/gh-design/createTimeLogger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import prettyMs from 'pretty-ms';
22

33
export const createTimeLogger = (st = +new Date()) => (...args: any[]) => {
44
const depth = new Error().stack?.split('\n').length ?? 0
5-
console.log(prettyMs(+new Date() - st).padStart(6, ' ') + '/'.repeat(depth), ...args)
5+
console.log(prettyMs(+new Date() - st).padStart(6, ' ') + ' '.repeat(depth/2|0), ...args)
66
};

app/tasks/gh-design/default-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const SLACK_CHANNEL_NAME = "product"; // Slack channel to notify about design it
55
// check all url = V/tasks/gh-design
66
// const CHECKALL_URL = process.env.VERCEL_PRODUCTION_URL || "https://comfy-pr.vercel.app";
77
const SLACK_MESSAGE_TEMPLATE = `🎨 *New Design {{ITEM_TYPE}}*: <{{URL}}|{{TITLE}}>`;
8-
const SLACK_MSG_URL_TEMPLATE = `https://comfy-organization.slack.com/archives/{{CHANNEL_ID}}/p{{TSNODOT}}`;
8+
// const SLACK_MSG_URL_TEMPLATE = `https://comfy-organization.slack.com/archives/{{CHANNEL_ID}}/p{{TSNODOT}}`;
99
const REQUEST_REVIEWERS = ["PabloWiedemann"];
1010
const REPOS_TO_SCAN_URLS = [
1111
"https://github.com/Comfy-Org/ComfyUI_frontend",
@@ -17,7 +17,7 @@ export const ghDesignDefaultConfig = {
1717
MATCH_LABEL,
1818
SLACK_CHANNEL_NAME,
1919
SLACK_MESSAGE_TEMPLATE,
20-
SLACK_MSG_URL_TEMPLATE,
20+
// SLACK_MSG_URL_TEMPLATE,
2121
REQUEST_REVIEWERS,
2222
REPOS_TO_SCAN_URLS,
2323
};

app/tasks/gh-design/gh-design.ts

Lines changed: 77 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#!/usr/bin/env bun --watch
12
import { db } from "@/src/db";
23
import { TaskMetaCollection } from "@/src/db/TaskMeta";
34
import { gh } from "@/src/gh";
@@ -13,23 +14,46 @@ import { z } from "zod";
1314
import { createTimeLogger } from "./createTimeLogger";
1415
import { ghDesignDefaultConfig } from "./default-config";
1516
import { slackMessageUrlParse, slackMessageUrlStringify } from "./slackMessageUrlParse";
17+
import { upsertSlackMessage } from "../gh-desktop-release-notification/upsertSlackMessage";
18+
import { ghPaged } from "@/src/paged";
1619
const tlog = createTimeLogger();
17-
// Task bot to scan for [Design] labels on PRs and issues and send notifications to product channel
20+
21+
/**
22+
* Github Design Task
23+
* -----------------------
24+
* Task bot to scan for [Design] labels on PRs and issues and send notifications to product channel
25+
* 1. scan specified repos for issues/PRs with [Design] label
26+
* 2. send Slack notification to #product channel
27+
* 3. request review from specified reviewers for PRs
28+
* 4. track open/closed/merged/approved status
29+
* 5. store processed items in database to avoid duplicates
30+
*/
31+
32+
// 1. scan these repos
33+
const REPOURLS = [
34+
'https://github.com/Comfy-Org/ComfyUI_frontend',
35+
'https://github.com/Comfy-Org/desktop',
36+
];
37+
38+
// 2. match these labels
39+
const MATCH_LABELS = ['Design'];
40+
41+
// 3.1 request review from these users
42+
const REQUEST_REVIEWERS = ["PabloWiedemann"];
43+
44+
// 3.2 notify to this slack channel
45+
const CHANNEL_NAME = 'product-design'
46+
const SLACK_MESSAGE_TEMPLATE = `🎨 *New Design {{ITEM_TYPE}}*: {{STATE}} <{{URL}}|{{TITLE}}> {{COMMENTS}} by {{GITHUBUSER}}`;
1847

1948
// Schema for GithubDesignTaskMeta validation
2049
export const githubDesignTaskMetaSchema = z.object({
2150
name: z.string().optional(),
2251
description: z.string().optional(),
2352

24-
// task config
25-
slackChannelName: z.string().optional(),
26-
slackMessageTemplate: z.string().optional(),
27-
repoUrls: z.array(z.string().url()).optional(),
28-
requestReviewers: z.array(z.string()).optional(),
29-
matchLabels: z.string().optional(),
53+
// task config, able to update via site web page
54+
// slackMessageTemplate: z.string().optional(),
3055

3156
// cache
32-
slackChannelId: z.string().optional(),
3357
lastRunAt: z.date().optional(),
3458
lastStatus: z.enum(["success", "error", "running"]).optional(),
3559
lastError: z.string().optional(),
@@ -107,93 +131,64 @@ export async function runGithubDesignTask() {
107131
description:
108132
"Task to scan for [Design] labeled issues and PRs in specified repositories and notify product channel",
109133
// Set defaults if not already set
110-
slackChannelId: "",
111134
//
112135
lastRunAt: new Date(),
113136
lastStatus: "running",
114137
lastError: "",
115138
});
116139

117-
// save default values if not set
118-
if (!meta.slackMessageTemplate)
119-
meta = await GithubDesignTaskMeta.$upsert({ slackMessageTemplate: ghDesignDefaultConfig.SLACK_MESSAGE_TEMPLATE });
120-
if (!meta.requestReviewers)
121-
meta = await GithubDesignTaskMeta.$upsert({ requestReviewers: ghDesignDefaultConfig.REQUEST_REVIEWERS });
122-
if (!meta.repoUrls) meta = await GithubDesignTaskMeta.$upsert({ repoUrls: ghDesignDefaultConfig.REPOS_TO_SCAN_URLS });
123-
if (!meta.matchLabels) meta = await GithubDesignTaskMeta.$upsert({ matchLabels: ghDesignDefaultConfig.MATCH_LABEL });
124-
if (!meta.slackChannelName)
125-
meta = await GithubDesignTaskMeta.$upsert({ slackChannelName: ghDesignDefaultConfig.SLACK_CHANNEL_NAME });
126-
if (!meta.slackChannelId) {
127-
tlog("Fetching Slack product channel...");
128-
meta = await GithubDesignTaskMeta.$upsert({
129-
slackChannelId: (await getSlackChannel(meta.slackChannelName!)).id,
130-
});
131-
}
132-
133140
tlog("TaskMeta: " + JSON.stringify(meta));
134-
135-
const channel = meta.slackChannelId || DIE("Missing Slack channel ID");
136-
tlog(`Slack channel: ${meta.slackChannelName} (${channel})`);
141+
tlog(`Slack channel: ${CHANNEL_NAME}`);
137142

138143
// Get configuration from meta or use defaults
139-
const slackMessageTemplate = meta.slackMessageTemplate || DIE("Missing Slack message template");
140-
const designItemsFlow = await sflow(meta.repoUrls || DIE("Missing repo URLs"))
141-
.map((e) => parseGithubRepoUrl(e))
142-
.pMap(
143-
async ({ owner, repo }) =>
144-
pageFlow(1, async (page) => {
145-
const per_page = 100;
146-
// will list both issues and PRs
147-
const { data: issues } = await gh.issues.listForRepo({
148-
owner,
149-
repo,
150-
page,
151-
per_page,
152-
labels: meta.matchLabels || DIE("missing match label"), // comma-separated list of labels
153-
state: "open", // scan only opened issues/PRs
154-
});
155-
tlog(`Found ${issues.length} Design labeled items in ${owner}/${repo}`);
156-
return { data: issues, next: issues.length >= per_page ? page + 1 : undefined };
157-
})
158-
.filter((e) => e.length)
159-
.flat()
160-
.map((item) => ({
161-
url: item.html_url,
162-
title: item.title,
163-
body: item.body,
164-
user: item.user?.login,
165-
type: item.pull_request ? ("pull_request" as const) : ("issue" as const),
166-
state: item.pull_request?.merged_at ? ("merged" as const) : (item.state as "open" | "closed"),
167-
stateAt: item.pull_request?.merged_at || item.closed_at || item.created_at,
168-
labels: item.labels.flatMap((e) => (typeof e === "string" ? [] : [e])).map((l) => l.name),
169-
comments: item.comments,
170-
})),
171-
{ concurrency: 3 },
172-
) // concurrency 3 repos
173-
.confluenceByConcat() // merge page flows
174-
.map(async function process(item) {
144+
// const slackMessageTemplate = meta.slackMessageTemplate || DIE("Missing Slack message template");
145+
// console.log("Using Slack message template:", JSON.stringify(slackMessageTemplate));
146+
147+
// Start processing design items
148+
const designItemsFlow = await sflow(REPOURLS)
149+
.map((url) =>
150+
ghPaged(gh.issues.listForRepo)({
151+
...parseGithubRepoUrl(url),
152+
labels: MATCH_LABELS.join(','), // comma-separated list of labels
153+
state: "open", // scan only opened issues/PRs
154+
})
155+
)
156+
.confluenceByParallel() // merge page flows
157+
// simplify issue items
158+
.map((issue) => ({
159+
url: issue.html_url,
160+
title: issue.title,
161+
body: issue.body,
162+
user: issue.user?.login,
163+
type: issue.pull_request ? ("pull_request" as const) : ("issue" as const),
164+
state: issue.pull_request?.merged_at ? ("merged" as const) : (issue.state as "open" | "closed"),
165+
stateAt: issue.pull_request?.merged_at || issue.closed_at || issue.created_at,
166+
labels: issue.labels.flatMap((e) => (typeof e === "string" ? [] : [e])).map((l) => l.name),
167+
comments: issue.comments,
168+
}))
169+
.map(async function processIssueItems(issueInfo) {
175170
tlog(
176-
`PROCESSING ${item.url}#${item.title.replace(/\s+/g, "+")}_${item.body?.slice(0, 20).replaceAll(/\s+/g, "+")}`,
171+
`PROCESSING ${issueInfo.url} #${issueInfo.title.replace(/\s+/g, "+")} ${issueInfo.body?.slice(0, 20).replaceAll(/\s+/g, "+")}`,
177172
);
178-
const url = item.url;
173+
const url = issueInfo.url;
179174
const { owner, repo, issue_number } = parseIssueUrl(url);
180175
// create task
181176
let task = await saveGithubDesignTask(url, {
182-
type: item.type,
183-
state: item.state,
184-
stateAt: new Date(item.stateAt),
185-
title: item.title,
186-
user: item.user || "?",
187-
comments: item.comments || 0,
188-
bodyHash: item.body ? sha256(item.body) : undefined,
177+
type: issueInfo.type, // issue or pull_request
178+
state: issueInfo.state,
179+
stateAt: new Date(issueInfo.stateAt),
180+
title: issueInfo.title,
181+
user: issueInfo.user || "?",
182+
comments: issueInfo.comments || 0,
183+
bodyHash: issueInfo.body ? sha256(issueInfo.body) : undefined,
189184
lastRunAt: new Date(),
190185
taskStatus: "pending",
191186
lastDoneAt: null, // reset lastDoneAt
192187
});
193188

194189
if (task.state === "open") {
195-
if (task.type === "pull_request" && meta.requestReviewers?.some((e) => !task.reviewers?.includes(e))) {
196-
const requestReviewers = meta.requestReviewers ?? DIE("Missing request reviewers");
190+
if (task.type === "pull_request" && REQUEST_REVIEWERS.some((e) => !task.reviewers?.includes(e))) {
191+
const requestReviewers = REQUEST_REVIEWERS;
197192
const newReviewers = requestReviewers.filter((e) => !task.reviewers?.includes(e));
198193
tlog(`Requesting reviewers: ${newReviewers.join(", ")}`);
199194
if (!dryRun) {
@@ -207,7 +202,8 @@ export async function runGithubDesignTask() {
207202
}
208203
}
209204

210-
const text = (meta.slackMessageTemplate || DIE("Missing Slack message template"))
205+
const text = SLACK_MESSAGE_TEMPLATE
206+
// (meta.slackMessageTemplate || DIE("Missing Slack message template"))
211207
.replace("{{COMMENTS}}", task.comments?.toString().replace(/^(.*)$/, "[r$1]") ?? "")
212208
.replace("{{STATE}}", task.state.toUpperCase())
213209
.replace("{{USERNAME}}", task.user ?? "=??=")
@@ -221,11 +217,7 @@ export async function runGithubDesignTask() {
221217
if (!task.slackUrl) {
222218
tlog(`Sending Slack Notification for design task: ${task.url} (${task.type})`);
223219
if (!dryRun) {
224-
const slack = getSlack();
225-
const msg = await slack.chat.postMessage({
226-
channel,
227-
text,
228-
});
220+
const msg = await upsertSlackMessage({ channelName: CHANNEL_NAME, text, });
229221
if (!msg.ok) {
230222
await saveGithubDesignTask(url, {
231223
error: `Failed to send Slack message: ${msg.error}`,
@@ -234,21 +226,19 @@ export async function runGithubDesignTask() {
234226
throw new Error(`Failed to send Slack message: ${msg.error}`);
235227
}
236228
task = await saveGithubDesignTask(url, {
237-
slackUrl: slackMessageUrlStringify({ channel, ts: msg.ts! }),
229+
slackUrl: slackMessageUrlStringify({ channel: msg.channel, ts: msg.ts! }),
238230
slackSentAt: new Date(),
239231
slackMsgHash,
240232
});
241233
tlog(`Slack message sent: ${task.slackUrl}`);
242234
}
243235
} else {
244-
// update slack message if outdated
236+
// update slack message if its outdated
245237
if (task.slackMsgHash !== slackMsgHash) {
246238
tlog(`Updating Slack message for task: ${task.url}`);
247239
if (!dryRun) {
248-
const slack = getSlack();
249-
await slack.chat.update({
250-
...slackMessageUrlParse(task.slackUrl),
251-
text,
240+
await upsertSlackMessage({
241+
...slackMessageUrlParse(task.slackUrl), text,
252242
});
253243
task = await saveGithubDesignTask(url, { slackMsgHash });
254244
tlog(`Slack message updated: ${task.slackUrl}`);

0 commit comments

Comments
 (0)