Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include requested apps in lookup_user #286

Merged
merged 1 commit into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions botto/mixins/reaction_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,9 @@ async def handle_role_reaction(self, payload: discord.RawReactionActionEvent):

# Acquire a lock so that multiple reactions don't trample over each other
async with self.tester_locks.setdefault(str(payload.user_id), asyncio.Lock()):
tester = await self.testflight_storage.find_tester(str(payload.member.id))
tester = await self.testflight_storage.find_tester(
discord_id=str(payload.member.id)
)
log.debug(f"Existing tester: {tester and tester.username or 'No'}")
if tester is None:
# This is the first time we've seen this tester
Expand Down Expand Up @@ -967,7 +969,9 @@ async def on_raw_member_remove(self, payload: discord.RawMemberRemoveEvent):
f"{payload.user.mention} is testing {testing_apps_text}"
f" but has left the server!",
)
tester = await self.testflight_storage.find_tester(str(payload.user.id))
tester = await self.testflight_storage.find_tester(
discord_id=str(payload.user.id)
)
if not tester:
await exit_notification_channel.send(
f"Failed to find Tester record for {payload.user.mention}. This should never happen!"
Expand Down
96 changes: 85 additions & 11 deletions botto/slash_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,33 @@ async def on_testflight_registration_error(ctx: Interaction, error: Exception):
default_permissions=discord.Permissions(administrator=True),
)

async def send_tester_details(ctx, tester_email):
from botto.storage.beta_testers import model

async def send_tester_details(ctx, tester_or_email: model.Tester | str):
tester_email: str
try:
tester_email = tester_or_email.email
except AttributeError:
tester_email = tester_or_email
log.info(f"Finding beta testers with email {tester_email}")
await ctx.response.defer(ephemeral=True, thinking=True)
matching_testers = await app_store_connect.find_beta_tester(email=tester_email)
apps_to_testers = {}
approved_apps_to_testers = {}
requested_apps_to_testers = {}
beta_tester_to_stored_tester = {}
for tester in matching_testers:
apps_to_testers[tester.id] = asyncio.create_task(
approved_apps_to_testers[tester.id] = asyncio.create_task(
testflight_storage.find_apps_by_beta_group(*tester.beta_group_ids)
)
if stored_tester := (
await testflight_storage.find_tester(email=tester_email)
if isinstance(tester_email, str)
else tester_email
):
beta_tester_to_stored_tester[tester.id] = stored_tester
requested_apps_to_testers[tester.id] = asyncio.create_task(
fetch_apps_by_tester_or_email(stored_tester)
)
response_message = ""
for tester in matching_testers:
response_message += f"**ID**: {tester.id}\n"
Expand All @@ -432,16 +450,38 @@ async def send_tester_details(ctx, tester_email):
response_message += f"**Last name**: {tester.last_name}\n"
if tester.email:
response_message += f"**Email name**: {tester.email}\n"
apps = await apps_to_testers[tester.id]
requested_apps = await requested_apps_to_testers[tester.id]
requested_app_names = [app.name for app in requested_apps]
log.debug(f"Requested app names: {requested_app_names}")
response_message += (
"**Apps (Requested)**: " + ",".join(requested_app_names) + "\n"
)
apps = await approved_apps_to_testers[tester.id]
app_names = [app.name for app in apps]
log.debug(f"App names: {app_names}")
response_message += "**Apps**: " + ",".join(app_names)
if len(app_names) > 0:
response_message += "\n\n"
else:
response_message += "\n"
response_message += "**Apps (in TestFlight)**: " + ",".join(app_names)
if len(app_names) == 0:
response_message += "*None*"
response_message += f"\n{testflight_storage.url_for_tester(beta_tester_to_stored_tester[tester.id])}\n\n"
await ctx.followup.send(f"{response_message}", ephemeral=True)

async def fetch_apps_by_tester_or_email(tester: model.Tester) -> list[model.App]:
from botto.storage.beta_testers.beta_testers_storage import (
RequestApprovalFilter,
)

if not tester:
raise ValueError("tester must not be None")
apps = [
await testflight_storage.fetch_app(r.app)
async for r in testflight_storage.list_requests(
tester.discord_id,
approval_filter=RequestApprovalFilter.UNAPPROVED,
exclude_removed=True,
)
]
return apps

@app_store.command(
name="lookup_tester",
description="Lookup details of a Beta Tester",
Expand All @@ -468,20 +508,30 @@ async def lookup_user(
ctx: Interaction,
member: discord.Member,
):
tester = await testflight_storage.find_tester(member.id)
tester = await testflight_storage.find_tester(discord_id=member.id)
if not tester:
await ctx.response.send_message(
f"User {member.mention} is not a beta tester", ephemeral=True
)
return
await send_tester_details(ctx, tester.email)
await send_tester_details(ctx, tester)

@app_store.error
async def on_app_store_error(ctx: Interaction, error: Exception):
log.error("Failed to query app store", exc_info=True)

client.tree.add_command(app_store)

@client.tree.context_menu(name="Raise Jira", guilds=[client.snailed_it_beta_guild])
@app_commands.checks.has_role("Snailed It")
@client.tree.error
async def raise_jira(ctx: discord.Interaction, message: discord.Message):
try:
log.debug("Opening Raise Jira form")
await ctx.response.send_message(view=RaiseJiraForm(), ephemeral=True)
except:
log.error("Failed", exc_info=True)

cache = app_commands.Group(
name="cache",
description="Manage caches",
Expand All @@ -498,6 +548,13 @@ async def on_app_store_error(ctx: Interaction, error: Exception):
default_permissions=discord.Permissions(administrator=True),
)

cache_refresh = app_commands.Group(
name="refresh",
description="Refresh caches",
parent=cache,
default_permissions=discord.Permissions(administrator=True),
)

@cache_clear.command(
name="reaction_roles",
description="Clear cached reaction roles",
Expand Down Expand Up @@ -533,6 +590,23 @@ async def clear_config(
await client.reaction_roles_config_storage.clear_server_cache(str(ctx.guild_id))
await ctx.response.send_message(f"Cleared server config cache", ephemeral=True)

@cache_refresh.command(
name="config",
description="Refresh all cached config (Note: This refreshes only config, not all caches associated with "
"TestFlight approvals)",
)
@app_commands.checks.has_role("Snailed It")
async def refresh_config(
ctx: Interaction,
):
tasks = asyncio.gather(
client.reaction_roles_config_storage.refresh_cache(),
client.config_storage.refresh_cache(),
)
await ctx.response.defer(ephemeral=True, thinking=True)
await tasks
await ctx.followup.send(f"Refreshed server config cache", ephemeral=True)

@cache.error
async def on_cache_error(ctx: Interaction, error: Exception):
log.error("Failed to execute cache command", exc_info=True)
Expand Down
29 changes: 23 additions & 6 deletions botto/storage/beta_testers/beta_testers_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,25 @@ async def fetch_tester(self, record_id: str) -> Optional[Tester]:
result = await self._get(self.testers_url + "/" + record_id)
return Tester.from_airtable(result)

async def find_tester(self, discord_id: str | int) -> Optional[Tester]:
log.debug(f"Finding tester with ID {discord_id}")
async def find_tester(
self, *, discord_id: Optional[str] = None, email: Optional[str] = None
) -> Optional[Tester]:
log.debug(f"Finding tester with {discord_id=}, {email}")
if not (discord_id or email):
raise ValueError("At least one search parameter is required")
try:
result_iterator = self._iterate(
self.testers_url, filter_by_formula=f"{{Discord ID}}='{discord_id}'"
)
formula = "AND("
if discord_id:
formula += f"{{Discord ID}}='{discord_id}'"
if email:
if discord_id:
formula += ","
formula += f"{{Email}}='{email}'"
formula += ")"
result_iterator = self._iterate(self.testers_url, filter_by_formula=formula)
tester_iterator = (Tester.from_airtable(x) async for x in result_iterator)
try:
return await tester_iterator.__anext__()
return await anext(tester_iterator)
except (StopIteration, StopAsyncIteration):
log.info(f"No Tester found with ID {discord_id}")
return None
Expand All @@ -137,6 +147,13 @@ async def find_tester_by_leave_message(
return None
raise

def url_for_tester(self, tester: Tester) -> str:
if tester.id is None:
raise MissingRecordIDError(tester)
return "https://airtable.com/{base}/tblVndkqCyp1dZShG/{record_id}".format(
base=self.airtable_base, record_id=tester.id
)

async def _fetch_reaction(
self, server_id: str, msg_id, reaction_name: str
) -> Optional[ReactionRole]:
Expand Down
Loading