1
1
from django .contrib .auth .models import AnonymousUser
2
- from django .db import IntegrityError , router , transaction
3
2
from django .db .models import Count , F , OuterRef , Q , Subquery
4
3
from rest_framework import serializers , status
5
4
from rest_framework .request import Request
10
9
from sentry .api .api_publish_status import ApiPublishStatus
11
10
from sentry .api .base import region_silo_endpoint
12
11
from sentry .api .bases .organization import OrganizationEndpoint , OrganizationPermission
13
- from sentry .api .paginator import ChainPaginator , OffsetPaginator
12
+ from sentry .api .paginator import ChainPaginator
14
13
from sentry .api .serializers import serialize
15
14
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
21
16
from sentry .models .groupsearchview import GroupSearchView , GroupSearchViewVisibility
22
17
from sentry .models .groupsearchviewlastvisited import GroupSearchViewLastVisited
23
18
from sentry .models .groupsearchviewstarred import GroupSearchViewStarred
@@ -31,7 +26,6 @@ class MemberPermission(OrganizationPermission):
31
26
scope_map = {
32
27
"GET" : ["member:read" , "member:write" ],
33
28
"POST" : ["member:read" , "member:write" ],
34
- "PUT" : ["member:read" , "member:write" ],
35
29
}
36
30
37
31
@@ -68,7 +62,6 @@ class OrganizationGroupSearchViewsEndpoint(OrganizationEndpoint):
68
62
publish_status = {
69
63
"GET" : ApiPublishStatus .EXPERIMENTAL ,
70
64
"POST" : ApiPublishStatus .EXPERIMENTAL ,
71
- "PUT" : ApiPublishStatus .EXPERIMENTAL ,
72
65
}
73
66
owner = ApiOwner .ISSUES
74
67
permission_classes = (MemberPermission ,)
@@ -236,145 +229,6 @@ def post(self, request: Request, organization: Organization) -> Response:
236
229
status = status .HTTP_201_CREATED ,
237
230
)
238
231
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
-
378
232
379
233
def pick_default_project (org : Organization , user : User | AnonymousUser ) -> int | None :
380
234
user_teams = Team .objects .get_for_user (organization = org , user = user )
0 commit comments