Browse Source

fix(workflow): Allow release query to contain a wildcard (#26650)

Scott Cooper 3 years ago
parent
commit
9561f267fd

+ 17 - 3
src/sentry/api/issue_search.py

@@ -1,5 +1,6 @@
 from dataclasses import asdict
 from functools import partial
+from typing import List, Union
 
 from sentry.api.event_search import AggregateFilter, SearchConfig, SearchValue, default_config
 from sentry.api.event_search import parse_search_query as base_parse_query
@@ -63,7 +64,16 @@ def convert_user_value(value, projects, user, environments):
     return [parse_user_value(username, user) for username in value]
 
 
-def convert_release_value(value, projects, user, environments):
+def convert_release_value(value, projects, user, environments) -> Union[str, List[str]]:
+    # TODO: This will make N queries. This should be ok, we don't typically have large
+    # lists of versions here, but we can look into batching it if needed.
+    releases = [parse_release(version, projects, environments) for version in value]
+    if len(releases) == 1:
+        return releases[0]
+    return releases
+
+
+def convert_first_release_value(value, projects, user, environments) -> List[str]:
     # TODO: This will make N queries. This should be ok, we don't typically have large
     # lists of versions here, but we can look into batching it if needed.
     return [parse_release(version, projects, environments) for version in value]
@@ -84,7 +94,7 @@ value_converters = {
     "assigned_to": convert_actor_or_none_value,
     "bookmarked_by": convert_user_value,
     "subscribed_by": convert_user_value,
-    "first_release": convert_release_value,
+    "first_release": convert_first_release_value,
     "release": convert_release_value,
     "status": convert_status_value,
 }
@@ -106,9 +116,13 @@ def convert_query_values(search_filters, projects, user, environments):
             new_value = converter(
                 to_list(search_filter.value.raw_value), projects, user, environments
             )
+            if isinstance(new_value, list):
+                operator = "IN" if search_filter.operator in EQUALITY_OPERATORS else "NOT IN"
+            else:
+                operator = "=" if search_filter.operator in EQUALITY_OPERATORS else "!="
             search_filter = search_filter._replace(
                 value=SearchValue(new_value),
-                operator="IN" if search_filter.operator in EQUALITY_OPERATORS else "NOT IN",
+                operator=operator,
             )
         elif isinstance(search_filter, AggregateFilter):
             raise InvalidSearchQuery(

+ 14 - 2
tests/sentry/api/test_issue_search.py

@@ -7,6 +7,7 @@ from sentry.api.event_search import (
 )
 from sentry.api.issue_search import (
     convert_actor_or_none_value,
+    convert_first_release_value,
     convert_query_values,
     convert_release_value,
     convert_user_value,
@@ -224,10 +225,21 @@ class ConvertUserValueTest(TestCase):
 
 class ConvertReleaseValueTest(TestCase):
     def test(self):
-        assert convert_release_value(["123"], [self.project], self.user, None) == ["123"]
+        assert convert_release_value(["123"], [self.project], self.user, None) == "123"
 
     def test_latest(self):
         release = self.create_release(self.project)
-        assert convert_release_value(["latest"], [self.project], self.user, None) == [
+        assert convert_release_value(["latest"], [self.project], self.user, None) == release.version
+        assert convert_release_value(["14.*"], [self.project], self.user, None) == "14.*"
+
+
+class ConvertFirstReleaseValueTest(TestCase):
+    def test(self):
+        assert convert_first_release_value(["123"], [self.project], self.user, None) == ["123"]
+
+    def test_latest(self):
+        release = self.create_release(self.project)
+        assert convert_first_release_value(["latest"], [self.project], self.user, None) == [
             release.version
         ]
+        assert convert_first_release_value(["14.*"], [self.project], self.user, None) == ["14.*"]

+ 18 - 0
tests/snuba/api/endpoints/test_organization_group_index.py

@@ -602,6 +602,24 @@ class GroupListTest(APITestCase, SnubaTestCase):
         assert len(issues) == 1
         assert int(issues[0]["id"]) == event.group.id
 
+    def test_lookup_by_release_wildcard(self):
+        self.login_as(self.user)
+        project = self.project
+        release = Release.objects.create(organization=project.organization, version="12345")
+        release.add_project(project)
+        event = self.store_event(
+            data={
+                "timestamp": iso_format(before_now(seconds=1)),
+                "tags": {"sentry:release": release.version},
+            },
+            project_id=project.id,
+        )
+
+        response = self.get_valid_response(release=release.version[:3] + "*")
+        issues = json.loads(response.content)
+        assert len(issues) == 1
+        assert int(issues[0]["id"]) == event.group.id
+
     def test_pending_delete_pending_merge_excluded(self):
         events = []
         for i in "abcd":

+ 15 - 0
tests/snuba/api/endpoints/test_project_group_index.py

@@ -293,6 +293,21 @@ class GroupListTest(APITestCase, SnubaTestCase):
         assert len(issues) == 1
         assert int(issues[0]["id"]) == group.id
 
+    def test_lookup_by_release_wildcard(self):
+        self.login_as(self.user)
+        version = "12345"
+        event = self.store_event(
+            data={"tags": {"sentry:release": version}}, project_id=self.project.id
+        )
+        group = event.group
+        release_wildcard = version[:3] + "*"
+        url = "{}?query={}".format(self.path, quote('release:"%s"' % release_wildcard))
+        response = self.client.get(url, format="json")
+        issues = json.loads(response.content)
+        assert response.status_code == 200
+        assert len(issues) == 1
+        assert int(issues[0]["id"]) == group.id
+
     def test_pending_delete_pending_merge_excluded(self):
         self.create_group(checksum="a" * 32, status=GroupStatus.PENDING_DELETION)
         group = self.create_group(checksum="b" * 32)