Skip to content

Commit f39fa46

Browse files
authored
ref(vercel): Assume one config per user/team (#26162)
1 parent 367898a commit f39fa46

File tree

4 files changed

+65
-66
lines changed

4 files changed

+65
-66
lines changed

src/sentry/integrations/vercel/integration.py

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
)
1818
from sentry.mediators.sentry_apps import InternalCreator
1919
from sentry.models import (
20-
Integration,
2120
Organization,
2221
Project,
2322
ProjectKey,
@@ -116,9 +115,16 @@ def get_client(self):
116115

117116
return VercelClient(access_token)
118117

119-
# note this could return a different integration if the user has multiple
120-
# installations with the same organization
121118
def get_configuration_id(self):
119+
# XXX(meredith): The "configurations" in the metadata is no longer
120+
# needed since Vercel restricted installation on their end to be
121+
# once per user/team. Eventually we should be able to just use
122+
# `self.metadata["installation_id"]`
123+
if not self.metadata.get("configurations"):
124+
return self.metadata["installation_id"]
125+
126+
# note this could return a different integration if the user has multiple
127+
# installations with the same organization
122128
for configuration_id, data in self.metadata["configurations"].items():
123129
if data["organization_id"] == self.organization_id:
124130
return configuration_id
@@ -301,19 +307,6 @@ def get_pipeline_views(self):
301307

302308
return [identity_pipeline_view]
303309

304-
def get_configuration_metadata(self, external_id):
305-
# If a vercel team or user was already installed on another sentry org
306-
# we want to make sure we don't overwrite the existing configurations. We
307-
# keep all the configurations so that if one of them is deleted from vercel's
308-
# side, the other sentry org will still have a working vercel integration.
309-
try:
310-
integration = Integration.objects.get(external_id=external_id, provider=self.key)
311-
except Integration.DoesNotExist:
312-
# first time setting up vercel team/user
313-
return {}
314-
315-
return integration.metadata["configurations"]
316-
317310
def build_integration(self, state):
318311
data = state["identity"]["data"]
319312
access_token = data["access_token"]
@@ -344,8 +337,6 @@ def build_integration(self, state):
344337
message = f"Could not create deployment webhook in Vercel: {details}"
345338
raise IntegrationError(message)
346339

347-
configurations = self.get_configuration_metadata(external_id)
348-
349340
integration = {
350341
"name": name,
351342
"external_id": external_id,
@@ -354,25 +345,14 @@ def build_integration(self, state):
354345
"installation_id": data["installation_id"],
355346
"installation_type": installation_type,
356347
"webhook_id": webhook["id"],
357-
"configurations": configurations,
358348
},
359349
"post_install_data": {"user_id": state["user_id"]},
360350
}
361351

362352
return integration
363353

364354
def post_install(self, integration, organization, extra=None):
365-
# add new configuration information to metadata
366-
configurations = integration.metadata.get("configurations") or {}
367-
configurations[integration.metadata["installation_id"]] = {
368-
"access_token": integration.metadata["access_token"],
369-
"webhook_id": integration.metadata["webhook_id"],
370-
"organization_id": organization.id,
371-
}
372-
integration.metadata["configurations"] = configurations
373-
integration.save()
374-
375-
# check if we have an installation already
355+
# check if we have an Vercel internal installation already
376356
if SentryAppInstallationForProvider.objects.filter(
377357
organization=organization, provider="vercel"
378358
).exists():

src/sentry/integrations/vercel/uninstall.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ def dispatch(self, request, *args, **kwargs):
1818
return super().dispatch(request, *args, **kwargs)
1919

2020
def post(self, request):
21+
# XXX(meredith): This is only handling the integration-configuration-removed
22+
# webhook event, in the future will need to check the "type" attribute
23+
# when handling more webhook event types
24+
# https://vercel.com/docs/integrations?query=event%20paylo#webhooks/events
25+
2126
# userId should always be present
2227
external_id = request.data.get("teamId") or request.data.get("userId")
2328
configuration_id = request.data["payload"]["configuration"]["id"]
@@ -49,6 +54,12 @@ def _delete(self, external_id, configuration_id, request):
4954
integration.delete()
5055
return self.respond(status=204)
5156

57+
# If we never set "configurations" in the integration, then we only have one
58+
# and therefore can delete it.
59+
if not integration.metadata.get("configurations"):
60+
integration.delete()
61+
return self.respond(status=204)
62+
5263
configuration = integration.metadata["configurations"].pop(configuration_id)
5364
# one of two cases:
5465
# 1.) someone is deleting from vercel's end and we still need to delete the

tests/sentry/integrations/vercel/test_integration.py

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,11 @@ def assert_setup_flow(self, is_team=False, multi_config_org=None, no_name=False)
9090

9191
assert integration.external_id == external_id
9292
assert integration.name == name
93-
configurations = {
94-
"my_config_id": {
95-
"access_token": "my_access_token",
96-
"webhook_id": "webhook-id",
97-
"organization_id": self.organization.id,
98-
}
99-
}
100-
if multi_config_org:
101-
configurations["orig_config_id"] = {
102-
"access_token": "orig_access_token",
103-
"webhook_id": "orig-webhook-id",
104-
"organization_id": multi_config_org.id,
105-
}
10693
assert integration.metadata == {
10794
"access_token": "my_access_token",
10895
"installation_id": "my_config_id",
10996
"installation_type": installation_type,
11097
"webhook_id": "webhook-id",
111-
"configurations": configurations,
11298
}
11399
assert OrganizationIntegration.objects.get(
114100
integration=integration, organization=self.organization
@@ -145,28 +131,6 @@ def test_use_existing_installation(self):
145131
self.assert_setup_flow(is_team=False)
146132
assert SentryAppInstallation.objects.count() == 1
147133

148-
@responses.activate
149-
def test_install_on_multiple_orgs(self):
150-
orig_org = self.create_organization()
151-
metadata = {
152-
"access_token": "orig_access_token",
153-
"installation_id": "orig_config_id",
154-
"installation_type": "team",
155-
"webhook_id": "orig-webhook-id",
156-
"configurations": {
157-
"orig_config_id": {
158-
"access_token": "orig_access_token",
159-
"webhook_id": "orig-webhook-id",
160-
"organization_id": orig_org.id,
161-
}
162-
},
163-
}
164-
Integration.objects.create(
165-
provider="vercel", name="My Team Name", external_id="my_team_id", metadata=metadata
166-
)
167-
168-
self.assert_setup_flow(is_team=True, multi_config_org=orig_org)
169-
170134
@responses.activate
171135
def test_update_organization_config(self):
172136
"""Test that Vercel environment variables are created"""

tests/sentry/integrations/vercel/test_uninstall.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,50 @@
2323

2424

2525
class VercelUninstallTest(APITestCase):
26+
def setUp(self):
27+
self.url = "/extensions/vercel/delete/"
28+
metadata = {
29+
"access_token": "my_access_token",
30+
"installation_id": "my_config_id",
31+
"installation_type": "team",
32+
"webhook_id": "my_webhook_id",
33+
}
34+
self.integration = Integration.objects.create(
35+
provider="vercel",
36+
external_id="vercel_team_id",
37+
name="My Vercel Team",
38+
metadata=metadata,
39+
)
40+
self.integration.add_organization(self.organization)
41+
42+
def _get_delete_response(self):
43+
# https://vercel.com/docs/integrations?query=event%20paylo#webhooks/events/integration-configuration-removed
44+
return """{
45+
"payload": {
46+
"configuration": {
47+
"id": "my_config_id",
48+
"projects": ["project_id1"]
49+
}
50+
},
51+
"teamId": "vercel_team_id",
52+
"userId": "vercel_user_id"
53+
}"""
54+
55+
def test_uninstall(self):
56+
response = self.client.post(
57+
path=self.url,
58+
data=self._get_delete_response(),
59+
content_type="application/json",
60+
)
61+
62+
assert response.status_code == 204
63+
assert not Integration.objects.filter(id=self.integration.id).exists()
64+
assert not OrganizationIntegration.objects.filter(
65+
integration_id=self.integration.id, organization_id=self.organization.id
66+
).exists()
67+
68+
69+
class VercelUninstallWithConfigurationsTest(APITestCase):
2670
def setUp(self):
2771
self.url = "/extensions/vercel/delete/"
2872
self.second_org = self.create_organization(name="Blah", owner=self.user)

0 commit comments

Comments
 (0)