Skip to content

Commit e5035c8

Browse files
authored
ref(shared-views): Remove position usage in groupsearchview serializer (#87297)
This PR refactors the groupsearchview serializer and consequently the `PUT` `/groupsearchviews` endpoint by removing the usage of the GroupSearchView.position column. This was the last non-test use case of the position column that needed to be removed before dropping the column.
1 parent d0c9dee commit e5035c8

File tree

3 files changed

+60
-22
lines changed

3 files changed

+60
-22
lines changed

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ class GroupSearchViewSerializerResponse(TypedDict):
1313
name: str
1414
query: str
1515
querySort: SORT_LITERALS
16-
position: int
1716
projects: list[int]
1817
isAllProjects: bool
1918
environments: list[str]
@@ -23,6 +22,10 @@ class GroupSearchViewSerializerResponse(TypedDict):
2322
dateUpdated: str
2423

2524

25+
class GroupSearchViewStarredSerializerResponse(GroupSearchViewSerializerResponse):
26+
position: int
27+
28+
2629
@register(GroupSearchView)
2730
class GroupSearchViewSerializer(Serializer):
2831
def __init__(self, *args, **kwargs):
@@ -67,7 +70,6 @@ def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewSerializerResp
6770
"name": obj.name,
6871
"query": obj.query,
6972
"querySort": obj.query_sort,
70-
"position": obj.position,
7173
"projects": projects,
7274
"isAllProjects": is_all_projects,
7375
"environments": obj.environments,
@@ -86,7 +88,7 @@ def __init__(self, *args, **kwargs):
8688
self.organization = kwargs.pop("organization", None)
8789
super().__init__(*args, **kwargs)
8890

89-
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewSerializerResponse:
91+
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewStarredSerializerResponse:
9092
serialized_view: GroupSearchViewSerializerResponse = serialize(
9193
obj.group_search_view,
9294
user,

src/sentry/issues/endpoints/organization_group_search_views.py

+45-16
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
from sentry.api.helpers.group_index.validators import ValidationError
1414
from sentry.api.paginator import SequencePaginator
1515
from sentry.api.serializers import serialize
16-
from sentry.api.serializers.models.groupsearchview import (
17-
GroupSearchViewSerializer,
18-
GroupSearchViewStarredSerializer,
19-
)
16+
from sentry.api.serializers.models.groupsearchview import GroupSearchViewStarredSerializer
2017
from sentry.api.serializers.rest_framework.groupsearchview import (
2118
GroupSearchViewValidator,
2219
GroupSearchViewValidatorResponse,
2320
)
2421
from sentry.models.groupsearchview import DEFAULT_TIME_FILTER, GroupSearchView
22+
from sentry.models.groupsearchviewlastvisited import GroupSearchViewLastVisited
2523
from sentry.models.groupsearchviewstarred import GroupSearchViewStarred
2624
from sentry.models.organization import Organization
2725
from sentry.models.project import Project
@@ -157,7 +155,9 @@ def put(self, request: Request, organization: Organization) -> Response:
157155

158156
try:
159157
with transaction.atomic(using=router.db_for_write(GroupSearchView)):
160-
bulk_update_views(organization, request.user.id, validated_data["views"])
158+
new_view_state = bulk_update_views(
159+
organization, request.user.id, validated_data["views"]
160+
)
161161
except IntegrityError as e:
162162
if (
163163
len(e.args) > 0
@@ -171,13 +171,38 @@ def put(self, request: Request, organization: Organization) -> Response:
171171
)
172172
return Response(status=status.HTTP_500_INTERNAL_SERVER_ERROR)
173173

174-
query = GroupSearchView.objects.filter(organization=organization, user_id=request.user.id)
174+
last_visited_views = GroupSearchViewLastVisited.objects.filter(
175+
organization=organization,
176+
user_id=request.user.id,
177+
group_search_view_id__in=[view.id for view in new_view_state],
178+
)
179+
last_visited_map = {lv.group_search_view_id: lv for lv in last_visited_views}
175180

176181
return self.paginate(
177182
request=request,
178-
queryset=query,
179-
order_by="position",
180-
on_results=lambda x: serialize(x, request.user, serializer=GroupSearchViewSerializer()),
183+
paginator=SequencePaginator(
184+
[
185+
(
186+
idx,
187+
{
188+
"id": str(view.id),
189+
"name": view.name,
190+
"query": view.query,
191+
"querySort": view.query_sort,
192+
"projects": list(view.projects.values_list("id", flat=True)),
193+
"isAllProjects": view.is_all_projects,
194+
"environments": view.environments,
195+
"timeFilters": view.time_filters,
196+
"dateCreated": view.date_added,
197+
"dateUpdated": view.date_updated,
198+
"lastVisited": last_visited_map.get(view.id, None),
199+
"position": idx,
200+
},
201+
)
202+
for idx, view in enumerate(new_view_state)
203+
]
204+
),
205+
on_results=lambda results: serialize(results, request.user),
181206
)
182207

183208

@@ -198,16 +223,18 @@ def validate_projects(
198223

199224
def bulk_update_views(
200225
org: Organization, user_id: int, views: list[GroupSearchViewValidatorResponse]
201-
) -> None:
226+
) -> list[GroupSearchView]:
202227
existing_view_ids = [view["id"] for view in views if "id" in view]
203228

204229
_delete_missing_views(org, user_id, view_ids_to_keep=existing_view_ids)
205-
230+
created_views = []
206231
for idx, view in enumerate(views):
207232
if "id" not in view:
208-
_create_view(org, user_id, view, position=idx)
233+
created_views.append(_create_view(org, user_id, view, position=idx))
209234
else:
210-
_update_existing_view(org, user_id, view, position=idx)
235+
created_views.append(_update_existing_view(org, user_id, view, position=idx))
236+
237+
return created_views
211238

212239

213240
def pick_default_project(org: Organization, user: User | AnonymousUser) -> int | None:
@@ -230,7 +257,7 @@ def _delete_missing_views(org: Organization, user_id: int, view_ids_to_keep: lis
230257

231258
def _update_existing_view(
232259
org: Organization, user_id: int, view: GroupSearchViewValidatorResponse, position: int
233-
) -> None:
260+
) -> GroupSearchView:
234261
try:
235262
gsv = GroupSearchView.objects.get(id=view["id"], user_id=user_id)
236263
gsv.name = view["name"]
@@ -255,17 +282,18 @@ def _update_existing_view(
255282
group_search_view=gsv,
256283
defaults={"position": position},
257284
)
285+
return gsv
258286
except GroupSearchView.DoesNotExist:
259287
# It is possible – though unlikely under normal circumstances – for a view to come in that
260288
# doesn't exist anymore. If, for example, the user has the issue stream open in separate
261289
# windows, deletes a view in one window, then updates it in the other before refreshing.
262290
# In this case, we decide to recreate the tab instead of leaving it deleted.
263-
_create_view(org, user_id, view, position)
291+
return _create_view(org, user_id, view, position)
264292

265293

266294
def _create_view(
267295
org: Organization, user_id: int, view: GroupSearchViewValidatorResponse, position: int
268-
) -> None:
296+
) -> GroupSearchView:
269297
gsv = GroupSearchView.objects.create(
270298
organization=org,
271299
user_id=user_id,
@@ -286,3 +314,4 @@ def _create_view(
286314
group_search_view=gsv,
287315
position=position,
288316
)
317+
return gsv

tests/sentry/issues/endpoints/test_organization_group_search_views.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from django.utils import timezone
33
from rest_framework.exceptions import ErrorDetail
44

5-
from sentry.api.serializers.base import serialize
65
from sentry.api.serializers.rest_framework.groupsearchview import GroupSearchViewValidatorResponse
76
from sentry.issues.endpoints.organization_group_search_views import DEFAULT_VIEWS
87
from sentry.models.groupsearchview import GroupSearchView
@@ -136,7 +135,12 @@ def test_get_user_one_custom_views(self) -> None:
136135
self.login_as(user=self.user)
137136
response = self.get_success_response(self.organization.slug)
138137

139-
assert response.data == serialize(objs["user_one_views"])
138+
assert response.data[0]["id"] == str(objs["user_one_views"][0].id)
139+
assert response.data[0]["position"] == 0
140+
assert response.data[1]["id"] == str(objs["user_one_views"][1].id)
141+
assert response.data[1]["position"] == 1
142+
assert response.data[2]["id"] == str(objs["user_one_views"][2].id)
143+
assert response.data[2]["position"] == 2
140144

141145
@with_feature({"organizations:issue-stream-custom-views": True})
142146
@with_feature({"organizations:global-views": True})
@@ -169,7 +173,10 @@ def test_get_user_two_custom_views(self) -> None:
169173
self.login_as(user=self.user_2)
170174
response = self.get_success_response(self.organization.slug)
171175

172-
assert response.data == serialize(objs["user_two_views"])
176+
assert response.data[0]["id"] == str(objs["user_two_views"][0].id)
177+
assert response.data[0]["position"] == 0
178+
assert response.data[1]["id"] == str(objs["user_two_views"][1].id)
179+
assert response.data[1]["position"] == 1
173180

174181
@with_feature({"organizations:issue-stream-custom-views": True})
175182
@with_feature({"organizations:global-views": True})

0 commit comments

Comments
 (0)