From ea15e1a9273b01ee100289913a626a4d896bcd17 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Mon, 8 Jul 2019 17:41:42 -0700 Subject: [PATCH 1/6] feat(api): Add option to fetch Org details without projects and teams This adds an option to fetch Organization details without projects and teams. This isdirection we want to move towards, but we do not want to break the existing details endpoint --- .../api/endpoints/organization_details.py | 4 +- .../api/serializers/models/organization.py | 85 +++++++++++-------- .../endpoints/test_organization_details.py | 25 ++++++ 3 files changed, 77 insertions(+), 37 deletions(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 83b2db1b778894..651a28657ad4b6 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -317,10 +317,12 @@ def get(self, request, organization): team should be created for. :auth: required """ + is_skinny = request.GET.get('skinny') == '1' + serializer = org_serializers.SkinnyDetailedOrganizationSerializer if is_skinny else org_serializers.DetailedOrganizationSerializer context = serialize( organization, request.user, - org_serializers.DetailedOrganizationSerializer(), + serializer(), access=request.access, ) return self.respond(context) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 9a7659f42d751f..860c884478b098 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -116,43 +116,13 @@ def serialize(self, obj, attrs, user): } -class DetailedOrganizationSerializer(OrganizationSerializer): +# Does not include project/teams list +class SkinnyDetailedOrganizationSerializer(OrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): - return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) - - def _project_list(self, organization, access): - member_projects = list(access.projects) - member_project_ids = [p.id for p in member_projects] - other_projects = list(Project.objects.filter( - organization=organization, - status=ProjectStatus.VISIBLE, - ).exclude(id__in=member_project_ids)) - project_list = sorted(other_projects + member_projects, key=lambda x: x.slug) - - for project in project_list: - project._organization_cache = organization - return project_list - - def _team_list(self, organization, access): - member_teams = list(access.teams) - member_team_ids = [p.id for p in member_teams] - other_teams = list(Team.objects.filter( - organization=organization, - status=TeamStatus.VISIBLE, - ).exclude(id__in=member_team_ids)) - team_list = sorted(other_teams + member_teams, key=lambda x: x.slug) - - for team in team_list: - team._organization_cache = organization - return team_list + return super(SkinnyDetailedOrganizationSerializer, self).get_attrs(item_list, user) def serialize(self, obj, attrs, user, access): from sentry import experiments - from sentry.api.serializers.models.project import ProjectSummarySerializer - from sentry.api.serializers.models.team import TeamSerializer - - team_list = self._team_list(obj, access) - project_list = self._project_list(obj, access) onboarding_tasks = list( OrganizationOnboardingTask.objects.filter( @@ -162,7 +132,7 @@ def serialize(self, obj, attrs, user, access): experiment_assignments = experiments.all(org=obj, actor=user) - context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user) + context = super(SkinnyDetailedOrganizationSerializer, self).serialize(obj, attrs, user) max_rate = quotas.get_maximum_quota(obj) context['experiments'] = experiment_assignments context['quota'] = { @@ -205,11 +175,54 @@ def serialize(self, obj, attrs, user, access): 'scrapeJavaScript': bool(obj.get_option('sentry:scrape_javascript', SCRAPE_JAVASCRIPT_DEFAULT)), 'trustedRelays': obj.get_option('sentry:trusted-relays', TRUSTED_RELAYS_DEFAULT) or [], }) - context['teams'] = serialize(team_list, user, TeamSerializer()) - context['projects'] = serialize(project_list, user, ProjectSummarySerializer()) context['access'] = access.scopes context['pendingAccessRequests'] = OrganizationAccessRequest.objects.filter( team__organization=obj, ).count() context['onboardingTasks'] = serialize(onboarding_tasks, user, OnboardingTasksSerializer()) return context + + +class DetailedOrganizationSerializer(SkinnyDetailedOrganizationSerializer): + def get_attrs(self, item_list, user, **kwargs): + return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) + + def _project_list(self, organization, access): + member_projects = list(access.projects) + member_project_ids = [p.id for p in member_projects] + other_projects = list(Project.objects.filter( + organization=organization, + status=ProjectStatus.VISIBLE, + ).exclude(id__in=member_project_ids)) + project_list = sorted(other_projects + member_projects, key=lambda x: x.slug) + + for project in project_list: + project._organization_cache = organization + return project_list + + def _team_list(self, organization, access): + member_teams = list(access.teams) + member_team_ids = [p.id for p in member_teams] + other_teams = list(Team.objects.filter( + organization=organization, + status=TeamStatus.VISIBLE, + ).exclude(id__in=member_team_ids)) + team_list = sorted(other_teams + member_teams, key=lambda x: x.slug) + + for team in team_list: + team._organization_cache = organization + return team_list + + def serialize(self, obj, attrs, user, access): + from sentry.api.serializers.models.project import ProjectSummarySerializer + from sentry.api.serializers.models.team import TeamSerializer + + context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user, access) + + team_list = self._team_list(obj, access) + project_list = self._project_list(obj, access) + + context['teams'] = serialize(team_list, user, TeamSerializer()) + context['projects'] = serialize(project_list, user, ProjectSummarySerializer()) + + return context diff --git a/tests/sentry/api/endpoints/test_organization_details.py b/tests/sentry/api/endpoints/test_organization_details.py index c79b87e6ab6c79..0ab24ed5ebc58b 100644 --- a/tests/sentry/api/endpoints/test_organization_details.py +++ b/tests/sentry/api/endpoints/test_organization_details.py @@ -96,6 +96,31 @@ def test_with_projects(self): assert len(team_slugs) == 2 assert 'deleted' not in team_slugs + def test_skinny_details_no_projects_or_teams(self): + user = self.create_user('owner@example.org') + org = self.create_organization(owner=user) + team = self.create_team( + name='appy', + organization=org, + members=[user]) + # Create non-member team to test response shape + self.create_team(name='no-member', organization=org) + + for i in range(2): + self.create_project(organization=org, teams=[team]) + + url = reverse( + 'sentry-api-0-organization-details', kwargs={ + 'organization_slug': org.slug, + } + ) + self.login_as(user=user) + + response = self.client.get(u'{}?skinny=1'.format(url), format='json') + + assert 'projects' not in response.data + assert 'teams' not in response.data + def test_as_superuser(self): self.user = self.create_user('super@example.org', is_superuser=True) org = self.create_organization(owner=self.user) From 05e7521a59d1af73cd0a0720644f0c547b10834d Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jul 2019 10:51:23 -0700 Subject: [PATCH 2/6] skinny->light --- src/sentry/api/endpoints/organization_details.py | 4 ++-- src/sentry/api/serializers/models/organization.py | 8 ++++---- tests/sentry/api/endpoints/test_organization_details.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 651a28657ad4b6..517a0428963061 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -317,8 +317,8 @@ def get(self, request, organization): team should be created for. :auth: required """ - is_skinny = request.GET.get('skinny') == '1' - serializer = org_serializers.SkinnyDetailedOrganizationSerializer if is_skinny else org_serializers.DetailedOrganizationSerializer + is_light = request.GET.get('light') == '1' + serializer = org_serializers.LightDetailedOrganizationSerializer if is_light else org_serializers.DetailedOrganizationSerializer context = serialize( organization, request.user, diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index 860c884478b098..f68eb7f906c6d8 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -117,9 +117,9 @@ def serialize(self, obj, attrs, user): # Does not include project/teams list -class SkinnyDetailedOrganizationSerializer(OrganizationSerializer): +class LightDetailedOrganizationSerializer(OrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): - return super(SkinnyDetailedOrganizationSerializer, self).get_attrs(item_list, user) + return super(LightDetailedOrganizationSerializer, self).get_attrs(item_list, user) def serialize(self, obj, attrs, user, access): from sentry import experiments @@ -132,7 +132,7 @@ def serialize(self, obj, attrs, user, access): experiment_assignments = experiments.all(org=obj, actor=user) - context = super(SkinnyDetailedOrganizationSerializer, self).serialize(obj, attrs, user) + context = super(LightDetailedOrganizationSerializer, self).serialize(obj, attrs, user) max_rate = quotas.get_maximum_quota(obj) context['experiments'] = experiment_assignments context['quota'] = { @@ -183,7 +183,7 @@ def serialize(self, obj, attrs, user, access): return context -class DetailedOrganizationSerializer(SkinnyDetailedOrganizationSerializer): +class DetailedOrganizationSerializer(LightDetailedOrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) diff --git a/tests/sentry/api/endpoints/test_organization_details.py b/tests/sentry/api/endpoints/test_organization_details.py index 0ab24ed5ebc58b..c9e3f0ad366aa5 100644 --- a/tests/sentry/api/endpoints/test_organization_details.py +++ b/tests/sentry/api/endpoints/test_organization_details.py @@ -96,7 +96,7 @@ def test_with_projects(self): assert len(team_slugs) == 2 assert 'deleted' not in team_slugs - def test_skinny_details_no_projects_or_teams(self): + def test_light_details_no_projects_or_teams(self): user = self.create_user('owner@example.org') org = self.create_organization(owner=user) team = self.create_team( @@ -116,7 +116,7 @@ def test_skinny_details_no_projects_or_teams(self): ) self.login_as(user=user) - response = self.client.get(u'{}?skinny=1'.format(url), format='json') + response = self.client.get(u'{}?light=1'.format(url), format='json') assert 'projects' not in response.data assert 'teams' not in response.data From 7006b05bd378550721cbb59bdfb67f7ba24134b0 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jul 2019 13:31:03 -0700 Subject: [PATCH 3/6] rename DetailedOrganizationSerializer --> DetailedOrganizationWithProjectsAndTeamsSerializer, change url param to `detailed=0` --- .../api/endpoints/accept_project_transfer.py | 4 ++-- .../api/endpoints/organization_details.py | 8 ++++---- .../api/serializers/models/organization.py | 19 +++++++++++++------ src/sentry/templatetags/sentry_api.py | 5 +++-- .../endpoints/test_organization_details.py | 4 ++-- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/sentry/api/endpoints/accept_project_transfer.py b/src/sentry/api/endpoints/accept_project_transfer.py index 1320236b225d25..4313ba182e46ce 100644 --- a/src/sentry/api/endpoints/accept_project_transfer.py +++ b/src/sentry/api/endpoints/accept_project_transfer.py @@ -11,7 +11,7 @@ from sentry.api.base import Endpoint, SessionAuthentication from sentry.api.decorators import sudo_required from sentry.api.serializers import serialize -from sentry.api.serializers.models.organization import DetailedOrganizationSerializer +from sentry.api.serializers.models.organization import DetailedOrganizationSerializerWithProjectsAndTeams from sentry.utils.signing import unsign from sentry.models import ( AuditLogEntryEvent, OrganizationMember, Organization, OrganizationStatus, Team, Project @@ -70,7 +70,7 @@ def get(self, request): 'organizations': serialize( list(organizations), request.user, - DetailedOrganizationSerializer(), + DetailedOrganizationSerializerWithProjectsAndTeams(), access=request.access ), 'project': { diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 517a0428963061..c8a2287ef5e6d6 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -317,8 +317,8 @@ def get(self, request, organization): team should be created for. :auth: required """ - is_light = request.GET.get('light') == '1' - serializer = org_serializers.LightDetailedOrganizationSerializer if is_light else org_serializers.DetailedOrganizationSerializer + is_detailed = request.GET.get('detailed', '1') != '0' + serializer = org_serializers.DetailedOrganizationProjectsAndTeamsSerializer if is_detailed else org_serializers.DetailedOrganizationSerializerWithProjectsAndTeams context = serialize( organization, request.user, @@ -390,7 +390,7 @@ def put(self, request, organization): serialize( organization, request.user, - org_serializers.DetailedOrganizationSerializer(), + org_serializers.DetailedOrganizationSerializerWithProjectsAndTeams(), access=request.access, ) ) @@ -461,7 +461,7 @@ def delete(self, request, organization): context = serialize( organization, request.user, - org_serializers.DetailedOrganizationSerializer(), + org_serializers.DetailedOrganizationSerializerWithProjectsAndTeams(), access=request.access, ) return self.respond(context, status=202) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index f68eb7f906c6d8..b677397bb15929 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -117,9 +117,9 @@ def serialize(self, obj, attrs, user): # Does not include project/teams list -class LightDetailedOrganizationSerializer(OrganizationSerializer): +class DetailedOrganizationSerializer(OrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): - return super(LightDetailedOrganizationSerializer, self).get_attrs(item_list, user) + return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) def serialize(self, obj, attrs, user, access): from sentry import experiments @@ -132,7 +132,7 @@ def serialize(self, obj, attrs, user, access): experiment_assignments = experiments.all(org=obj, actor=user) - context = super(LightDetailedOrganizationSerializer, self).serialize(obj, attrs, user) + context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user) max_rate = quotas.get_maximum_quota(obj) context['experiments'] = experiment_assignments context['quota'] = { @@ -183,9 +183,10 @@ def serialize(self, obj, attrs, user, access): return context -class DetailedOrganizationSerializer(LightDetailedOrganizationSerializer): +class DetailedOrganizationSerializerWithProjectsAndTeams(DetailedOrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): - return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) + return super(DetailedOrganizationSerializerWithProjectsAndTeams, + self).get_attrs(item_list, user) def _project_list(self, organization, access): member_projects = list(access.projects) @@ -217,7 +218,13 @@ def serialize(self, obj, attrs, user, access): from sentry.api.serializers.models.project import ProjectSummarySerializer from sentry.api.serializers.models.team import TeamSerializer - context = super(DetailedOrganizationSerializer, self).serialize(obj, attrs, user, access) + context = super( + DetailedOrganizationSerializerWithProjectsAndTeams, + self).serialize( + obj, + attrs, + user, + access) team_list = self._team_list(obj, access) project_list = self._project_list(obj, access) diff --git a/src/sentry/templatetags/sentry_api.py b/src/sentry/templatetags/sentry_api.py index 16d43d8a715bab..af87dfcd366923 100644 --- a/src/sentry/templatetags/sentry_api.py +++ b/src/sentry/templatetags/sentry_api.py @@ -5,7 +5,8 @@ from sentry.auth.access import from_user, NoAccess from sentry.api.serializers.base import serialize as serialize_func -from sentry.api.serializers.models.organization import (DetailedOrganizationSerializer) +from sentry.api.serializers.models.organization import ( + DetailedOrganizationSerializerWithProjectsAndTeams) from sentry.utils import json register = template.Library() @@ -38,7 +39,7 @@ def serialize_detailed_org(context, obj): context = serialize_func( obj, user, - DetailedOrganizationSerializer(), + DetailedOrganizationSerializerWithProjectsAndTeams(), access=access ) diff --git a/tests/sentry/api/endpoints/test_organization_details.py b/tests/sentry/api/endpoints/test_organization_details.py index c9e3f0ad366aa5..a4febd7888012b 100644 --- a/tests/sentry/api/endpoints/test_organization_details.py +++ b/tests/sentry/api/endpoints/test_organization_details.py @@ -96,7 +96,7 @@ def test_with_projects(self): assert len(team_slugs) == 2 assert 'deleted' not in team_slugs - def test_light_details_no_projects_or_teams(self): + def test_details_no_projects_or_teams(self): user = self.create_user('owner@example.org') org = self.create_organization(owner=user) team = self.create_team( @@ -116,7 +116,7 @@ def test_light_details_no_projects_or_teams(self): ) self.login_as(user=user) - response = self.client.get(u'{}?light=1'.format(url), format='json') + response = self.client.get(u'{}?detailed=0'.format(url), format='json') assert 'projects' not in response.data assert 'teams' not in response.data From af2a1311736d3a05e053a6f6a93ec651ac580efc Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jul 2019 14:43:18 -0700 Subject: [PATCH 4/6] remove comment --- src/sentry/api/serializers/models/organization.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py index b677397bb15929..92dd60d6bc297b 100644 --- a/src/sentry/api/serializers/models/organization.py +++ b/src/sentry/api/serializers/models/organization.py @@ -116,7 +116,6 @@ def serialize(self, obj, attrs, user): } -# Does not include project/teams list class DetailedOrganizationSerializer(OrganizationSerializer): def get_attrs(self, item_list, user, **kwargs): return super(DetailedOrganizationSerializer, self).get_attrs(item_list, user) From 2c777d07cbf97f63a62c09b2b116ec449c08165c Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 9 Jul 2019 16:53:59 -0700 Subject: [PATCH 5/6] add comment --- src/sentry/api/endpoints/organization_details.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index c8a2287ef5e6d6..61436ef5a01680 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -315,6 +315,7 @@ def get(self, request, organization): :pparam string organization_slug: the slug of the organization the team should be created for. + :param string detailed: Specify '0' to retrieve details without projects and teams. :auth: required """ is_detailed = request.GET.get('detailed', '1') != '0' From d16edc333dc4d02e776f9dadad045d94d44c625d Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 10 Jul 2019 10:28:42 -0700 Subject: [PATCH 6/6] wat --- src/sentry/api/endpoints/organization_details.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sentry/api/endpoints/organization_details.py b/src/sentry/api/endpoints/organization_details.py index 61436ef5a01680..3210140a37791a 100644 --- a/src/sentry/api/endpoints/organization_details.py +++ b/src/sentry/api/endpoints/organization_details.py @@ -319,7 +319,7 @@ def get(self, request, organization): :auth: required """ is_detailed = request.GET.get('detailed', '1') != '0' - serializer = org_serializers.DetailedOrganizationProjectsAndTeamsSerializer if is_detailed else org_serializers.DetailedOrganizationSerializerWithProjectsAndTeams + serializer = org_serializers.DetailedOrganizationSerializerWithProjectsAndTeams if is_detailed else org_serializers.DetailedOrganizationSerializer context = serialize( organization, request.user,