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

Fix removing testers who are testing multiple apps #294

Merged
merged 1 commit into from
Sep 19, 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
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 @@ -842,12 +842,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 @@ -861,47 +876,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
Loading