-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
229 additions
and
0 deletions.
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
59 changes: 59 additions & 0 deletions
59
src/sentry/workflow_engine/handlers/condition/latest_release_handler.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,59 @@ | ||
from typing import Any | ||
|
||
from sentry import tagstore | ||
from sentry.eventstore.models import GroupEvent | ||
from sentry.models.environment import Environment | ||
from sentry.models.release import Release | ||
from sentry.rules.filters.latest_release import get_project_release_cache_key | ||
from sentry.search.utils import get_latest_release | ||
from sentry.utils.cache import cache | ||
from sentry.workflow_engine.models.data_condition import Condition | ||
from sentry.workflow_engine.registry import condition_handler_registry | ||
from sentry.workflow_engine.types import DataConditionHandler, WorkflowJob | ||
|
||
|
||
@condition_handler_registry.register(Condition.LATEST_RELEASE) | ||
class LatestReleaseConditionHandler(DataConditionHandler[WorkflowJob]): | ||
@staticmethod | ||
def get_latest_release(environment_id: int | None, event: GroupEvent) -> Release | None: | ||
cache_key = get_project_release_cache_key(event.group.project_id, environment_id) | ||
cached_latest_release = cache.get(cache_key) | ||
if cached_latest_release is None: | ||
organization_id = event.group.project.organization_id | ||
environments = None | ||
if environment_id: | ||
environments = [Environment.objects.get(id=environment_id)] | ||
try: | ||
latest_release_version = get_latest_release( | ||
[event.group.project], | ||
environments, | ||
organization_id, | ||
)[0] | ||
except Release.DoesNotExist: | ||
return None | ||
latest_release = Release.objects.filter( | ||
version=latest_release_version, organization_id=organization_id | ||
).first() | ||
if latest_release: | ||
cache.set(cache_key, latest_release, 600) | ||
return latest_release | ||
else: | ||
cache.set(cache_key, False, 600) | ||
return cached_latest_release | ||
|
||
@staticmethod | ||
def evaluate_value(job: WorkflowJob, comparison: Any) -> bool: | ||
event = job["event"] | ||
workflow = job.get("workflow") | ||
environment_id = workflow.environment_id if workflow else None | ||
latest_release = LatestReleaseConditionHandler.get_latest_release(environment_id, event) | ||
if not latest_release: | ||
return False | ||
|
||
releases = ( | ||
v.lower() | ||
for k, v in event.tags | ||
if k.lower() == "release" or tagstore.backend.get_standardized_key(k) == "release" | ||
) | ||
|
||
return any(release == latest_release.version.lower() for release in releases) |
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
154 changes: 154 additions & 0 deletions
154
tests/sentry/workflow_engine/handlers/condition/test_latest_release_handler.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,154 @@ | ||
from datetime import UTC, datetime | ||
|
||
import pytest | ||
|
||
from sentry.models.release import Release | ||
from sentry.rules.filters.latest_release import LatestReleaseFilter, get_project_release_cache_key | ||
from sentry.testutils.skips import requires_snuba | ||
from sentry.utils.cache import cache | ||
from sentry.workflow_engine.models.data_condition import Condition | ||
from sentry.workflow_engine.models.workflow import Workflow | ||
from sentry.workflow_engine.types import WorkflowJob | ||
from tests.sentry.workflow_engine.handlers.condition.test_base import ConditionTestCase | ||
|
||
pytestmark = [requires_snuba, pytest.mark.sentry_metrics] | ||
|
||
|
||
class TestLatestReleaseCondition(ConditionTestCase): | ||
condition = Condition.LATEST_RELEASE | ||
rule_cls = LatestReleaseFilter | ||
payload = { | ||
"id": LatestReleaseFilter.id, | ||
} | ||
|
||
def setup_group_event_and_job(self): | ||
self.group_event = self.event.for_group(self.group) | ||
self.job = WorkflowJob( | ||
{ | ||
"event": self.group_event, | ||
"workflow": Workflow(environment_id=None), | ||
} | ||
) | ||
|
||
def setUp(self): | ||
super().setUp() | ||
self.job = WorkflowJob( | ||
{ | ||
"event": self.group_event, | ||
} | ||
) | ||
self.dc = self.create_data_condition( | ||
type=self.condition, | ||
comparison=True, | ||
condition_result=True, | ||
) | ||
|
||
def test_dual_write(self): | ||
dcg = self.create_data_condition_group() | ||
dc = self.translate_to_data_condition(self.payload, dcg) | ||
|
||
assert dc.type == self.condition | ||
assert dc.comparison is True | ||
assert dc.condition_result is True | ||
assert dc.condition_group == dcg | ||
|
||
def test_latest_release(self): | ||
oldRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="1", | ||
date_added=datetime(2020, 9, 1, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
oldRelease.add_project(self.project) | ||
|
||
newRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="2", | ||
date_added=datetime(2020, 9, 2, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
newRelease.add_project(self.project) | ||
|
||
self.event.data["tags"] = (("release", newRelease.version),) | ||
self.assert_passes(self.dc, self.job) | ||
|
||
def test_latest_release_no_match(self): | ||
oldRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="1", | ||
date_added=datetime(2020, 9, 1, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
oldRelease.add_project(self.project) | ||
|
||
newRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="2", | ||
date_added=datetime(2020, 9, 2, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
newRelease.add_project(self.project) | ||
|
||
self.event.data["tags"] = (("release", oldRelease.version),) | ||
self.assert_does_not_pass(self.dc, self.job) | ||
|
||
def test_caching(self): | ||
oldRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="1", | ||
date_added=datetime(2020, 9, 1, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
oldRelease.add_project(self.project) | ||
self.event.data["tags"] = (("release", oldRelease.version),) | ||
self.assert_passes(self.dc, self.job) | ||
|
||
newRelease = Release.objects.create( | ||
organization_id=self.organization.id, | ||
version="2", | ||
date_added=datetime(2020, 9, 2, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
newRelease.add_project(self.project) | ||
|
||
# ensure we clear the cache after creating a new release | ||
cache_key = get_project_release_cache_key(self.event.group.project_id) | ||
assert cache.get(cache_key) is None | ||
|
||
self.assert_does_not_pass(self.dc, self.job) | ||
|
||
# ensure we clear the cache when a release is deleted | ||
newRelease.safe_delete() | ||
cache_key = get_project_release_cache_key(self.event.group.project_id) | ||
assert cache.get(cache_key) is None | ||
|
||
# rule should pass again because the latest release is oldRelease | ||
self.assert_passes(self.dc, self.job) | ||
|
||
def test_latest_release_with_environment(self): | ||
self.create_release( | ||
project=self.event.group.project, | ||
version="1", | ||
date_added=datetime(2020, 9, 1, 3, 8, 24, 880386, tzinfo=UTC), | ||
environments=[self.environment], | ||
) | ||
|
||
new_release = self.create_release( | ||
project=self.event.group.project, | ||
version="2", | ||
date_added=datetime(2020, 9, 2, 3, 8, 24, 880386, tzinfo=UTC), | ||
environments=[self.environment], | ||
) | ||
|
||
other_env_release = self.create_release( | ||
project=self.event.group.project, | ||
version="4", | ||
date_added=datetime(2020, 9, 3, 3, 8, 24, 880386, tzinfo=UTC), | ||
) | ||
|
||
self.job = WorkflowJob( | ||
{ | ||
"event": self.group_event, | ||
"workflow": Workflow(environment_id=self.environment.id), | ||
} | ||
) | ||
|
||
self.event.data["tags"] = (("release", new_release.version),) | ||
self.assert_passes(self.dc, self.job) | ||
|
||
self.event.data["tags"] = (("release", other_env_release.version),) | ||
self.assert_does_not_pass(self.dc, self.job) |