mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
2483 lines
92 KiB
Python
2483 lines
92 KiB
Python
from datetime import datetime
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from sentry.exceptions import InvalidSearchQuery
|
|
from sentry.models.transaction_threshold import (
|
|
ProjectTransactionThreshold,
|
|
ProjectTransactionThresholdOverride,
|
|
TransactionMetric,
|
|
)
|
|
from sentry.search.events.constants import (
|
|
PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
)
|
|
from sentry.search.events.types import HistogramParams
|
|
from sentry.snuba import discover
|
|
from sentry.snuba.dataset import Dataset
|
|
from sentry.testutils.cases import TestCase
|
|
from sentry.testutils.helpers.datetime import before_now
|
|
from sentry.utils.snuba import get_array_column_alias
|
|
|
|
ARRAY_COLUMNS = ["measurements", "span_op_breakdowns"]
|
|
|
|
|
|
# TODO: convert these to QueryBuilder tests
|
|
@pytest.mark.skip("These tests are specific to json which we no longer use")
|
|
class QueryTransformTest(TestCase):
|
|
"""
|
|
This test mocks snuba.raw_query to let us isolate column transformations.
|
|
"""
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_query_parse_error(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
with pytest.raises(InvalidSearchQuery):
|
|
discover.query(
|
|
selected_columns=[],
|
|
query="foo(id):<1dino",
|
|
params={"project_id": [self.project.id]},
|
|
)
|
|
assert mock_query.call_count == 0
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_query_no_fields(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
with pytest.raises(InvalidSearchQuery) as err:
|
|
discover.query(
|
|
selected_columns=[],
|
|
query="event.type:transaction",
|
|
params={"project_id": [self.project.id]},
|
|
)
|
|
assert "No columns selected" in str(err)
|
|
assert mock_query.call_count == 0
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_field_alias_macro(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "user"}, {"name": "project_id"}],
|
|
"data": [{"user": "[email protected]", "project_id": self.project.id}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["user", "project"], query="", params={"project_id": [self.project.id]}
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=[
|
|
"user",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{self.project.id}'"]],
|
|
["array", [f"'{self.project.slug}'"]],
|
|
"''",
|
|
],
|
|
"project",
|
|
],
|
|
],
|
|
aggregations=[],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
conditions=[],
|
|
groupby=[],
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_project_filter_limits_automatic_fields(self, mock_query):
|
|
project2 = self.create_project(organization=self.organization)
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "title"}, {"name": "project_id"}],
|
|
"data": [{"title": "stuff", "project_id": project2.id}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["title", "project"],
|
|
query=f"project:{project2.slug}",
|
|
params={"project_id": [self.project.id, project2.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=[
|
|
"title",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{project2.id}'"]],
|
|
["array", [f"'{project2.slug}'"]],
|
|
"''",
|
|
],
|
|
"project",
|
|
],
|
|
],
|
|
aggregations=[],
|
|
filter_keys={"project_id": [project2.id]},
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
conditions=[["project_id", "=", project2.id]],
|
|
groupby=[],
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_project_with_aggregate_grouping(self, mock_query):
|
|
project2 = self.create_project(organization=self.organization)
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "title"}, {"name": "project_id"}],
|
|
"data": [{"title": "stuff", "project_id": project2.id}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["title", "project", "p99()"],
|
|
query=f"project:{project2.slug}",
|
|
params={"project_id": [self.project.id, project2.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=[
|
|
"title",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{project2.id}'"]],
|
|
["array", [f"'{project2.slug}'"]],
|
|
"''",
|
|
],
|
|
"project",
|
|
],
|
|
],
|
|
aggregations=[["quantile(0.99)", "duration", "p99"]],
|
|
filter_keys={"project_id": [project2.id]},
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
conditions=[["project_id", "=", project2.id]],
|
|
groupby=["title", "project_id"],
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_no_auto_fields(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "count"}],
|
|
"data": [{"count": 1}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["count()"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=False,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=[],
|
|
aggregations=[["count", None, "count"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
conditions=[],
|
|
groupby=[],
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_aliasing_in_function(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"transaction.duration",
|
|
"count_unique(transaction.duration)",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction", "duration"],
|
|
aggregations=[["uniq", "duration", "count_unique_transaction_duration"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
conditions=[],
|
|
groupby=["transaction", "duration"],
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_aggregate_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "p95"}],
|
|
"data": [{"transaction": "api.do_things", "p95": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "p95()", "count_unique(transaction)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
["quantile(0.95)", "duration", "p95"],
|
|
["uniq", "transaction", "count_unique_transaction"],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_failure_rate_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "failure_rate"}],
|
|
"data": [{"transaction": "api.do_things", "failure_rate": 0.314159}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "failure_rate()"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[["failure_rate()", None, "failure_rate"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_apdex_new_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
{"name": "apdex"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 300),
|
|
"apdex": 0.15,
|
|
}
|
|
],
|
|
}
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"apdex()",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=["transaction", "project_threshold_config"],
|
|
conditions=[],
|
|
aggregations=[
|
|
[
|
|
"apdex(multiIf(equals(tupleElement(project_threshold_config,1),'lcp'),if(has(measurements.key,'lcp'),arrayElement(measurements.value,indexOf(measurements.key,'lcp')),NULL),duration),tupleElement(project_threshold_config,2))",
|
|
None,
|
|
"apdex",
|
|
]
|
|
],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"tuple",
|
|
["'duration'", 300],
|
|
"project_threshold_config",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_user_misery_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "user_misery_300"}],
|
|
"data": [{"transaction": "api.do_things", "user_misery_300": 0.15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "user_misery(300)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"ifNull(divide(plus(uniqIf(user, greater(duration, 1200)), 5.8875), plus(uniq(user), 117.75)), 0)",
|
|
None,
|
|
"user_misery_300",
|
|
]
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_user_misery_new_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
{"name": "user_misery"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 300),
|
|
"user_misery": 0.15,
|
|
}
|
|
],
|
|
}
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"user_misery()",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=["transaction", "project_threshold_config"],
|
|
conditions=[],
|
|
aggregations=[
|
|
[
|
|
"ifNull(divide(plus(uniqIf(user,greater(multiIf(equals(tupleElement(project_threshold_config,1),'lcp'),if(has(measurements.key,'lcp'),arrayElement(measurements.value,indexOf(measurements.key,'lcp')),NULL),duration),multiply(tupleElement(project_threshold_config,2),4))),5.8875),plus(uniq(user),117.75)),0)",
|
|
None,
|
|
"user_misery",
|
|
]
|
|
],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"tuple",
|
|
["'duration'", 300],
|
|
"project_threshold_config",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_count_miserable_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "count_miserable_user_300"}],
|
|
"data": [{"transaction": "api.do_things", "count_miserable_user_300": 15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "count_miserable(user, 300)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"uniqIf(user, greater(duration, 1200))",
|
|
None,
|
|
"count_miserable_user_300",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_count_miserable_allows_zero_threshold(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "count_miserable_user_0"}],
|
|
"data": [{"transaction": "api.do_things", "count_miserable_user_0": 15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "count_miserable(user,0)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"uniqIf(user, greater(duration, 0))",
|
|
None,
|
|
"count_miserable_user_0",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_apdex_allows_zero_threshold(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "apdex_0"}],
|
|
"data": [{"transaction": "api.do_things", "apdex_0": 15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "apdex(0)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"apdex(duration, 0)",
|
|
None,
|
|
"apdex_0",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_project_threshold_config_alias_no_configured_thresholds(
|
|
self, mock_query
|
|
):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 300),
|
|
}
|
|
],
|
|
}
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"project_threshold_config",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=[],
|
|
conditions=[],
|
|
aggregations=[],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"tuple",
|
|
["'duration'", 300],
|
|
"project_threshold_config",
|
|
],
|
|
"event_id",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{self.project.id}'"]],
|
|
["array", ["'bar'"]],
|
|
"''",
|
|
],
|
|
"`project.name`",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_threshold_config_selected_with_project_threshold_configured(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 400),
|
|
}
|
|
],
|
|
}
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project_id=self.project.id,
|
|
organization_id=self.organization.id,
|
|
threshold=200,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"project_threshold_config",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=[],
|
|
conditions=[],
|
|
aggregations=[],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"if",
|
|
[
|
|
[
|
|
"equals",
|
|
[
|
|
[
|
|
"indexOf",
|
|
[["array", [["toUInt64", [self.project.id]]]], "project_id"],
|
|
PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
],
|
|
0,
|
|
],
|
|
],
|
|
["tuple", ["'duration'", 300]],
|
|
[
|
|
"arrayElement",
|
|
[
|
|
["array", [["tuple", ["'duration'", 200]]]],
|
|
[
|
|
"indexOf",
|
|
[["array", [["toUInt64", [self.project.id]]]], "project_id"],
|
|
PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
"project_threshold_config",
|
|
],
|
|
"event_id",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{self.project.id}'"]],
|
|
["array", ["'bar'"]],
|
|
"''",
|
|
],
|
|
"`project.name`",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_threshold_config_selected_with_txn_threshold_configured(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 400),
|
|
}
|
|
],
|
|
}
|
|
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction="transaction/threshold",
|
|
project_id=self.project.id,
|
|
organization_id=self.organization.id,
|
|
threshold=200,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"project_threshold_config",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=[],
|
|
conditions=[],
|
|
aggregations=[],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"if",
|
|
[
|
|
[
|
|
"equals",
|
|
[
|
|
[
|
|
"indexOf",
|
|
[
|
|
[
|
|
"array",
|
|
[
|
|
[
|
|
"tuple",
|
|
[
|
|
["toUInt64", [self.project.id]],
|
|
"'transaction/threshold'",
|
|
],
|
|
],
|
|
],
|
|
],
|
|
["tuple", ["project_id", "transaction"]],
|
|
],
|
|
PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
],
|
|
0,
|
|
],
|
|
],
|
|
["tuple", ["'duration'", 300]],
|
|
[
|
|
"arrayElement",
|
|
[
|
|
["array", [["tuple", ["'duration'", 200]]]],
|
|
[
|
|
"indexOf",
|
|
[
|
|
[
|
|
"array",
|
|
[
|
|
[
|
|
"tuple",
|
|
[
|
|
["toUInt64", [self.project.id]],
|
|
"'transaction/threshold'",
|
|
],
|
|
],
|
|
],
|
|
],
|
|
["tuple", ["project_id", "transaction"]],
|
|
],
|
|
PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
"project_threshold_config",
|
|
],
|
|
"event_id",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{self.project.id}'"]],
|
|
["array", ["'bar'"]],
|
|
"''",
|
|
],
|
|
"`project.name`",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_threshold_config_selected_with_project_and_txn_thresholds_configured(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 400),
|
|
}
|
|
],
|
|
}
|
|
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction="transaction/threshold",
|
|
project_id=self.project.id,
|
|
organization_id=self.organization.id,
|
|
threshold=200,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project_id=self.project.id,
|
|
organization_id=self.organization.id,
|
|
threshold=200,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"project_threshold_config",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=[],
|
|
conditions=[],
|
|
aggregations=[],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"if",
|
|
[
|
|
[
|
|
"equals",
|
|
[
|
|
[
|
|
"indexOf",
|
|
[
|
|
[
|
|
"array",
|
|
[
|
|
[
|
|
"tuple",
|
|
[
|
|
["toUInt64", [self.project.id]],
|
|
"'transaction/threshold'",
|
|
],
|
|
],
|
|
],
|
|
],
|
|
["tuple", ["project_id", "transaction"]],
|
|
],
|
|
PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
],
|
|
0,
|
|
],
|
|
],
|
|
[
|
|
"if",
|
|
[
|
|
[
|
|
"equals",
|
|
[
|
|
[
|
|
"indexOf",
|
|
[
|
|
["array", [["toUInt64", [self.project.id]]]],
|
|
"project_id",
|
|
],
|
|
PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
],
|
|
0,
|
|
],
|
|
],
|
|
["tuple", ["'duration'", 300]],
|
|
[
|
|
"arrayElement",
|
|
[
|
|
["array", [["tuple", ["'duration'", 200]]]],
|
|
[
|
|
"indexOf",
|
|
[
|
|
["array", [["toUInt64", [self.project.id]]]],
|
|
"project_id",
|
|
],
|
|
PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
],
|
|
[
|
|
"arrayElement",
|
|
[
|
|
["array", [["tuple", ["'duration'", 200]]]],
|
|
[
|
|
"indexOf",
|
|
[
|
|
[
|
|
"array",
|
|
[
|
|
[
|
|
"tuple",
|
|
[
|
|
["toUInt64", [self.project.id]],
|
|
"'transaction/threshold'",
|
|
],
|
|
],
|
|
],
|
|
],
|
|
["tuple", ["project_id", "transaction"]],
|
|
],
|
|
PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
],
|
|
],
|
|
],
|
|
],
|
|
"project_threshold_config",
|
|
],
|
|
"event_id",
|
|
"project_id",
|
|
[
|
|
"transform",
|
|
[
|
|
["toString", ["project_id"]],
|
|
["array", [f"'{self.project.id}'"]],
|
|
["array", ["'bar'"]],
|
|
"''",
|
|
],
|
|
"`project.name`",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
dataset=Dataset.Discover,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_count_miserable_new_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [
|
|
{"name": "transaction"},
|
|
{"name": "project_threshold_config"},
|
|
{"name": "count_miserable_user_project_threshold_config"},
|
|
],
|
|
"data": [
|
|
{
|
|
"transaction": "api.do_things",
|
|
"project_threshold_config": ("duration", 400),
|
|
"count_miserable_user": 15,
|
|
}
|
|
],
|
|
}
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"count_miserable(user)",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id], "organization_id": self.organization.id},
|
|
auto_fields=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
start=None,
|
|
end=None,
|
|
groupby=["transaction", "project_threshold_config"],
|
|
conditions=[],
|
|
aggregations=[
|
|
[
|
|
"""
|
|
uniqIf(user, greater(
|
|
multiIf(
|
|
equals(tupleElement(project_threshold_config, 1), 'lcp'),
|
|
if(has(measurements.key, 'lcp'), arrayElement(measurements.value, indexOf(measurements.key, 'lcp')), NULL),
|
|
duration
|
|
),
|
|
multiply(tupleElement(project_threshold_config, 2), 4)
|
|
))
|
|
""".replace(
|
|
"\n", ""
|
|
).replace(
|
|
" ", ""
|
|
),
|
|
None,
|
|
"count_miserable_user",
|
|
]
|
|
],
|
|
selected_columns=[
|
|
"transaction",
|
|
[
|
|
"tuple",
|
|
["'duration'", 300],
|
|
"project_threshold_config",
|
|
],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
having=[],
|
|
orderby=None,
|
|
limit=50,
|
|
dataset=Dataset.Discover,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_percentile_range_function(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "firstPercentile"}],
|
|
"data": [{"transaction": "api.do_things", "firstPercentile": 15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"percentile_range(transaction.duration, 0.5, greater, 2020-05-02T14:45:01) as percentile_range_1",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"quantileIf(0.50)",
|
|
[
|
|
"duration",
|
|
["greater", [["toDateTime", ["'2020-05-02T14:45:01'"]], "timestamp"]],
|
|
],
|
|
"percentile_range_1",
|
|
]
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_selected_columns_avg_range_function(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "firstAverage"}],
|
|
"data": [{"transaction": "api.do_things", "firstAverage": 15}],
|
|
}
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"avg_range(transaction.duration, greater, 2020-05-02T14:45:01) as avg_range_1",
|
|
],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
[
|
|
"avgIf",
|
|
[
|
|
"duration",
|
|
["greater", [["toDateTime", ["'2020-05-02T14:45:01'"]], "timestamp"]],
|
|
],
|
|
"avg_range_1",
|
|
]
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_percentile_function(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "percentile_transaction_duration_0_75"}],
|
|
"data": [
|
|
{"transaction": "api.do_things", "percentile_transaction_duration_0_75": 1123}
|
|
],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "percentile(transaction.duration, 0.75)"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[["quantile(0.75)", "duration", "percentile_transaction_duration_0_75"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_orderby_limit_offset(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "title"}, {"name": "project.id"}],
|
|
"data": [{"project.id": "tester", "title": "test title"}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["project.id", "title"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
orderby=["project.id"],
|
|
offset=100,
|
|
limit=200,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["project_id", "title"],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
orderby=["project_id"],
|
|
aggregations=[],
|
|
end=None,
|
|
start=None,
|
|
conditions=[],
|
|
groupby=[],
|
|
having=[],
|
|
limit=200,
|
|
offset=100,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_orderby_must_be_selected_if_aggregate(self, mock_query):
|
|
with pytest.raises(InvalidSearchQuery):
|
|
discover.query(
|
|
selected_columns=["transaction", "transaction.duration"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
orderby=["count()"],
|
|
)
|
|
assert mock_query.call_count == 0
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_orderby_aggregate_alias(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "count_id"}, {"name": "project.id"}],
|
|
"data": [{"project.id": "tester", "count_id": 10}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["count(id)", "project.id", "id"],
|
|
query="",
|
|
params={"project_id": [self.project.id]},
|
|
orderby=["count_id"],
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["project_id", "event_id"],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
orderby=["count_id"],
|
|
aggregations=[["count", None, "count_id"]],
|
|
end=None,
|
|
start=None,
|
|
conditions=[],
|
|
groupby=["project_id", "event_id"],
|
|
having=[],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_conditions_order_and_groupby_aliasing(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["timestamp", "transaction", "transaction.duration", "count()"],
|
|
query="transaction.op:ok transaction.duration:200 sdk.name:python tags[projectid]:123",
|
|
params={"project_id": [self.project.id]},
|
|
orderby=["-timestamp", "-count"],
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["timestamp", "transaction", "duration"],
|
|
aggregations=[["count", None, "count"]],
|
|
conditions=[
|
|
["transaction_op", "=", "ok"],
|
|
["duration", "=", 200],
|
|
["sdk_name", "=", "python"],
|
|
[["ifNull", ["tags[projectid]", "''"]], "=", "123"],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["timestamp", "transaction", "duration"],
|
|
having=[],
|
|
orderby=["-timestamp", "-count"],
|
|
dataset=Dataset.Discover,
|
|
end=None,
|
|
start=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_conditions_nested_function_aliasing(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}],
|
|
"data": [{"transaction": "api.do_things"}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "count()"],
|
|
query="event.type:transaction user.email:*@sentry.io message:recent-searches",
|
|
params={"project_id": [self.project.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[
|
|
["type", "=", "transaction"],
|
|
[["match", ["email", r"'(?i)^.*@sentry\.io$'"]], "=", 1],
|
|
[["positionCaseInsensitive", ["message", "'recent-searches'"]], "!=", 0],
|
|
],
|
|
aggregations=[["count", None, "count"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
having=[],
|
|
orderby=None,
|
|
end=None,
|
|
start=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_condition_transform(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "transaction.duration"],
|
|
query="http.method:GET",
|
|
params={"project_id": [self.project.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction", "duration"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=[],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[],
|
|
having=[],
|
|
orderby=None,
|
|
end=None,
|
|
start=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_condition_projectid_transform(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
# The project_id column is not a public column, but we
|
|
# have to let it through in conditions to ensure project.name works.
|
|
discover.query(
|
|
selected_columns=["transaction", "transaction.duration"],
|
|
query="project_id:1",
|
|
params={"project_id": [self.project.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction", "duration"],
|
|
conditions=[["project_id", "=", 1]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=[],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[],
|
|
having=[],
|
|
orderby=None,
|
|
end=None,
|
|
start=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_condition_projectname_transform(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
project2 = self.create_project(organization=self.organization)
|
|
|
|
# project.name is in the public schema and should be converted to a
|
|
# project_id condition.
|
|
discover.query(
|
|
selected_columns=["transaction", "transaction.duration"],
|
|
query=f"project.name:{project2.slug}",
|
|
params={"project_id": [self.project.id, project2.id]},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction", "duration"],
|
|
conditions=[["project_id", "=", project2.id]],
|
|
filter_keys={"project_id": [project2.id]},
|
|
groupby=[],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[],
|
|
having=[],
|
|
orderby=None,
|
|
end=None,
|
|
start=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_params_forward(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
discover.query(
|
|
selected_columns=["transaction", "transaction.duration"],
|
|
query="http.method:GET",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction", "duration"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=[],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[],
|
|
having=[],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_aggregate_conditions(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
discover.query(
|
|
selected_columns=["transaction", "avg(transaction.duration)"],
|
|
query="http.method:GET avg(transaction.duration):>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[["avg", "duration", "avg_transaction_duration"]],
|
|
having=[["avg_transaction_duration", ">", 5]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_alias_aggregate_conditions(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
discover.query(
|
|
selected_columns=["transaction", "p95()"],
|
|
query="http.method:GET p95():>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[["quantile(0.95)", "duration", "p95"]],
|
|
having=[["p95", ">", 5]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_duration_aliases(self, mock_query):
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
test_cases = [
|
|
("1ms", 1),
|
|
("1.5s", 1500),
|
|
("23.4m", 1000 * 60 * 23.4),
|
|
("1.00min", 1000 * 60),
|
|
("3.45hr", 1000 * 60 * 60 * 3.45),
|
|
("1.23h", 1000 * 60 * 60 * 1.23),
|
|
("3wk", 1000 * 60 * 60 * 24 * 7 * 3),
|
|
("2.1w", 1000 * 60 * 60 * 24 * 7 * 2.1),
|
|
]
|
|
for query_string, value in test_cases:
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "p95()"],
|
|
query=f"http.method:GET p95():>{query_string}",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[["quantile(0.95)", "duration", "p95"]],
|
|
having=[["p95", ">", value]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_alias_aggregate_conditions_with_brackets(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
discover.query(
|
|
selected_columns=["transaction", "p95()"],
|
|
query="http.method:GET p95():>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[["quantile(0.95)", "duration", "p95"]],
|
|
having=[["p95", ">", 5]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_aggregate_date_conditions(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"avg(transaction.duration)",
|
|
"stddev(transaction.duration)",
|
|
"max(timestamp)",
|
|
],
|
|
query="http.method:GET max(timestamp):>2019-12-01",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[
|
|
["avg", "duration", "avg_transaction_duration"],
|
|
["stddevSamp", "duration", "stddev_transaction_duration"],
|
|
["max", "timestamp", "max_timestamp"],
|
|
],
|
|
having=[["max_timestamp", ">", 1575158400]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_aggregate_duration_alias(self, mock_query):
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
test_cases = [
|
|
("1ms", 1),
|
|
("1.5s", 1500),
|
|
("1.00min", 1000 * 60),
|
|
("3.45hr", 1000 * 60 * 60 * 3.45),
|
|
]
|
|
for query_string, value in test_cases:
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
discover.query(
|
|
selected_columns=[
|
|
"transaction",
|
|
"avg(transaction.duration)",
|
|
"stddev(transaction.duration)",
|
|
"max(timestamp)",
|
|
],
|
|
query=f"http.method:GET avg(transaction.duration):>{query_string}",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
groupby=["transaction"],
|
|
dataset=Dataset.Discover,
|
|
aggregations=[
|
|
["avg", "duration", "avg_transaction_duration"],
|
|
["stddevSamp", "duration", "stddev_transaction_duration"],
|
|
["max", "timestamp", "max_timestamp"],
|
|
],
|
|
having=[["avg_transaction_duration", ">", value]],
|
|
end=end_time,
|
|
start=start_time,
|
|
orderby=None,
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_aggregate_condition_missing_selected_column(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
with pytest.raises(InvalidSearchQuery):
|
|
discover.query(
|
|
selected_columns=["transaction"],
|
|
query="http.method:GET max(timestamp):>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_aggregate_condition_missing_with_auto(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
with pytest.raises(InvalidSearchQuery):
|
|
discover.query(
|
|
selected_columns=["transaction"],
|
|
query="http.method:GET max(timestamp):>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
auto_aggregations=True,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_no_aggregate_conditions_with_auto(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
with pytest.raises(AssertionError):
|
|
discover.query(
|
|
selected_columns=["transaction"],
|
|
query="http.method:GET max(timestamp):>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=False,
|
|
auto_aggregations=True,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_auto_aggregation(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
discover.query(
|
|
selected_columns=["transaction", "p95()"],
|
|
query="http.method:GET max(timestamp):>5",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
auto_aggregations=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
["quantile(0.95)", "duration", "p95"],
|
|
["max", "timestamp", "max_timestamp"],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[["http_method", "=", "GET"]],
|
|
start=start_time,
|
|
end=end_time,
|
|
orderby=None,
|
|
having=[["max_timestamp", ">", 5.0]],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_auto_aggregation_with_boolean_conditions(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "duration"}],
|
|
"data": [{"transaction": "api.do_things", "duration": 200}],
|
|
}
|
|
start_time = before_now(minutes=10)
|
|
end_time = before_now(minutes=1)
|
|
|
|
discover.query(
|
|
selected_columns=["transaction", "min(timestamp)"],
|
|
query="max(timestamp):>5 AND min(timestamp):<10",
|
|
params={"project_id": [self.project.id], "start": start_time, "end": end_time},
|
|
use_aggregate_conditions=True,
|
|
auto_aggregations=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[
|
|
["min", "timestamp", "min_timestamp"],
|
|
["max", "timestamp", "max_timestamp"],
|
|
],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
start=start_time,
|
|
end=end_time,
|
|
orderby=None,
|
|
having=[["max_timestamp", ">", 5.0], ["min_timestamp", "<", 10.0]],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_function_conditions(self, mock_query):
|
|
mock_query.return_value = {
|
|
"meta": [{"name": "transaction"}, {"name": "percentile_transaction_duration_0_75"}],
|
|
"data": [
|
|
{"transaction": "api.do_things", "percentile_transaction_duration_0_75": 1123}
|
|
],
|
|
}
|
|
discover.query(
|
|
selected_columns=["transaction", "percentile(transaction.duration, 0.75)"],
|
|
query="percentile(transaction.duration, 0.75):>100",
|
|
params={"project_id": [self.project.id]},
|
|
auto_fields=True,
|
|
use_aggregate_conditions=True,
|
|
)
|
|
mock_query.assert_called_with(
|
|
selected_columns=["transaction"],
|
|
aggregations=[["quantile(0.75)", "duration", "percentile_transaction_duration_0_75"]],
|
|
filter_keys={"project_id": [self.project.id]},
|
|
dataset=Dataset.Discover,
|
|
groupby=["transaction"],
|
|
conditions=[],
|
|
end=None,
|
|
start=None,
|
|
orderby=None,
|
|
having=[["percentile_transaction_duration_0_75", ">", 100]],
|
|
limit=50,
|
|
offset=None,
|
|
referrer=None,
|
|
)
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_find_histogram_min_max(self, mock_query):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
# no rows returned from snuba
|
|
mock_query.side_effect = [{"meta": [], "data": []}]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (None, None), f"failing for {array_column}"
|
|
|
|
# more than 2 rows returned snuba
|
|
mock_query.side_effect = [{"meta": [], "data": [{}, {}]}]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (None, None), f"failing for {array_column}"
|
|
|
|
# None rows are returned from snuba
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"min_{alias}_foo"},
|
|
{"name": f"max_{alias}_foo"},
|
|
],
|
|
"data": [{f"min_{alias}_foo": None, f"max_{alias}_foo": None}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (None, None), f"failing for {array_column}"
|
|
|
|
# use the given min/max
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], 1, 2, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (1, 2), f"failing for {array_column}"
|
|
|
|
# use the given min, but query for max
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [{"name": f"max_{alias}_foo"}],
|
|
"data": [{f"max_{alias}_foo": 3.45}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], 1.23, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (
|
|
1.23,
|
|
3.45,
|
|
), f"failing for {array_column}"
|
|
|
|
# use the given min, but query for max. the given min will be above
|
|
# the queried max
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [{"name": f"max_{alias}_foo"}],
|
|
"data": [{f"max_{alias}_foo": 3.45}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], 3.5, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (
|
|
3.5,
|
|
3.5,
|
|
), f"failing for {array_column}"
|
|
|
|
# use the given max, but query for min. the given max will be below
|
|
# the queried min
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [{"name": f"min_{alias}_foo"}],
|
|
"data": [{f"min_{alias}_foo": 3.45}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, 3.4, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (
|
|
3.4,
|
|
3.4,
|
|
), f"failing for {array_column}"
|
|
|
|
# use the given max, but query for min
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [{"name": f"min_{alias}_foo"}],
|
|
"data": [{f"min_{alias}_foo": 1.23}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, 3.45, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (
|
|
1.23,
|
|
3.45,
|
|
), f"failing for {array_column}"
|
|
|
|
# single min/max returned from snuba
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"min_{alias}_foo"},
|
|
{"name": f"max_{alias}_foo"},
|
|
],
|
|
"data": [{f"min_{alias}_foo": 1.23, f"max_{alias}_foo": 3.45}],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo"], None, None, "", {"project_id": [self.project.id]}
|
|
)
|
|
assert values == (
|
|
1.23,
|
|
3.45,
|
|
), f"failing for {array_column}"
|
|
|
|
# multiple min/max returned from snuba
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"min_{alias}_foo"},
|
|
{"name": f"min_{alias}_bar"},
|
|
{"name": f"min_{alias}_baz"},
|
|
{"name": f"max_{alias}_foo"},
|
|
{"name": f"max_{alias}_bar"},
|
|
{"name": f"max_{alias}_baz"},
|
|
],
|
|
"data": [
|
|
{
|
|
f"min_{alias}_foo": 1.23,
|
|
f"min_{alias}_bar": 1.34,
|
|
f"min_{alias}_baz": 1.45,
|
|
f"max_{alias}_foo": 3.45,
|
|
f"max_{alias}_bar": 3.56,
|
|
f"max_{alias}_baz": 3.67,
|
|
}
|
|
],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo", f"{alias}.bar", f"{alias}.baz"],
|
|
None,
|
|
None,
|
|
"",
|
|
{"project_id": [self.project.id]},
|
|
)
|
|
assert values == (
|
|
1.23,
|
|
3.67,
|
|
), f"failing for {array_column}"
|
|
|
|
# multiple min/max with some Nones returned from snuba
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"min_{alias}_foo"},
|
|
{"name": f"min_{alias}_bar"},
|
|
{"name": f"min_{alias}_baz"},
|
|
{"name": f"max_{alias}_foo"},
|
|
{"name": f"max_{alias}_bar"},
|
|
{"name": f"max_{alias}_baz"},
|
|
],
|
|
"data": [
|
|
{
|
|
f"min_{alias}_foo": 1.23,
|
|
f"min_{alias}_bar": None,
|
|
f"min_{alias}_baz": 1.45,
|
|
f"max_{alias}_foo": 3.45,
|
|
f"max_{alias}_bar": None,
|
|
f"max_{alias}_baz": 3.67,
|
|
}
|
|
],
|
|
},
|
|
]
|
|
values = discover.find_histogram_min_max(
|
|
[f"{alias}.foo", f"{alias}.bar", f"{alias}.baz"],
|
|
None,
|
|
None,
|
|
"",
|
|
{"project_id": [self.project.id]},
|
|
)
|
|
assert values == (
|
|
1.23,
|
|
3.67,
|
|
), f"failing for {array_column}"
|
|
|
|
def test_find_histogram_params(self):
|
|
# min and max is None
|
|
assert discover.find_histogram_params(1, None, None, 1) == (1, 1, 0, 1)
|
|
# min is None
|
|
assert discover.find_histogram_params(1, None, 1, 10) == (1, 1, 0, 10)
|
|
# max is None
|
|
assert discover.find_histogram_params(1, 1, None, 100) == (1, 1, 100, 100)
|
|
|
|
assert discover.find_histogram_params(10, 0, 9, 1) == (10, 1, 0, 1)
|
|
assert discover.find_histogram_params(10, 0, 10, 1) == (6, 2, 0, 1)
|
|
assert discover.find_histogram_params(10, 0, 99, 1) == (10, 10, 0, 1)
|
|
assert discover.find_histogram_params(10, 0, 100, 1) == (6, 20, 0, 1)
|
|
assert discover.find_histogram_params(5, 10, 19, 10) == (5, 20, 100, 10)
|
|
assert discover.find_histogram_params(5, 10, 19.9, 10) == (5, 20, 100, 10)
|
|
assert discover.find_histogram_params(10, 10, 20, 1) == (6, 2, 10, 1)
|
|
assert discover.find_histogram_params(10, 10, 20, 10) == (6, 20, 100, 10)
|
|
assert discover.find_histogram_params(10, 10, 20, 100) == (9, 120, 1000, 100)
|
|
|
|
def test_normalize_histogram_results_empty(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{array_column}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{array_column}.foo": [
|
|
{"bin": 0, "count": 0},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_empty_multiple(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{array_column}.bar", f"{array_column}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{array_column}.bar": [
|
|
{"bin": 0, "count": 0},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
f"{array_column}.foo": [
|
|
{"bin": 0, "count": 0},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_full(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 1,
|
|
"count": 2,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 1,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 2},
|
|
{"bin": 2, "count": 1},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_full_multiple(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 1,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 1,
|
|
"count": 2,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 1,
|
|
"count": 2,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 1,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.bar", f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.bar": [
|
|
{"bin": 0, "count": 1},
|
|
{"bin": 1, "count": 2},
|
|
{"bin": 2, "count": 3},
|
|
],
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 2},
|
|
{"bin": 2, "count": 1},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_partial(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_partial_multiple(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 3,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.bar", f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.bar": [
|
|
{"bin": 0, "count": 0},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 3},
|
|
],
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_ignore_unexpected_rows(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_1_0_1": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
# this row shouldn't be used because "baz" isn't an expected array_column
|
|
{
|
|
f"array_join_{array_column}_key": "baz",
|
|
f"histogram_{array_column}_value_1_0_1": 1,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 3,
|
|
},
|
|
# this row shouldn't be used because 3 isn't an expected bin
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 3,
|
|
"count": 3,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.bar", f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(3, 1, 0, 1),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.bar": [
|
|
{"bin": 0, "count": 0},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 3},
|
|
],
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_normalize_histogram_results_adjust_for_precision(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
results = {
|
|
"meta": {
|
|
f"array_join_{array_column}_key": "string",
|
|
f"histogram_{array_column}_value_25_0_100": "number",
|
|
"count": "integer",
|
|
},
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_25_0_100": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_25_0_100": 25,
|
|
"count": 2,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_25_0_100": 50,
|
|
"count": 1,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_25_0_100": 75,
|
|
"count": 1,
|
|
},
|
|
],
|
|
}
|
|
normalized_results = discover.normalize_histogram_results(
|
|
[f"{alias}.foo"],
|
|
f"array_join({array_column}_key)",
|
|
HistogramParams(4, 25, 0, 100),
|
|
results,
|
|
array_column,
|
|
)
|
|
assert normalized_results == {
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 0.25, "count": 2},
|
|
{"bin": 0.50, "count": 1},
|
|
{"bin": 0.75, "count": 1},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_histogram_query(self, mock_query):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"min_{alias}_foo"},
|
|
{"name": f"max_{alias}_foo"},
|
|
],
|
|
"data": [
|
|
{
|
|
f"min_{alias}_bar": 2,
|
|
f"min_{alias}_foo": 0,
|
|
f"max_{alias}_bar": 2,
|
|
f"max_{alias}_foo": 2,
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"meta": [
|
|
{"name": f"array_join_{array_column}_key", "type": "String"},
|
|
{"name": f"histogram_{array_column}_value_1_0_1", "type": "Float64"},
|
|
{"name": "count", "type": "UInt64"},
|
|
],
|
|
"data": [
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 0,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_1_0_1": 2,
|
|
"count": 1,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
results = discover.histogram_query(
|
|
[f"{alias}.bar", f"{alias}.foo"],
|
|
"",
|
|
{"project_id": [self.project.id]},
|
|
3,
|
|
0,
|
|
)
|
|
assert results == {
|
|
f"{alias}.bar": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 0},
|
|
],
|
|
f"{alias}.foo": [
|
|
{"bin": 0, "count": 3},
|
|
{"bin": 1, "count": 0},
|
|
{"bin": 2, "count": 1},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
def test_histogram_query_with_bad_fields(self):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
with pytest.raises(InvalidSearchQuery) as err:
|
|
discover.histogram_query(
|
|
[f"{alias}.bar", "transaction.duration"],
|
|
"",
|
|
{"project_id": [self.project.id]},
|
|
3,
|
|
0,
|
|
)
|
|
assert "multihistogram expected either all measurements or all breakdowns" in str(
|
|
err
|
|
), f"failing for {array_column}"
|
|
|
|
@patch("sentry.snuba.discover.raw_query")
|
|
def test_histogram_query_with_optionals(self, mock_query):
|
|
for array_column in ARRAY_COLUMNS:
|
|
alias = get_array_column_alias(array_column)
|
|
mock_query.side_effect = [
|
|
{
|
|
"meta": [
|
|
{"name": f"array_join_{array_column}_key", "type": "String"},
|
|
{"name": f"histogram_{array_column}_value_5_5_10", "type": "Float64"},
|
|
{"name": "count", "type": "UInt64"},
|
|
],
|
|
"data": [
|
|
# this row shouldn't be used because it lies outside the boundary
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_5_5_10": 0,
|
|
"count": 1,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_5_5_10": 5,
|
|
"count": 3,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_5_5_10": 10,
|
|
"count": 2,
|
|
},
|
|
{
|
|
f"array_join_{array_column}_key": "foo",
|
|
f"histogram_{array_column}_value_5_5_10": 15,
|
|
"count": 1,
|
|
},
|
|
# this row shouldn't be used because it lies outside the boundary
|
|
{
|
|
f"array_join_{array_column}_key": "bar",
|
|
f"histogram_{array_column}_value_5_5_10": 30,
|
|
"count": 2,
|
|
},
|
|
],
|
|
},
|
|
]
|
|
results = discover.histogram_query(
|
|
[f"{alias}.bar", f"{alias}.foo"],
|
|
"",
|
|
{"project_id": [self.project.id]},
|
|
3,
|
|
1,
|
|
0.5,
|
|
2,
|
|
)
|
|
assert results == {
|
|
f"{alias}.bar": [
|
|
{"bin": 0.5, "count": 0},
|
|
{"bin": 1.0, "count": 2},
|
|
{"bin": 1.5, "count": 0},
|
|
],
|
|
f"{alias}.foo": [
|
|
{"bin": 0.5, "count": 3},
|
|
{"bin": 1.0, "count": 0},
|
|
{"bin": 1.5, "count": 1},
|
|
],
|
|
}, f"failing for {array_column}"
|
|
|
|
|
|
def test_zerofill():
|
|
results = discover.zerofill(
|
|
{}, datetime(2019, 1, 2, 0, 0), datetime(2019, 1, 9, 23, 59, 59), 86400, "time"
|
|
)
|
|
results_desc = discover.zerofill(
|
|
{}, datetime(2019, 1, 2, 0, 0), datetime(2019, 1, 9, 23, 59, 59), 86400, "-time"
|
|
)
|
|
|
|
assert results == list(reversed(results_desc))
|
|
|
|
# Bucket for the 2, 3, 4, 5, 6, 7, 8, 9
|
|
assert len(results) == 8
|
|
|
|
assert results[0]["time"] == 1546387200
|
|
assert results[7]["time"] == 1546992000
|