Skip to content

Commit d202005

Browse files
committed
Use paginated select for workflows and artifacts
1 parent 2fe5327 commit d202005

File tree

2 files changed

+149
-95
lines changed

2 files changed

+149
-95
lines changed

bot/src/ghutils/ui/components/paginated_select.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ def __init__(
7878
# reuse the logic in the decorator
7979
self.options = options
8080

81+
async def fetch_first_page(self, interaction: Interaction):
82+
"""Fetch and switch to page 1."""
83+
self._page_cache.pop(1, None)
84+
await self._switch_to_page(interaction, 1)
85+
86+
def clear_cached_pages(self):
87+
self._page_cache.clear()
88+
8189
@property
8290
def page_getter(self):
8391
"""A decorator to set the page getter function.
@@ -189,10 +197,18 @@ async def callback(self, interaction: Interaction):
189197
if PREVIOUS_PAGE_VALUE in selected:
190198
# go to previous page
191199
await self._switch_to_page(interaction, self._page - 1)
200+
if interaction.response.is_done():
201+
await interaction.edit_original_response(view=self.view)
202+
else:
203+
await interaction.response.edit_message(view=self.view)
192204

193205
elif NEXT_PAGE_VALUE in selected:
194206
# go to next page
195207
await self._switch_to_page(interaction, self._page + 1)
208+
if interaction.response.is_done():
209+
await interaction.edit_original_response(view=self.view)
210+
else:
211+
await interaction.response.edit_message(view=self.view)
196212

197213
else:
198214
# normal selection
@@ -265,11 +281,6 @@ async def _switch_to_page(self, interaction: Interaction, page: int):
265281
else:
266282
self.placeholder = None
267283

268-
if interaction.response.is_done():
269-
await interaction.edit_original_response(view=self.view)
270-
else:
271-
await interaction.response.edit_message(view=self.view)
272-
273284
def _clear_remote_page_selection(self):
274285
self.placeholder = None
275286

bot/src/ghutils/ui/views/select_artifact.py

Lines changed: 133 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Iterable, Self
1+
from typing import Any, Self
22

33
import humanize
44
from discord import ButtonStyle, Interaction, SelectOption
@@ -8,11 +8,10 @@
88
Container,
99
LayoutView,
1010
Section,
11-
Select,
1211
TextDisplay,
1312
)
1413
from githubkit import GitHub
15-
from githubkit.rest import Artifact, FullRepository, ShortBranch, Workflow, WorkflowRun
14+
from githubkit.rest import Artifact, FullRepository, Workflow, WorkflowRun
1615
from yarl import URL
1716

1817
from ghutils.core.bot import GHUtilsBot
@@ -24,7 +23,6 @@
2423
)
2524
from ghutils.ui.components.visibility import add_visibility_buttons
2625
from ghutils.utils.discord.commands import AnyInteractionCommand
27-
from ghutils.utils.discord.components import update_select_menu_default
2826
from ghutils.utils.discord.mentions import relative_timestamp
2927
from ghutils.utils.github import gh_request
3028
from ghutils.utils.strings import join_truthy, truncate_str
@@ -122,87 +120,80 @@ def set_artifact(
122120

123121

124122
class 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

Comments
 (0)