-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(aci): Update API to allow filter monitors by assignee #95501
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
Merged
+298
−1
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
7580ceb
feat(aci): APIs to allow filter monitors by assignee
leedongwei 9d41b27
fix(pr): Add API tests for querying assignees
leedongwei f3dd95e
fix(pr): Types
leedongwei e349f18
fix(pr): Accidentally deleted test
leedongwei faf3be8
fix(pr): Cursor PR reviews
leedongwei 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 hidden or 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 hidden or 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 |
---|---|---|
@@ -1,12 +1,14 @@ | ||
from unittest import mock | ||
|
||
from django.db.models import Q | ||
from rest_framework.exceptions import ErrorDetail | ||
|
||
from sentry.api.serializers import serialize | ||
from sentry.grouping.grouptype import ErrorGroupType | ||
from sentry.incidents.grouptype import MetricIssue | ||
from sentry.incidents.models.alert_rule import AlertRuleDetectionType | ||
from sentry.models.environment import Environment | ||
from sentry.search.utils import _HACKY_INVALID_USER | ||
from sentry.snuba.dataset import Dataset | ||
from sentry.snuba.models import ( | ||
QuerySubscription, | ||
|
@@ -19,6 +21,7 @@ | |
from sentry.testutils.silo import region_silo_test | ||
from sentry.uptime.grouptype import UptimeDomainCheckFailure | ||
from sentry.uptime.types import DATA_SOURCE_UPTIME_SUBSCRIPTION | ||
from sentry.workflow_engine.endpoints.organization_detector_index import convert_assignee_values | ||
from sentry.workflow_engine.models import DataCondition, DataConditionGroup, DataSource, Detector | ||
from sentry.workflow_engine.models.data_condition import Condition | ||
from sentry.workflow_engine.models.detector_workflow import DetectorWorkflow | ||
|
@@ -275,6 +278,191 @@ def test_general_query(self): | |
) | ||
assert {d["name"] for d in response3.data} == {detector.name, detector2.name} | ||
|
||
def test_query_by_assignee_user_email(self): | ||
user = self.create_user(email="[email protected]") | ||
self.create_member(organization=self.organization, user=user) | ||
|
||
assigned_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Assigned Detector", | ||
type=MetricIssue.slug, | ||
owner_user_id=user.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Unassigned Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": f"assignee:{user.email}"}, | ||
) | ||
assert {d["name"] for d in response.data} == {assigned_detector.name} | ||
|
||
def test_query_by_assignee_user_username(self): | ||
user = self.create_user(username="testuser") | ||
self.create_member(organization=self.organization, user=user) | ||
|
||
assigned_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Assigned Detector", | ||
type=MetricIssue.slug, | ||
owner_user_id=user.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Unassigned Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": f"assignee:{user.username}"}, | ||
) | ||
assert {d["name"] for d in response.data} == {assigned_detector.name} | ||
|
||
def test_query_by_assignee_team(self): | ||
team = self.create_team(organization=self.organization, slug="test-team") | ||
self.project.add_team(team) | ||
|
||
assigned_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Team Detector", | ||
type=MetricIssue.slug, | ||
owner_team_id=team.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Unassigned Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": f"assignee:#{team.slug}"}, | ||
) | ||
assert {d["name"] for d in response.data} == {assigned_detector.name} | ||
|
||
def test_query_by_assignee_me(self): | ||
self.login_as(user=self.user) | ||
|
||
assigned_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="My Detector", | ||
type=MetricIssue.slug, | ||
owner_user_id=self.user.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Other Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": "assignee:me"}, | ||
) | ||
assert {d["name"] for d in response.data} == {assigned_detector.name} | ||
|
||
def test_query_by_assignee_none(self): | ||
user = self.create_user() | ||
self.create_member(organization=self.organization, user=user) | ||
team = self.create_team(organization=self.organization) | ||
|
||
self.create_detector( | ||
project_id=self.project.id, | ||
name="User Assigned", | ||
type=MetricIssue.slug, | ||
owner_user_id=user.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Team Assigned", | ||
type=MetricIssue.slug, | ||
owner_team_id=team.id, | ||
) | ||
unassigned_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Unassigned Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": "assignee:none"}, | ||
) | ||
assert {d["name"] for d in response.data} == {unassigned_detector.name} | ||
|
||
def test_query_by_assignee_multiple_values(self): | ||
user = self.create_user(email="[email protected]") | ||
self.create_member(organization=self.organization, user=user) | ||
team = self.create_team(organization=self.organization, slug="test-team") | ||
self.project.add_team(team) | ||
|
||
detector1 = self.create_detector( | ||
project_id=self.project.id, | ||
name="Detector 1", | ||
type=MetricIssue.slug, | ||
owner_user_id=user.id, | ||
) | ||
detector2 = self.create_detector( | ||
project_id=self.project.id, | ||
name="Detector 2", | ||
type=MetricIssue.slug, | ||
owner_team_id=team.id, | ||
) | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Other Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={ | ||
"project": self.project.id, | ||
"query": f"assignee:[{user.email}, #{team.slug}]", | ||
}, | ||
) | ||
assert {d["name"] for d in response.data} == {detector1.name, detector2.name} | ||
|
||
def test_query_by_assignee_negation(self): | ||
user = self.create_user(email="[email protected]") | ||
self.create_member(organization=self.organization, user=user) | ||
|
||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Excluded Detector", | ||
type=MetricIssue.slug, | ||
owner_user_id=user.id, | ||
) | ||
included_detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Included Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": f"!assignee:{user.email}"}, | ||
) | ||
assert {d["name"] for d in response.data} == {included_detector.name} | ||
|
||
def test_query_by_assignee_invalid_user(self): | ||
self.create_detector( | ||
project_id=self.project.id, | ||
name="Valid Detector", | ||
type=MetricIssue.slug, | ||
) | ||
|
||
# Query with non-existent user should return no results | ||
response = self.get_success_response( | ||
self.organization.slug, | ||
qs_params={"project": self.project.id, "query": "assignee:[email protected]"}, | ||
) | ||
assert len(response.data) == 0 | ||
|
||
|
||
@region_silo_test | ||
@apply_feature_flag_on_cls("organizations:incidents") | ||
|
@@ -610,3 +798,69 @@ def test_owner_not_in_organization(self): | |
status_code=400, | ||
) | ||
assert "owner" in response.data | ||
|
||
|
||
@region_silo_test | ||
class ConvertAssigneeValuesTest(APITestCase): | ||
"""Test the convert_assignee_values function""" | ||
|
||
def setUp(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible for us to have a test that actually filters by assignee? |
||
super().setUp() | ||
self.user = self.create_user() | ||
self.team = self.create_team(organization=self.organization) | ||
self.other_user = self.create_user() | ||
self.create_member(organization=self.organization, user=self.other_user) | ||
self.projects = [self.project] | ||
|
||
def test_convert_assignee_values_user_email(self): | ||
result = convert_assignee_values([self.user.email], self.projects, self.user) | ||
expected = Q(owner_user_id=self.user.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_user_username(self): | ||
result = convert_assignee_values([self.user.username], self.projects, self.user) | ||
expected = Q(owner_user_id=self.user.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_team_slug(self): | ||
result = convert_assignee_values([f"#{self.team.slug}"], self.projects, self.user) | ||
expected = Q(owner_team_id=self.team.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_me(self): | ||
result = convert_assignee_values(["me"], self.projects, self.user) | ||
expected = Q(owner_user_id=self.user.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_none(self): | ||
result = convert_assignee_values(["none"], self.projects, self.user) | ||
expected = Q(owner_team_id__isnull=True, owner_user_id__isnull=True) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_multiple(self): | ||
result = convert_assignee_values( | ||
[str(self.user.email), f"#{self.team.slug}"], self.projects, self.user | ||
) | ||
expected = Q(owner_user_id=self.user.id) | Q(owner_team_id=self.team.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_mixed(self): | ||
result = convert_assignee_values( | ||
["me", "none", f"#{self.team.slug}"], self.projects, self.user | ||
) | ||
expected = ( | ||
Q(owner_user_id=self.user.id) | ||
| Q(owner_team_id__isnull=True, owner_user_id__isnull=True) | ||
| Q(owner_team_id=self.team.id) | ||
) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_invalid(self): | ||
result = convert_assignee_values(["999999"], self.projects, self.user) | ||
expected = Q(owner_user_id=_HACKY_INVALID_USER.id) | ||
self.assertEqual(str(result), str(expected)) | ||
|
||
def test_convert_assignee_values_empty(self): | ||
result = convert_assignee_values([], self.projects, self.user) | ||
expected = Q() | ||
self.assertEqual(str(result), str(expected)) |
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.
super nit: is
'!='
defined as a literal anywhere? just a little nervous about 'magic' strings.Uh oh!
There was an error while loading. Please reload this page.
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.
Unfortunately not. It's a repeated pattern in the codebase seen in other parts of the product too.