-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(custom-views): add custom views get endpoint #71942
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
6312bbb
Create endpoint, serializer, and tests
MichaelSun48 f4a126e
Added url and export
MichaelSun48 f078be3
Add method to tests
MichaelSun48 0ab0957
Fix broken test
MichaelSun48 aa9de10
Remove references to Put/post endpoint
MichaelSun48 066b6f6
gate endpoint behind feature flag, add decorator to tests
MichaelSun48 a4f03f3
Return default view if user has none
MichaelSun48 2ec387c
Return 404 if no flag
MichaelSun48 99d35cb
better typing
MichaelSun48 94d7a72
Change serialize return type, make type descriptions better
MichaelSun48 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) | ||
MichaelSun48 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Return only the prioritized view if user has no custom views yet | ||
MichaelSun48 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this not be typed as
SortOptions
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried for like an hour to get this to work before just giving up. For some reason, mypy refuses to enforce the type if I set it to
SortOptions
- I setquerySort
of aview
instance to"awioef"
and mypy thought it was fine.