mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
932 lines
38 KiB
Python
932 lines
38 KiB
Python
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from snuba_sdk import And, Column, Condition, Entity, Function, Join, Op, Relationship
|
|
|
|
from sentry.exceptions import (
|
|
IncompatibleMetricsQuery,
|
|
InvalidQuerySubscription,
|
|
InvalidSearchQuery,
|
|
UnsupportedQuerySubscription,
|
|
)
|
|
from sentry.models.group import GroupStatus
|
|
from sentry.search.events.constants import METRICS_MAP
|
|
from sentry.sentry_metrics import indexer
|
|
from sentry.sentry_metrics.use_case_id_registry import UseCaseID
|
|
from sentry.sentry_metrics.utils import resolve, resolve_tag_key, resolve_tag_value
|
|
from sentry.snuba.dataset import Dataset, EntityKey
|
|
from sentry.snuba.entity_subscription import (
|
|
EventsEntitySubscription,
|
|
MetricsCountersEntitySubscription,
|
|
MetricsSetsEntitySubscription,
|
|
PerformanceMetricsEntitySubscription,
|
|
PerformanceTransactionsEntitySubscription,
|
|
SessionsEntitySubscription,
|
|
get_entity_key_from_snuba_query,
|
|
get_entity_subscription,
|
|
get_entity_subscription_from_snuba_query,
|
|
)
|
|
from sentry.snuba.metrics.naming_layer.mri import SessionMRI
|
|
from sentry.snuba.models import SnubaQuery
|
|
from sentry.testutils.cases import TestCase
|
|
from sentry.testutils.helpers import Feature
|
|
from sentry.testutils.skips import requires_snuba
|
|
|
|
pytestmark = [pytest.mark.sentry_metrics, requires_snuba]
|
|
|
|
|
|
@pytest.mark.snuba_ci
|
|
class EntitySubscriptionTestCase(TestCase):
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
for tag in [
|
|
SessionMRI.RAW_SESSION.value,
|
|
SessionMRI.RAW_USER.value,
|
|
"session.status",
|
|
"init",
|
|
"crashed",
|
|
]:
|
|
indexer.record(use_case_id=UseCaseID.SESSIONS, org_id=self.organization.id, string=tag)
|
|
|
|
def test_get_entity_subscriptions_for_sessions_dataset_non_supported_aggregate(self) -> None:
|
|
aggregate = "count(sessions)"
|
|
with pytest.raises(UnsupportedQuerySubscription):
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Sessions,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
|
|
def test_get_entity_subscriptions_for_sessions_dataset_missing_organization(self) -> None:
|
|
aggregate = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate"
|
|
with pytest.raises(InvalidQuerySubscription):
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Sessions,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
)
|
|
|
|
def test_build_query_builder_invalid_fields_raise_error(self) -> None:
|
|
entities = [
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Sessions,
|
|
aggregate="percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate",
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
),
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.ERROR,
|
|
dataset=Dataset.Events,
|
|
aggregate="count_unique(user)",
|
|
time_window=3600,
|
|
),
|
|
]
|
|
for entity in entities:
|
|
with pytest.raises(InvalidSearchQuery, match="Invalid key for this search: timestamp"):
|
|
entity.build_query_builder("timestamp:-24h", [self.project.id], None)
|
|
|
|
def test_get_entity_subscriptions_for_sessions_dataset(self) -> None:
|
|
aggregate = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Sessions,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, SessionsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id
|
|
}
|
|
assert entity_subscription.dataset == Dataset.Sessions
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None
|
|
).get_snql_query()
|
|
snql_query.query.select.sort(key=lambda q: q.function)
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
function="identity", parameters=[Column(name="sessions")], alias="_total_count"
|
|
),
|
|
Function(
|
|
function="if",
|
|
parameters=[
|
|
Function(function="greater", parameters=[Column(name="sessions"), 0]),
|
|
Function(
|
|
function="divide",
|
|
parameters=[Column(name="sessions_crashed"), Column(name="sessions")],
|
|
),
|
|
None,
|
|
],
|
|
alias="_crash_rate_alert_aggregate",
|
|
),
|
|
]
|
|
assert snql_query.query.where == [
|
|
Condition(Column(name="project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column(name="org_id"), Op.EQ, self.organization.id),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_metrics_dataset_non_supported_aggregate(self) -> None:
|
|
aggregate = "count(sessions)"
|
|
with pytest.raises(UnsupportedQuerySubscription):
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
|
|
def test_get_entity_subscription_for_metrics_dataset_missing_organization(self) -> None:
|
|
aggregate = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate"
|
|
with pytest.raises(InvalidQuerySubscription):
|
|
get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
)
|
|
|
|
# This test has been kept in order to validate whether the old queries through metrics are supported, in the future
|
|
# this should be removed.
|
|
def test_get_entity_subscription_for_metrics_dataset_for_users(self) -> None:
|
|
org_id = self.organization.id
|
|
use_case_id = UseCaseID.SESSIONS
|
|
|
|
aggregate = "percentage(users_crashed, users) AS _crash_rate_alert_aggregate"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, MetricsSetsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 10,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.Metrics
|
|
session_status = resolve_tag_key(use_case_id, org_id, "session.status")
|
|
session_status_crashed = resolve_tag_value(use_case_id, org_id, "crashed")
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None, {"organization_id": self.organization.id}
|
|
).get_snql_query()
|
|
key = lambda func: func.alias
|
|
assert sorted(snql_query.query.select, key=key) == sorted(
|
|
[
|
|
Function("uniq", parameters=[Column("value")], alias="count"),
|
|
Function(
|
|
"uniqIf",
|
|
parameters=[
|
|
Column(name="value"),
|
|
Function(
|
|
function="equals",
|
|
parameters=[
|
|
Column(session_status),
|
|
session_status_crashed,
|
|
],
|
|
),
|
|
],
|
|
alias="crashed",
|
|
),
|
|
],
|
|
key=key,
|
|
)
|
|
assert snql_query.query.where == [
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(
|
|
Column("metric_id"),
|
|
Op.EQ,
|
|
resolve(
|
|
UseCaseID.SESSIONS,
|
|
self.organization.id,
|
|
entity_subscription.metric_key.value,
|
|
),
|
|
),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_metrics_dataset_for_users_with_metrics_layer(self) -> None:
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
org_id = self.organization.id
|
|
use_case_id = UseCaseID.SESSIONS
|
|
|
|
aggregate = "percentage(users_crashed, users) AS _crash_rate_alert_aggregate"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, MetricsSetsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 10,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.Metrics
|
|
session_status = resolve_tag_key(use_case_id, org_id, "session.status")
|
|
session_status_crashed = resolve_tag_value(use_case_id, org_id, "crashed")
|
|
metric_id = resolve(use_case_id, org_id, entity_subscription.metric_key.value)
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None, {"organization_id": self.organization.id}
|
|
).get_snql_query()
|
|
key = lambda func: func.alias
|
|
assert sorted(snql_query.query.select, key=key) == sorted(
|
|
[
|
|
Function(
|
|
"uniqIf",
|
|
parameters=[
|
|
Column("value"),
|
|
Function("equals", [Column("metric_id"), metric_id]),
|
|
],
|
|
alias="count",
|
|
),
|
|
Function(
|
|
"uniqIf",
|
|
parameters=[
|
|
Column("value"),
|
|
Function(
|
|
"and",
|
|
parameters=[
|
|
Function("equals", [Column("metric_id"), metric_id]),
|
|
Function(
|
|
"equals", [Column(session_status), session_status_crashed]
|
|
),
|
|
],
|
|
),
|
|
],
|
|
alias="crashed",
|
|
),
|
|
],
|
|
key=key,
|
|
)
|
|
assert snql_query.query.where == [
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("metric_id"), Op.IN, [metric_id]),
|
|
]
|
|
|
|
# This test has been kept in order to validate whether the old queries through metrics are supported, in the future
|
|
# this should be removed.
|
|
def test_get_entity_subscription_for_metrics_dataset_for_sessions(self) -> None:
|
|
org_id = self.organization.id
|
|
use_case_id = UseCaseID.SESSIONS
|
|
aggregate = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, MetricsCountersEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 10,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.Metrics
|
|
session_status = resolve_tag_key(use_case_id, org_id, "session.status")
|
|
session_status_crashed = resolve_tag_value(use_case_id, org_id, "crashed")
|
|
session_status_init = resolve_tag_value(use_case_id, org_id, "init")
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None, {"organization_id": self.organization.id}
|
|
).get_snql_query()
|
|
key = lambda func: func.alias
|
|
assert sorted(snql_query.query.select, key=key) == sorted(
|
|
[
|
|
Function(
|
|
function="sumIf",
|
|
parameters=[
|
|
Column("value"),
|
|
Function(
|
|
"equals", parameters=[Column(session_status), session_status_init]
|
|
),
|
|
],
|
|
alias="count",
|
|
),
|
|
Function(
|
|
"sumIf",
|
|
parameters=[
|
|
Column(name="value"),
|
|
Function(
|
|
"equals", parameters=[Column(session_status), session_status_crashed]
|
|
),
|
|
],
|
|
alias="crashed",
|
|
),
|
|
],
|
|
key=key,
|
|
)
|
|
assert snql_query.query.where == [
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(
|
|
Column("metric_id"),
|
|
Op.EQ,
|
|
resolve(use_case_id, self.organization.id, entity_subscription.metric_key.value),
|
|
),
|
|
Condition(
|
|
Column(session_status),
|
|
Op.IN,
|
|
[session_status_crashed, session_status_init],
|
|
),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_metrics_dataset_for_sessions_with_metrics_layer(
|
|
self,
|
|
) -> None:
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
org_id = self.organization.id
|
|
use_case_id = UseCaseID.SESSIONS
|
|
aggregate = "percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.CRASH_RATE,
|
|
dataset=Dataset.Metrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, MetricsCountersEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 10,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.Metrics
|
|
session_status = resolve_tag_key(use_case_id, org_id, "session.status")
|
|
session_status_crashed = resolve_tag_value(use_case_id, org_id, "crashed")
|
|
session_status_init = resolve_tag_value(use_case_id, org_id, "init")
|
|
metric_id = resolve(use_case_id, org_id, entity_subscription.metric_key.value)
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None, {"organization_id": self.organization.id}
|
|
).get_snql_query()
|
|
key = lambda func: func.alias
|
|
assert sorted(snql_query.query.select, key=key) == sorted(
|
|
[
|
|
Function(
|
|
"sumIf",
|
|
parameters=[
|
|
Column("value"),
|
|
Function(
|
|
"and",
|
|
parameters=[
|
|
Function("equals", [Column("metric_id"), metric_id]),
|
|
Function(
|
|
"equals", [Column(session_status), session_status_init]
|
|
),
|
|
],
|
|
),
|
|
],
|
|
alias="count",
|
|
),
|
|
Function(
|
|
"sumIf",
|
|
parameters=[
|
|
Column("value"),
|
|
Function(
|
|
"and",
|
|
parameters=[
|
|
Function("equals", [Column("metric_id"), metric_id]),
|
|
Function(
|
|
"equals", [Column(session_status), session_status_crashed]
|
|
),
|
|
],
|
|
),
|
|
],
|
|
alias="crashed",
|
|
),
|
|
],
|
|
key=key,
|
|
)
|
|
assert snql_query.query.where == [
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(
|
|
Column(session_status),
|
|
Op.IN,
|
|
[session_status_crashed, session_status_init],
|
|
),
|
|
Condition(Column("metric_id"), Op.IN, [metric_id]),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_performance_transactions_dataset(self) -> None:
|
|
aggregate = "percentile(transaction.duration,.95)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.PERFORMANCE,
|
|
dataset=Dataset.Transactions,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
)
|
|
assert isinstance(entity_subscription, PerformanceTransactionsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {}
|
|
assert entity_subscription.dataset == Dataset.Transactions
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"", [self.project.id], None
|
|
).get_snql_query()
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
"quantile(0.95)",
|
|
parameters=[Column(name="duration")],
|
|
alias="percentile_transaction_duration__95",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [Condition(Column("project_id"), Op.IN, [self.project.id])]
|
|
|
|
# This test has been kept in order to validate whether the old queries through metrics are supported, in the future
|
|
# this should be removed.
|
|
def test_get_entity_subscription_for_performance_metrics_dataset(self) -> None:
|
|
aggregate = "percentile(transaction.duration,.95)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.PERFORMANCE,
|
|
dataset=Dataset.PerformanceMetrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, PerformanceMetricsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 60,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.PerformanceMetrics
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"",
|
|
[self.project.id],
|
|
None,
|
|
{
|
|
"organization_id": self.organization.id,
|
|
},
|
|
).get_snql_query()
|
|
|
|
metric_id = resolve(
|
|
UseCaseID.TRANSACTIONS, self.organization.id, METRICS_MAP["transaction.duration"]
|
|
)
|
|
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
function="arrayElement",
|
|
parameters=[
|
|
Function(
|
|
function="quantilesIf(0.95)",
|
|
parameters=[
|
|
Column(name="value"),
|
|
Function(
|
|
function="equals",
|
|
parameters=[Column(name="metric_id"), metric_id],
|
|
),
|
|
],
|
|
),
|
|
1,
|
|
],
|
|
alias="percentile_transaction_duration__95",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(Column("metric_id"), Op.IN, [metric_id]),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_performance_metrics_dataset_with_metrics_layer(
|
|
self,
|
|
) -> None:
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
aggregate = "percentile(transaction.duration,.95)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.PERFORMANCE,
|
|
dataset=Dataset.PerformanceMetrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, PerformanceMetricsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 60,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.PerformanceMetrics
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"",
|
|
[self.project.id],
|
|
None,
|
|
{
|
|
"organization_id": self.organization.id,
|
|
},
|
|
).get_snql_query()
|
|
|
|
metric_id = resolve(
|
|
UseCaseID.TRANSACTIONS, self.organization.id, METRICS_MAP["transaction.duration"]
|
|
)
|
|
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
"arrayElement",
|
|
parameters=[
|
|
Function(
|
|
"quantilesIf(0.95)",
|
|
parameters=[
|
|
Column("value"),
|
|
Function(
|
|
"equals",
|
|
parameters=[Column("metric_id"), metric_id],
|
|
),
|
|
],
|
|
),
|
|
1,
|
|
],
|
|
alias="percentile_transaction_duration__95",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("metric_id"), Op.IN, [metric_id]),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_performance_metrics_dataset_with_custom_metric_and_metrics_layer(
|
|
self,
|
|
) -> None:
|
|
mri = "d:custom/sentry.process_profile.track_outcome@second"
|
|
indexer.record(use_case_id=UseCaseID.CUSTOM, org_id=self.organization.id, string=mri)
|
|
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
aggregate = f"max({mri})"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.PERFORMANCE,
|
|
dataset=Dataset.PerformanceMetrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
assert isinstance(entity_subscription, PerformanceMetricsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {
|
|
"organization": self.organization.id,
|
|
"granularity": 60,
|
|
}
|
|
assert entity_subscription.dataset == Dataset.PerformanceMetrics
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"",
|
|
[self.project.id],
|
|
None,
|
|
{
|
|
"organization_id": self.organization.id,
|
|
},
|
|
).get_snql_query()
|
|
|
|
metric_id = resolve(UseCaseID.CUSTOM, self.organization.id, mri)
|
|
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
"maxIf",
|
|
parameters=[
|
|
Column(
|
|
"value",
|
|
),
|
|
Function(
|
|
"equals",
|
|
parameters=[
|
|
Column("metric_id"),
|
|
10000,
|
|
],
|
|
alias=None,
|
|
),
|
|
],
|
|
alias="max_d_custom_sentry_process_profile_track_outcome_second",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [
|
|
Condition(Column("org_id"), Op.EQ, self.organization.id),
|
|
Condition(Column("project_id"), Op.IN, [self.project.id]),
|
|
Condition(Column("metric_id"), Op.IN, [metric_id]),
|
|
]
|
|
|
|
def test_get_entity_subscription_with_multiple_entities_with_metrics_layer(
|
|
self,
|
|
) -> None:
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
aggregate = "percentile(transaction.duration,.95)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.PERFORMANCE,
|
|
dataset=Dataset.PerformanceMetrics,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
extra_fields={"org_id": self.organization.id},
|
|
)
|
|
with patch(
|
|
"sentry.snuba.entity_subscription.PerformanceMetricsEntitySubscription.get_snql_aggregations",
|
|
# We have two aggregates on the metrics dataset but one with generic_metrics_sets and the other with
|
|
# generic_metrics_distributions.
|
|
return_value=[aggregate, "count_unique(user)"],
|
|
):
|
|
with pytest.raises(IncompatibleMetricsQuery):
|
|
entity_subscription.build_query_builder(
|
|
"",
|
|
[self.project.id],
|
|
None,
|
|
{"organization_id": self.organization.id},
|
|
).get_snql_query()
|
|
|
|
def test_get_entity_subscription_for_events_dataset(self) -> None:
|
|
aggregate = "count_unique(user)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.ERROR,
|
|
dataset=Dataset.Events,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
)
|
|
assert isinstance(entity_subscription, EventsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {}
|
|
assert entity_subscription.dataset == Dataset.Events
|
|
|
|
entity = Entity(Dataset.Events.value, alias=Dataset.Events.value)
|
|
|
|
with self.feature("organizations:metric-alert-ignore-archived"):
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"release:latest", [self.project.id], None
|
|
).get_snql_query()
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
function="uniq",
|
|
parameters=[Column(name="tags[sentry:user]", entity=entity)],
|
|
alias="count_unique_user",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [
|
|
And(
|
|
[
|
|
Condition(Column("type", entity=entity), Op.EQ, "error"),
|
|
Condition(
|
|
Function(
|
|
function="ifNull",
|
|
parameters=[Column(name="tags[sentry:release]", entity=entity), ""],
|
|
),
|
|
Op.IN,
|
|
[""],
|
|
),
|
|
]
|
|
),
|
|
Condition(Column("project_id", entity=entity), Op.IN, [self.project.id]),
|
|
]
|
|
|
|
def test_get_entity_subscription_for_events_dataset_with_join(self) -> None:
|
|
aggregate = "count_unique(user)"
|
|
entity_subscription = get_entity_subscription(
|
|
query_type=SnubaQuery.Type.ERROR,
|
|
dataset=Dataset.Events,
|
|
aggregate=aggregate,
|
|
time_window=3600,
|
|
)
|
|
assert isinstance(entity_subscription, EventsEntitySubscription)
|
|
assert entity_subscription.aggregate == aggregate
|
|
assert entity_subscription.get_entity_extra_params() == {}
|
|
assert entity_subscription.dataset == Dataset.Events
|
|
|
|
e_entity = Entity(Dataset.Events.value, alias=Dataset.Events.value)
|
|
g_entity = Entity("group_attributes", alias="ga")
|
|
|
|
with self.feature("organizations:metric-alert-ignore-archived"):
|
|
snql_query = entity_subscription.build_query_builder(
|
|
"status:unresolved", [self.project.id], None
|
|
).get_snql_query()
|
|
assert snql_query.query.match == Join([Relationship(e_entity, "attributes", g_entity)])
|
|
assert snql_query.query.select == [
|
|
Function(
|
|
function="uniq",
|
|
parameters=[Column(name="tags[sentry:user]", entity=e_entity)],
|
|
alias="count_unique_user",
|
|
)
|
|
]
|
|
assert snql_query.query.where == [
|
|
And(
|
|
[
|
|
Condition(Column("type", entity=e_entity), Op.EQ, "error"),
|
|
Condition(
|
|
Column("group_status", entity=g_entity), Op.IN, [GroupStatus.UNRESOLVED]
|
|
),
|
|
]
|
|
),
|
|
Condition(Column("project_id", entity=e_entity), Op.IN, [self.project.id]),
|
|
Condition(Column("project_id", entity=g_entity), Op.IN, [self.project.id]),
|
|
]
|
|
|
|
|
|
class GetEntitySubscriptionFromSnubaQueryTest(TestCase):
|
|
def test(self):
|
|
cases = [
|
|
(EventsEntitySubscription, SnubaQuery.Type.ERROR, Dataset.Events, "count()"),
|
|
(
|
|
PerformanceTransactionsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Transactions,
|
|
"count()",
|
|
),
|
|
(
|
|
PerformanceMetricsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Metrics,
|
|
"count()",
|
|
),
|
|
(
|
|
PerformanceMetricsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"count()",
|
|
),
|
|
(
|
|
PerformanceMetricsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Metrics,
|
|
"count_unique(user)",
|
|
),
|
|
(
|
|
PerformanceMetricsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"count_unique(user)",
|
|
),
|
|
(
|
|
PerformanceMetricsEntitySubscription,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"max(d:custom/sentry.process_profile.track_outcome@second)",
|
|
),
|
|
(
|
|
MetricsCountersEntitySubscription,
|
|
SnubaQuery.Type.CRASH_RATE,
|
|
Dataset.Metrics,
|
|
"percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate",
|
|
),
|
|
(
|
|
MetricsSetsEntitySubscription,
|
|
SnubaQuery.Type.CRASH_RATE,
|
|
Dataset.Metrics,
|
|
"percentage(users_crashed, users) AS _crash_rate_alert_aggregate",
|
|
),
|
|
]
|
|
|
|
for expected_entity_subscription, query_type, dataset, aggregate in cases:
|
|
snuba_query = SnubaQuery.objects.create(
|
|
time_window=60,
|
|
type=query_type.value,
|
|
dataset=dataset.value,
|
|
aggregate=aggregate,
|
|
resolution=5,
|
|
)
|
|
assert isinstance(
|
|
get_entity_subscription_from_snuba_query(snuba_query, self.organization.id),
|
|
expected_entity_subscription,
|
|
)
|
|
|
|
|
|
class GetEntityKeyFromSnubaQueryTest(TestCase):
|
|
def test(self):
|
|
cases = [
|
|
(EntityKey.Events, SnubaQuery.Type.ERROR, Dataset.Events, "count()", "", True, True),
|
|
(
|
|
EntityKey.Transactions,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Transactions,
|
|
"count()",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsDistributions,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Metrics,
|
|
"count()",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsSets,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.Metrics,
|
|
"count_unique(user)",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsDistributions,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"count()",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsSets,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"count_unique(user)",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsCounters,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"sum(c:custom/sentry.process_profile.track_outcome@second)",
|
|
"",
|
|
# Custom metrics are not supported when the metrics layer integration with mqb is disabled.
|
|
False,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsDistributions,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"max(d:custom/sentry.process_profile.track_outcome@second)",
|
|
"",
|
|
# Custom metrics are not supported when the metrics layer integration with mqb is disabled.
|
|
False,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsSets,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"count_unique(s:custom/sentry.process_profile.track_outcome@second)",
|
|
"",
|
|
# Custom metrics are not supported when the metrics layer integration with mqb is disabled.
|
|
False,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.GenericMetricsGauges,
|
|
SnubaQuery.Type.PERFORMANCE,
|
|
Dataset.PerformanceMetrics,
|
|
"last(g:custom/sentry.process_profile.track_outcome@second)",
|
|
"",
|
|
# Custom metrics are not supported when the metrics layer integration with mqb is disabled.
|
|
False,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.MetricsCounters,
|
|
SnubaQuery.Type.CRASH_RATE,
|
|
Dataset.Metrics,
|
|
"percentage(sessions_crashed, sessions) AS _crash_rate_alert_aggregate",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
(
|
|
EntityKey.MetricsSets,
|
|
SnubaQuery.Type.CRASH_RATE,
|
|
Dataset.Metrics,
|
|
"percentage(users_crashed, users) AS _crash_rate_alert_aggregate",
|
|
"",
|
|
True,
|
|
True,
|
|
),
|
|
]
|
|
|
|
for (
|
|
expected_entity_key,
|
|
query_type,
|
|
dataset,
|
|
aggregate,
|
|
query,
|
|
supported_with_no_metrics_layer,
|
|
supported_with_metrics_layer,
|
|
) in cases:
|
|
snuba_query = SnubaQuery.objects.create(
|
|
time_window=60,
|
|
type=query_type.value,
|
|
dataset=dataset.value,
|
|
aggregate=aggregate,
|
|
query=query,
|
|
resolution=5,
|
|
)
|
|
|
|
if supported_with_no_metrics_layer:
|
|
assert expected_entity_key == get_entity_key_from_snuba_query(
|
|
snuba_query, self.organization.id, self.project.id
|
|
)
|
|
|
|
if supported_with_metrics_layer:
|
|
with Feature("organizations:use-metrics-layer-in-alerts"):
|
|
assert expected_entity_key == get_entity_key_from_snuba_query(
|
|
snuba_query, self.organization.id, self.project.id
|
|
)
|