-
-
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(feedback): add organization feedback index GET endpoint #56065
Merged
Merged
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
782dd26
fix(replays api): change limit to be per_page
michellewzhang f8b0a6f
Revert "fix(replays api): change limit to be per_page"
michellewzhang 6658164
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang c0a34c8
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang 457e1fb
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang cdf3c8a
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang e829175
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang be1bf62
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang 5996201
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang 5705dc1
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang 33abf63
Merge branch 'master' of github.com:getsentry/sentry
michellewzhang 8aaaf4f
feat(feedback): add organization feedback index GET endpoint
michellewzhang 76c019d
add tests
michellewzhang 02444cc
update model with organization_id stored and add per_page for endpoint
michellewzhang 8ffe896
update endpoint to inherit from OrganizationEndpoint
michellewzhang d8dafc5
fix typing
michellewzhang 0a3c887
update naming to feedback_id
michellewzhang a3bd505
update snake case and remove unused code
michellewzhang 011a8f6
undo
michellewzhang 57228f7
update filter to be by org id
michellewzhang 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
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
35 changes: 35 additions & 0 deletions
35
src/sentry/feedback/endpoints/organization_feedback_index.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,35 @@ | ||
from __future__ import annotations | ||
|
||
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 | ||
from sentry.api.paginator import OffsetPaginator | ||
from sentry.api.serializers.base import serialize | ||
from sentry.feedback.models import Feedback | ||
from sentry.feedback.serializers import FeedbackSerializer | ||
from sentry.models import Organization | ||
|
||
|
||
@region_silo_endpoint | ||
class OrganizationFeedbackIndexEndpoint(OrganizationEndpoint): | ||
publish_status = { | ||
"GET": ApiPublishStatus.EXPERIMENTAL, | ||
} | ||
owner = ApiOwner.FEEDBACK | ||
|
||
def get(self, request: Request, organization: Organization) -> Response: | ||
if not features.has("organizations:user-feedback-ingest", organization, actor=request.user): | ||
return Response(status=404) | ||
|
||
feedback_list = Feedback.objects.filter(project_id__in=organization.project_set.all()) | ||
michellewzhang marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return self.paginate( | ||
request=request, | ||
queryset=feedback_list, | ||
on_results=lambda x: serialize(x, request.user, FeedbackSerializer()), | ||
paginator_cls=OffsetPaginator, | ||
) |
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
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,53 @@ | ||
from typing import Any, Optional, TypedDict | ||
|
||
from sentry.api.serializers.base import Serializer, register | ||
from sentry.feedback.models import Feedback | ||
|
||
|
||
class FeedbackResponseType(TypedDict): | ||
browser: Optional[Any] | ||
locale: Optional[Any] | ||
tags: Optional[Any] | ||
device: Optional[Any] | ||
os: Optional[Any] | ||
user: Optional[Any] | ||
replay_id: Optional[str] | ||
url: Optional[str] | ||
dist: Optional[str] | ||
sdk: Any | ||
contact_email: str | ||
environment: str | ||
feedback_id: str | ||
message: str | ||
platform: str | ||
project_id: str | ||
release: str | ||
status: str | ||
timestamp: str | ||
|
||
|
||
@register(Feedback) | ||
class FeedbackSerializer(Serializer): | ||
def serialize(self, obj, attrs, user, **kwargs) -> FeedbackResponseType: | ||
res: FeedbackResponseType = { | ||
"browser": obj.data.get("browser") or {}, | ||
"locale": obj.data.get("locale") or {}, | ||
"tags": obj.data.get("tags") or {}, | ||
"device": obj.data.get("device") or {}, | ||
"os": obj.data.get("os") or {}, | ||
"user": obj.data.get("user") or {}, | ||
"replay_id": obj.replay_id, | ||
"dist": obj.data.get("dist"), | ||
"sdk": obj.data.get("sdk"), | ||
"contact_email": obj.data.get("feedback").get("contact_email"), | ||
"environment": obj.data.get("environment"), | ||
"feedback_id": obj.feedback_id, | ||
"message": obj.message, | ||
"platform": obj.data.get("platform"), | ||
"project_id": obj.project_id, | ||
"release": obj.data.get("release"), | ||
"status": "unresolved", | ||
"timestamp": obj.date_added, | ||
"url": obj.url, | ||
} | ||
return res |
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
143 changes: 143 additions & 0 deletions
143
tests/sentry/feedback/test_organization_feedback_index.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,143 @@ | ||
import datetime | ||
import uuid | ||
|
||
from django.urls import reverse | ||
from rest_framework.exceptions import ErrorDetail | ||
|
||
from sentry.feedback.models import Feedback | ||
from sentry.testutils.cases import APITestCase | ||
|
||
|
||
class OrganizationFeedbackIndexTest(APITestCase): | ||
get_endpoint = "sentry-api-0-organization-feedback-index" | ||
post_endpoint = "sentry-api-0-feedback-ingest" | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.login_as(user=self.user) | ||
|
||
def test_get_feedback_list(self): | ||
# Successful GET | ||
Feedback.objects.create( | ||
data={ | ||
"environment": "production", | ||
"feedback": { | ||
"contact_email": "colton.allen@sentry.io", | ||
"message": "I really like this user-feedback feature!", | ||
"replay_id": "ec3b4dc8b79f417596f7a1aa4fcca5d2", | ||
"url": "https://docs.sentry.io/platforms/javascript/", | ||
}, | ||
"platform": "javascript", | ||
"release": "version@1.3", | ||
"sdk": {"name": "sentry.javascript.react", "version": "6.18.1"}, | ||
"tags": {"key": "value"}, | ||
"user": { | ||
"email": "username@example.com", | ||
"id": "123", | ||
"ip_address": "127.0.0.1", | ||
"name": "user", | ||
"username": "user2270129", | ||
}, | ||
"dist": "abc123", | ||
"contexts": {}, | ||
}, | ||
date_added=datetime.datetime.fromtimestamp(1234456), | ||
feedback_id=uuid.UUID("1ffe0775ac0f4417aed9de36d9f6f8dc"), | ||
url="https://docs.sentry.io/platforms/javascript/", | ||
message="I really like this user-feedback feature!", | ||
replay_id=uuid.UUID("ec3b4dc8b79f417596f7a1aa4fcca5d2"), | ||
project_id=self.project.id, | ||
organization_id=self.organization.id, | ||
) | ||
|
||
Feedback.objects.create( | ||
data={ | ||
"environment": "prod", | ||
"feedback": { | ||
"contact_email": "michelle.zhang@sentry.io", | ||
"message": "I also really like this user-feedback feature!", | ||
"replay_id": "zc3b5xy8b79f417596f7a1tt4fffa5d2", | ||
"url": "https://docs.sentry.io/platforms/electron/", | ||
}, | ||
"platform": "electron", | ||
"release": "version@1.3", | ||
"request": { | ||
"headers": { | ||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" | ||
} | ||
}, | ||
"sdk": {"name": "sentry.javascript.react", "version": "5.18.1"}, | ||
"tags": {"key": "value"}, | ||
"user": { | ||
"email": "username@example.com", | ||
"id": "123", | ||
"ip_address": "127.0.0.1", | ||
"name": "user", | ||
"username": "user2270129", | ||
}, | ||
"dist": "abc123", | ||
"contexts": {}, | ||
}, | ||
date_added=datetime.datetime.fromtimestamp(12344100333), | ||
feedback_id=uuid.UUID("2ffe0775ac0f4417aed9de36d9f6f8ab"), | ||
url="https://docs.sentry.io/platforms/electron/", | ||
message="I also really like this user-feedback feature!", | ||
replay_id=uuid.UUID("ec3b4dc8b79f417596f7a1aa4fcca5d2"), | ||
project_id=self.project.id, | ||
organization_id=self.organization.id, | ||
) | ||
|
||
with self.feature({"organizations:user-feedback-ingest": True}): | ||
path = reverse(self.get_endpoint, args=[self.organization.slug]) | ||
response = self.client.get(path) | ||
assert response.status_code == 200 | ||
|
||
# Should get what we have in the database | ||
assert len(response.data) == 2 | ||
# Test first item | ||
feedback = response.data[0] | ||
assert feedback["dist"] == "abc123" | ||
assert feedback["url"] == "https://docs.sentry.io/platforms/javascript/" | ||
assert feedback["message"] == "I really like this user-feedback feature!" | ||
assert feedback["feedback_id"] == uuid.UUID("1ffe0775ac0f4417aed9de36d9f6f8dc") | ||
assert feedback["platform"] == "javascript" | ||
assert feedback["sdk"]["name"] == "sentry.javascript.react" | ||
assert feedback["tags"]["key"] == "value" | ||
assert feedback["contact_email"] == "colton.allen@sentry.io" | ||
|
||
# Test second item | ||
feedback = response.data[1] | ||
assert feedback["environment"] == "prod" | ||
assert feedback["url"] == "https://docs.sentry.io/platforms/electron/" | ||
assert feedback["message"] == "I also really like this user-feedback feature!" | ||
assert feedback["feedback_id"] == uuid.UUID("2ffe0775ac0f4417aed9de36d9f6f8ab") | ||
assert feedback["platform"] == "electron" | ||
assert feedback["sdk"]["name"] == "sentry.javascript.react" | ||
assert feedback["sdk"]["version"] == "5.18.1" | ||
|
||
# Testing GET parameters | ||
# For now, only testing per_page; others (such as sort, query) are not well-defined or not necessary for MVP | ||
response = self.client.get( | ||
path=path, | ||
data={"per_page": 1}, | ||
content_type="application/json", | ||
) | ||
assert response.status_code == 200 | ||
assert len(response.data) == 1 | ||
|
||
def test_no_feature_enabled(self): | ||
# Unsuccessful GET | ||
with self.feature({"organizations:user-feedback-ingest": False}): | ||
path = reverse(self.get_endpoint, args=[self.organization.slug]) | ||
get_response = self.client.get(path) | ||
assert get_response.status_code == 404 | ||
|
||
def test_bad_slug_path(self): | ||
# Bad slug in path should lead to unsuccessful GET | ||
with self.feature({"organizations:user-feedback-ingest": True}): | ||
path = reverse(self.get_endpoint, args=["testslug123345"]) | ||
get_response = self.client.get(path) | ||
assert get_response.status_code == 404 | ||
assert get_response.data == { | ||
"detail": ErrorDetail(string="The requested resource does not exist", code="error") | ||
} |
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.
will add a shim in future PR for these fields