@@ -9,6 +9,7 @@ import { parseIssueUrl } from "@/src/parseIssueUrl";
9
9
import { parseGithubRepoUrl } from "@/src/parseOwnerRepo" ;
10
10
import DIE from "@snomiao/die" ;
11
11
import chalk from "chalk" ;
12
+ import isCI from "is-ci" ;
12
13
import sflow , { pageFlow } from "sflow" ;
13
14
import { P } from "ts-pattern" ;
14
15
import z from "zod" ;
@@ -110,169 +111,167 @@ const saveTask = async (pr: Partial<ComfyCorePRs> & { url: string }) => {
110
111
if ( import . meta. main ) {
111
112
// Designed to be mon to sat, TIME CHECKING
112
113
// Pacific Daylight Time
113
-
114
+ await runCorePingTask ( ) ;
115
+ if ( isCI ) {
116
+ await db . close ( ) ;
117
+ process . exit ( 0 ) ;
118
+ }
119
+ }
120
+ async function runCorePingTask ( ) {
114
121
// drop everytime since outdated data is useless, we kept lastSlackmessage in Meta collection which is enough
115
122
await ComfyCorePRs . drop ( ) ;
116
123
117
124
console . log ( "start" , import . meta. file ) ;
118
125
let freshCount = 0 ;
119
126
120
- await sflow ( coreReviewTrackerConfig . REPOLIST )
121
- . flatMap ( ( repoUrl ) => [
122
- // handle opening pr and it's comments
127
+ const processedTasks = await sflow ( coreReviewTrackerConfig . REPOLIST )
128
+ . map ( ( repoUrl ) =>
123
129
pageFlow ( 1 , async ( page , per_page = 100 ) => {
124
130
const { data } = await ghc . pulls . list ( { ...parseGithubRepoUrl ( repoUrl ) , page, per_page, state : "open" } ) ;
125
131
return { data, next : data . length >= per_page ? page + 1 : null } ;
126
- } )
127
- . flat ( )
128
-
129
- . map ( async ( pr ) => {
130
- const html_url = pr . html_url ;
131
- const corePrLabel = pr . labels . find ( ( e ) =>
132
- tsmatch ( e )
133
- . with ( { name : P . union ( "Core" , "Core-Important" ) } , ( l ) => l )
134
- . otherwise ( ( ) => null ) ,
135
- ) ;
136
- let task = await saveTask ( {
137
- url : pr . html_url ,
138
- title : pr . title ,
139
- created_at : new Date ( pr . created_at ) ,
140
- labels : pr . labels . map ( ( e ) => e . name ) ,
141
- } ) ;
132
+ } ) . flat ( ) ,
133
+ )
134
+ . confluenceByConcat ( )
135
+ . map ( async ( pr ) => {
136
+ const html_url = pr . html_url ;
137
+ const corePrLabel = pr . labels . find ( ( e ) =>
138
+ tsmatch ( e )
139
+ . with ( { name : P . union ( "Core" , "Core-Important" ) } , ( l ) => l )
140
+ . otherwise ( ( ) => null ) ,
141
+ ) ;
142
+ let task = await saveTask ( {
143
+ url : pr . html_url ,
144
+ title : pr . title ,
145
+ created_at : new Date ( pr . created_at ) ,
146
+ labels : pr . labels . map ( ( e ) => e . name ) ,
147
+ } ) ;
142
148
143
- if ( ! corePrLabel ) return saveTask ( { url : html_url , status : "unrelated" } ) ;
144
- if ( pr . state === "closed" ) return saveTask ( { url : html_url , status : "closed" } ) ;
145
- if ( pr . draft ) return saveTask ( { url : html_url , status : "unrelated" , statusMsg : "Draft PR, skipping" } ) ;
149
+ if ( ! corePrLabel ) return saveTask ( { url : html_url , status : "unrelated" } ) ;
150
+ if ( pr . state === "closed" ) return saveTask ( { url : html_url , status : "closed" } ) ;
151
+ if ( pr . draft ) return saveTask ( { url : html_url , status : "unrelated" , statusMsg : "Draft PR, skipping" } ) ;
146
152
147
- // check timeline events
148
- const timeline = await fetchFullTimeline ( html_url ) ;
153
+ // check timeline events
154
+ const timeline = await fetchFullTimeline ( html_url ) ;
149
155
150
- // Check recent events
151
- const lastLabelEvent =
152
- timeline
153
- . map ( ( e ) =>
154
- tsmatch ( e )
155
- . with ( { label : { name : corePrLabel . name } } , ( e ) => e )
156
- . otherwise ( ( ) => null ) ,
157
- )
158
- . findLast ( Boolean ) || DIE ( `No ${ corePrLabel . name } label event found` ) ;
156
+ // Check recent events
157
+ const lastLabelEvent =
158
+ timeline
159
+ . map ( ( e ) =>
160
+ tsmatch ( e )
161
+ . with ( { label : { name : corePrLabel . name } } , ( e ) => e )
162
+ . otherwise ( ( ) => null ) ,
163
+ )
164
+ . findLast ( Boolean ) || DIE ( `No ${ corePrLabel . name } label event found` ) ;
159
165
160
- task = await saveTask ( { url : pr . html_url , last_labeled_at : new Date ( lastLabelEvent . created_at ) } ) ;
161
- const lastReviewEvent =
162
- timeline
163
- . map ( ( e ) =>
164
- tsmatch ( e )
165
- . with (
166
- {
167
- event : "reviewed" ,
168
- author_association : P . union ( "COLLABORATOR" , "MEMBER" , "OWNER" ) ,
169
- submitted_at : P . string ,
170
- } ,
171
- ( e ) => e as GH [ "timeline-reviewed-event" ] ,
172
- )
173
- . otherwise ( ( ) => null ) ,
166
+ task = await saveTask ( { url : pr . html_url , last_labeled_at : new Date ( lastLabelEvent . created_at ) } ) ;
167
+ const lastReviewEvent =
168
+ timeline
169
+ . map ( ( e ) =>
170
+ tsmatch ( e )
171
+ . with (
172
+ {
173
+ event : "reviewed" ,
174
+ author_association : P . union ( "COLLABORATOR" , "MEMBER" , "OWNER" ) ,
175
+ submitted_at : P . string ,
176
+ } ,
177
+ ( e ) => e as GH [ "timeline-reviewed-event" ] ,
174
178
)
175
- . filter ( ( e ) => e ?. submitted_at ) // ignore pending reviews
176
- . findLast ( Boolean ) || null ;
177
- if ( lastReviewEvent )
178
- task = await saveTask ( { url : pr . html_url , last_reviewed_at : new Date ( lastReviewEvent . submitted_at ! ) } ) ;
179
+ . otherwise ( ( ) => null ) ,
180
+ )
181
+ . filter ( ( e ) => e ?. submitted_at ) // ignore pending reviews
182
+ . findLast ( Boolean ) || null ;
183
+ if ( lastReviewEvent )
184
+ task = await saveTask ( { url : pr . html_url , last_reviewed_at : new Date ( lastReviewEvent . submitted_at ! ) } ) ;
179
185
180
- const lastCommentEvent =
181
- timeline
182
- . map ( ( e ) =>
183
- tsmatch ( e )
184
- . with (
185
- { event : "commented" , author_association : P . union ( "COLLABORATOR" , "MEMBER" , "OWNER" ) } ,
186
- ( e ) => e as GH [ "timeline-comment-event" ] ,
187
- )
188
- . otherwise ( ( ) => null ) ,
186
+ const lastCommentEvent =
187
+ timeline
188
+ . map ( ( e ) =>
189
+ tsmatch ( e )
190
+ . with (
191
+ { event : "commented" , author_association : P . union ( "COLLABORATOR" , "MEMBER" , "OWNER" ) } ,
192
+ ( e ) => e as GH [ "timeline-comment-event" ] ,
189
193
)
190
- . findLast ( Boolean ) || null ;
191
- if ( lastCommentEvent )
192
- task = await saveTask ( { url : pr . html_url , last_reviewed_at : new Date ( lastCommentEvent . created_at ) } ) ;
193
-
194
- //
195
- lastReviewEvent && console . log ( { lastLabelEvent, lastReviewEvent } ) ;
196
- const isReviewed = task ?. last_reviewed_at && + task . last_reviewed_at > + task . last_labeled_at ! ;
197
- const isCommented = task ?. last_commented_at && + task . last_commented_at > + task . last_labeled_at ! ;
198
-
199
- const createdAt = new Date ( lastLabelEvent . created_at ) ;
200
- const now = new Date ( ) ;
201
- const diff = now . getTime ( ) - createdAt . getTime ( ) ;
202
- const isFresh = diff <= 24 * 60 * 60 * 1000 ;
194
+ . otherwise ( ( ) => null ) ,
195
+ )
196
+ . findLast ( Boolean ) || null ;
197
+ if ( lastCommentEvent )
198
+ task = await saveTask ( { url : pr . html_url , last_reviewed_at : new Date ( lastCommentEvent . created_at ) } ) ;
203
199
204
- const status = isReviewed ? "reviewed" : isCommented ? "reviewed" : isFresh ? "fresh" : "stale" ;
200
+ //
201
+ lastReviewEvent && console . log ( { lastLabelEvent, lastReviewEvent } ) ;
202
+ const isReviewed = task ?. last_reviewed_at && + task . last_reviewed_at > + task . last_labeled_at ! ;
203
+ const isCommented = task ?. last_commented_at && + task . last_commented_at > + task . last_labeled_at ! ;
205
204
206
- const hours = Math . floor ( diff / ( 60 * 60 * 1000 ) ) ;
207
- const sanitizedTitle = pr . title . replace ( / \W + / g, " " ) . trim ( ) ;
208
- const statusMsg = `@${ pr . user ?. login } 's ${ corePrLabel . name } PR <${ pr . html_url } |${ sanitizedTitle } > is waiting for your feedback for more than ${ hours } hours.` ;
209
- console . log ( statusMsg ) ;
210
- console . log ( pr . html_url + " " + pr . labels . map ( ( e ) => e . name ) ) ;
205
+ const createdAt = new Date ( lastLabelEvent . created_at ) ;
206
+ const now = new Date ( ) ;
207
+ const diff = now . getTime ( ) - createdAt . getTime ( ) ;
208
+ const isFresh = diff <= 24 * 60 * 60 * 1000 ;
211
209
212
- return await saveTask ( { url : html_url , status, statusMsg } ) ;
213
- } ) ,
210
+ const status = isReviewed ? "reviewed" : isCommented ? "reviewed" : isFresh ? "fresh" : "stale" ;
214
211
215
- // // handle issue comments, (also including pr comments)
216
- // pageFlow(1, async (page, per_page = 100) => {
217
- // const { data } = await ghc.issues.listCommentsForRepo({ ...parseGithubRepoUrl(repoUrl), page, per_page }) ;
218
- // return { data, next: data.length >= per_page ? page + 1 : null } ;
219
- // }).flat(),
212
+ const hours = Math . floor ( diff / ( 60 * 60 * 1000 ) ) ;
213
+ const sanitizedTitle = pr . title . replace ( / \W + / g , " " ) . trim ( ) ;
214
+ const statusMsg = `@ ${ pr . user ?. login } 's ${ corePrLabel . name } PR < ${ pr . html_url } | ${ sanitizedTitle } > is waiting for your feedback for more than ${ hours } hours.` ;
215
+ console . log ( statusMsg ) ;
216
+ console . log ( pr . html_url + " " + pr . labels . map ( ( e ) => e . name ) ) ;
220
217
221
- // // handle pr review comments
222
- // pageFlow(1, async (page, per_page = 100) => {
223
- // const { data } = await ghc.pulls.listReviewCommentsForRepo({ ...parseGithubRepoUrl(repoUrl), page, per_page });
224
- // return { data, next: data.length >= per_page ? page + 1 : null };
225
- // }).flat(),
226
- ] )
227
- . confluenceByConcat ( )
228
- . map ( ( pr ) => {
229
- // console.log(e);
230
- // e.body;
218
+ return await saveTask ( { url : html_url , status, statusMsg } ) ;
231
219
} )
232
- . run ( ) ;
220
+ . toArray ( ) ;
233
221
234
- const corePRs = await ComfyCorePRs . find ( { } ) . sort ( { last_labeled_at : 1 } ) . toArray ( ) ;
222
+ // processedTasks
223
+ const corePRs = await ComfyCorePRs . find ( {
224
+ status : { $in : [ "fresh" , "stale" ] } ,
225
+ } )
226
+ . sort ( { last_labeled_at : 1 } )
227
+ . toArray ( ) ;
235
228
236
229
console . log ( "ready to send slack message to notify @comfy" ) ;
237
- const staleCorePRs = corePRs . filter ( ( pr ) => pr . status === "stale" ) ;
238
- const staleCorePRsMessage = staleCorePRs
239
- . map ( ( pr ) => pr . statusMsg || `- <${ pr . url } |${ pr . title } > ${ pr . labels } ` )
240
- . join ( "\n" ) ;
241
- const freshCorePRs = corePRs . filter ( ( pr ) => pr . status === "fresh" ) ;
242
230
243
- const freshMsg = ! freshCorePRs . length
244
- ? ""
245
- : `and there are ${ freshCorePRs . length } more fresh Core/Core-Important PRs.\n` ;
246
- const notifyMessage = `Hey <@comfy>, Here's x${ staleCorePRs . length } Core/Important PRs waiting your feedback!\n\n${ staleCorePRsMessage } \n${ freshMsg } \nSent from CorePing.ts by <@snomiao> cc <@yoland>` ;
247
- console . log ( chalk . bgBlue ( notifyMessage ) ) ;
231
+ const notifyMessage = ! corePRs . length
232
+ ? `Congratulations! All Core/Important PRs are reviewed! 🎉🎉🎉 \nSent from CorePing.ts by <@snomiao> cc <@Yoland>`
233
+ : `Hey <@comfy>, Here's x${ corePRs . length } Core/Important PRs waiting your feedback!\n\n${ corePRs . map ( ( pr ) => pr . statusMsg || `- <${ pr . url } |${ pr . title } > ${ pr . labels } ` ) . join ( "\n" ) } \nSent from CorePing.ts by <@snomiao> cc <@Yoland>` ;
248
234
235
+ console . log ( chalk . bgBlue ( notifyMessage ) ) ;
249
236
// TODO: update message with delete line when it's reviewed
250
237
// send or update slack message
251
238
let meta = await Meta . $upsert ( { } ) ;
252
- // if <24 h since last sent (not edit), update that msg
253
- if (
239
+
240
+ // can only post new message: tz: PST, day: working day + sat, time: 10-12am
241
+ const canPostNewMessage = ( ( ) => {
242
+ const now = new Date ( ) ;
243
+ const pstTime = new Date ( now . toLocaleString ( "en-US" , { timeZone : "America/Los_Angeles" } ) ) ;
244
+ const day = pstTime . getDay ( ) ; // 0=Sunday, 1=Monday, ..., 6=Saturday
245
+ const hour = pstTime . getHours ( ) ;
246
+
247
+ // Working days (Mon-Fri) + Saturday, but not Sunday
248
+ const isValidDay = day >= 1 && day <= 6 ;
249
+ // 10am-12pm PST (10-11:59)
250
+ const isValidTime = hour >= 10 && hour < 12 ;
251
+
252
+ return isValidDay && isValidTime ;
253
+ } ) ( ) ;
254
+
255
+ const canUpdateExistingMessage =
254
256
meta . lastSlackMessage &&
255
257
meta . lastSlackMessage . sendAt &&
256
- new Date ( ) . getTime ( ) - new Date ( meta . lastSlackMessage . sendAt ) . getTime ( ) < 23.9 * 60 * 60 * 1000
257
- ) {
258
- const msg = await upsertSlackMessage ( {
259
- text : notifyMessage ,
260
- channelName : cfg . slackChannelName ,
261
- url : meta . lastSlackMessage . url ,
262
- } ) ;
263
- meta = await Meta . $upsert ( { lastSlackMessage : { text : msg . text , url : msg . url , sendAt : new Date ( ) } } ) ;
264
- } else {
265
- // if >24h or not exist, post a new msg
266
- const msg = await upsertSlackMessage ( {
267
- text : notifyMessage ,
268
- channelName : cfg . slackChannelName ,
269
- } ) ;
270
- meta = await Meta . $upsert ( { lastSlackMessage : { text : msg . text , url : msg . url , sendAt : new Date ( ) } } ) ;
271
- }
258
+ new Date ( ) . getTime ( ) - new Date ( meta . lastSlackMessage . sendAt ) . getTime ( ) <= 23.9 * 60 * 60 * 1000 ;
259
+
260
+ // if <24 h since last sent (not edit), update that msg
261
+ const msgUpdateUrl = canUpdateExistingMessage && ! canPostNewMessage ? meta . lastSlackMessage ?. url : undefined ;
262
+
263
+ // DIE("check " + JSON.stringify(msgUpdateUrl));
264
+ const msg = await upsertSlackMessage ( {
265
+ text : notifyMessage ,
266
+ channelName : cfg . slackChannelName ,
267
+ url : msgUpdateUrl ,
268
+ } ) ;
269
+
270
+ console . log ( "message posted: " + msg . url ) ;
271
+ meta = await Meta . $upsert ( { lastSlackMessage : { text : msg . text , url : msg . url , sendAt : new Date ( ) } } ) ;
272
272
273
273
console . log ( "done" , import . meta. file ) ;
274
274
}
275
-
276
275
/**
277
276
* get full timeline
278
277
* - [Issue event types - GitHub Docs]( https://docs.github.com/en/rest/using-the-rest-api/issue-event-types?apiVersion=2022-11-28 )
0 commit comments