1- from typing import Any , Iterable , Self
1+ from typing import Any , Self
22
33import humanize
44from discord import ButtonStyle , Interaction , SelectOption
88 Container ,
99 LayoutView ,
1010 Section ,
11- Select ,
1211 TextDisplay ,
1312)
1413from githubkit import GitHub
15- from githubkit .rest import Artifact , FullRepository , ShortBranch , Workflow , WorkflowRun
14+ from githubkit .rest import Artifact , FullRepository , Workflow , WorkflowRun
1615from yarl import URL
1716
1817from ghutils .core .bot import GHUtilsBot
2423)
2524from ghutils .ui .components .visibility import add_visibility_buttons
2625from ghutils .utils .discord .commands import AnyInteractionCommand
27- from ghutils .utils .discord .components import update_select_menu_default
2826from ghutils .utils .discord .mentions import relative_timestamp
2927from ghutils .utils .github import gh_request
3028from ghutils .utils .strings import join_truthy , truncate_str
@@ -122,87 +120,80 @@ def set_artifact(
122120
123121
124122class SelectArtifactView (LayoutView ):
123+ bot : GHUtilsBot
124+ github : GitHub [Any ]
125+ command : AnyInteractionCommand
126+ repo : FullRepository
127+
128+ workflows : dict [int , Workflow ]
129+ artifacts : dict [str , Artifact ]
130+
131+ workflow : Workflow | None
132+ workflow_run : WorkflowRun | None
133+ branch : str | None
134+ artifact : Artifact | None
135+ previous_artifact_name : str | None
136+
125137 def __init__ (
126138 self ,
127139 * ,
128140 bot : GHUtilsBot ,
129141 github : GitHub [Any ],
130142 command : AnyInteractionCommand ,
131143 repo : FullRepository ,
132- workflows : list [Workflow ],
133- branches : list [ShortBranch ],
134144 ):
145+ """Do not use this constructor directly!"""
146+
135147 super ().__init__ (timeout = 5 * 60 )
136148
137149 self .bot = bot
138150 self .github = github
139151 self .command = command
140152 self .repo = repo
141153
142- self .workflows = {workflow .id : workflow for workflow in workflows }
143- self .artifacts : dict [str , Artifact ] = {}
144-
145- self .workflow : Workflow | None = None
146- self .workflow_run : WorkflowRun | None = None
147- self .branch : str | None = None
148- self .artifact : Artifact | None = None
149-
150- self .workflow_select .options = [
151- SelectOption (
152- label = truncate_str (workflow .name , 100 ),
153- value = str (workflow .id ),
154- description = truncate_str (
155- workflow .path .removeprefix (WORKFLOW_PREFIX ), 100
156- ),
157- )
158- for workflow in workflows
159- ]
154+ self .workflows = {}
155+ self .artifacts = {}
160156
161- self .branch_select .options = _get_branch_options (branches )
157+ self .workflow = None
158+ self .workflow_run = None
159+ self .branch = None
160+ self .artifact = None
161+ self .previous_artifact_name = None
162162
163+ self .workflow_row .add_item (self .workflow_select )
163164 self .branch_row .add_item (self .branch_select )
165+ self .artifact_select_row .add_item (self .artifact_select )
164166
165167 self .clear_items ()
166168 self .add_item (self .workflow_label_text )
167169 self .add_item (self .workflow_row )
168170 self .add_item (self .branch_label_text )
169171 self .add_item (self .branch_row )
170172
173+ async def async_init (self , interaction : Interaction ) -> Self :
174+ await self .workflow_select .fetch_first_page (interaction )
175+ await self .branch_select .fetch_first_page (interaction )
176+ return self
177+
171178 @classmethod
172179 async def new (cls , interaction : Interaction , repo : FullRepository ) -> Self :
173180 async with GHUtilsBot .github_app_of (interaction ) as (github , _ ):
174- workflows_response = await gh_request (
175- github .rest .actions .async_list_repo_workflows (
176- owner = repo .owner .login ,
177- repo = repo .name ,
178- per_page = 25 ,
179- )
180- )
181-
182- branches = await gh_request (
183- github .rest .repos .async_list_branches (
184- owner = repo .owner .login ,
185- repo = repo .name ,
186- per_page = MAX_PAGE_LENGTH ,
187- )
188- )
189-
190- return cls (
181+ return await cls (
191182 bot = GHUtilsBot .of (interaction ),
192183 github = github ,
193184 command = interaction .command ,
194185 repo = repo ,
195- workflows = workflows_response .workflows ,
196- branches = branches ,
197- )
186+ ).async_init (interaction )
198187
199- async def refresh_artifacts (self ):
188+ async def refresh_artifacts (self , interaction : Interaction ):
200189 if self .workflow is None :
201190 return
202191
203- old_artifact_name = self .artifact .name if self .artifact else None
192+ self . previous_artifact_name = self .artifact .name if self .artifact else None
204193 self .artifact = None
205194 self .workflow_run = None
195+ self .artifact_select .clear_cached_pages ()
196+ self .artifacts .clear ()
206197
207198 self .remove_item (self .artifact_label_text )
208199 self .remove_item (self .artifact_select_row )
@@ -236,42 +227,18 @@ async def refresh_artifacts(self):
236227
237228 self .workflow_run = run = runs [0 ]
238229
239- artifacts = (
240- await github .rest .actions .async_list_workflow_run_artifacts (
241- owner = self .repo .owner .login ,
242- repo = self .repo .name ,
243- run_id = run .id ,
244- per_page = 25 ,
245- )
246- ).parsed_data .artifacts
247-
248- if not artifacts :
230+ await self .artifact_select .fetch_first_page (interaction )
231+ if not self .artifact_select .options :
249232 self .set_error ("❌ No artifacts found." , URL (run .html_url ))
250233 return
251234
252- self .artifacts .clear ()
253- self .artifact_select .options .clear ()
254-
255- for artifact in artifacts :
256- self .artifacts [artifact .name ] = artifact
257- self .artifact_select .options .append (
258- SelectOption (
259- label = artifact .name ,
260- description = join_truthy (
261- " " ,
262- artifact .created_at
263- and "Created " + humanize .naturaltime (artifact .created_at ),
264- artifact .expired and "(expired)" ,
265- ),
266- default = old_artifact_name == artifact .name ,
267- )
268- )
269-
270235 self .add_item (self .artifact_label_text )
271236 self .add_item (self .artifact_select_row )
272237
273- if old_artifact_name in self .artifacts :
274- self .set_artifact (old_artifact_name )
238+ if self .previous_artifact_name in self .artifacts :
239+ self .set_artifact (self .previous_artifact_name )
240+
241+ self .previous_artifact_name = None
275242
276243 def set_error (self , message : str , url : URL ):
277244 self .error_section .clear_items ()
@@ -311,20 +278,55 @@ def set_artifact(self, artifact_name: str) -> bool:
311278
312279 workflow_row = ActionRow [Any ]()
313280
314- @workflow_row . select (
281+ @paginated_select (
315282 min_values = 1 ,
316283 max_values = 1 ,
317284 )
318- async def workflow_select (self , interaction : Interaction , select : Select [Any ]):
319- workflow_id = int (update_select_menu_default (select , True ).value )
285+ async def workflow_select (
286+ self ,
287+ interaction : Interaction ,
288+ select : PaginatedSelect [Any ],
289+ ):
290+ assert select .selected_option
291+ workflow_id = int (select .selected_option .value )
320292
321293 if self .workflow is None or self .workflow .id != workflow_id :
322294 self .workflow = self .workflows [workflow_id ]
323- await self .refresh_artifacts ()
295+ await self .refresh_artifacts (interaction )
324296 await interaction .response .edit_message (view = self )
325297 else :
326298 await interaction .response .defer ()
327299
300+ @workflow_select .page_getter ()
301+ async def workflow_select_page_getter (
302+ self ,
303+ interaction : Interaction ,
304+ select : PaginatedSelect [Any ],
305+ page : int ,
306+ ) -> list [SelectOption ]:
307+ workflows = (
308+ await self .github .rest .actions .async_list_repo_workflows (
309+ owner = self .repo .owner .login ,
310+ repo = self .repo .name ,
311+ per_page = MAX_PAGE_LENGTH ,
312+ page = page ,
313+ )
314+ ).parsed_data .workflows
315+
316+ options = list [SelectOption ]()
317+ for workflow in workflows :
318+ self .workflows [workflow .id ] = workflow
319+ options .append (
320+ SelectOption (
321+ label = truncate_str (workflow .name , 100 ),
322+ value = str (workflow .id ),
323+ description = truncate_str (
324+ workflow .path .removeprefix (WORKFLOW_PREFIX ), 100
325+ ),
326+ )
327+ )
328+ return options
329+
328330 # branch
329331
330332 branch_label_text = TextDisplay [Any ]("**Branch** (optional)" )
@@ -344,7 +346,7 @@ async def branch_select(
344346
345347 if self .branch != branch :
346348 self .branch = branch
347- await self .refresh_artifacts ()
349+ await self .refresh_artifacts (interaction )
348350 await interaction .response .edit_message (view = self )
349351 else :
350352 await interaction .response .defer ()
@@ -364,26 +366,71 @@ async def branch_select_page_getter(
364366 page = page ,
365367 )
366368 )
367- return _get_branch_options ( branches )
369+ return [ SelectOption ( label = branch . name ) for branch in branches ]
368370
369371 # artifact
370372
371373 artifact_label_text = TextDisplay [Any ]("**Artifact**" )
372374
373375 artifact_select_row = ActionRow [Any ]()
374376
375- @artifact_select_row . select (
377+ @paginated_select (
376378 min_values = 1 ,
377379 max_values = 1 ,
378380 )
379- async def artifact_select (self , interaction : Interaction , select : Select [Any ]):
380- artifact_name = update_select_menu_default (select , True ).value
381+ async def artifact_select (
382+ self ,
383+ interaction : Interaction ,
384+ select : PaginatedSelect [Any ],
385+ ):
386+ assert select .selected_option
387+ artifact_name = select .selected_option .value
381388
382389 if self .set_artifact (artifact_name ):
383390 await interaction .response .edit_message (view = self )
384391 else :
385392 await interaction .response .defer ()
386393
394+ @artifact_select .page_getter ()
395+ async def artifact_select_page_getter (
396+ self ,
397+ interaction : Interaction ,
398+ select : PaginatedSelect [Any ],
399+ page : int ,
400+ ) -> list [SelectOption ]:
401+ if not self .workflow_run :
402+ return []
403+
404+ artifacts = (
405+ await self .github .rest .actions .async_list_workflow_run_artifacts (
406+ owner = self .repo .owner .login ,
407+ repo = self .repo .name ,
408+ run_id = self .workflow_run .id ,
409+ per_page = MAX_PAGE_LENGTH ,
410+ page = page ,
411+ )
412+ ).parsed_data .artifacts
413+
414+ if not artifacts :
415+ return []
416+
417+ options = list [SelectOption ]()
418+ for artifact in artifacts :
419+ self .artifacts [artifact .name ] = artifact
420+ options .append (
421+ SelectOption (
422+ label = artifact .name ,
423+ description = join_truthy (
424+ " " ,
425+ artifact .created_at
426+ and "Created " + humanize .naturaltime (artifact .created_at ),
427+ artifact .expired and "(expired)" ,
428+ ),
429+ default = self .previous_artifact_name == artifact .name ,
430+ )
431+ )
432+ return options
433+
387434 # result
388435
389436 error_section = Section [Any ](accessory = Button ())
@@ -417,7 +464,3 @@ async def send_as_public_button(
417464 await interaction .response .edit_message ()
418465 await interaction .delete_original_response ()
419466 await interaction .followup .send (view = self , ephemeral = False )
420-
421-
422- def _get_branch_options (branches : Iterable [ShortBranch ]) -> list [SelectOption ]:
423- return [SelectOption (label = branch .name ) for branch in branches ]
0 commit comments