Skip to content

Commit 96bdadb

Browse files
MichaelSun48iamrajjoshi
authored andcommitted
ref(shared-views): Delete default views logic and PUT endpoint (#91352)
With the release of shared views, there are a few clean up things to do on the backend: 1. Having no starred views is a valid state now, so we should no longer return a default view if a user has no starred views. 2. The bulk update endpoint is now completely deprecated, since it was only used in the old UI. We can now delete that endpoint along with all of its helper functions and serializers (thank god). I confirmed that traffic to that endpoint has gone to 0 since the release yesterday (internal [discover query](https://sentry.sentry.io/explore/discover/results/?field=count()&name=Deprecated%20Issue%20Views%20Endpoints&query=transaction:/api/0/organizations/%7Borganization_id_or_slug%7D/group-search-views/%20http.method:PUT&queryDataset=transaction-like&sort=count&statsPeriod=7d&yAxis=count()) I'll probably merge this early next week once we are sure the release is stable
1 parent 64a6f88 commit 96bdadb

File tree

4 files changed

+4
-686
lines changed

4 files changed

+4
-686
lines changed

src/sentry/api/serializers/rest_framework/groupsearchview.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,6 @@ def validate(self, data) -> GroupSearchViewValidatorResponse:
6565
return data
6666

6767

68-
class GroupSearchViewValidator(serializers.Serializer):
69-
# TODO(msun): Remove min_length when tabbed views are removed
70-
views = serializers.ListField(
71-
child=ViewValidator(), required=True, min_length=1, max_length=MAX_VIEWS
72-
)
73-
74-
def validate(self, data):
75-
return data
76-
77-
7868
class GroupSearchViewPostValidator(ViewValidator):
7969
starred = serializers.BooleanField(required=False)
8070

src/sentry/issues/endpoints/organization_group_search_views.py

Lines changed: 2 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from django.contrib.auth.models import AnonymousUser
2-
from django.db import IntegrityError, router, transaction
32
from django.db.models import Count, F, OuterRef, Q, Subquery
43
from rest_framework import serializers, status
54
from rest_framework.request import Request
@@ -10,14 +9,10 @@
109
from sentry.api.api_publish_status import ApiPublishStatus
1110
from sentry.api.base import region_silo_endpoint
1211
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
13-
from sentry.api.paginator import ChainPaginator, OffsetPaginator
12+
from sentry.api.paginator import ChainPaginator
1413
from sentry.api.serializers import serialize
1514
from sentry.api.serializers.models.groupsearchview import GroupSearchViewSerializer
16-
from sentry.api.serializers.rest_framework.groupsearchview import (
17-
GroupSearchViewPostValidator,
18-
GroupSearchViewValidator,
19-
GroupSearchViewValidatorResponse,
20-
)
15+
from sentry.api.serializers.rest_framework.groupsearchview import GroupSearchViewPostValidator
2116
from sentry.models.groupsearchview import GroupSearchView, GroupSearchViewVisibility
2217
from sentry.models.groupsearchviewlastvisited import GroupSearchViewLastVisited
2318
from sentry.models.groupsearchviewstarred import GroupSearchViewStarred
@@ -31,7 +26,6 @@ class MemberPermission(OrganizationPermission):
3126
scope_map = {
3227
"GET": ["member:read", "member:write"],
3328
"POST": ["member:read", "member:write"],
34-
"PUT": ["member:read", "member:write"],
3529
}
3630

3731

@@ -68,7 +62,6 @@ class OrganizationGroupSearchViewsEndpoint(OrganizationEndpoint):
6862
publish_status = {
6963
"GET": ApiPublishStatus.EXPERIMENTAL,
7064
"POST": ApiPublishStatus.EXPERIMENTAL,
71-
"PUT": ApiPublishStatus.EXPERIMENTAL,
7265
}
7366
owner = ApiOwner.ISSUES
7467
permission_classes = (MemberPermission,)
@@ -236,145 +229,6 @@ def post(self, request: Request, organization: Organization) -> Response:
236229
status=status.HTTP_201_CREATED,
237230
)
238231

239-
def put(self, request: Request, organization: Organization) -> Response:
240-
"""
241-
Bulk updates the current organization member's custom views. This endpoint
242-
will delete any views that are not included in the request, add views if
243-
they are new, and update existing views if they are included in the request.
244-
This endpoint is explcititly designed to be used by our frontend.
245-
"""
246-
if not features.has(
247-
"organizations:issue-stream-custom-views", organization, actor=request.user
248-
):
249-
return Response(status=status.HTTP_404_NOT_FOUND)
250-
251-
serializer = GroupSearchViewValidator(
252-
data=request.data, context={"organization": organization}
253-
)
254-
255-
if not serializer.is_valid():
256-
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
257-
258-
validated_data = serializer.validated_data
259-
260-
try:
261-
with transaction.atomic(using=router.db_for_write(GroupSearchView)):
262-
new_view_ids_state = bulk_update_views(
263-
organization, request.user.id, validated_data["views"]
264-
)
265-
except IntegrityError:
266-
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
267-
268-
new_user_starred_views = (
269-
GroupSearchView.objects.filter(id__in=new_view_ids_state)
270-
.prefetch_related("projects")
271-
.order_by("groupsearchviewstarred__position")
272-
)
273-
274-
has_global_views = features.has("organizations:global-views", organization)
275-
default_project = pick_default_project(organization, request.user)
276-
277-
return self.paginate(
278-
request=request,
279-
queryset=new_user_starred_views,
280-
paginator_cls=OffsetPaginator,
281-
on_results=lambda x: serialize(
282-
x,
283-
request.user,
284-
serializer=GroupSearchViewSerializer(
285-
has_global_views=has_global_views,
286-
default_project=default_project,
287-
organization=organization,
288-
),
289-
),
290-
)
291-
292-
293-
def bulk_update_views(
294-
org: Organization, user_id: int, views: list[GroupSearchViewValidatorResponse]
295-
) -> list[int]:
296-
existing_view_ids = [view["id"] for view in views if "id" in view]
297-
298-
_delete_missing_views(org, user_id, view_ids_to_keep=existing_view_ids)
299-
created_view_ids = []
300-
for idx, view in enumerate(views):
301-
if "id" not in view:
302-
created_view_ids.append(_create_view(org, user_id, view, position=idx).id)
303-
else:
304-
created_view_ids.append(_update_existing_view(org, user_id, view, position=idx).id)
305-
306-
return created_view_ids
307-
308-
309-
def _delete_missing_views(org: Organization, user_id: int, view_ids_to_keep: list[str]) -> None:
310-
GroupSearchView.objects.filter(organization=org, user_id=user_id).exclude(
311-
id__in=view_ids_to_keep
312-
).delete()
313-
314-
315-
def _update_existing_view(
316-
org: Organization, user_id: int, view: GroupSearchViewValidatorResponse, position: int
317-
) -> GroupSearchView:
318-
try:
319-
gsv = GroupSearchView.objects.get(id=view["id"], user_id=user_id)
320-
gsv.name = view["name"]
321-
gsv.query = view["query"]
322-
gsv.query_sort = view["querySort"]
323-
gsv.is_all_projects = view.get("isAllProjects", False)
324-
325-
if "projects" in view:
326-
gsv.projects.set(view["projects"])
327-
328-
if "environments" in view:
329-
gsv.environments = view["environments"]
330-
331-
if "timeFilters" in view:
332-
gsv.time_filters = view["timeFilters"]
333-
334-
gsv.save()
335-
GroupSearchViewStarred.objects.update_or_create(
336-
organization=org,
337-
user_id=user_id,
338-
group_search_view=gsv,
339-
defaults={
340-
"position": position,
341-
"visibility": GroupSearchViewVisibility.ORGANIZATION,
342-
},
343-
)
344-
return gsv
345-
except GroupSearchView.DoesNotExist:
346-
# It is possible – though unlikely under normal circumstances – for a view to come in that
347-
# doesn't exist anymore. If, for example, the user has the issue stream open in separate
348-
# windows, deletes a view in one window, then updates it in the other before refreshing.
349-
# In this case, we decide to recreate the tab instead of leaving it deleted.
350-
return _create_view(org, user_id, view, position)
351-
352-
353-
def _create_view(
354-
org: Organization, user_id: int, view: GroupSearchViewValidatorResponse, position: int
355-
) -> GroupSearchView:
356-
gsv = GroupSearchView.objects.create(
357-
organization=org,
358-
user_id=user_id,
359-
name=view["name"],
360-
query=view["query"],
361-
query_sort=view["querySort"],
362-
is_all_projects=view.get("isAllProjects", False),
363-
environments=view.get("environments", []),
364-
time_filters=view.get("timeFilters", {"period": "14d"}),
365-
visibility=GroupSearchViewVisibility.ORGANIZATION,
366-
)
367-
if "projects" in view:
368-
gsv.projects.set(view["projects"] or [])
369-
370-
GroupSearchViewStarred.objects.create(
371-
organization=org,
372-
user_id=user_id,
373-
group_search_view=gsv,
374-
position=position,
375-
)
376-
return gsv
377-
378232

379233
def pick_default_project(org: Organization, user: User | AnonymousUser) -> int | None:
380234
user_teams = Team.objects.get_for_user(organization=org, user=user)

src/sentry/issues/endpoints/organization_group_search_views_starred.py

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@
88
from sentry.api.api_publish_status import ApiPublishStatus
99
from sentry.api.base import region_silo_endpoint
1010
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
11-
from sentry.api.paginator import SequencePaginator
1211
from sentry.api.serializers import serialize
1312
from sentry.api.serializers.models.groupsearchviewstarred import GroupSearchViewStarredSerializer
14-
from sentry.models.groupsearchview import DEFAULT_VIEWS
1513
from sentry.models.groupsearchviewstarred import GroupSearchViewStarred
1614
from sentry.models.organization import Organization
1715
from sentry.models.project import Project
@@ -57,32 +55,6 @@ def get(self, request: Request, organization: Organization) -> Response:
5755
organization=organization, user_id=request.user.id
5856
)
5957

60-
# TODO(msun): Remove when tabbed views are deprecated
61-
62-
if not starred_views.exists() and not features.has(
63-
"organizations:enforce-stacked-navigation", organization
64-
):
65-
return self.paginate(
66-
request=request,
67-
paginator=SequencePaginator(
68-
[
69-
(
70-
idx,
71-
{
72-
**view,
73-
"projects": (
74-
[]
75-
if has_global_views
76-
else [pick_default_project(organization, request.user)]
77-
),
78-
},
79-
)
80-
for idx, view in enumerate(DEFAULT_VIEWS)
81-
]
82-
),
83-
on_results=lambda results: serialize(results, request.user),
84-
)
85-
8658
return self.paginate(
8759
request=request,
8860
queryset=starred_views,

0 commit comments

Comments
 (0)