Skip to content

Commit

Permalink
Fix removing testers who are testing multiple apps
Browse files Browse the repository at this point in the history
  • Loading branch information
dwrss committed Sep 19, 2024
1 parent b0941eb commit 7dbea44
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 44 deletions.
27 changes: 25 additions & 2 deletions botto/clients/app_store_connect.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ async def run_command(key_id):
first_name=attributes.get("firstName"),
last_name=attributes.get("lastName"),
invite_type=attributes.get("inviteType"),
beta_group_ids=beta_groups,
beta_group_ids=beta_groups or [],
)
)
return testers
Expand Down Expand Up @@ -182,6 +182,29 @@ async def delete_beta_tester(self, id: str, app: App):
response.raise_for_status()
except ClientResponseError:
log.error(
f"Unable to remove beta tester with id {id}. Response: {response_body}",
f"Unable to remove beta tester with id {id}. Response: {await response.json()}",
)
raise

async def remove_from_beta_group(self, id: str, app: App):
if app.app_store_key_id is None:
raise ApiKeyNotSetError(app)
async with aiohttp.ClientSession() as session:
data = {
"id": app.beta_group_id,
"type": "betaGroups",
}
response = await session.delete(
"https://api.appstoreconnect.apple.com/v1/betaTesters/{id}/relationships/betaGroups".format(
id=id
),
json={"data": data},
headers=self.make_auth_header(app.app_store_key_id),
)
try:
response.raise_for_status()
except ClientResponseError:
log.error(
f"Unable to remove beta tester with id {id} from {app}. Response: {await response.json()}",
)
raise
104 changes: 66 additions & 38 deletions botto/mixins/reaction_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,12 +809,27 @@ async def remove_tester_from_app_store(
self,
payload: discord.RawReactionActionEvent,
tester: Tester,
app: Optional[model.App] = None,
apps: Optional[list[model.App]] = None,
):
log.info(f"Removing {tester} from {apps or 'all apps'}")
try:
testers_with_email = await self.app_store_connect_client.find_beta_tester(
tester.email, app
)
if apps:
testers_with_email = await asyncio.gather(
*[
asyncio.create_task(
self.app_store_connect_client.find_beta_tester(
tester.email, app
)
)
for app in apps
]
)
else:
testers_with_email = (
await self.app_store_connect_client.find_beta_tester(
tester.email, None
)
)

channel = self.get_channel(payload.channel_id)
message = channel.get_partial_message(payload.message_id)
Expand All @@ -828,47 +843,60 @@ async def remove_tester_from_app_store(
)
return

if len(testers_with_email) > 1:
msg = f"Found multiple testers with email '{tester.email}': {testers_with_email}"
log.info(msg)
await channel.send(
f"{payload.member.mention} {msg}",
reference=message.to_reference(),
mention_author=False,
)
return
selected_app_beta_group_ids = (
{app.beta_group_id for app in apps} if apps else {}
)

removed_app_ids: set[str] = set()
for app_store_tester in testers_with_email:
if app and app.beta_group_id not in app_store_tester.beta_group_ids:
msg = f"{tester.email} not in group {app.beta_group_id}, removal unnecessary"
log.info(msg)
return

apps = await self.testflight_storage.find_apps_by_beta_group(
*app_store_tester.beta_group_ids
apps_for_beta_group = (
await self.testflight_storage.find_apps_by_beta_group(
*app_store_tester.beta_group_ids
)
)
apps_by_key = {a.app_store_key_id: a for a in apps}
async with asyncio.TaskGroup() as g:
for app in apps_by_key.values():
g.create_task(
self.app_store_connect_client.delete_beta_tester(
app_store_tester.id, app
if not apps or all(
groupId in selected_app_beta_group_ids
for groupId in app_store_tester.beta_group_ids
):
log.debug(f"Apps for beta group: {apps_for_beta_group}")
apps_by_key = {
a.app_store_key_id: a for a in apps_for_beta_group
}
for app in apps_by_key.values():
g.create_task(
self.app_store_connect_client.delete_beta_tester(
app_store_tester.id, app
)
)
)
removed_app_ids.add(app.id)
else:
for app in filter(
lambda app: app.beta_group_id
in selected_app_beta_group_ids,
apps_for_beta_group,
):
g.create_task(
self.app_store_connect_client.remove_from_beta_group(
app.beta_group_id, app
)
)
removed_app_ids.add(app.id)

log.info(f"Removed {tester} from Beta Testers")
records_to_update = [
r
async for r in self.testflight_storage.list_requests(
tester_id=tester.discord_id,
app_ids=[app.id for app in apps],
approval_filter=RequestApprovalFilter.APPROVED,
exclude_removed=True,
)
]
for r in records_to_update:
r.removed = True
await self.testflight_storage.update_requests(records_to_update)

records_to_update = [
r
async for r in self.testflight_storage.list_requests(
tester_id=tester.discord_id,
app_ids=removed_app_ids,
approval_filter=RequestApprovalFilter.APPROVED,
exclude_removed=True,
)
]
for r in records_to_update:
r.removed = True
await self.testflight_storage.update_requests(records_to_update)
except AppStoreConnectError as error:
channel = self.get_channel(payload.channel_id)
message = channel.get_partial_message(payload.message_id)
Expand Down
4 changes: 2 additions & 2 deletions botto/storage/beta_testers/beta_testers_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging
from enum import Enum, auto
from functools import partial
from typing import Optional, Union, AsyncGenerator
from typing import Optional, Union, AsyncGenerator, Iterable

from cachetools import TTLCache
from cachetools.keys import hashkey
Expand Down Expand Up @@ -235,7 +235,7 @@ async def update_requests(
def list_requests(
self,
tester_id: Union[str, int],
app_ids: Optional[list[Union[str, int]]] = None,
app_ids: Optional[Iterable[Union[str, int]]] = None,
approval_filter: RequestApprovalFilter = RequestApprovalFilter.ALL,
exclude_removed: bool = False,
) -> AsyncGenerator[TestingRequest, None]:
Expand Down
13 changes: 11 additions & 2 deletions botto/storage/beta_testers/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,22 @@ class App:
@classmethod
def from_airtable(cls, data: dict) -> "App":
fields = data["fields"]
app_store_key_id = fields["App Store Key ID"]
if not app_store_key_id or len(app_store_key_id) != 1:
raise ValueError("App Store Key ID expected to be a list of length 1")

app_store_server_key_id = fields.get("App Store Server Key ID")
if not app_store_server_key_id or len(app_store_server_key_id) != 1:
raise ValueError(
"App Store Server Key ID expected to be a list of length 1"
)
return cls(
id=data["id"],
name=fields["Name"],
approval_channel=fields.get("Approval Channel"),
reaction_role_ids=fields.get("Reaction Role IDs", []),
app_store_key_id=fields["App Store Key ID"],
app_store_server_key_id=fields.get("App Store Server Key ID"),
app_store_key_id=app_store_key_id[0],
app_store_server_key_id=app_store_server_key_id[0],
beta_group_id=fields.get("Beta Group ID"),
bundle_id=fields.get("Bundle ID"),
apple_id=fields.get("Apple ID"),
Expand Down

0 comments on commit 7dbea44

Please sign in to comment.