-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(custom-views): add custom views get endpoint (#71942)
This endpoint creates the `GET` `organizations/<org_id_or_slug>/group-search-views/` Endpoint along with some tests. This endpoint will be responsible for fetching a user's custom views within an organization.⚠️ This PR is dependent on #71731 being ~~merged~~ run in production, and this branch will need to be rebased upon that happening
- Loading branch information
1 parent
9051647
commit f98a882
Showing
6 changed files
with
222 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing import TypedDict | ||
|
||
from sentry.api.serializers import Serializer, register | ||
from sentry.models.groupsearchview import GroupSearchView | ||
from sentry.models.savedsearch import SORT_LITERALS | ||
|
||
|
||
class GroupSearchViewSerializerResponse(TypedDict): | ||
id: str | ||
name: str | ||
query: str | ||
querySort: SORT_LITERALS | ||
position: int | ||
dateCreated: str | None | ||
dateUpdated: str | None | ||
|
||
|
||
@register(GroupSearchView) | ||
class GroupSearchViewSerializer(Serializer): | ||
def serialize(self, obj, attrs, user, **kwargs) -> GroupSearchViewSerializerResponse: | ||
return { | ||
"id": str(obj.id), | ||
"name": obj.name, | ||
"query": obj.query, | ||
"querySort": obj.query_sort, | ||
"position": obj.position, | ||
"dateCreated": obj.date_added, | ||
"dateUpdated": obj.date_updated, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
src/sentry/issues/endpoints/organization_group_search_views.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from rest_framework import status | ||
from rest_framework.request import Request | ||
from rest_framework.response import Response | ||
|
||
from sentry import features | ||
from sentry.api.api_owners import ApiOwner | ||
from sentry.api.api_publish_status import ApiPublishStatus | ||
from sentry.api.base import region_silo_endpoint | ||
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission | ||
from sentry.api.paginator import SequencePaginator | ||
from sentry.api.serializers import serialize | ||
from sentry.api.serializers.models.groupsearchview import ( | ||
GroupSearchViewSerializer, | ||
GroupSearchViewSerializerResponse, | ||
) | ||
from sentry.models.groupsearchview import GroupSearchView | ||
from sentry.models.organization import Organization | ||
from sentry.models.savedsearch import SortOptions | ||
|
||
DEFAULT_VIEWS: list[GroupSearchViewSerializerResponse] = [ | ||
{ | ||
"id": "", | ||
"name": "Prioritized", | ||
"query": "is:unresolved issue.priority:[high, medium]", | ||
"querySort": SortOptions.DATE.value, | ||
"position": 0, | ||
"dateCreated": None, | ||
"dateUpdated": None, | ||
} | ||
] | ||
|
||
|
||
class MemberPermission(OrganizationPermission): | ||
scope_map = { | ||
"GET": ["member:read", "member:write"], | ||
} | ||
|
||
|
||
@region_silo_endpoint | ||
class OrganizationGroupSearchViewsEndpoint(OrganizationEndpoint): | ||
publish_status = { | ||
"GET": ApiPublishStatus.EXPERIMENTAL, | ||
} | ||
owner = ApiOwner.ISSUES | ||
permission_classes = (MemberPermission,) | ||
|
||
def get(self, request: Request, organization: Organization) -> Response: | ||
""" | ||
List the current organization member's custom views | ||
````````````````````````````````````````` | ||
Retrieve a list of custom views for the current organization member. | ||
""" | ||
if not features.has("organizations:issue-stream-custom-views", organization): | ||
return Response(status=status.HTTP_404_NOT_FOUND) | ||
|
||
query = GroupSearchView.objects.filter(organization=organization, user_id=request.user.id) | ||
|
||
# Return only the prioritized view if user has no custom views yet | ||
if not query.exists(): | ||
return self.paginate( | ||
request=request, | ||
paginator=SequencePaginator( | ||
[(idx, view) for idx, view in enumerate(DEFAULT_VIEWS)] | ||
), | ||
on_results=lambda results: serialize(results, request.user), | ||
) | ||
|
||
return self.paginate( | ||
request=request, | ||
queryset=query, | ||
order_by="position", | ||
on_results=lambda x: serialize(x, request.user, serializer=GroupSearchViewSerializer()), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
tests/sentry/issues/endpoints/test_organization_group_search_views.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
from sentry.api.serializers.base import serialize | ||
from sentry.models.groupsearchview import GroupSearchView | ||
from sentry.testutils.cases import APITestCase | ||
from sentry.testutils.helpers.features import with_feature | ||
|
||
|
||
class OrganizationGroupSearchViewsTest(APITestCase): | ||
endpoint = "sentry-api-0-organization-group-search-views" | ||
method = "get" | ||
|
||
def create_base_data(self): | ||
user_1 = self.user | ||
self.user_2 = self.create_user() | ||
self.user_3 = self.create_user() | ||
|
||
self.create_member(organization=self.organization, user=self.user_2) | ||
self.create_member(organization=self.organization, user=self.user_3) | ||
|
||
first_custom_view_user_one = GroupSearchView.objects.create( | ||
name="Custom View One", | ||
organization=self.organization, | ||
user_id=user_1.id, | ||
query="is:unresolved", | ||
query_sort="date", | ||
position=0, | ||
) | ||
|
||
# This is out of order to test that the endpoint returns the views in the correct order | ||
third_custom_view_user_one = GroupSearchView.objects.create( | ||
name="Custom View Three", | ||
organization=self.organization, | ||
user_id=user_1.id, | ||
query="is:ignored", | ||
query_sort="freq", | ||
position=2, | ||
) | ||
|
||
second_custom_view_user_one = GroupSearchView.objects.create( | ||
name="Custom View Two", | ||
organization=self.organization, | ||
user_id=user_1.id, | ||
query="is:resolved", | ||
query_sort="new", | ||
position=1, | ||
) | ||
|
||
first_custom_view_user_two = GroupSearchView.objects.create( | ||
name="Custom View One", | ||
organization=self.organization, | ||
user_id=self.user_2.id, | ||
query="is:unresolved", | ||
query_sort="date", | ||
position=0, | ||
) | ||
|
||
second_custom_view_user_two = GroupSearchView.objects.create( | ||
name="Custom View Two", | ||
organization=self.organization, | ||
user_id=self.user_2.id, | ||
query="is:resolved", | ||
query_sort="new", | ||
position=1, | ||
) | ||
|
||
return { | ||
"user_one_views": [ | ||
first_custom_view_user_one, | ||
second_custom_view_user_one, | ||
third_custom_view_user_one, | ||
], | ||
"user_two_views": [first_custom_view_user_two, second_custom_view_user_two], | ||
} | ||
|
||
@with_feature({"organizations:issue-stream-custom-views": True}) | ||
def test_get_user_one_custom_views(self): | ||
objs = self.create_base_data() | ||
|
||
self.login_as(user=self.user) | ||
response = self.get_success_response(self.organization.slug) | ||
|
||
assert response.data == serialize(objs["user_one_views"]) | ||
|
||
@with_feature({"organizations:issue-stream-custom-views": True}) | ||
def test_get_user_two_custom_views(self): | ||
objs = self.create_base_data() | ||
|
||
self.login_as(user=self.user_2) | ||
response = self.get_success_response(self.organization.slug) | ||
|
||
assert response.data == serialize(objs["user_two_views"]) | ||
|
||
@with_feature({"organizations:issue-stream-custom-views": True}) | ||
def test_get_default_views(self): | ||
self.create_base_data() | ||
|
||
self.login_as(user=self.user_3) | ||
response = self.get_success_response(self.organization.slug) | ||
assert len(response.data) == 1 | ||
|
||
view = response.data[0] | ||
|
||
assert view["name"] == "Prioritized" | ||
assert view["query"] == "is:unresolved issue.priority:[high, medium]" | ||
assert view["querySort"] == "date" | ||
assert view["position"] == 0 |