Skip to content
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 10 commits into from
Jun 14, 2024
16 changes: 16 additions & 0 deletions src/sentry/api/serializers/models/groupsearchview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sentry.api.serializers import Serializer, register
from sentry.models.groupsearchview import GroupSearchView


@register(GroupSearchView)
class GroupSearchViewSerializer(Serializer):
def serialize(self, obj, attrs, user):
MichaelSun48 marked this conversation as resolved.
Show resolved Hide resolved
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,
}
6 changes: 6 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
GroupEventsEndpoint,
OrganizationActivityEndpoint,
OrganizationGroupIndexEndpoint,
OrganizationGroupSearchViewsEndpoint,
OrganizationReleasePreviousCommitsEndpoint,
OrganizationSearchesEndpoint,
ProjectStacktraceLinkEndpoint,
Expand Down Expand Up @@ -1703,6 +1704,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
),
name="sentry-api-0-organization-monitor-check-in-attachment",
),
re_path(
r"^(?P<organization_id_or_slug>[^\/]+)/group-search-views/$",
OrganizationGroupSearchViewsEndpoint.as_view(),
name="sentry-api-0-organization-group-search-views",
),
# Pinned and saved search
re_path(
r"^(?P<organization_id_or_slug>[^\/]+)/pinned-searches/$",
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/issues/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from .group_events import GroupEventsEndpoint
from .organization_activity import OrganizationActivityEndpoint
from .organization_group_index import OrganizationGroupIndexEndpoint
from .organization_group_search_views import OrganizationGroupSearchViewsEndpoint
from .organization_release_previous_commits import OrganizationReleasePreviousCommitsEndpoint
from .organization_searches import OrganizationSearchesEndpoint
from .project_stacktrace_link import ProjectStacktraceLinkEndpoint
Expand All @@ -12,6 +13,7 @@
"GroupEventsEndpoint",
"OrganizationActivityEndpoint",
"OrganizationGroupIndexEndpoint",
"OrganizationGroupSearchViewsEndpoint",
"OrganizationReleasePreviousCommitsEndpoint",
"OrganizationSearchesEndpoint",
"ProjectStacktraceLinkEndpoint",
Expand Down
68 changes: 68 additions & 0 deletions src/sentry/issues/endpoints/organization_group_search_views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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
from sentry.models.groupsearchview import GroupSearchView
from sentry.models.organization import Organization

DEFAULT_VIEWS = [
MichaelSun48 marked this conversation as resolved.
Show resolved Hide resolved
{
"id": "",
"name": "Prioritized",
"query": "is:unresolved issue.priority:[high, medium]",
"querySort": "date",
MichaelSun48 marked this conversation as resolved.
Show resolved Hide resolved
"position": 0,
}
]


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_405_METHOD_NOT_ALLOWED)
MichaelSun48 marked this conversation as resolved.
Show resolved Hide resolved

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()),
)
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
Loading