1212from prisma .enums import TicketStatus
1313
1414
15- async def get_is_stale (ts : str ) -> bool :
16- try :
17- replies = await env .slack_client .conversations_replies (
18- channel = env .slack_help_channel , ts = ts , limit = 1000
19- )
20- last_reply = (
21- replies .get ("messages" , [])[- 1 ] if replies .get ("messages" ) else None
22- )
23- if not last_reply :
24- logging .error ("No replies found - this should never happen" )
25- await send_heartbeat (f"No replies found for ticket { ts } " )
26- return False
27- return (
28- datetime .now (tz = timezone .utc )
29- - datetime .fromtimestamp (float (last_reply ["ts" ]), tz = timezone .utc )
30- ) > timedelta (days = 3 )
31- except SlackApiError as e :
32- if e .response ["error" ] == "ratelimited" :
33- retry_after = int (e .response .headers .get ("Retry-After" , 1 ))
34- logging .warning (
35- f"Rate limited while fetching replies for ticket { ts } . Retrying after { retry_after } seconds."
36- )
37- await asyncio .sleep (retry_after )
38- return await get_is_stale (ts )
39- else :
40- logging .error (
41- f"Error fetching replies for ticket { ts } : { e .response ['error' ]} "
15+ async def get_is_stale (ts : str , max_retries : int = 3 ) -> bool :
16+ for attempt in range (max_retries ):
17+ try :
18+ replies = await env .slack_client .conversations_replies (
19+ channel = env .slack_help_channel , ts = ts , limit = 1000
4220 )
43- await send_heartbeat (
44- f"Error fetching replies for ticket { ts } : { e . response [ 'error' ] } "
21+ last_reply = (
22+ replies . get ( "messages" , [])[ - 1 ] if replies . get ( "messages" ) else None
4523 )
46- return False
24+ if not last_reply :
25+ logging .error ("No replies found - this should never happen" )
26+ await send_heartbeat (f"No replies found for ticket { ts } " )
27+ return False
28+ return (
29+ datetime .now (tz = timezone .utc )
30+ - datetime .fromtimestamp (float (last_reply ["ts" ]), tz = timezone .utc )
31+ ) > timedelta (days = 3 )
32+ except SlackApiError as e :
33+ if e .response ["error" ] == "ratelimited" :
34+ retry_after = int (e .response .headers .get ("Retry-After" , 1 ))
35+ # Exponential backoff: wait longer on each retry
36+ wait_time = retry_after * (2 ** attempt )
37+ logging .warning (
38+ f"Rate limited while fetching replies for ticket { ts } . "
39+ f"Attempt { attempt + 1 } /{ max_retries } . Retrying after { wait_time } seconds."
40+ )
41+ await asyncio .sleep (wait_time )
42+ if attempt == max_retries - 1 :
43+ logging .error (f"Max retries exceeded for ticket { ts } " )
44+ return False
45+ if e .response ["error" ] == "thread_not_found" :
46+ logging .warning (
47+ f"Thread not found for ticket { ts } . This might be a deleted thread."
48+ )
49+ await send_heartbeat (f"Thread not found for ticket { ts } ." )
50+ await env .db .ticket .update (
51+ where = {"msgTs" : ts },
52+ data = {
53+ "status" : TicketStatus .CLOSED ,
54+ "closedAt" : datetime .now (),
55+ "closedBy" : {"connect" : {"slackId" : env .slack_maintainer_id }},
56+ },
57+ )
58+ return False
59+ else :
60+ logging .error (
61+ f"Error fetching replies for ticket { ts } : { e .response ['error' ]} "
62+ )
63+ await send_heartbeat (
64+ f"Error fetching replies for ticket { ts } : { e .response ['error' ]} "
65+ )
66+ return False
67+ return False
4768
4869
4970async def close_stale_tickets ():
@@ -58,21 +79,38 @@ async def close_stale_tickets():
5879 try :
5980 tickets = await env .db .ticket .find_many (
6081 where = {"NOT" : [{"status" : TicketStatus .CLOSED }]},
61- include = {
62- "openedBy" : True ,
63- },
82+ include = {"openedBy" : True , "assignedTo" : True },
6483 )
6584 stale = 0
6685
67- for ticket in tickets :
68- if await get_is_stale (ticket .msgTs ):
69- stale += 1
70- await resolve (
71- ticket .msgTs ,
72- ticket .openedBy .slackId , # type: ignore (this is valid - see include above)
73- env .slack_client ,
74- stale = True ,
75- )
86+ # Process tickets in batches to avoid overwhelming the API
87+ batch_size = 10
88+ for i in range (0 , len (tickets ), batch_size ):
89+ batch = tickets [i : i + batch_size ]
90+ logging .info (
91+ f"Processing batch { i // batch_size + 1 } /{ (len (tickets ) + batch_size - 1 ) // batch_size } "
92+ )
93+
94+ for ticket in batch :
95+ await asyncio .sleep (1.2 ) # Rate limiting delay
96+
97+ if await get_is_stale (ticket .msgTs ):
98+ stale += 1
99+ resolver = (
100+ ticket .assignedToId
101+ if ticket .assignedToId
102+ else ticket .openedById
103+ )
104+ await resolve (
105+ ticket .msgTs ,
106+ resolver , # type: ignore (this is explicitly fetched in the db call)
107+ env .slack_client ,
108+ stale = True ,
109+ )
110+
111+ # Longer delay between batches
112+ if i + batch_size < len (tickets ):
113+ await asyncio .sleep (5 )
76114
77115 await send_heartbeat (f"Closed { stale } stale tickets." )
78116
0 commit comments