Skip to content

Commit 46392e1

Browse files
committed
feat: Migrate organizations list to control
1 parent 8246cf3 commit 46392e1

File tree

1 file changed

+151
-0
lines changed

1 file changed

+151
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import logging
2+
from enum import Enum
3+
4+
from django.db.models import Count, Q
5+
from rest_framework.response import Response
6+
7+
from sentry.api.paginator import DateTimePaginator, OffsetPaginator
8+
from sentry.api.serializers import serialize
9+
from sentry.db.models.query import in_iexact
10+
from sentry.models.organization import Organization, OrganizationStatus
11+
from sentry.models.organizationmember import OrganizationMember
12+
from sentry.models.projectplatform import ProjectPlatform
13+
from sentry.search.utils import tokenize_query
14+
from sentry.users.services.user.service import user_service
15+
from sentry.utils.cursors import Cursor
16+
from sentry.utils.pagination_factory import PaginatorLike
17+
18+
logger = logging.getLogger(__name__)
19+
20+
21+
class SortBy(Enum):
22+
MEMBERS = "members"
23+
PROJECTS = "projects"
24+
DATE_ADDED = "date"
25+
26+
27+
class Show(Enum):
28+
ALL = "all"
29+
30+
31+
def list_organizations(
32+
*,
33+
actor_user_id: int,
34+
owner_only: bool = False,
35+
query: str | None = None,
36+
show: Show | None = None,
37+
sort_by: SortBy | None = "date",
38+
cursor: Cursor | None = None,
39+
per_page: int = 100,
40+
# actor specific stuff
41+
actor_is_active_superuser: bool = False,
42+
actor_organization_id: int | None = None,
43+
actor_project_id: int | None = None,
44+
):
45+
"""
46+
Return a list of organizations available to the authenticated session in a region.
47+
This is particularly useful for requests with a user bound context. For API key-based requests this will only return the organization that belongs to the key.
48+
"""
49+
queryset = Organization.objects.distinct()
50+
51+
if actor_project_id is not None:
52+
queryset = queryset.filter(id=actor_project_id.organization_id)
53+
elif actor_organization_id is not None:
54+
queryset = queryset.filter(id=actor_organization_id)
55+
56+
if owner_only:
57+
# This is used when closing an account
58+
# also fetches organizations in which you are a member of an owner team
59+
queryset = Organization.objects.get_organizations_where_user_is_owner(user_id=actor_user_id)
60+
org_results = []
61+
for org in sorted(queryset, key=lambda x: x.name):
62+
# O(N) query
63+
org_results.append(
64+
{"organization": serialize(org), "singleOwner": org.has_single_owner()}
65+
)
66+
67+
return Response(org_results)
68+
69+
elif not (actor_is_active_superuser and show == "all"):
70+
queryset = queryset.filter(
71+
id__in=OrganizationMember.objects.filter(user_id=actor_user_id).values("organization")
72+
)
73+
if actor_organization_id is not None and queryset.count() > 1:
74+
# If a token is limited to one organization, this endpoint should only return that one organization
75+
queryset = queryset.filter(id=actor_organization_id)
76+
77+
if query:
78+
tokens = tokenize_query(query)
79+
for key, value in tokens.items():
80+
if key == "query":
81+
query_value = " ".join(value)
82+
user_ids = {
83+
u.id
84+
for u in user_service.get_many_by_email(emails=[query_value], is_verified=False)
85+
}
86+
queryset = queryset.filter(
87+
Q(name__icontains=query_value)
88+
| Q(slug__icontains=query_value)
89+
| Q(member_set__user_id__in=user_ids)
90+
)
91+
elif key == "slug":
92+
queryset = queryset.filter(in_iexact("slug", value))
93+
elif key == "email":
94+
user_ids = {
95+
u.id for u in user_service.get_many_by_email(emails=value, is_verified=False)
96+
}
97+
queryset = queryset.filter(Q(member_set__user_id__in=user_ids))
98+
elif key == "platform":
99+
queryset = queryset.filter(
100+
project__in=ProjectPlatform.objects.filter(platform__in=value).values(
101+
"project_id"
102+
)
103+
)
104+
elif key == "id":
105+
queryset = queryset.filter(id__in=value)
106+
elif key == "status":
107+
try:
108+
queryset = queryset.filter(
109+
status__in=[OrganizationStatus[v.upper()] for v in value]
110+
)
111+
except KeyError:
112+
queryset = queryset.none()
113+
elif key == "member_id":
114+
queryset = queryset.filter(
115+
id__in=OrganizationMember.objects.filter(id__in=value).values("organization")
116+
)
117+
else:
118+
queryset = queryset.none()
119+
120+
paginator_cls: type[PaginatorLike]
121+
if sort_by == "members":
122+
queryset = queryset.annotate(member_count=Count("member_set"))
123+
order_by = "-member_count"
124+
paginator_cls = OffsetPaginator
125+
elif sort_by == "projects":
126+
queryset = queryset.annotate(project_count=Count("project"))
127+
order_by = "-project_count"
128+
paginator_cls = OffsetPaginator
129+
else:
130+
order_by = "-date_added"
131+
paginator_cls = DateTimePaginator
132+
133+
paginator = paginator_cls()
134+
cursor_result = paginator.get_result(
135+
limit=per_page,
136+
cursor=cursor,
137+
order_by=order_by,
138+
)
139+
140+
# TODO: missing user to serialize
141+
results = [serialize(org) for org in cursor_result.results]
142+
143+
return {
144+
"results": results,
145+
"cursor": {
146+
"next": cursor_result.next,
147+
"prev": cursor_result.prev,
148+
"hits": cursor_result.hits,
149+
"max_hits": cursor_result.max_hits,
150+
},
151+
}

0 commit comments

Comments
 (0)