mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
6474 lines
244 KiB
Python
6474 lines
244 KiB
Python
import math
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from typing import Any
|
|
from unittest import mock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from django.test import override_settings
|
|
from django.urls import reverse
|
|
from django.utils import timezone as django_timezone
|
|
from snuba_sdk.column import Column
|
|
from snuba_sdk.function import Function
|
|
|
|
from sentry.discover.models import TeamKeyTransaction
|
|
from sentry.issues.grouptype import ProfileFileIOGroupType
|
|
from sentry.models.group import GroupStatus
|
|
from sentry.models.projectteam import ProjectTeam
|
|
from sentry.models.releaseprojectenvironment import ReleaseStages
|
|
from sentry.models.transaction_threshold import (
|
|
ProjectTransactionThreshold,
|
|
ProjectTransactionThresholdOverride,
|
|
TransactionMetric,
|
|
)
|
|
from sentry.search.events import constants
|
|
from sentry.testutils.cases import (
|
|
APITestCase,
|
|
PerformanceIssueTestCase,
|
|
ProfilesSnubaTestCase,
|
|
SnubaTestCase,
|
|
)
|
|
from sentry.testutils.helpers import parse_link_header
|
|
from sentry.testutils.helpers.datetime import before_now, freeze_time, iso_format
|
|
from sentry.testutils.silo import region_silo_test
|
|
from sentry.testutils.skips import requires_not_arm64
|
|
from sentry.types.group import GroupSubStatus
|
|
from sentry.utils import json
|
|
from sentry.utils.samples import load_data
|
|
from tests.sentry.issues.test_utils import SearchIssueTestMixin
|
|
|
|
MAX_QUERYABLE_TRANSACTION_THRESHOLDS = 1
|
|
|
|
|
|
class OrganizationEventsEndpointTestBase(APITestCase, SnubaTestCase):
|
|
viewname = "sentry-api-0-organization-events"
|
|
referrer = "api.organization-events"
|
|
# Some base data for create_span
|
|
base_span: dict[str, Any] = {
|
|
"is_segment": False,
|
|
"retention_days": 90,
|
|
"tags": {},
|
|
"sentry_tags": {},
|
|
"measurements": {},
|
|
}
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.nine_mins_ago = before_now(minutes=9)
|
|
self.ten_mins_ago = before_now(minutes=10)
|
|
self.ten_mins_ago_iso = iso_format(self.ten_mins_ago)
|
|
self.eleven_mins_ago = before_now(minutes=11)
|
|
self.eleven_mins_ago_iso = iso_format(self.eleven_mins_ago)
|
|
self.transaction_data = load_data("transaction", timestamp=self.ten_mins_ago)
|
|
self.features = {}
|
|
|
|
def client_get(self, *args, **kwargs):
|
|
return self.client.get(*args, **kwargs)
|
|
|
|
def reverse_url(self):
|
|
return reverse(
|
|
self.viewname,
|
|
kwargs={"organization_slug": self.organization.slug},
|
|
)
|
|
|
|
def do_request(self, query, features=None, **kwargs):
|
|
if features is None:
|
|
features = {"organizations:discover-basic": True}
|
|
features.update(self.features)
|
|
self.login_as(user=self.user)
|
|
with self.feature(features):
|
|
return self.client_get(self.reverse_url(), query, format="json", **kwargs)
|
|
|
|
def load_data(self, platform="transaction", timestamp=None, duration=None, **kwargs):
|
|
if timestamp is None:
|
|
timestamp = self.ten_mins_ago
|
|
|
|
min_age = before_now(minutes=10)
|
|
if timestamp > min_age:
|
|
# Sentry does some rounding of timestamps to improve cache hits in snuba.
|
|
# This can result in events not being returns if the timestamps
|
|
# are too recent.
|
|
raise Exception(
|
|
f"Please define a timestamp older than 10 minutes to avoid flakey tests. Want a timestamp before {min_age}, got: {timestamp} "
|
|
)
|
|
|
|
start_timestamp = None
|
|
if duration is not None:
|
|
start_timestamp = timestamp - duration
|
|
start_timestamp = start_timestamp - timedelta(
|
|
microseconds=start_timestamp.microsecond % 1000
|
|
)
|
|
|
|
return load_data(platform, timestamp=timestamp, start_timestamp=start_timestamp, **kwargs)
|
|
|
|
def create_span(
|
|
self, extra_data=None, organization=None, project=None, start_ts=None, duration=1000
|
|
):
|
|
"""Create span json, not required for store_span, but with no params passed should just work out of the box"""
|
|
if organization is None:
|
|
organization = self.organization
|
|
if project is None:
|
|
project = self.project
|
|
if start_ts is None:
|
|
start_ts = datetime.now() - timedelta(minutes=1)
|
|
if extra_data is None:
|
|
extra_data = {}
|
|
span = self.base_span.copy()
|
|
# Load some defaults
|
|
span.update(
|
|
{
|
|
"event_id": uuid4().hex,
|
|
"organization_id": organization.id,
|
|
"project_id": project.id,
|
|
"trace_id": uuid4().hex,
|
|
"span_id": uuid4().hex[:16],
|
|
"parent_span_id": uuid4().hex[:16],
|
|
"segment_id": uuid4().hex[:16],
|
|
"group_raw": uuid4().hex[:16],
|
|
"profile_id": uuid4().hex,
|
|
# Multiply by 1000 cause it needs to be ms
|
|
"start_timestamp_ms": int(start_ts.timestamp() * 1000),
|
|
"timestamp": int(start_ts.timestamp() * 1000),
|
|
"received": start_ts.timestamp(),
|
|
"duration_ms": duration,
|
|
"exclusive_time_ms": duration,
|
|
}
|
|
)
|
|
# Load any specific custom data
|
|
span.update(extra_data)
|
|
# coerce to string
|
|
for tag, value in dict(span["tags"]).items():
|
|
span["tags"][tag] = str(value)
|
|
return span
|
|
|
|
|
|
@region_silo_test
|
|
class OrganizationEventsEndpointTest(OrganizationEventsEndpointTestBase, PerformanceIssueTestCase):
|
|
def test_no_projects(self):
|
|
response = self.do_request({})
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"] == []
|
|
assert response.data["meta"] == {
|
|
"tips": {"query": "Need at least one valid project to query."}
|
|
}
|
|
|
|
def test_environment_filter(self):
|
|
self.create_environment(self.project, name="production")
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"environment": "production",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["id", "project.id"],
|
|
"project": [self.project.id],
|
|
"environment": ["staging", "production"],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 2
|
|
|
|
def test_performance_view_feature(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["id", "project.id"], "project": [self.project.id]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
def test_multi_project_feature_gate_rejection(self):
|
|
team = self.create_team(organization=self.organization, members=[self.user])
|
|
|
|
project = self.create_project(organization=self.organization, teams=[team])
|
|
project2 = self.create_project(organization=self.organization, teams=[team])
|
|
|
|
query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400
|
|
assert "events from multiple projects" in response.data["detail"]
|
|
|
|
def test_multi_project_feature_gate_replays(self):
|
|
team = self.create_team(organization=self.organization, members=[self.user])
|
|
|
|
project = self.create_project(organization=self.organization, teams=[team])
|
|
project2 = self.create_project(organization=self.organization, teams=[team])
|
|
|
|
query = {"field": ["id", "project.id"], "project": [project.id, project2.id]}
|
|
response = self.do_request(query, **{"HTTP_X-Sentry-Replay-Request": "1"})
|
|
assert response.status_code == 200
|
|
|
|
def test_invalid_search_terms(self):
|
|
self.create_project()
|
|
|
|
query = {"field": ["id"], "query": "hi \n there"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== "Parse error at 'hi \n ther' (column 4). This is commonly caused by unmatched parentheses. Enclose any text in double quotes."
|
|
)
|
|
|
|
def test_invalid_trace_span(self):
|
|
self.create_project()
|
|
|
|
query = {"field": ["id"], "query": "trace.span:invalid"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== "trace.span must be a valid 16 character hex (containing only digits, or a-f characters)"
|
|
)
|
|
|
|
query = {"field": ["id"], "query": "trace.parent_span:invalid"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== "trace.parent_span must be a valid 16 character hex (containing only digits, or a-f characters)"
|
|
)
|
|
|
|
query = {"field": ["id"], "query": "trace.span:*"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"] == "Wildcard conditions are not permitted on `trace.span` field"
|
|
)
|
|
|
|
query = {"field": ["id"], "query": "trace.parent_span:*"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== "Wildcard conditions are not permitted on `trace.parent_span` field"
|
|
)
|
|
|
|
def test_has_trace_context(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"contexts": {
|
|
"trace": {
|
|
"span_id": "a" * 16,
|
|
"trace_id": "b" * 32,
|
|
},
|
|
},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["id", "trace.parent_span"], "query": "has:trace.span"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["id"] == "a" * 32
|
|
|
|
query = {"field": ["id"], "query": "has:trace.parent_span"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_treat_status_as_tag_discover_transaction(self):
|
|
event_1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"status": "good"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event_2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"status": "bad"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["event_id"],
|
|
"query": "!status:good",
|
|
"dataset": "discover",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"] == [
|
|
{"event_id": "", "id": event_2.event_id, "project.name": self.project.slug}
|
|
]
|
|
|
|
query = {"field": ["event_id"], "query": ["status:good"], "dataset": "discover"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"] == [
|
|
{"event_id": "", "id": event_1.event_id, "project.name": self.project.slug}
|
|
]
|
|
|
|
def test_not_has_trace_context(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"contexts": {
|
|
"trace": {
|
|
"span_id": "a" * 16,
|
|
"trace_id": "b" * 32,
|
|
},
|
|
},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["id", "trace.parent_span"], "query": "!has:trace.span"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
query = {"field": ["id"], "query": "!has:trace.parent_span"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["id"] == "a" * 32
|
|
|
|
def test_parent_span_id_in_context(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"contexts": {
|
|
"trace": {
|
|
"span_id": "a" * 16,
|
|
"trace_id": "b" * 32,
|
|
"parent_span_id": "c" * 16,
|
|
},
|
|
},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["id"], "query": f"trace.parent_span:{'c' * 16}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["id"] == "a" * 32
|
|
|
|
def test_out_of_retention(self):
|
|
self.create_project()
|
|
with self.options({"system.event-retention-days": 10}):
|
|
query = {
|
|
"field": ["id", "timestamp"],
|
|
"orderby": ["-timestamp", "-id"],
|
|
"start": iso_format(before_now(days=20)),
|
|
"end": iso_format(before_now(days=15)),
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert response.data["detail"] == "Invalid date range. Please try a more recent date range."
|
|
|
|
def test_raw_data(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.eleven_mins_ago_iso,
|
|
"user": {"ip_address": "127.0.0.1", "email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"ip_address": "127.0.0.1", "email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["id", "project.id", "user.email", "user.ip", "timestamp"],
|
|
"orderby": "-timestamp",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["id"] == "b" * 32
|
|
assert data[0]["project.id"] == self.project.id
|
|
assert data[0]["user.email"] == "[email protected]"
|
|
assert "project.name" not in data[0], "project.id does not auto select name"
|
|
assert "project" not in data[0]
|
|
|
|
meta = response.data["meta"]
|
|
field_meta = meta["fields"]
|
|
assert field_meta["id"] == "string"
|
|
assert field_meta["user.email"] == "string"
|
|
assert field_meta["user.ip"] == "string"
|
|
assert field_meta["timestamp"] == "date"
|
|
|
|
def test_project_name(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["project.name", "environment"]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project.name"] == self.project.slug
|
|
assert "project.id" not in response.data["data"][0]
|
|
assert response.data["data"][0]["environment"] == "staging"
|
|
|
|
def test_project_without_name(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["project", "environment"]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project"] == self.project.slug
|
|
assert response.data["meta"]["fields"]["project"] == "string"
|
|
assert "project.id" not in response.data["data"][0]
|
|
assert response.data["data"][0]["environment"] == "staging"
|
|
|
|
def test_project_in_query(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["project", "count()"],
|
|
"query": f'project:"{self.project.slug}"',
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project"] == self.project.slug
|
|
assert "project.id" not in response.data["data"][0]
|
|
|
|
def test_project_in_query_not_in_header(self):
|
|
project = self.create_project()
|
|
other_project = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["project", "count()"],
|
|
"query": 'project:"%s"' % project.slug,
|
|
"statsPeriod": "14d",
|
|
"project": other_project.id,
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== f"Invalid query. Project(s) {project.slug} do not exist or are not actively selected."
|
|
)
|
|
|
|
def test_project_in_query_does_not_exist(self):
|
|
self.create_project()
|
|
|
|
query = {"field": ["project", "count()"], "query": "project:morty", "statsPeriod": "14d"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== "Invalid query. Project(s) morty do not exist or are not actively selected."
|
|
)
|
|
|
|
def test_not_project_in_query_but_in_header(self):
|
|
team = self.create_team(organization=self.organization, members=[self.user])
|
|
|
|
project = self.create_project(organization=self.organization, teams=[team])
|
|
project2 = self.create_project(organization=self.organization, teams=[team])
|
|
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group2"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["id", "project.id"],
|
|
"project": [project.id],
|
|
"query": f"!project:{project2.slug}",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
|
|
|
|
def test_not_project_in_query_with_all_projects(self):
|
|
team = self.create_team(organization=self.organization, members=[self.user])
|
|
|
|
project = self.create_project(organization=self.organization, teams=[team])
|
|
project2 = self.create_project(organization=self.organization, teams=[team])
|
|
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group2"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["id", "project.id"],
|
|
"project": [-1],
|
|
"query": f"!project:{project2.slug}",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200
|
|
assert response.data["data"] == [{"id": "a" * 32, "project.id": project.id}]
|
|
|
|
def test_project_condition_used_for_automatic_filters(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["project", "count()"],
|
|
"query": f'project:"{self.project.slug}"',
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project"] == self.project.slug
|
|
assert "project.id" not in response.data["data"][0]
|
|
|
|
def test_auto_insert_project_name_when_event_id_present(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {"field": ["id"], "statsPeriod": "1h"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"] == [{"project.name": self.project.slug, "id": "a" * 32}]
|
|
|
|
def test_auto_insert_project_name_when_event_id_present_with_aggregate(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {"field": ["id", "count()"], "statsPeriod": "1h"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"] == [
|
|
{"project.name": self.project.slug, "id": "a" * 32, "count()": 1}
|
|
]
|
|
|
|
def test_performance_short_group_id(self):
|
|
event = self.create_performance_issue()
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{event.group.project.slug} issue:{event.group.qualified_short_id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_multiple_performance_short_group_ids_filter(self):
|
|
event1 = self.create_performance_issue()
|
|
event2 = self.create_performance_issue()
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{event1.group.project.slug} issue:[{event1.group.qualified_short_id},{event2.group.qualified_short_id}]",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 2
|
|
|
|
def test_event_id_with_in_search(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging1",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"environment": "staging2",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
# Should not show up
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"environment": "staging3",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {
|
|
"field": ["id", "environment"],
|
|
"statsPeriod": "1h",
|
|
"query": f"id:[{'a' * 32}, {'b' * 32}]",
|
|
"orderby": "environment",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
assert response.data["data"][0]["id"] == "a" * 32
|
|
assert response.data["data"][1]["id"] == "b" * 32
|
|
|
|
def test_user_search(self):
|
|
self.transaction_data["user"] = {
|
|
"email": "[email protected]",
|
|
"id": "123",
|
|
"ip_address": "127.0.0.1",
|
|
"username": "foo",
|
|
}
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
fields = {
|
|
"email": "user.email",
|
|
"id": "user.id",
|
|
"ip_address": "user.ip",
|
|
"username": "user.username",
|
|
}
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
for key, value in self.transaction_data["user"].items():
|
|
field = fields[key]
|
|
query = {
|
|
"field": ["project", "user"],
|
|
"query": f"{field}:{value}",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project"] == self.project.slug
|
|
assert response.data["data"][0]["user"] == "id:123"
|
|
|
|
def test_has_user(self):
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
for value in self.transaction_data["user"].values():
|
|
query = {"field": ["project", "user"], "query": "has:user", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["user"] == "ip:{}".format(
|
|
self.transaction_data["user"]["ip_address"]
|
|
)
|
|
|
|
def test_team_param_no_access(self):
|
|
org = self.create_organization(
|
|
owner=self.user, # use other user as owner
|
|
name="foo",
|
|
flags=0, # disable default allow_joinleave
|
|
)
|
|
project = self.create_project(name="baz", organization=org)
|
|
|
|
user = self.create_user()
|
|
self.login_as(user=user, superuser=False)
|
|
|
|
team = self.create_team(organization=org, name="Team Bar")
|
|
project.add_team(team)
|
|
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=project.id,
|
|
)
|
|
|
|
query = {"field": ["id", "project.id"], "project": [project.id], "team": [team.id]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 403, response.content
|
|
|
|
assert response.data["detail"] == "You do not have permission to perform this action."
|
|
|
|
def test_team_is_nan(self):
|
|
query = {"field": ["id"], "project": [self.project.id], "team": [math.nan]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
assert response.data["detail"] == "Invalid Team ID: nan"
|
|
|
|
def test_comparison_operators_on_numeric_field(self):
|
|
event = self.store_event(
|
|
{"timestamp": iso_format(before_now(minutes=1))}, project_id=self.project.id
|
|
)
|
|
|
|
query = {"field": ["issue"], "query": f"issue.id:>{event.group.id - 1}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
query = {"field": ["issue"], "query": f"issue.id:>{event.group.id}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_negation_on_numeric_field_excludes_issue(self):
|
|
event = self.store_event({"timestamp": self.ten_mins_ago_iso}, project_id=self.project.id)
|
|
|
|
query = {"field": ["issue"], "query": f"issue.id:{event.group.id}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
query = {"field": ["issue"], "query": f"!issue.id:{event.group.id}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_negation_on_numeric_in_filter_excludes_issue(self):
|
|
event = self.store_event({"timestamp": self.ten_mins_ago_iso}, project_id=self.project.id)
|
|
|
|
query = {"field": ["issue"], "query": f"issue.id:[{event.group.id}]"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
query = {"field": ["issue"], "query": f"!issue.id:[{event.group.id}]"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_negation_on_duration_filter_excludes_transaction(self):
|
|
event = self.store_event(self.transaction_data, project_id=self.project.id)
|
|
duration = int(event.data.get("timestamp") - event.data.get("start_timestamp")) * 1000
|
|
|
|
query = {"field": ["transaction"], "query": f"transaction.duration:{duration}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["id"] == event.event_id
|
|
|
|
query = {"field": ["transaction"], "query": f"!transaction.duration:{duration}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_has_issue(self):
|
|
event = self.store_event({"timestamp": self.ten_mins_ago_iso}, project_id=self.project.id)
|
|
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
|
|
# should only show 1 event of type default
|
|
query = {"field": ["project", "issue"], "query": "has:issue", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
# should only show 1 event of type default
|
|
query = {
|
|
"field": ["project", "issue"],
|
|
"query": "event.type:default has:issue",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
# should show no results because no the default event has an issue
|
|
query = {
|
|
"field": ["project", "issue"],
|
|
"query": "event.type:default !has:issue",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
# should show no results because no transactions have issues
|
|
query = {
|
|
"field": ["project", "issue"],
|
|
"query": "event.type:transaction has:issue",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
# should only show 1 event of type transaction since they don't have issues
|
|
query = {
|
|
"field": ["project", "issue"],
|
|
"query": "event.type:transaction !has:issue",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == "unknown"
|
|
|
|
@pytest.mark.skip("Cannot look up group_id of transaction events")
|
|
def test_unknown_issue(self):
|
|
event = self.store_event({"timestamp": self.ten_mins_ago_iso}, project_id=self.project.id)
|
|
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["project", "issue"], "query": "issue:unknown", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == "unknown"
|
|
|
|
query = {"field": ["project", "issue"], "query": "!issue:unknown", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["issue"] == event.group.qualified_short_id
|
|
|
|
def test_negative_user_search(self):
|
|
user_data = {"email": "[email protected]", "id": "123", "username": "foo"}
|
|
|
|
# Load an event with data that shouldn't match
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/transactions/nomatch"
|
|
event_user = user_data.copy()
|
|
event_user["id"] = "undefined"
|
|
data["user"] = event_user
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
# Load a matching event
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/transactions/matching"
|
|
data["user"] = user_data
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["project", "user"],
|
|
"query": '!user:"id:undefined"',
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["user"] == "id:{}".format(user_data["id"])
|
|
assert "user.email" not in response.data["data"][0]
|
|
assert "user.id" not in response.data["data"][0]
|
|
|
|
def test_not_project_in_query(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["project", "count()"],
|
|
"query": '!project:"%s"' % project1.slug,
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["project"] == project2.slug
|
|
assert "project.id" not in response.data["data"][0]
|
|
|
|
def test_error_handled_condition(self):
|
|
prototype = self.load_data(platform="android-ndk")
|
|
events = (
|
|
("a" * 32, "not handled", False),
|
|
("b" * 32, "was handled", True),
|
|
("c" * 32, "undefined", None),
|
|
)
|
|
for event in events:
|
|
prototype["event_id"] = event[0]
|
|
prototype["message"] = event[1]
|
|
prototype["exception"]["values"][0]["value"] = event[1]
|
|
prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
|
|
prototype["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=prototype, project_id=self.project.id)
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["message", "error.handled"],
|
|
"query": "error.handled:0",
|
|
"orderby": "message",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 1 == len(response.data["data"])
|
|
assert 0 == response.data["data"][0]["error.handled"]
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["message", "error.handled"],
|
|
"query": "error.handled:1",
|
|
"orderby": "message",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 2 == len(response.data["data"])
|
|
assert 1 == response.data["data"][0]["error.handled"]
|
|
assert 1 == response.data["data"][1]["error.handled"]
|
|
|
|
def test_error_unhandled_condition(self):
|
|
prototype = self.load_data(platform="android-ndk")
|
|
events = (
|
|
("a" * 32, "not handled", False),
|
|
("b" * 32, "was handled", True),
|
|
("c" * 32, "undefined", None),
|
|
)
|
|
for event in events:
|
|
prototype["event_id"] = event[0]
|
|
prototype["message"] = event[1]
|
|
prototype["exception"]["values"][0]["value"] = event[1]
|
|
prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
|
|
prototype["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=prototype, project_id=self.project.id)
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["message", "error.unhandled", "error.handled"],
|
|
"query": "error.unhandled:true",
|
|
"orderby": "message",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 1 == len(response.data["data"])
|
|
assert 0 == response.data["data"][0]["error.handled"]
|
|
assert 1 == response.data["data"][0]["error.unhandled"]
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["message", "error.handled", "error.unhandled"],
|
|
"query": "error.unhandled:false",
|
|
"orderby": "message",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 2 == len(response.data["data"])
|
|
assert 1 == response.data["data"][0]["error.handled"]
|
|
assert 0 == response.data["data"][0]["error.unhandled"]
|
|
assert 1 == response.data["data"][1]["error.handled"]
|
|
assert 0 == response.data["data"][1]["error.unhandled"]
|
|
|
|
def test_groupby_error_handled_and_unhandled(self):
|
|
prototype = self.load_data(platform="android-ndk")
|
|
events = (
|
|
("a" * 32, "not handled", False),
|
|
("b" * 32, "was handled", True),
|
|
("c" * 32, "undefined", None),
|
|
)
|
|
for event in events:
|
|
prototype["event_id"] = event[0]
|
|
prototype["message"] = event[1]
|
|
prototype["exception"]["values"][0]["value"] = event[1]
|
|
prototype["exception"]["values"][0]["mechanism"]["handled"] = event[2]
|
|
prototype["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=prototype, project_id=self.project.id)
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["error.handled", "count()"],
|
|
"query": "event.type:error",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 2 == len(response.data["data"])
|
|
assert 0 == response.data["data"][0]["error.handled"]
|
|
assert 1 == response.data["data"][0]["count()"]
|
|
assert 1 == response.data["data"][1]["error.handled"]
|
|
assert 2 == response.data["data"][1]["count()"]
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["error.unhandled", "count()"],
|
|
"query": "event.type:error",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 2 == len(response.data["data"])
|
|
assert 0 == response.data["data"][0]["error.unhandled"]
|
|
assert 2 == response.data["data"][0]["count()"]
|
|
assert 1 == response.data["data"][1]["error.unhandled"]
|
|
assert 1 == response.data["data"][1]["count()"]
|
|
|
|
def test_error_main_thread_condition(self):
|
|
prototype = self.load_data(platform="android-ndk")
|
|
|
|
prototype["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=prototype, project_id=self.project.id)
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["id", "project.id"],
|
|
"query": "error.main_thread:true",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 1 == len(response.data["data"])
|
|
|
|
with self.feature("organizations:discover-basic"):
|
|
query = {
|
|
"field": ["id", "project.id"],
|
|
"query": "error.main_thread:false",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.data
|
|
assert 0 == len(response.data["data"])
|
|
|
|
def test_implicit_groupby(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.eleven_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["count(id)", "project.id", "issue.id"], "orderby": "issue.id"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0] == {
|
|
"project.id": self.project.id,
|
|
"issue.id": event1.group_id,
|
|
"count(id)": 2,
|
|
}
|
|
assert data[1] == {
|
|
"project.id": self.project.id,
|
|
"issue.id": event2.group_id,
|
|
"count(id)": 1,
|
|
}
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["count(id)"] == "integer"
|
|
|
|
def test_orderby(self):
|
|
self.store_event(
|
|
data={"event_id": "a" * 32, "timestamp": self.eleven_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": "b" * 32, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": "c" * 32, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {"field": ["id", "timestamp"], "orderby": ["-timestamp", "-id"]}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert data[0]["id"] == "c" * 32
|
|
assert data[1]["id"] == "b" * 32
|
|
assert data[2]["id"] == "a" * 32
|
|
|
|
def test_sort_title(self):
|
|
self.store_event(
|
|
data={"event_id": "a" * 32, "message": "zlast", "timestamp": self.eleven_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": "b" * 32, "message": "second", "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={"event_id": "c" * 32, "message": "first", "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {"field": ["id", "title"], "sort": "title"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert data[0]["id"] == "c" * 32
|
|
assert data[1]["id"] == "b" * 32
|
|
assert data[2]["id"] == "a" * 32
|
|
|
|
def test_sort_invalid(self):
|
|
self.create_project()
|
|
|
|
query = {"field": ["id"], "sort": "garbage"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400
|
|
assert "sort by" in response.data["detail"]
|
|
|
|
def test_latest_release_alias(self):
|
|
event1 = self.store_event(
|
|
data={"event_id": "a" * 32, "timestamp": self.eleven_mins_ago_iso, "release": "0.8"},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {"field": ["issue.id", "release"], "query": "release:latest"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event1.group_id
|
|
assert data[0]["release"] == "0.8"
|
|
|
|
event2 = self.store_event(
|
|
data={"event_id": "a" * 32, "timestamp": self.ten_mins_ago_iso, "release": "0.9"},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["issue.id", "release"], "query": "release:latest"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event2.group_id
|
|
assert data[0]["release"] == "0.9"
|
|
|
|
def test_semver(self):
|
|
release_1 = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
release_3 = self.create_release(version="[email protected]")
|
|
|
|
release_1_e_1 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_1_e_2 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_2_e_1 = self.store_event(
|
|
data={"release": release_2.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_2_e_2 = self.store_event(
|
|
data={"release": release_2.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_3_e_1 = self.store_event(
|
|
data={"release": release_3.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_3_e_2 = self.store_event(
|
|
data={"release": release_3.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:>1.2.3"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_2_e_1,
|
|
release_2_e_2,
|
|
release_3_e_1,
|
|
release_3_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:>=1.2.3"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
release_2_e_1,
|
|
release_2_e_2,
|
|
release_3_e_1,
|
|
release_3_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:<1.2.4"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_ALIAS}:1.2.3"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"!{constants.SEMVER_ALIAS}:1.2.3"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_2_e_1,
|
|
release_2_e_2,
|
|
release_3_e_1,
|
|
release_3_e_2,
|
|
}
|
|
|
|
def test_release_stage(self):
|
|
replaced_release = self.create_release(
|
|
version="replaced_release",
|
|
environments=[self.environment],
|
|
adopted=django_timezone.now(),
|
|
unadopted=django_timezone.now(),
|
|
)
|
|
adopted_release = self.create_release(
|
|
version="adopted_release",
|
|
environments=[self.environment],
|
|
adopted=django_timezone.now(),
|
|
)
|
|
self.create_release(version="not_adopted_release", environments=[self.environment])
|
|
|
|
adopted_release_e_1 = self.store_event(
|
|
data={
|
|
"release": adopted_release.version,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"environment": self.environment.name,
|
|
},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
adopted_release_e_2 = self.store_event(
|
|
data={
|
|
"release": adopted_release.version,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"environment": self.environment.name,
|
|
},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
replaced_release_e_1 = self.store_event(
|
|
data={
|
|
"release": replaced_release.version,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"environment": self.environment.name,
|
|
},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
replaced_release_e_2 = self.store_event(
|
|
data={
|
|
"release": replaced_release.version,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"environment": self.environment.name,
|
|
},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
|
|
query = {
|
|
"field": ["id"],
|
|
"query": f"{constants.RELEASE_STAGE_ALIAS}:{ReleaseStages.ADOPTED.value}",
|
|
"environment": [self.environment.name],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
adopted_release_e_1,
|
|
adopted_release_e_2,
|
|
}
|
|
|
|
query = {
|
|
"field": ["id"],
|
|
"query": f"!{constants.RELEASE_STAGE_ALIAS}:{ReleaseStages.LOW_ADOPTION.value}",
|
|
"environment": [self.environment.name],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
adopted_release_e_1,
|
|
adopted_release_e_2,
|
|
replaced_release_e_1,
|
|
replaced_release_e_2,
|
|
}
|
|
|
|
query = {
|
|
"field": ["id"],
|
|
"query": f"{constants.RELEASE_STAGE_ALIAS}:[{ReleaseStages.ADOPTED.value}, {ReleaseStages.REPLACED.value}]",
|
|
"environment": [self.environment.name],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
adopted_release_e_1,
|
|
adopted_release_e_2,
|
|
replaced_release_e_1,
|
|
replaced_release_e_2,
|
|
}
|
|
|
|
def test_semver_package(self):
|
|
release_1 = self.create_release(version="[email protected]")
|
|
release_2 = self.create_release(version="[email protected]")
|
|
|
|
release_1_e_1 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_1_e_2 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_2_e_1 = self.store_event(
|
|
data={"release": release_2.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_PACKAGE_ALIAS}:test"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_PACKAGE_ALIAS}:test2"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_2_e_1,
|
|
}
|
|
|
|
def test_semver_build(self):
|
|
release_1 = self.create_release(version="[email protected]+123")
|
|
release_2 = self.create_release(version="[email protected]+124")
|
|
|
|
release_1_e_1 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_1_e_2 = self.store_event(
|
|
data={"release": release_1.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
release_2_e_1 = self.store_event(
|
|
data={"release": release_2.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
).event_id
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_BUILD_ALIAS}:123"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
}
|
|
|
|
query = {"field": ["id"], "query": f"{constants.SEMVER_BUILD_ALIAS}:124"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_2_e_1,
|
|
}
|
|
query = {"field": ["id"], "query": f"!{constants.SEMVER_BUILD_ALIAS}:124"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert {r["id"] for r in response.data["data"]} == {
|
|
release_1_e_1,
|
|
release_1_e_2,
|
|
}
|
|
|
|
def test_aliased_fields(self):
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["issue.id", "count(id)", "count_unique(user)"], "orderby": "issue.id"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event1.group_id
|
|
assert data[0]["count(id)"] == 1
|
|
assert data[0]["count_unique(user)"] == 1
|
|
assert "projectid" not in data[0]
|
|
assert "project.id" not in data[0]
|
|
assert data[1]["issue.id"] == event2.group_id
|
|
assert data[1]["count(id)"] == 2
|
|
assert data[1]["count_unique(user)"] == 2
|
|
|
|
def test_aggregate_field_with_dotted_param(self):
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"id": "123", "email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"id": "123", "email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"id": "456", "email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {
|
|
"field": ["issue.id", "issue_title", "count(id)", "count_unique(user.email)"],
|
|
"orderby": "issue.id",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event1.group_id
|
|
assert data[0]["count(id)"] == 1
|
|
assert data[0]["count_unique(user.email)"] == 1
|
|
assert "projectid" not in data[0]
|
|
assert "project.id" not in data[0]
|
|
assert data[1]["issue.id"] == event2.group_id
|
|
assert data[1]["count(id)"] == 2
|
|
assert data[1]["count_unique(user.email)"] == 2
|
|
|
|
def test_failure_rate_alias_field(self):
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/failure_rate/success"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/failure_rate/unknown"
|
|
data["contexts"]["trace"]["status"] = "unknown_error"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
for i in range(6):
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = f"/failure_rate/{i}"
|
|
data["contexts"]["trace"]["status"] = "unauthenticated"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {"field": ["failure_rate()"], "query": "event.type:transaction"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["failure_rate()"] == 0.75
|
|
|
|
def test_count_miserable_alias_field(self):
|
|
events = [
|
|
("one", 300),
|
|
("one", 300),
|
|
("two", 3000),
|
|
("two", 3000),
|
|
("three", 300),
|
|
("three", 3000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/count_miserable/horribilis/{idx}"
|
|
data["user"] = {"email": f"{event[0]}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
query = {"field": ["count_miserable(user, 300)"], "query": "event.type:transaction"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["count_miserable(user, 300)"] == 2
|
|
|
|
@mock.patch(
|
|
"sentry.search.events.fields.MAX_QUERYABLE_TRANSACTION_THRESHOLDS",
|
|
MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
|
|
)
|
|
@mock.patch(
|
|
"sentry.search.events.datasets.discover.MAX_QUERYABLE_TRANSACTION_THRESHOLDS",
|
|
MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
|
|
)
|
|
def test_too_many_transaction_thresholds(self):
|
|
project_transaction_thresholds = []
|
|
project_ids = []
|
|
for i in range(MAX_QUERYABLE_TRANSACTION_THRESHOLDS + 1):
|
|
project = self.create_project(name=f"bulk_txn_{i}")
|
|
project_ids.append(project.id)
|
|
project_transaction_thresholds.append(
|
|
ProjectTransactionThreshold(
|
|
organization=self.organization,
|
|
project=project,
|
|
threshold=400,
|
|
metric=TransactionMetric.LCP.value,
|
|
)
|
|
)
|
|
|
|
ProjectTransactionThreshold.objects.bulk_create(project_transaction_thresholds)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"count_miserable(user)",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": project_ids,
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
features={
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert (
|
|
response.data["detail"]
|
|
== "Exceeded 1 configured transaction thresholds limit, try with fewer Projects."
|
|
)
|
|
|
|
def test_count_miserable_new_alias_field(self):
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=400,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
events = [
|
|
("one", 400),
|
|
("one", 400),
|
|
("two", 3000),
|
|
("two", 3000),
|
|
("three", 300),
|
|
("three", 3000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
|
|
data["user"] = {"email": f"{idx}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"count_miserable(user)",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
"sort": "count_miserable_user",
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 3
|
|
data = response.data["data"]
|
|
assert data[0]["count_miserable(user)"] == 0
|
|
assert data[1]["count_miserable(user)"] == 1
|
|
assert data[2]["count_miserable(user)"] == 2
|
|
|
|
query["query"] = "event.type:transaction count_miserable(user):>0"
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert abs(data[0]["count_miserable(user)"]) == 1
|
|
assert abs(data[1]["count_miserable(user)"]) == 2
|
|
|
|
def test_user_misery_denominator(self):
|
|
"""This is to test against a bug where the denominator of misery(total unique users) was wrong
|
|
This is because the total unique users for a LCP misery should only count users that have had a txn with lcp,
|
|
and not count all transactions (ie. uniq_if(transaction has lcp) not just uniq())
|
|
"""
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=600,
|
|
metric=TransactionMetric.LCP.value,
|
|
)
|
|
lcps = [
|
|
400,
|
|
400,
|
|
300,
|
|
3000,
|
|
3000,
|
|
3000,
|
|
]
|
|
for idx, lcp in enumerate(lcps):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = "/misery/new/"
|
|
data["user"] = {"email": f"{idx}@example.com"}
|
|
data["measurements"] = {
|
|
"lcp": {"value": lcp},
|
|
}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
# Shouldn't count towards misery
|
|
data = self.load_data(timestamp=self.ten_mins_ago, duration=timedelta(milliseconds=0))
|
|
data["transaction"] = "/misery/new/"
|
|
data["user"] = {"email": "[email protected]"}
|
|
data["measurements"] = {}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"user_misery()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
"sort": "-user_misery",
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
# (3 frustrated + 5.8875) / (6 + 117.75)
|
|
assert abs(data[0]["user_misery()"] - 0.071818) < 0.0001
|
|
|
|
def test_user_misery_alias_field(self):
|
|
events = [
|
|
("one", 300),
|
|
("one", 300),
|
|
("two", 3000),
|
|
("two", 3000),
|
|
("three", 300),
|
|
("three", 3000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/user_misery/{idx}"
|
|
data["user"] = {"email": f"{event[0]}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
query = {"field": ["user_misery(300)"], "query": "event.type:transaction"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert abs(data[0]["user_misery(300)"] - 0.0653) < 0.0001
|
|
|
|
def test_apdex_denominator_correct(self):
|
|
"""This is to test against a bug where the denominator of apdex(total count) was wrong
|
|
|
|
This is because the total_count for a LCP apdex should only count transactions that have lcp, and not count
|
|
all transactions (ie. count_if(transaction has lcp) not just count())
|
|
"""
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=600,
|
|
metric=TransactionMetric.LCP.value,
|
|
)
|
|
lcps = [
|
|
400,
|
|
400,
|
|
300,
|
|
800,
|
|
3000,
|
|
3000,
|
|
3000,
|
|
]
|
|
for idx, lcp in enumerate(lcps):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = "/apdex/new/"
|
|
data["user"] = {"email": f"{idx}@example.com"}
|
|
data["measurements"] = {
|
|
"lcp": {"value": lcp},
|
|
}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
# Shouldn't count towards apdex
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(milliseconds=0),
|
|
)
|
|
data["transaction"] = "/apdex/new/"
|
|
data["user"] = {"email": "[email protected]"}
|
|
data["measurements"] = {}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"apdex()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
"sort": "-apdex",
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
# 3 satisfied + 1 tolerated => 3.5/7
|
|
assert data[0]["apdex()"] == 0.5
|
|
|
|
def test_apdex_new_alias_field(self):
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=400,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
events = [
|
|
("one", 400),
|
|
("one", 400),
|
|
("two", 3000),
|
|
("two", 3000),
|
|
("three", 300),
|
|
("three", 3000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/apdex/new/{event[0]}"
|
|
data["user"] = {"email": f"{idx}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"apdex()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
"sort": "-apdex",
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 3
|
|
data = response.data["data"]
|
|
assert data[0]["apdex()"] == 1.0
|
|
assert data[1]["apdex()"] == 0.5
|
|
assert data[2]["apdex()"] == 0.0
|
|
|
|
query["query"] = "event.type:transaction apdex():>0.50"
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["apdex()"] == 1.0
|
|
|
|
def test_user_misery_alias_field_with_project_threshold(self):
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=400,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
events = [
|
|
("one", 400),
|
|
("one", 400),
|
|
("two", 3000),
|
|
("two", 3000),
|
|
("three", 300),
|
|
("three", 3000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/count_miserable/horribilis/{event[0]}"
|
|
data["user"] = {"email": f"{idx}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"user_misery()",
|
|
],
|
|
"orderby": "user_misery()",
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
}
|
|
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 3
|
|
data = response.data["data"]
|
|
assert data[0]["user_misery()"] == pytest.approx(0.04916, rel=1e-3)
|
|
assert data[1]["user_misery()"] == pytest.approx(0.05751, rel=1e-3)
|
|
assert data[2]["user_misery()"] == pytest.approx(0.06586, rel=1e-3)
|
|
|
|
query["query"] = "event.type:transaction user_misery():>0.050"
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["user_misery()"] == pytest.approx(0.05751, rel=1e-3)
|
|
assert data[1]["user_misery()"] == pytest.approx(0.06586, rel=1e-3)
|
|
|
|
def test_user_misery_alias_field_with_transaction_threshold(self):
|
|
events = [
|
|
("one", 300),
|
|
("two", 300),
|
|
("one", 3000),
|
|
("two", 3000),
|
|
("three", 400),
|
|
("four", 4000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/count_miserable/horribilis/{idx}"
|
|
data["user"] = {"email": f"{event[0]}@example.com"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
if idx % 2:
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction=f"/count_miserable/horribilis/{idx}",
|
|
project=self.project,
|
|
organization=self.project.organization,
|
|
threshold=100 * idx,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"user_misery()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"orderby": "transaction",
|
|
"project": [self.project.id],
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
expected = [
|
|
("/count_miserable/horribilis/0", ["duration", 300], 0.049578),
|
|
("/count_miserable/horribilis/1", ["duration", 100], 0.049578),
|
|
("/count_miserable/horribilis/2", ["duration", 300], 0.058),
|
|
("/count_miserable/horribilis/3", ["duration", 300], 0.058),
|
|
("/count_miserable/horribilis/4", ["duration", 300], 0.049578),
|
|
("/count_miserable/horribilis/5", ["duration", 500], 0.058),
|
|
]
|
|
|
|
assert len(response.data["data"]) == 6
|
|
data = response.data["data"]
|
|
for i, record in enumerate(expected):
|
|
name, threshold_config, misery = record
|
|
assert data[i]["transaction"] == name
|
|
assert data[i]["project_threshold_config"] == threshold_config
|
|
assert data[i]["user_misery()"] == pytest.approx(misery, rel=1e-3)
|
|
|
|
query["query"] = "event.type:transaction user_misery():>0.050"
|
|
|
|
response = self.do_request(
|
|
query,
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 3
|
|
data = response.data["data"]
|
|
assert data[0]["user_misery()"] == pytest.approx(0.058, rel=1e-3)
|
|
assert data[1]["user_misery()"] == pytest.approx(0.058, rel=1e-3)
|
|
assert data[2]["user_misery()"] == pytest.approx(0.058, rel=1e-3)
|
|
|
|
def test_user_misery_alias_field_with_transaction_threshold_and_project_threshold(self):
|
|
project = self.create_project()
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=project,
|
|
organization=project.organization,
|
|
threshold=100,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
events = [
|
|
("one", 300),
|
|
("two", 300),
|
|
("one", 3000),
|
|
("two", 3000),
|
|
("three", 400),
|
|
("four", 4000),
|
|
]
|
|
for idx, event in enumerate(events):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + idx)),
|
|
duration=timedelta(milliseconds=event[1]),
|
|
)
|
|
data["event_id"] = f"{idx}" * 32
|
|
data["transaction"] = f"/count_miserable/horribilis/{idx}"
|
|
data["user"] = {"email": f"{event[0]}@example.com"}
|
|
self.store_event(data, project_id=project.id)
|
|
|
|
if idx % 2:
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction=f"/count_miserable/horribilis/{idx}",
|
|
project=project,
|
|
organization=project.organization,
|
|
threshold=100 * idx,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
project2 = self.create_project()
|
|
|
|
data = self.load_data()
|
|
data["transaction"] = "/count_miserable/horribilis/project2"
|
|
data["user"] = {"email": "[email protected]"}
|
|
self.store_event(data, project_id=project2.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"user_misery()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"orderby": "transaction",
|
|
"project": [project.id, project2.id],
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
features={
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
expected = [
|
|
(
|
|
"/count_miserable/horribilis/0",
|
|
["duration", 100],
|
|
0.049578,
|
|
), # Uses project threshold
|
|
("/count_miserable/horribilis/1", ["duration", 100], 0.049578), # Uses txn threshold
|
|
("/count_miserable/horribilis/2", ["duration", 100], 0.058), # Uses project threshold
|
|
("/count_miserable/horribilis/3", ["duration", 300], 0.058), # Uses txn threshold
|
|
(
|
|
"/count_miserable/horribilis/4",
|
|
["duration", 100],
|
|
0.049578,
|
|
), # Uses project threshold
|
|
("/count_miserable/horribilis/5", ["duration", 500], 0.058), # Uses txn threshold
|
|
("/count_miserable/horribilis/project2", ["duration", 300], 0.058), # Uses fallback
|
|
]
|
|
|
|
assert len(response.data["data"]) == 7
|
|
data = response.data["data"]
|
|
for i, record in enumerate(expected):
|
|
name, threshold_config, misery = record
|
|
assert data[i]["transaction"] == name
|
|
assert data[i]["project_threshold_config"] == threshold_config
|
|
assert data[i]["user_misery()"] == pytest.approx(misery, rel=1e-3)
|
|
|
|
query["query"] = "event.type:transaction user_misery():>0.050"
|
|
|
|
response = self.do_request(
|
|
query,
|
|
features={
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 4
|
|
|
|
def test_aggregation(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
"tags": {"sub_customer.is-Enterprise-42": "1"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "staging",
|
|
"tags": {"sub_customer.is-Enterprise-42": "1"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
"tags": {"sub_customer.is-Enterprise-42": "0"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "d" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
"tags": {"sub_customer.is-Enterprise-42": "1"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["sub_customer.is-Enterprise-42", "count(sub_customer.is-Enterprise-42)"],
|
|
"orderby": "sub_customer.is-Enterprise-42",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["count(sub_customer.is-Enterprise-42)"] == 1
|
|
assert data[1]["count(sub_customer.is-Enterprise-42)"] == 3
|
|
|
|
def test_aggregation_comparison(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "d" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_3"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "e" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_3"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["issue.id", "count(id)", "count_unique(user)"],
|
|
"query": "count(id):>1 count_unique(user):>1",
|
|
"orderby": "issue.id",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event.group_id
|
|
assert data[0]["count(id)"] == 2
|
|
assert data[0]["count_unique(user)"] == 2
|
|
|
|
def test_aggregation_alias_comparison(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=3),
|
|
)
|
|
data["transaction"] = "/aggregates/2"
|
|
event = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "p95()"],
|
|
"query": "event.type:transaction p95():<4000",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event.transaction
|
|
assert data[0]["p95()"] == 3000
|
|
|
|
def test_auto_aggregations(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=3),
|
|
)
|
|
data["transaction"] = "/aggregates/2"
|
|
event = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "p75()"],
|
|
"query": "event.type:transaction p95():<4000",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event.transaction
|
|
|
|
query = {
|
|
"field": ["transaction"],
|
|
"query": "event.type:transaction p95():<4000",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 400, response.content
|
|
|
|
def test_aggregation_comparison_with_conditions(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "staging",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "d" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["issue.id", "count(id)"],
|
|
"query": "count(id):>1 user.email:[email protected] environment:prod",
|
|
"orderby": "issue.id",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event.group_id
|
|
assert data[0]["count(id)"] == 2
|
|
|
|
def test_aggregation_date_comparison_with_conditions(self):
|
|
event = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "staging",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "d" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
query = {
|
|
"field": ["issue.id", "max(timestamp)"],
|
|
"query": "max(timestamp):>1 user.email:[email protected] environment:prod",
|
|
"orderby": "issue.id",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
assert response.data["meta"]["fields"]["max(timestamp)"] == "date"
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event.group_id
|
|
|
|
def test_percentile_function(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
event1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=3),
|
|
)
|
|
data["transaction"] = "/aggregates/2"
|
|
event2 = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "percentile(transaction.duration, 0.95)"],
|
|
"query": "event.type:transaction",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event1.transaction
|
|
assert data[0]["percentile(transaction.duration, 0.95)"] == 5000
|
|
assert data[1]["transaction"] == event2.transaction
|
|
assert data[1]["percentile(transaction.duration, 0.95)"] == 3000
|
|
|
|
def test_percentile_function_as_condition(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
event1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=3),
|
|
)
|
|
data["transaction"] = "/aggregates/2"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "percentile(transaction.duration, 0.95)"],
|
|
"query": "event.type:transaction percentile(transaction.duration, 0.95):>4000",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event1.transaction
|
|
assert data[0]["percentile(transaction.duration, 0.95)"] == 5000
|
|
|
|
def test_epm_function(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
event1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=3),
|
|
)
|
|
data["transaction"] = "/aggregates/2"
|
|
event2 = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "epm()"],
|
|
"query": "event.type:transaction",
|
|
"orderby": ["transaction"],
|
|
"start": self.eleven_mins_ago_iso,
|
|
"end": iso_format(self.nine_mins_ago),
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event1.transaction
|
|
assert data[0]["epm()"] == 0.5
|
|
assert data[1]["transaction"] == event2.transaction
|
|
assert data[1]["epm()"] == 0.5
|
|
meta = response.data["meta"]
|
|
assert meta["fields"]["epm()"] == "rate"
|
|
assert meta["units"]["epm()"] == "1/minute"
|
|
|
|
def test_nonexistent_fields(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["issue_world.id"]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["issue_world.id"] == ""
|
|
|
|
def test_no_requested_fields_or_grouping(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"query": "test"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
assert response.data["detail"] == "No columns selected"
|
|
|
|
def test_condition_on_aggregate_misses(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["issue.id"], "query": "event_count:>0", "orderby": "issue.id"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_next_prev_link_headers(self):
|
|
events = [("a", "group_1"), ("b", "group_2"), ("c", "group_2"), ("d", "group_2")]
|
|
for e in events:
|
|
self.store_event(
|
|
data={
|
|
"event_id": e[0] * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": [e[1]],
|
|
"user": {"email": "[email protected]"},
|
|
"tags": {"language": "C++"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["count(id)", "issue.id", "context.key"],
|
|
"sort": "-count_id",
|
|
"query": "language:C++",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
links = parse_link_header(response["Link"])
|
|
for link in links:
|
|
assert "field=issue.id" in link
|
|
assert "field=count%28id%29" in link
|
|
assert "field=context.key" in link
|
|
assert "sort=-count_id" in link
|
|
assert "query=language%3AC%2B%2B" in link
|
|
|
|
assert len(response.data["data"]) == 2
|
|
data = response.data["data"]
|
|
assert data[0]["count(id)"] == 3
|
|
assert data[1]["count(id)"] == 1
|
|
|
|
def test_empty_count_query(self):
|
|
event = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["1123581321"],
|
|
"user": {"email": "[email protected]"},
|
|
"tags": {"language": "C++"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"query": f"issue.id:{event.group_id} timestamp:>{self.ten_mins_ago_iso}",
|
|
"statsPeriod": "14d",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count()"] == 0
|
|
|
|
def test_stack_wildcard_condition(self):
|
|
data = self.load_data(platform="javascript")
|
|
data["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=data, project_id=self.project.id)
|
|
|
|
query = {"field": ["stack.filename", "message"], "query": "stack.filename:*.js"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["meta"]["fields"]["message"] == "string"
|
|
|
|
def test_email_wildcard_condition(self):
|
|
data = self.load_data(platform="javascript")
|
|
data["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=data, project_id=self.project.id)
|
|
|
|
query = {"field": ["stack.filename", "message"], "query": "user.email:*@example.org"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["meta"]["fields"]["message"] == "string"
|
|
|
|
def test_release_wildcard_condition(self):
|
|
release = self.create_release(version="[email protected]+123")
|
|
|
|
self.store_event(
|
|
data={"release": release.version, "timestamp": self.ten_mins_ago_iso},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["stack.filename", "release"], "query": "release:test*"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["release"] == release.version
|
|
|
|
def test_transaction_event_type(self):
|
|
self.store_event(data=self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "transaction.duration", "transaction.status"],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["meta"]["fields"]["transaction.duration"] == "duration"
|
|
assert response.data["meta"]["fields"]["transaction.status"] == "string"
|
|
assert response.data["meta"]["units"]["transaction.duration"] == "millisecond"
|
|
assert response.data["data"][0]["transaction.status"] == "ok"
|
|
|
|
def test_trace_columns(self):
|
|
self.store_event(data=self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {"field": ["trace"], "query": "event.type:transaction"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["meta"]["fields"]["trace"] == "string"
|
|
assert (
|
|
response.data["data"][0]["trace"]
|
|
== self.transaction_data["contexts"]["trace"]["trace_id"]
|
|
)
|
|
|
|
def test_issue_in_columns(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["id", "issue"], "orderby": ["id"]}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["id"] == event1.event_id
|
|
assert data[0]["issue.id"] == event1.group_id
|
|
assert data[0]["issue"] == event1.group.qualified_short_id
|
|
assert data[1]["id"] == event2.event_id
|
|
assert data[1]["issue.id"] == event2.group_id
|
|
assert data[1]["issue"] == event2.group.qualified_short_id
|
|
|
|
def test_issue_in_search_and_columns(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
tests = [
|
|
("issue", "issue:%s" % event1.group.qualified_short_id),
|
|
("issue.id", "issue:%s" % event1.group.qualified_short_id),
|
|
("issue", "issue.id:%s" % event1.group_id),
|
|
("issue.id", "issue.id:%s" % event1.group_id),
|
|
]
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
for testdata in tests:
|
|
query = {"field": [testdata[0]], "query": testdata[1]}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["id"] == event1.event_id
|
|
assert data[0]["issue.id"] == event1.group_id
|
|
if testdata[0] == "issue":
|
|
assert data[0]["issue"] == event1.group.qualified_short_id
|
|
else:
|
|
assert data[0].get("issue", None) is None
|
|
|
|
def test_issue_negation(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "go really fast plz",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["title", "issue.id"],
|
|
"query": f"!issue:{event1.group.qualified_short_id}",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["title"] == event2.title
|
|
assert data[0]["issue.id"] == event2.group_id
|
|
|
|
def test_search_for_nonexistent_issue(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["count()"], "query": "issue.id:112358"}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count()"] == 0
|
|
|
|
def test_issue_alias_inside_aggregate(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["project", "count(id)", "count_unique(issue.id)", "count_unique(issue)"],
|
|
"sort": "-count(id)",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count(id)"] == 2
|
|
assert data[0]["count_unique(issue.id)"] == 2
|
|
assert data[0]["count_unique(issue)"] == 2
|
|
|
|
def test_project_alias_inside_aggregate(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"count(id)",
|
|
"count_unique(project.id)",
|
|
"count_unique(project)",
|
|
],
|
|
"sort": "-count(id)",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count(id)"] == 2
|
|
assert data[0]["count_unique(project.id)"] == 2
|
|
assert data[0]["count_unique(project)"] == 2
|
|
|
|
def test_user_display(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"username": "catherine"},
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "user.display"],
|
|
"query": "user.display:cath*",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
result = {r["user.display"] for r in data}
|
|
assert result == {"catherine", "[email protected]"}
|
|
|
|
def test_user_display_with_aggregates(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "user.display", "count_unique(title)"],
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
result = {r["user.display"] for r in data}
|
|
assert result == {"[email protected]"}
|
|
|
|
query = {"field": ["event.type", "count_unique(user.display)"], "statsPeriod": "24h"}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count_unique(user.display)"] == 1
|
|
|
|
def test_orderby_user_display(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"username": "catherine"},
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "user.display"],
|
|
"query": "user.display:cath*",
|
|
"statsPeriod": "24h",
|
|
"orderby": "-user.display",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
result = [r["user.display"] for r in data]
|
|
# because we're ordering by `-user.display`, we expect the results in reverse sorted order
|
|
assert result == ["[email protected]", "catherine"]
|
|
|
|
def test_orderby_user_display_with_aggregates(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"username": "catherine"},
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "user.display", "count_unique(title)"],
|
|
"query": "user.display:cath*",
|
|
"statsPeriod": "24h",
|
|
"orderby": "user.display",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
result = [r["user.display"] for r in data]
|
|
# because we're ordering by `user.display`, we expect the results in sorted order
|
|
assert result == ["catherine", "[email protected]"]
|
|
|
|
def test_any_field_alias(self):
|
|
day_ago = before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": iso_format(day_ago),
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"any(user.display)",
|
|
"any(timestamp.to_day)",
|
|
"any(timestamp.to_hour)",
|
|
],
|
|
"statsPeriod": "7d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
result = {r["any(user.display)"] for r in data}
|
|
assert result == {"[email protected]"}
|
|
result = {r["any(timestamp.to_day)"][:19] for r in data}
|
|
assert result == {iso_format(day_ago.replace(hour=0, minute=0, second=0, microsecond=0))}
|
|
result = {r["any(timestamp.to_hour)"][:19] for r in data}
|
|
assert result == {iso_format(day_ago.replace(minute=0, second=0, microsecond=0))}
|
|
|
|
def test_field_aliases_in_conflicting_functions(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": iso_format(
|
|
before_now(days=1).replace(hour=10, minute=11, second=12, microsecond=13)
|
|
),
|
|
"user": {"email": "[email protected]"},
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
|
|
field_aliases = ["user.display", "timestamp.to_day", "timestamp.to_hour"]
|
|
for alias in field_aliases:
|
|
query = {
|
|
"field": [alias, f"any({alias})"],
|
|
"statsPeriod": "7d",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 400, response.content
|
|
assert (
|
|
response.data["detail"]
|
|
== f"A single field cannot be used both inside and outside a function in the same query. To use {alias} you must first remove the function(s): any({alias})"
|
|
)
|
|
|
|
@pytest.mark.skip(
|
|
"""
|
|
For some reason ClickHouse errors when there are two of the same string literals
|
|
(in this case the empty string "") in a query and one is in the prewhere clause.
|
|
Does not affect production or ClickHouse versions > 20.4.
|
|
"""
|
|
)
|
|
def test_has_message(self):
|
|
event = self.store_event(
|
|
{"timestamp": self.ten_mins_ago_iso, "message": "a"}, project_id=self.project.id
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["project", "message"], "query": "has:message", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["message"] == event.message
|
|
|
|
query = {"field": ["project", "message"], "query": "!has:message", "statsPeriod": "14d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_has_transaction_status(self):
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "count(id)"],
|
|
"query": "event.type:transaction has:transaction.status",
|
|
"sort": "-count(id)",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count(id)"] == 1
|
|
|
|
@pytest.mark.xfail(reason="Started failing on ClickHouse 21.8")
|
|
def test_not_has_transaction_status(self):
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "count(id)"],
|
|
"query": "event.type:transaction !has:transaction.status",
|
|
"sort": "-count(id)",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count(id)"] == 0
|
|
|
|
def test_tag_that_looks_like_aggregation(self):
|
|
data = {
|
|
"message": "Failure state",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"count_diff": 99},
|
|
}
|
|
self.store_event(data, project_id=self.project.id)
|
|
query = {
|
|
"field": ["message", "count_diff", "count()"],
|
|
"query": "",
|
|
"project": [self.project.id],
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
assert "string" == meta["count_diff"], "tags should not be counted as integers"
|
|
assert "string" == meta["message"]
|
|
assert "integer" == meta["count()"]
|
|
assert 1 == len(response.data["data"])
|
|
data = response.data["data"][0]
|
|
assert "99" == data["count_diff"]
|
|
assert "Failure state" == data["message"]
|
|
assert 1 == data["count()"]
|
|
|
|
def test_aggregate_negation(self):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "count()"],
|
|
"query": "event.type:transaction count():1",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
|
|
query = {
|
|
"field": ["event.type", "count()"],
|
|
"query": "event.type:transaction !count():1",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 0
|
|
|
|
def test_all_aggregates_in_columns(self):
|
|
data = self.load_data(
|
|
timestamp=self.eleven_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/1"
|
|
data["contexts"]["trace"]["status"] = "unauthenticated"
|
|
event = self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"p50()",
|
|
"p75()",
|
|
"p95()",
|
|
"p99()",
|
|
"p100()",
|
|
"percentile(transaction.duration, 0.99)",
|
|
"apdex(300)",
|
|
"count_miserable(user, 300)",
|
|
"user_misery(300)",
|
|
"failure_rate()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
units = response.data["meta"]["units"]
|
|
assert meta["p50()"] == "duration"
|
|
assert meta["p75()"] == "duration"
|
|
assert meta["p95()"] == "duration"
|
|
assert meta["p99()"] == "duration"
|
|
assert meta["p100()"] == "duration"
|
|
assert meta["percentile(transaction.duration, 0.99)"] == "duration"
|
|
assert meta["apdex(300)"] == "number"
|
|
assert meta["failure_rate()"] == "percentage"
|
|
assert meta["user_misery(300)"] == "number"
|
|
assert meta["count_miserable(user, 300)"] == "integer"
|
|
|
|
assert units["p50()"] == "millisecond"
|
|
assert units["p75()"] == "millisecond"
|
|
assert units["p95()"] == "millisecond"
|
|
assert units["p99()"] == "millisecond"
|
|
assert units["p100()"] == "millisecond"
|
|
assert units["percentile(transaction.duration, 0.99)"] == "millisecond"
|
|
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p50()"] == 5000
|
|
assert data[0]["p75()"] == 5000
|
|
assert data[0]["p95()"] == 5000
|
|
assert data[0]["p99()"] == 5000
|
|
assert data[0]["p100()"] == 5000
|
|
assert data[0]["percentile(transaction.duration, 0.99)"] == 5000
|
|
assert data[0]["apdex(300)"] == 0.0
|
|
assert data[0]["count_miserable(user, 300)"] == 1
|
|
assert data[0]["user_misery(300)"] == 0.058
|
|
assert data[0]["failure_rate()"] == 0.5
|
|
|
|
features = {
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
}
|
|
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"p50()",
|
|
"p75()",
|
|
"p95()",
|
|
"p99()",
|
|
"p100()",
|
|
"percentile(transaction.duration, 0.99)",
|
|
"apdex(300)",
|
|
"apdex()",
|
|
"count_miserable(user, 300)",
|
|
"user_misery(300)",
|
|
"failure_rate()",
|
|
"count_miserable(user)",
|
|
"user_misery()",
|
|
],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
units = response.data["meta"]["units"]
|
|
assert meta["p50()"] == "duration"
|
|
assert meta["p75()"] == "duration"
|
|
assert meta["p95()"] == "duration"
|
|
assert meta["p99()"] == "duration"
|
|
assert meta["p100()"] == "duration"
|
|
assert meta["percentile(transaction.duration, 0.99)"] == "duration"
|
|
assert meta["apdex(300)"] == "number"
|
|
assert meta["apdex()"] == "number"
|
|
assert meta["failure_rate()"] == "percentage"
|
|
assert meta["user_misery(300)"] == "number"
|
|
assert meta["count_miserable(user, 300)"] == "integer"
|
|
assert meta["project_threshold_config"] == "string"
|
|
assert meta["user_misery()"] == "number"
|
|
assert meta["count_miserable(user)"] == "integer"
|
|
|
|
assert units["p50()"] == "millisecond"
|
|
assert units["p75()"] == "millisecond"
|
|
assert units["p95()"] == "millisecond"
|
|
assert units["p99()"] == "millisecond"
|
|
assert units["p100()"] == "millisecond"
|
|
assert units["percentile(transaction.duration, 0.99)"] == "millisecond"
|
|
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p50()"] == 5000
|
|
assert data[0]["p75()"] == 5000
|
|
assert data[0]["p95()"] == 5000
|
|
assert data[0]["p99()"] == 5000
|
|
assert data[0]["p100()"] == 5000
|
|
assert data[0]["percentile(transaction.duration, 0.99)"] == 5000
|
|
assert data[0]["apdex(300)"] == 0.0
|
|
assert data[0]["apdex()"] == 0.0
|
|
assert data[0]["count_miserable(user, 300)"] == 1
|
|
assert data[0]["user_misery(300)"] == 0.058
|
|
assert data[0]["failure_rate()"] == 0.5
|
|
assert data[0]["project_threshold_config"] == ["duration", 300]
|
|
assert data[0]["user_misery()"] == 0.058
|
|
assert data[0]["count_miserable(user)"] == 1
|
|
|
|
query = {
|
|
"field": ["event.type", "last_seen()", "latest_event()"],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert self.ten_mins_ago_iso[:-5] in data[0]["last_seen()"]
|
|
assert data[0]["latest_event()"] == event.event_id
|
|
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"count()",
|
|
"count(id)",
|
|
"count_unique(project)",
|
|
"min(transaction.duration)",
|
|
"max(transaction.duration)",
|
|
"avg(transaction.duration)",
|
|
"stddev(transaction.duration)",
|
|
"var(transaction.duration)",
|
|
"cov(transaction.duration, transaction.duration)",
|
|
"corr(transaction.duration, transaction.duration)",
|
|
"linear_regression(transaction.duration, transaction.duration)",
|
|
"sum(transaction.duration)",
|
|
],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count()"] == 2
|
|
assert data[0]["count(id)"] == 2
|
|
assert data[0]["count_unique(project)"] == 1
|
|
assert data[0]["min(transaction.duration)"] == 5000
|
|
assert data[0]["max(transaction.duration)"] == 5000
|
|
assert data[0]["avg(transaction.duration)"] == 5000
|
|
assert data[0]["stddev(transaction.duration)"] == 0.0
|
|
assert data[0]["var(transaction.duration)"] == 0.0
|
|
assert data[0]["cov(transaction.duration, transaction.duration)"] == 0.0
|
|
assert data[0]["corr(transaction.duration, transaction.duration)"] == 0.0
|
|
assert data[0]["linear_regression(transaction.duration, transaction.duration)"] == [0, 0]
|
|
assert data[0]["sum(transaction.duration)"] == 10000
|
|
|
|
@requires_not_arm64
|
|
def test_null_user_misery_returns_zero(self):
|
|
self.transaction_data["user"] = None
|
|
self.transaction_data["transaction"] = "/no_users/1"
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
|
|
query = {
|
|
"field": ["user_misery(300)"],
|
|
"query": "event.type:transaction",
|
|
}
|
|
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["user_misery(300)"] == "number"
|
|
data = response.data["data"]
|
|
assert data[0]["user_misery(300)"] == 0
|
|
|
|
@requires_not_arm64
|
|
def test_null_user_misery_new_returns_zero(self):
|
|
self.transaction_data["user"] = None
|
|
self.transaction_data["transaction"] = "/no_users/1"
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
features = {
|
|
"organizations:discover-basic": True,
|
|
}
|
|
|
|
query = {
|
|
"field": ["user_misery()"],
|
|
"query": "event.type:transaction",
|
|
}
|
|
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["user_misery()"] == "number"
|
|
data = response.data["data"]
|
|
assert data[0]["user_misery()"] == 0
|
|
|
|
def test_all_aggregates_in_query(self):
|
|
data = self.load_data(
|
|
timestamp=self.eleven_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/2"
|
|
data["contexts"]["trace"]["status"] = "unauthenticated"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"p50()",
|
|
"p75()",
|
|
"p95()",
|
|
"percentile(transaction.duration, 0.99)",
|
|
"p100()",
|
|
],
|
|
"query": "event.type:transaction p50():>100 p75():>1000 p95():>1000 p100():>1000 percentile(transaction.duration, 0.99):>1000",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p50()"] == 5000
|
|
assert data[0]["p75()"] == 5000
|
|
assert data[0]["p95()"] == 5000
|
|
assert data[0]["p100()"] == 5000
|
|
assert data[0]["percentile(transaction.duration, 0.99)"] == 5000
|
|
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"apdex(300)",
|
|
"count_miserable(user, 300)",
|
|
"user_misery(300)",
|
|
"failure_rate()",
|
|
],
|
|
"query": "event.type:transaction apdex(300):>-1.0 failure_rate():>0.25",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["apdex(300)"] == 0.0
|
|
assert data[0]["count_miserable(user, 300)"] == 1
|
|
assert data[0]["user_misery(300)"] == 0.058
|
|
assert data[0]["failure_rate()"] == 0.5
|
|
|
|
query = {
|
|
"field": ["event.type", "last_seen()", "latest_event()"],
|
|
"query": "event.type:transaction last_seen():>1990-12-01T00:00:00",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
|
|
query = {
|
|
"field": ["event.type", "count()", "count(id)", "count_unique(transaction)"],
|
|
"query": "event.type:transaction count():>1 count(id):>1 count_unique(transaction):>1",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count()"] == 2
|
|
assert data[0]["count(id)"] == 2
|
|
assert data[0]["count_unique(transaction)"] == 2
|
|
|
|
query = {
|
|
"field": [
|
|
"event.type",
|
|
"min(transaction.duration)",
|
|
"max(transaction.duration)",
|
|
"avg(transaction.duration)",
|
|
"sum(transaction.duration)",
|
|
"stddev(transaction.duration)",
|
|
"var(transaction.duration)",
|
|
"cov(transaction.duration, transaction.duration)",
|
|
"corr(transaction.duration, transaction.duration)",
|
|
],
|
|
"query": " ".join(
|
|
[
|
|
"event.type:transaction",
|
|
"min(transaction.duration):>1000",
|
|
"max(transaction.duration):>1000",
|
|
"avg(transaction.duration):>1000",
|
|
"sum(transaction.duration):>1000",
|
|
"stddev(transaction.duration):>=0.0",
|
|
"var(transaction.duration):>=0.0",
|
|
"cov(transaction.duration, transaction.duration):>=0.0",
|
|
# correlation is nan because variance is 0
|
|
# "corr(transaction.duration, transaction.duration):>=0.0",
|
|
]
|
|
),
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["min(transaction.duration)"] == 5000
|
|
assert data[0]["max(transaction.duration)"] == 5000
|
|
assert data[0]["avg(transaction.duration)"] == 5000
|
|
assert data[0]["sum(transaction.duration)"] == 10000
|
|
assert data[0]["stddev(transaction.duration)"] == 0.0
|
|
assert data[0]["var(transaction.duration)"] == 0.0
|
|
assert data[0]["cov(transaction.duration, transaction.duration)"] == 0.0
|
|
assert data[0]["corr(transaction.duration, transaction.duration)"] == 0.0
|
|
|
|
query = {
|
|
"field": ["event.type", "apdex(400)"],
|
|
"query": "event.type:transaction apdex(400):0",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["apdex(400)"] == 0
|
|
|
|
def test_functions_in_orderby(self):
|
|
data = self.load_data(
|
|
timestamp=self.eleven_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/failure_rate/2"
|
|
data["contexts"]["trace"]["status"] = "unauthenticated"
|
|
event = self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "p75()"],
|
|
"sort": "-p75",
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p75()"] == 5000
|
|
|
|
query = {
|
|
"field": ["event.type", "percentile(transaction.duration, 0.99)"],
|
|
"sort": "-percentile_transaction_duration_0_99",
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["percentile(transaction.duration, 0.99)"] == 5000
|
|
|
|
query = {
|
|
"field": ["event.type", "apdex(300)"],
|
|
"sort": "-apdex(300)",
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["apdex(300)"] == 0.0
|
|
|
|
query = {
|
|
"field": ["event.type", "latest_event()"],
|
|
"query": "event.type:transaction",
|
|
"sort": "latest_event",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["latest_event()"] == event.event_id
|
|
|
|
query = {
|
|
"field": ["event.type", "count_unique(transaction)"],
|
|
"query": "event.type:transaction",
|
|
"sort": "-count_unique_transaction",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count_unique(transaction)"] == 2
|
|
|
|
query = {
|
|
"field": ["event.type", "min(transaction.duration)"],
|
|
"query": "event.type:transaction",
|
|
"sort": "-min_transaction_duration",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["min(transaction.duration)"] == 5000
|
|
|
|
def test_issue_alias_in_aggregate(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.eleven_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["event.type", "count_unique(issue)"], "query": "count_unique(issue):>1"}
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count_unique(issue)"] == 2
|
|
|
|
def test_deleted_issue_in_results(self):
|
|
event1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.eleven_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event2.group.delete()
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["issue", "count()"], "sort": "issue.id"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["issue"] == event1.group.qualified_short_id
|
|
assert data[1]["issue"] == "unknown"
|
|
|
|
def test_last_seen_negative_duration(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "f" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {"field": ["id", "last_seen()"], "query": "last_seen():-30d"}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["id"] == "f" * 32
|
|
|
|
def test_last_seen_aggregate_condition(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "f" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["id", "last_seen()"],
|
|
"query": f"last_seen():>{iso_format(before_now(days=30))}",
|
|
}
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["id"] == "f" * 32
|
|
|
|
def test_conditional_filter(self):
|
|
for v in ["a", "b"]:
|
|
self.store_event(
|
|
data={
|
|
"event_id": v * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["id"],
|
|
"query": "id:{} OR id:{}".format("a" * 32, "b" * 32),
|
|
"orderby": "id",
|
|
}
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
response = self.do_request(query, features=features)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["id"] == "a" * 32
|
|
assert data[1]["id"] == "b" * 32
|
|
|
|
def test_aggregation_comparison_with_conditional_filter(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "staging",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
event = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "d" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "canary",
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": ["issue.id", "count(id)"],
|
|
"query": "count(id):>1 user.email:[email protected] AND (environment:prod OR environment:staging)",
|
|
"orderby": "issue.id",
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["issue.id"] == event.group_id
|
|
assert data[0]["count(id)"] == 2
|
|
|
|
def run_test_in_query(self, query, expected_events, expected_negative_events=None):
|
|
params = {
|
|
"field": ["id"],
|
|
"query": query,
|
|
"orderby": "id",
|
|
}
|
|
response = self.do_request(
|
|
params, {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
)
|
|
assert response.status_code == 200, response.content
|
|
assert [row["id"] for row in response.data["data"]] == [e.event_id for e in expected_events]
|
|
|
|
if expected_negative_events is not None:
|
|
params["query"] = f"!{query}"
|
|
response = self.do_request(
|
|
params,
|
|
{"organizations:discover-basic": True, "organizations:global-views": True},
|
|
)
|
|
assert response.status_code == 200, response.content
|
|
assert [row["id"] for row in response.data["data"]] == [
|
|
e.event_id for e in expected_negative_events
|
|
]
|
|
|
|
def test_in_query_events(self):
|
|
project_1 = self.create_project()
|
|
event_1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
"message": "group1",
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "prod",
|
|
"tags": {"random": "123"},
|
|
"release": "1.0",
|
|
},
|
|
project_id=project_1.id,
|
|
)
|
|
project_2 = self.create_project()
|
|
event_2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_2"],
|
|
"message": "group2",
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "staging",
|
|
"tags": {"random": "456"},
|
|
"stacktrace": {"frames": [{"filename": "src/app/group2.py"}]},
|
|
"release": "1.2",
|
|
},
|
|
project_id=project_2.id,
|
|
)
|
|
project_3 = self.create_project()
|
|
event_3 = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_3"],
|
|
"message": "group3",
|
|
"user": {"email": "[email protected]"},
|
|
"environment": "canary",
|
|
"tags": {"random": "789"},
|
|
},
|
|
project_id=project_3.id,
|
|
)
|
|
|
|
self.run_test_in_query("environment:[prod, staging]", [event_1, event_2], [event_3])
|
|
self.run_test_in_query("environment:[staging]", [event_2], [event_1, event_3])
|
|
self.run_test_in_query(
|
|
"user.email:[[email protected], [email protected]]", [event_1, event_3], [event_2]
|
|
)
|
|
self.run_test_in_query("user.email:[[email protected]]", [event_3], [event_1, event_2])
|
|
self.run_test_in_query(
|
|
"user.display:[[email protected], [email protected]]", [event_1, event_3], [event_2]
|
|
)
|
|
self.run_test_in_query(
|
|
'message:["group2 src/app/group2.py in ?", group1]', [event_1, event_2], [event_3]
|
|
)
|
|
|
|
self.run_test_in_query(
|
|
f"issue.id:[{event_1.group_id},{event_2.group_id}]", [event_1, event_2]
|
|
)
|
|
self.run_test_in_query(
|
|
f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}]",
|
|
[event_1, event_2],
|
|
)
|
|
self.run_test_in_query(
|
|
f"issue:[{event_1.group.qualified_short_id},{event_2.group.qualified_short_id}, unknown]",
|
|
[event_1, event_2],
|
|
)
|
|
self.run_test_in_query(f"project_id:[{project_3.id},{project_2.id}]", [event_2, event_3])
|
|
self.run_test_in_query(
|
|
f"project.name:[{project_3.slug},{project_2.slug}]", [event_2, event_3]
|
|
)
|
|
self.run_test_in_query("random:[789,456]", [event_2, event_3], [event_1])
|
|
self.run_test_in_query("tags[random]:[789,456]", [event_2, event_3], [event_1])
|
|
self.run_test_in_query("release:[1.0,1.2]", [event_1, event_2], [event_3])
|
|
|
|
def test_in_query_events_stack(self):
|
|
test_js = self.store_event(
|
|
self.load_data(
|
|
platform="javascript",
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
),
|
|
project_id=self.project.id,
|
|
)
|
|
test_java = self.store_event(
|
|
self.load_data(
|
|
platform="java",
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
),
|
|
project_id=self.project.id,
|
|
)
|
|
self.run_test_in_query(
|
|
"stack.filename:[../../sentry/scripts/views.js]", [test_js], [test_java]
|
|
)
|
|
|
|
def test_in_query_transactions(self):
|
|
data = self.transaction_data.copy()
|
|
data["event_id"] = "a" * 32
|
|
data["contexts"]["trace"]["status"] = "ok"
|
|
transaction_1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.transaction_data.copy()
|
|
data["event_id"] = "b" * 32
|
|
data["contexts"]["trace"]["status"] = "aborted"
|
|
transaction_2 = self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.transaction_data.copy()
|
|
data["event_id"] = "c" * 32
|
|
data["contexts"]["trace"]["status"] = "already_exists"
|
|
transaction_3 = self.store_event(data, project_id=self.project.id)
|
|
|
|
self.run_test_in_query(
|
|
"transaction.status:[aborted, already_exists]",
|
|
[transaction_2, transaction_3],
|
|
[transaction_1],
|
|
)
|
|
|
|
def test_messed_up_function_values(self):
|
|
# TODO (evanh): It would be nice if this surfaced an error to the user.
|
|
# The problem: The && causes the parser to treat that term not as a bad
|
|
# function call but a valid raw search with parens in it. It's not trivial
|
|
# to change the parser to recognize "bad function values" and surface them.
|
|
for v in ["a", "b"]:
|
|
self.store_event(
|
|
data={
|
|
"event_id": v * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group_1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"project",
|
|
"epm()",
|
|
"p50()",
|
|
"p95()",
|
|
"failure_rate()",
|
|
"apdex(300)",
|
|
"count_unique(user)",
|
|
"user_misery(300)",
|
|
"count_miserable(user, 300)",
|
|
],
|
|
"query": "failure_rate():>0.003&& users:>10 event.type:transaction",
|
|
"sort": "-failure_rate",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 0
|
|
|
|
def test_context_fields_between_datasets(self):
|
|
event_data = self.load_data(platform="android")
|
|
transaction_data = self.load_data()
|
|
event_data["spans"] = transaction_data["spans"]
|
|
event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
|
|
event_data["type"] = "transaction"
|
|
event_data["transaction"] = "/failure_rate/1"
|
|
event_data["timestamp"] = iso_format(self.ten_mins_ago)
|
|
event_data["start_timestamp"] = iso_format(before_now(minutes=10, seconds=5))
|
|
event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
|
|
self.store_event(event_data, project_id=self.project.id)
|
|
event_data["type"] = "error"
|
|
self.store_event(event_data, project_id=self.project.id)
|
|
|
|
fields = [
|
|
"os.build",
|
|
"os.kernel_version",
|
|
"device.arch",
|
|
# TODO: battery level is not consistent across both datasets
|
|
# "device.battery_level",
|
|
"device.brand",
|
|
"device.charging",
|
|
"device.locale",
|
|
"device.model_id",
|
|
"device.name",
|
|
"device.online",
|
|
"device.orientation",
|
|
"device.simulator",
|
|
"device.uuid",
|
|
]
|
|
|
|
data = [
|
|
{"field": fields + ["location", "count()"], "query": "event.type:error"},
|
|
{"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
|
|
]
|
|
|
|
for datum in data:
|
|
response = self.do_request(datum)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1, datum
|
|
results = response.data["data"]
|
|
assert results[0]["count()"] == 1, datum
|
|
|
|
for field in fields:
|
|
key, value = field.split(".", 1)
|
|
expected = str(event_data["contexts"][key][value])
|
|
assert results[0][field] == expected, field + str(datum)
|
|
|
|
def test_http_fields_between_datasets(self):
|
|
event_data = self.load_data(platform="android")
|
|
transaction_data = self.load_data()
|
|
event_data["spans"] = transaction_data["spans"]
|
|
event_data["contexts"]["trace"] = transaction_data["contexts"]["trace"]
|
|
event_data["type"] = "transaction"
|
|
event_data["transaction"] = "/failure_rate/1"
|
|
event_data["timestamp"] = iso_format(self.ten_mins_ago)
|
|
event_data["start_timestamp"] = iso_format(before_now(minutes=10, seconds=5))
|
|
event_data["user"]["geo"] = {"country_code": "US", "region": "CA", "city": "San Francisco"}
|
|
event_data["request"] = transaction_data["request"]
|
|
self.store_event(event_data, project_id=self.project.id)
|
|
event_data["type"] = "error"
|
|
self.store_event(event_data, project_id=self.project.id)
|
|
|
|
fields = ["http.method", "http.referer", "http.url"]
|
|
expected = ["GET", "fixtures.transaction", "http://countries:8010/country_by_code/"]
|
|
|
|
data = [
|
|
{"field": fields + ["location", "count()"], "query": "event.type:error"},
|
|
{"field": fields + ["duration", "count()"], "query": "event.type:transaction"},
|
|
]
|
|
|
|
for datum in data:
|
|
response = self.do_request(datum)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1, datum
|
|
results = response.data["data"]
|
|
assert results[0]["count()"] == 1, datum
|
|
|
|
for field, exp in zip(fields, expected):
|
|
assert results[0][field] == exp, field + str(datum)
|
|
|
|
def test_failure_count_alias_field(self):
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/failure_count/success"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = "/failure_count/unknown"
|
|
data["contexts"]["trace"]["status"] = "unknown_error"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
for i in range(6):
|
|
data = self.transaction_data.copy()
|
|
data["transaction"] = f"/failure_count/{i}"
|
|
data["contexts"]["trace"]["status"] = "unauthenticated"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {"field": ["count()", "failure_count()"], "query": "event.type:transaction"}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["count()"] == 8
|
|
assert data[0]["failure_count()"] == 6
|
|
|
|
@mock.patch("sentry.utils.snuba.quantize_time")
|
|
def test_quantize_dates(self, mock_quantize):
|
|
self.create_project()
|
|
mock_quantize.return_value = before_now(days=1).replace(tzinfo=timezone.utc)
|
|
|
|
# Don't quantize short time periods
|
|
query = {"statsPeriod": "1h", "query": "", "field": ["id", "timestamp"]}
|
|
self.do_request(query)
|
|
|
|
# Don't quantize absolute date periods
|
|
self.do_request(query)
|
|
query = {
|
|
"start": iso_format(before_now(days=20)),
|
|
"end": iso_format(before_now(days=15)),
|
|
"query": "",
|
|
"field": ["id", "timestamp"],
|
|
}
|
|
self.do_request(query)
|
|
assert len(mock_quantize.mock_calls) == 0
|
|
|
|
# Quantize long date periods
|
|
query = {"field": ["id", "timestamp"], "statsPeriod": "90d", "query": ""}
|
|
self.do_request(query)
|
|
assert len(mock_quantize.mock_calls) == 2
|
|
|
|
def test_limit_number_of_fields(self):
|
|
self.create_project()
|
|
for i in range(1, 25):
|
|
response = self.do_request({"field": ["id"] * i})
|
|
if i <= 20:
|
|
assert response.status_code == 200
|
|
else:
|
|
assert response.status_code == 400
|
|
assert (
|
|
response.data["detail"]
|
|
== "You can view up to 20 fields at a time. Please delete some and try again."
|
|
)
|
|
|
|
def test_percentile_function_meta_types(self):
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"percentile(transaction.duration, 0.95)",
|
|
"percentile(measurements.fp, 0.95)",
|
|
"percentile(measurements.fcp, 0.95)",
|
|
"percentile(measurements.lcp, 0.95)",
|
|
"percentile(measurements.fid, 0.95)",
|
|
"percentile(measurements.ttfb, 0.95)",
|
|
"percentile(measurements.ttfb.requesttime, 0.95)",
|
|
"percentile(measurements.cls, 0.95)",
|
|
"percentile(measurements.foo, 0.95)",
|
|
"percentile(measurements.bar, 0.95)",
|
|
],
|
|
"query": "",
|
|
"orderby": ["transaction"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["percentile(transaction.duration, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.fp, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.fcp, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.lcp, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.fid, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.ttfb, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.ttfb.requesttime, 0.95)"] == "duration"
|
|
assert meta["percentile(measurements.cls, 0.95)"] == "number"
|
|
assert meta["percentile(measurements.foo, 0.95)"] == "number"
|
|
assert meta["percentile(measurements.bar, 0.95)"] == "number"
|
|
|
|
units = response.data["meta"]["units"]
|
|
assert units["percentile(transaction.duration, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.fp, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.fcp, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.lcp, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.fid, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.ttfb, 0.95)"] == "millisecond"
|
|
assert units["percentile(measurements.ttfb.requesttime, 0.95)"] == "millisecond"
|
|
|
|
def test_count_at_least_query(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
response = self.do_request({"field": "count_at_least(measurements.fcp, 0)"})
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["count_at_least(measurements.fcp, 0)"] == 1
|
|
|
|
# a value that's a little bigger than the stored fcp
|
|
fcp = int(self.transaction_data["measurements"]["fcp"]["value"] + 1)
|
|
response = self.do_request({"field": f"count_at_least(measurements.fcp, {fcp})"})
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0][f"count_at_least(measurements.fcp, {fcp})"] == 0
|
|
|
|
def test_measurements_query(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"measurements.fp",
|
|
"measurements.fcp",
|
|
"measurements.lcp",
|
|
"measurements.fid",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
for field in query["field"]:
|
|
measure = field.split(".", 1)[1]
|
|
assert (
|
|
response.data["data"][0][field]
|
|
== self.transaction_data["measurements"][measure]["value"]
|
|
)
|
|
|
|
query = {
|
|
"field": [
|
|
"measurements.fP",
|
|
"measurements.Fcp",
|
|
"measurements.LcP",
|
|
"measurements.FID",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
for field in query["field"]:
|
|
measure = field.split(".", 1)[1].lower()
|
|
assert (
|
|
response.data["data"][0][field]
|
|
== self.transaction_data["measurements"][measure]["value"]
|
|
)
|
|
|
|
def test_measurements_aggregations(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
# should try all the potential aggregates
|
|
# Skipped tests for stddev and var since sampling one data point
|
|
# results in nan.
|
|
query = {
|
|
"field": [
|
|
"percentile(measurements.fcp, 0.5)",
|
|
"count_unique(measurements.fcp)",
|
|
"min(measurements.fcp)",
|
|
"max(measurements.fcp)",
|
|
"avg(measurements.fcp)",
|
|
"sum(measurements.fcp)",
|
|
],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert (
|
|
response.data["data"][0]["percentile(measurements.fcp, 0.5)"]
|
|
== self.transaction_data["measurements"]["fcp"]["value"]
|
|
)
|
|
assert response.data["data"][0]["count_unique(measurements.fcp)"] == 1
|
|
assert (
|
|
response.data["data"][0]["min(measurements.fcp)"]
|
|
== self.transaction_data["measurements"]["fcp"]["value"]
|
|
)
|
|
assert (
|
|
response.data["data"][0]["max(measurements.fcp)"]
|
|
== self.transaction_data["measurements"]["fcp"]["value"]
|
|
)
|
|
assert (
|
|
response.data["data"][0]["avg(measurements.fcp)"]
|
|
== self.transaction_data["measurements"]["fcp"]["value"]
|
|
)
|
|
assert (
|
|
response.data["data"][0]["sum(measurements.fcp)"]
|
|
== self.transaction_data["measurements"]["fcp"]["value"]
|
|
)
|
|
|
|
def get_measurement_condition_response(self, query_str, field):
|
|
query = {
|
|
"field": ["transaction", "count()"] + (field if field else []),
|
|
"query": query_str,
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
return response
|
|
|
|
def assert_measurement_condition_without_results(self, query_str, field=None):
|
|
response = self.get_measurement_condition_response(query_str, field)
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def assert_measurement_condition_with_results(self, query_str, field=None):
|
|
response = self.get_measurement_condition_response(query_str, field)
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["transaction"] == self.transaction_data["metadata"]["title"]
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_measurements_conditions(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
fcp = self.transaction_data["measurements"]["fcp"]["value"]
|
|
|
|
# equality condition
|
|
# We use json dumps here to ensure precision when converting from float to str
|
|
# This is necessary because equality on floating point values need to be precise
|
|
self.assert_measurement_condition_with_results(f"measurements.fcp:{json.dumps(fcp)}")
|
|
|
|
# greater than condition
|
|
self.assert_measurement_condition_with_results(f"measurements.fcp:>{fcp - 1}")
|
|
self.assert_measurement_condition_without_results(f"measurements.fcp:>{fcp + 1}")
|
|
|
|
# less than condition
|
|
self.assert_measurement_condition_with_results(f"measurements.fcp:<{fcp + 1}")
|
|
self.assert_measurement_condition_without_results(f"measurements.fcp:<{fcp - 1}")
|
|
|
|
# has condition
|
|
self.assert_measurement_condition_with_results("has:measurements.fcp")
|
|
self.assert_measurement_condition_without_results("!has:measurements.fcp")
|
|
|
|
def test_measurements_aggregation_conditions(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
fcp = self.transaction_data["measurements"]["fcp"]["value"]
|
|
functions = [
|
|
"percentile(measurements.fcp, 0.5)",
|
|
"min(measurements.fcp)",
|
|
"max(measurements.fcp)",
|
|
"avg(measurements.fcp)",
|
|
"sum(measurements.fcp)",
|
|
]
|
|
|
|
for function in functions:
|
|
self.assert_measurement_condition_with_results(
|
|
f"{function}:>{fcp - 1}", field=[function]
|
|
)
|
|
self.assert_measurement_condition_without_results(
|
|
f"{function}:>{fcp + 1}", field=[function]
|
|
)
|
|
self.assert_measurement_condition_with_results(
|
|
f"{function}:<{fcp + 1}", field=[function]
|
|
)
|
|
self.assert_measurement_condition_without_results(
|
|
f"{function}:<{fcp - 1}", field=[function]
|
|
)
|
|
|
|
count_unique = "count_unique(measurements.fcp)"
|
|
self.assert_measurement_condition_with_results(f"{count_unique}:1", field=[count_unique])
|
|
self.assert_measurement_condition_without_results(f"{count_unique}:0", field=[count_unique])
|
|
|
|
def test_compare_numeric_aggregate(self):
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"p75(measurements.fcp)",
|
|
"compare_numeric_aggregate(p75_measurements_fcp,greater,0)",
|
|
],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert (
|
|
response.data["data"][0]["compare_numeric_aggregate(p75_measurements_fcp,greater,0)"]
|
|
== 1
|
|
)
|
|
|
|
query = {
|
|
"field": ["p75()", "compare_numeric_aggregate(p75,equals,0)"],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["compare_numeric_aggregate(p75,equals,0)"] == 0
|
|
|
|
def test_no_team_key_transactions(self):
|
|
transactions = [
|
|
"/blah_transaction/",
|
|
"/foo_transaction/",
|
|
"/zoo_transaction/",
|
|
]
|
|
|
|
for transaction in transactions:
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
query = {
|
|
"team": "myteams",
|
|
"project": [self.project.id],
|
|
# use the order by to ensure the result order
|
|
"orderby": "transaction",
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
assert data[1]["team_key_transaction"] == 0
|
|
assert data[1]["transaction"] == "/foo_transaction/"
|
|
assert data[2]["team_key_transaction"] == 0
|
|
assert data[2]["transaction"] == "/zoo_transaction/"
|
|
|
|
def test_team_key_transactions_my_teams(self):
|
|
team1 = self.create_team(organization=self.organization, name="Team A")
|
|
self.create_team_membership(team1, user=self.user)
|
|
self.project.add_team(team1)
|
|
|
|
team2 = self.create_team(organization=self.organization, name="Team B")
|
|
self.project.add_team(team2)
|
|
|
|
transactions = ["/blah_transaction/"]
|
|
key_transactions = [
|
|
(team1, "/foo_transaction/"),
|
|
(team2, "/zoo_transaction/"),
|
|
]
|
|
|
|
for transaction in transactions:
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
for team, transaction in key_transactions:
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
TeamKeyTransaction.objects.create(
|
|
organization=self.organization,
|
|
transaction=transaction,
|
|
project_team=ProjectTeam.objects.get(project=self.project, team=team),
|
|
)
|
|
|
|
query = {
|
|
"team": "myteams",
|
|
"project": [self.project.id],
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
|
|
query["orderby"] = ["team_key_transaction", "transaction"]
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
assert data[1]["team_key_transaction"] == 0
|
|
assert data[1]["transaction"] == "/zoo_transaction/"
|
|
assert data[2]["team_key_transaction"] == 1
|
|
assert data[2]["transaction"] == "/foo_transaction/"
|
|
|
|
# not specifying any teams should use my teams
|
|
query = {
|
|
"project": [self.project.id],
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
|
|
query["orderby"] = ["team_key_transaction", "transaction"]
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
assert data[1]["team_key_transaction"] == 0
|
|
assert data[1]["transaction"] == "/zoo_transaction/"
|
|
assert data[2]["team_key_transaction"] == 1
|
|
assert data[2]["transaction"] == "/foo_transaction/"
|
|
|
|
def test_team_key_transactions_orderby(self):
|
|
team1 = self.create_team(organization=self.organization, name="Team A")
|
|
team2 = self.create_team(organization=self.organization, name="Team B")
|
|
|
|
transactions = ["/blah_transaction/"]
|
|
key_transactions = [
|
|
(team1, "/foo_transaction/"),
|
|
(team2, "/zoo_transaction/"),
|
|
]
|
|
|
|
for transaction in transactions:
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
for team, transaction in key_transactions:
|
|
self.create_team_membership(team, user=self.user)
|
|
self.project.add_team(team)
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
TeamKeyTransaction.objects.create(
|
|
organization=self.organization,
|
|
transaction=transaction,
|
|
project_team=ProjectTeam.objects.get(project=self.project, team=team),
|
|
)
|
|
|
|
query = {
|
|
"team": "myteams",
|
|
"project": [self.project.id],
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
|
|
# test ascending order
|
|
query["orderby"] = ["team_key_transaction", "transaction"]
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
assert data[1]["team_key_transaction"] == 1
|
|
assert data[1]["transaction"] == "/foo_transaction/"
|
|
assert data[2]["team_key_transaction"] == 1
|
|
assert data[2]["transaction"] == "/zoo_transaction/"
|
|
|
|
# test descending order
|
|
query["orderby"] = ["-team_key_transaction", "-transaction"]
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
assert data[0]["team_key_transaction"] == 1
|
|
assert data[0]["transaction"] == "/zoo_transaction/"
|
|
assert data[1]["team_key_transaction"] == 1
|
|
assert data[1]["transaction"] == "/foo_transaction/"
|
|
assert data[2]["team_key_transaction"] == 0
|
|
assert data[2]["transaction"] == "/blah_transaction/"
|
|
|
|
def test_team_key_transactions_query(self):
|
|
team1 = self.create_team(organization=self.organization, name="Team A")
|
|
team2 = self.create_team(organization=self.organization, name="Team B")
|
|
|
|
transactions = ["/blah_transaction/"]
|
|
key_transactions = [
|
|
(team1, "/foo_transaction/"),
|
|
(team2, "/zoo_transaction/"),
|
|
]
|
|
|
|
for transaction in transactions:
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
for team, transaction in key_transactions:
|
|
self.create_team_membership(team, user=self.user)
|
|
self.project.add_team(team)
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
TeamKeyTransaction.objects.create(
|
|
organization=self.organization,
|
|
project_team=ProjectTeam.objects.get(
|
|
project=self.project,
|
|
team=team,
|
|
),
|
|
transaction=transaction,
|
|
)
|
|
|
|
query = {
|
|
"team": "myteams",
|
|
"project": [self.project.id],
|
|
# use the order by to ensure the result order
|
|
"orderby": "transaction",
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
|
|
# key transactions
|
|
query["query"] = "has:team_key_transaction"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["team_key_transaction"] == 1
|
|
assert data[0]["transaction"] == "/foo_transaction/"
|
|
assert data[1]["team_key_transaction"] == 1
|
|
assert data[1]["transaction"] == "/zoo_transaction/"
|
|
|
|
# key transactions
|
|
query["query"] = "team_key_transaction:true"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["team_key_transaction"] == 1
|
|
assert data[0]["transaction"] == "/foo_transaction/"
|
|
assert data[1]["team_key_transaction"] == 1
|
|
assert data[1]["transaction"] == "/zoo_transaction/"
|
|
|
|
# not key transactions
|
|
query["query"] = "!has:team_key_transaction"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
|
|
# not key transactions
|
|
query["query"] = "team_key_transaction:false"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["team_key_transaction"] == 0
|
|
assert data[0]["transaction"] == "/blah_transaction/"
|
|
|
|
def test_too_many_team_key_transactions(self):
|
|
MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS = 1
|
|
with mock.patch(
|
|
"sentry.search.events.fields.MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS",
|
|
MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS,
|
|
):
|
|
team = self.create_team(organization=self.organization, name="Team A")
|
|
self.create_team_membership(team, user=self.user)
|
|
self.project.add_team(team)
|
|
project_team = ProjectTeam.objects.get(project=self.project, team=team)
|
|
|
|
for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1):
|
|
transaction = f"transaction-{team.id}-{i}"
|
|
self.transaction_data["transaction"] = transaction
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
|
|
TeamKeyTransaction.objects.bulk_create(
|
|
[
|
|
TeamKeyTransaction(
|
|
organization=self.organization,
|
|
project_team=project_team,
|
|
transaction=f"transaction-{team.id}-{i}",
|
|
)
|
|
for i in range(MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS + 1)
|
|
]
|
|
)
|
|
|
|
query = {
|
|
"team": "myteams",
|
|
"project": [self.project.id],
|
|
"orderby": "transaction",
|
|
"field": [
|
|
"team_key_transaction",
|
|
"transaction",
|
|
"transaction.status",
|
|
"project",
|
|
"epm()",
|
|
"failure_rate()",
|
|
"percentile(transaction.duration, 0.95)",
|
|
],
|
|
}
|
|
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert (
|
|
sum(row["team_key_transaction"] for row in data)
|
|
== MAX_QUERYABLE_TEAM_KEY_TRANSACTIONS
|
|
)
|
|
|
|
def test_no_pagination_param(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["id", "project.id"], "project": [self.project.id], "noPagination": True}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
assert "Link" not in response
|
|
|
|
def test_nan_result(self):
|
|
query = {"field": ["apdex(300)"], "project": [self.project.id], "query": f"id:{'0' * 32}"}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["apdex(300)"] == 0
|
|
|
|
def test_equation_simple(self):
|
|
event_data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
)
|
|
event_data["breakdowns"]["span_ops"]["ops.http"]["value"] = 1500
|
|
self.store_event(data=event_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["spans.http", "equation|spans.http / 3"],
|
|
"project": [self.project.id],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(
|
|
query,
|
|
{
|
|
"organizations:discover-basic": True,
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert (
|
|
response.data["data"][0]["equation|spans.http / 3"]
|
|
== event_data["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
|
|
)
|
|
assert response.data["meta"]["fields"]["equation|spans.http / 3"] == "number"
|
|
|
|
def test_equation_sort(self):
|
|
event_data = self.transaction_data.copy()
|
|
event_data["breakdowns"] = {"span_ops": {"ops.http": {"value": 1500}}}
|
|
self.store_event(data=event_data, project_id=self.project.id)
|
|
|
|
event_data2 = self.transaction_data.copy()
|
|
event_data2["breakdowns"] = {"span_ops": {"ops.http": {"value": 2000}}}
|
|
self.store_event(data=event_data2, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["spans.http", "equation|spans.http / 3"],
|
|
"project": [self.project.id],
|
|
"orderby": "equation|spans.http / 3",
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(
|
|
query,
|
|
{
|
|
"organizations:discover-basic": True,
|
|
},
|
|
)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 2
|
|
assert (
|
|
response.data["data"][0]["equation|spans.http / 3"]
|
|
== event_data["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
|
|
)
|
|
assert (
|
|
response.data["data"][1]["equation|spans.http / 3"]
|
|
== event_data2["breakdowns"]["span_ops"]["ops.http"]["value"] / 3
|
|
)
|
|
|
|
def test_equation_operation_limit(self):
|
|
query = {
|
|
"field": ["spans.http", f"equation|spans.http{' * 2' * 11}"],
|
|
"project": [self.project.id],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(
|
|
query,
|
|
{
|
|
"organizations:discover-basic": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
@mock.patch("sentry.api.bases.organization_events.MAX_FIELDS", 2)
|
|
def test_equation_field_limit(self):
|
|
query = {
|
|
"field": ["spans.http", "transaction.duration", "equation|5 * 2"],
|
|
"project": [self.project.id],
|
|
"query": "event.type:transaction",
|
|
}
|
|
response = self.do_request(
|
|
query,
|
|
{
|
|
"organizations:discover-basic": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
def test_count_if(self):
|
|
unicode_phrase1 = "\u716e\u6211\u66f4\u591a\u7684\u98df\u7269\uff0c\u6211\u9913\u4e86"
|
|
for i in range(5):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + i)),
|
|
duration=timedelta(milliseconds=100 if i < 3 else 200),
|
|
)
|
|
data["tags"] = {
|
|
"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no",
|
|
"unicode-phrase": unicode_phrase1 if i == 0 else "no",
|
|
}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"count_if(transaction.duration, less, 150)",
|
|
"count_if(transaction.duration, greater, 150)",
|
|
"count_if(sub_customer.is-Enterprise-42, equals, yes)",
|
|
"count_if(sub_customer.is-Enterprise-42, notEquals, yes)",
|
|
f"count_if(unicode-phrase, equals, {unicode_phrase1})",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
assert response.data["data"][0]["count_if(transaction.duration, less, 150)"] == 3
|
|
assert response.data["data"][0]["count_if(transaction.duration, greater, 150)"] == 2
|
|
|
|
assert response.data["data"][0]["count_if(sub_customer.is-Enterprise-42, equals, yes)"] == 1
|
|
assert (
|
|
response.data["data"][0]["count_if(sub_customer.is-Enterprise-42, notEquals, yes)"] == 4
|
|
)
|
|
assert response.data["data"][0][f"count_if(unicode-phrase, equals, {unicode_phrase1})"] == 1
|
|
|
|
def test_count_if_array_field(self):
|
|
data = self.load_data(platform="javascript")
|
|
data["timestamp"] = self.ten_mins_ago_iso
|
|
self.store_event(data=data, project_id=self.project.id)
|
|
query = {
|
|
"field": [
|
|
"count_if(stack.filename, equals, raven.js)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["count_if(stack.filename, equals, raven.js)"] == 1
|
|
|
|
def test_count_if_measurements_cls(self):
|
|
data = self.transaction_data.copy()
|
|
data["measurements"] = {"cls": {"value": 0.5}}
|
|
self.store_event(data, project_id=self.project.id)
|
|
data["measurements"] = {"cls": {"value": 0.1}}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"count_if(measurements.cls, greater, 0.05)",
|
|
"count_if(measurements.cls, less, 0.3)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
assert response.data["data"][0]["count_if(measurements.cls, greater, 0.05)"] == 2
|
|
assert response.data["data"][0]["count_if(measurements.cls, less, 0.3)"] == 1
|
|
|
|
def test_count_if_filter(self):
|
|
for i in range(5):
|
|
data = self.load_data(
|
|
timestamp=before_now(minutes=(10 + i)),
|
|
duration=timedelta(milliseconds=100 if i < 3 else 200),
|
|
)
|
|
data["tags"] = {"sub_customer.is-Enterprise-42": "yes" if i == 0 else "no"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"count_if(transaction.duration, less, 150)",
|
|
],
|
|
"query": "count_if(transaction.duration, less, 150):>2",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
assert response.data["data"][0]["count_if(transaction.duration, less, 150)"] == 3
|
|
|
|
query = {
|
|
"field": [
|
|
"count_if(transaction.duration, less, 150)",
|
|
],
|
|
"query": "count_if(transaction.duration, less, 150):<2",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 0
|
|
|
|
def test_filters_with_escaped_asterisk(self):
|
|
self.transaction_data["transaction"] = r"/:a*/:b-:c(\d\.\e+)"
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "transaction.duration"],
|
|
# make sure to escape the asterisk so it's not treated as a wildcard
|
|
"query": r'transaction:"/:a\*/:b-:c(\d\.\e+)"',
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
def test_filters_with_back_slashes(self):
|
|
self.transaction_data["transaction"] = r"a\b\c@d"
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "transaction.duration"],
|
|
"query": r'transaction:"a\b\c@d"',
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
assert len(response.data["data"]) == 1
|
|
|
|
def test_mobile_measurements(self):
|
|
self.transaction_data["measurements"]["frames_total"] = {"value": 100}
|
|
self.transaction_data["measurements"]["frames_slow"] = {"value": 10}
|
|
self.transaction_data["measurements"]["frames_frozen"] = {"value": 5}
|
|
self.transaction_data["measurements"]["stall_count"] = {"value": 2}
|
|
self.transaction_data["measurements"]["stall_total_time"] = {"value": 12}
|
|
self.transaction_data["measurements"]["stall_longest_time"] = {"value": 7}
|
|
self.store_event(self.transaction_data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": [
|
|
"measurements.frames_total",
|
|
"measurements.frames_slow",
|
|
"measurements.frames_frozen",
|
|
"measurements.frames_slow_rate",
|
|
"measurements.frames_frozen_rate",
|
|
"measurements.stall_count",
|
|
"measurements.stall_total_time",
|
|
"measurements.stall_longest_time",
|
|
"measurements.stall_percentage",
|
|
],
|
|
"query": "",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["measurements.frames_total"] == 100
|
|
assert data[0]["measurements.frames_slow"] == 10
|
|
assert data[0]["measurements.frames_frozen"] == 5
|
|
assert data[0]["measurements.frames_slow_rate"] == 0.1
|
|
assert data[0]["measurements.frames_frozen_rate"] == 0.05
|
|
assert data[0]["measurements.stall_count"] == 2
|
|
assert data[0]["measurements.stall_total_time"] == 12
|
|
assert data[0]["measurements.stall_longest_time"] == 7
|
|
assert data[0]["measurements.stall_percentage"] == 0.004
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["measurements.frames_total"] == "number"
|
|
assert meta["measurements.frames_slow"] == "number"
|
|
assert meta["measurements.frames_frozen"] == "number"
|
|
assert meta["measurements.frames_slow_rate"] == "percentage"
|
|
assert meta["measurements.frames_frozen_rate"] == "percentage"
|
|
assert meta["measurements.stall_count"] == "number"
|
|
assert meta["measurements.stall_total_time"] == "number"
|
|
assert meta["measurements.stall_longest_time"] == "number"
|
|
assert meta["measurements.stall_percentage"] == "percentage"
|
|
|
|
query = {
|
|
"field": [
|
|
"p75(measurements.frames_slow_rate)",
|
|
"p75(measurements.frames_frozen_rate)",
|
|
"percentile(measurements.frames_slow_rate,0.5)",
|
|
"percentile(measurements.frames_frozen_rate,0.5)",
|
|
"p75(measurements.stall_percentage)",
|
|
"percentile(measurements.stall_percentage,0.5)",
|
|
],
|
|
"query": "",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p75(measurements.frames_slow_rate)"] == 0.1
|
|
assert data[0]["p75(measurements.frames_frozen_rate)"] == 0.05
|
|
assert data[0]["p75(measurements.stall_percentage)"] == 0.004
|
|
assert data[0]["percentile(measurements.frames_slow_rate,0.5)"] == 0.1
|
|
assert data[0]["percentile(measurements.frames_frozen_rate,0.5)"] == 0.05
|
|
assert data[0]["percentile(measurements.stall_percentage,0.5)"] == 0.004
|
|
meta = response.data["meta"]["fields"]
|
|
assert meta["p75(measurements.frames_slow_rate)"] == "percentage"
|
|
assert meta["p75(measurements.frames_frozen_rate)"] == "percentage"
|
|
assert meta["p75(measurements.stall_percentage)"] == "percentage"
|
|
assert meta["percentile(measurements.frames_slow_rate,0.5)"] == "percentage"
|
|
assert meta["percentile(measurements.stall_percentage,0.5)"] == "percentage"
|
|
|
|
def test_project_auto_fields(self):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"environment": "staging",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {"field": ["environment"]}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["environment"] == "staging"
|
|
assert response.data["data"][0]["project.name"] == self.project.slug
|
|
|
|
def test_timestamp_different_from_params(self):
|
|
fifteen_days_ago = iso_format(before_now(days=15))
|
|
fifteen_days_later = iso_format(before_now(days=-15))
|
|
|
|
for query_text in [
|
|
f"timestamp:<{fifteen_days_ago}",
|
|
f"timestamp:<={fifteen_days_ago}",
|
|
f"timestamp:>{fifteen_days_later}",
|
|
f"timestamp:>={fifteen_days_later}",
|
|
]:
|
|
query = {
|
|
"field": ["count()"],
|
|
"query": query_text,
|
|
"statsPeriod": "14d",
|
|
"project": self.project.id,
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 400, query_text
|
|
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_removes_unnecessary_default_project_and_transaction_thresholds(self, mock_snql_query):
|
|
mock_snql_query.side_effect = [{"meta": {}, "data": []}]
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.organization,
|
|
# these are the default values that we use
|
|
threshold=constants.DEFAULT_PROJECT_THRESHOLD,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction="transaction",
|
|
project=self.project,
|
|
organization=self.organization,
|
|
# these are the default values that we use
|
|
threshold=constants.DEFAULT_PROJECT_THRESHOLD,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
query = {
|
|
"field": ["apdex()", "user_misery()"],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id],
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
features={
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
assert mock_snql_query.call_count == 1
|
|
|
|
assert (
|
|
Function("tuple", ["duration", 300], "project_threshold_config")
|
|
in mock_snql_query.call_args_list[0][0][0].query.select
|
|
)
|
|
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_removes_unnecessary_default_project_and_transaction_thresholds_keeps_others(
|
|
self, mock_snql_query
|
|
):
|
|
mock_snql_query.side_effect = [{"meta": {}, "data": []}]
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=self.project,
|
|
organization=self.organization,
|
|
# these are the default values that we use
|
|
threshold=constants.DEFAULT_PROJECT_THRESHOLD,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction="transaction",
|
|
project=self.project,
|
|
organization=self.organization,
|
|
# these are the default values that we use
|
|
threshold=constants.DEFAULT_PROJECT_THRESHOLD,
|
|
metric=TransactionMetric.DURATION.value,
|
|
)
|
|
|
|
project = self.create_project()
|
|
|
|
ProjectTransactionThreshold.objects.create(
|
|
project=project,
|
|
organization=self.organization,
|
|
threshold=100,
|
|
metric=TransactionMetric.LCP.value,
|
|
)
|
|
ProjectTransactionThresholdOverride.objects.create(
|
|
transaction="transaction",
|
|
project=project,
|
|
organization=self.organization,
|
|
threshold=200,
|
|
metric=TransactionMetric.LCP.value,
|
|
)
|
|
|
|
query = {
|
|
"field": ["apdex()", "user_misery()"],
|
|
"query": "event.type:transaction",
|
|
"project": [self.project.id, project.id],
|
|
}
|
|
|
|
response = self.do_request(
|
|
query,
|
|
features={
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
},
|
|
)
|
|
|
|
assert response.status_code == 200, response.content
|
|
|
|
assert mock_snql_query.call_count == 1
|
|
|
|
project_threshold_override_config_index = Function(
|
|
"indexOf",
|
|
[
|
|
# only 1 transaction override is present here
|
|
# because the other use to the default values
|
|
[(Function("toUInt64", [project.id]), "transaction")],
|
|
(Column("project_id"), Column("transaction")),
|
|
],
|
|
"project_threshold_override_config_index",
|
|
)
|
|
|
|
project_threshold_config_index = Function(
|
|
"indexOf",
|
|
[
|
|
# only 1 project override is present here
|
|
# because the other use to the default values
|
|
[Function("toUInt64", [project.id])],
|
|
Column("project_id"),
|
|
],
|
|
"project_threshold_config_index",
|
|
)
|
|
|
|
assert (
|
|
Function(
|
|
"if",
|
|
[
|
|
Function("equals", [project_threshold_override_config_index, 0]),
|
|
Function(
|
|
"if",
|
|
[
|
|
Function("equals", [project_threshold_config_index, 0]),
|
|
("duration", 300),
|
|
Function(
|
|
"arrayElement", [[("lcp", 100)], project_threshold_config_index]
|
|
),
|
|
],
|
|
),
|
|
Function(
|
|
"arrayElement",
|
|
[[("lcp", 200)], project_threshold_override_config_index],
|
|
),
|
|
],
|
|
"project_threshold_config",
|
|
)
|
|
in mock_snql_query.call_args_list[0][0][0].query.select
|
|
)
|
|
|
|
def test_count_web_vitals(self):
|
|
# Good
|
|
self.transaction_data["measurements"] = {
|
|
"lcp": {"value": constants.VITAL_THRESHOLDS["lcp"]["meh"] - 100},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
# Meh
|
|
self.transaction_data["measurements"] = {
|
|
"lcp": {"value": constants.VITAL_THRESHOLDS["lcp"]["meh"] + 100},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"count_web_vitals(measurements.lcp, poor)",
|
|
"count_web_vitals(measurements.lcp, meh)",
|
|
"count_web_vitals(measurements.lcp, good)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0] == {
|
|
"count_web_vitals(measurements.lcp, poor)": 0,
|
|
"count_web_vitals(measurements.lcp, meh)": 2,
|
|
"count_web_vitals(measurements.lcp, good)": 1,
|
|
}
|
|
|
|
def test_count_web_vitals_invalid_vital(self):
|
|
query = {
|
|
"field": [
|
|
"count_web_vitals(measurements.foo, poor)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
query = {
|
|
"field": [
|
|
"count_web_vitals(tags[lcp], poor)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
query = {
|
|
"field": [
|
|
"count_web_vitals(transaction.duration, poor)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
query = {
|
|
"field": [
|
|
"count_web_vitals(measurements.lcp, bad)",
|
|
],
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
def test_tag_that_looks_like_aggregate(self):
|
|
data = self.load_data()
|
|
data["tags"] = {"p95": "<5k"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["p95"],
|
|
"query": "p95:<5k",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["p95"] == "<5k"
|
|
|
|
def test_chained_or_query_meta_tip(self):
|
|
query = {
|
|
"field": ["transaction"],
|
|
"query": "transaction:a OR transaction:b",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
meta = response.data["meta"]
|
|
assert meta["tips"] == {
|
|
"query": "Did you know you can replace chained or conditions like `field:a OR field:b OR field:c` with `field:[a,b,c]`",
|
|
"columns": None,
|
|
}
|
|
|
|
@override_settings(SENTRY_SELF_HOSTED=False)
|
|
def test_no_ratelimit(self):
|
|
query = {
|
|
"field": ["transaction"],
|
|
"project": [self.project.id],
|
|
}
|
|
with freeze_time("2000-01-01"):
|
|
for _ in range(15):
|
|
self.do_request(query)
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
|
|
def test_transaction_source(self):
|
|
query = {
|
|
"field": ["transaction"],
|
|
"query": "transaction.source:task",
|
|
"project": [self.project.id],
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
|
|
def test_readable_device_name(self):
|
|
data = self.load_data()
|
|
data["tags"] = {"device": "iPhone14,3"}
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["device"],
|
|
"query": "",
|
|
"project": [self.project.id],
|
|
"readable": True,
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["device"] == "iPhone14,3"
|
|
assert data[0]["readable"] == "iPhone 13 Pro Max"
|
|
|
|
def test_http_status_code(self):
|
|
project1 = self.create_project()
|
|
project2 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"http.status_code": "200"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"contexts": {"response": {"status_code": 400}},
|
|
},
|
|
project_id=project2.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "http.status_code"],
|
|
"query": "",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
result = {r["http.status_code"] for r in data}
|
|
assert result == {"200", "400"}
|
|
|
|
def test_http_status_code_context_priority(self):
|
|
project1 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"http.status_code": "200"},
|
|
"contexts": {"response": {"status_code": 400}},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["event.type", "http.status_code"],
|
|
"query": "",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["http.status_code"] == "400"
|
|
|
|
def test_total_count(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(data=self.load_data(platform="javascript"), project_id=project1.id)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["transaction", "total.count", "count()"],
|
|
"query": "!transaction:/example",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["total.count"] == 3
|
|
|
|
def test_total_count_by_itself(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(data=self.load_data(platform="javascript"), project_id=project1.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["total.count"],
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 400, response.content
|
|
|
|
def test_total_count_equation(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(data=self.load_data(platform="javascript"), project_id=project1.id)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["transaction", "count()", "total.count", "equation|count()/total.count"],
|
|
"query": "",
|
|
"orderby": "equation|count()/total.count",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["equation|count()/total.count"] == 0.25
|
|
assert data[1]["equation|count()/total.count"] == 0.75
|
|
|
|
def test_total_count_filter(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(data=self.load_data(platform="javascript"), project_id=project1.id)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"total.count": ">45"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["transaction", "count()", "total.count"],
|
|
"query": "total.count:>45",
|
|
"orderby": "count()",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["total.count"] == 1
|
|
|
|
def test_total_sum_transaction_duration_equation(self):
|
|
for i in range(3):
|
|
data = self.load_data(
|
|
timestamp=self.eleven_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/endpoint/1"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/endpoint/2"
|
|
self.store_event(data, project_id=self.project.id)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
|
|
query = {
|
|
"field": [
|
|
"transaction",
|
|
"sum(transaction.duration)",
|
|
"total.transaction_duration",
|
|
"equation|sum(transaction.duration)/total.transaction_duration",
|
|
],
|
|
"query": "",
|
|
"orderby": "-equation|sum(transaction.duration)/total.transaction_duration",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 2
|
|
assert data[0]["equation|sum(transaction.duration)/total.transaction_duration"] == 0.75
|
|
assert data[1]["equation|sum(transaction.duration)/total.transaction_duration"] == 0.25
|
|
|
|
def test_device_class(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"device.class": f"{i + 1}"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["device.class"],
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 3
|
|
result = (*map(lambda columns: columns["device.class"], data),)
|
|
assert "low" in result
|
|
assert "medium" in result
|
|
assert "high" in result
|
|
|
|
def test_device_class_filter_low(self):
|
|
project1 = self.create_project()
|
|
for i in range(3):
|
|
self.store_event(data=self.load_data(platform="javascript"), project_id=project1.id)
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"transaction": "/example",
|
|
"message": "how to make fast",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"tags": {"device.class": "1"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
|
|
features = {"organizations:discover-basic": True, "organizations:global-views": True}
|
|
query = {
|
|
"field": ["device.class", "count()"],
|
|
"query": "device.class:low",
|
|
"orderby": "count()",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
assert data[0]["count()"] == 1
|
|
|
|
def test_group_id_as_custom_tag(self):
|
|
project1 = self.create_project()
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"message": "poof",
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"user": {"email": self.user.email},
|
|
"tags": {"group_id": "this should just get returned"},
|
|
},
|
|
project_id=project1.id,
|
|
)
|
|
query = {
|
|
"field": ["group_id"],
|
|
"query": "",
|
|
"orderby": "group_id",
|
|
"statsPeriod": "24h",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["group_id"] == "this should just get returned"
|
|
|
|
def test_floored_epm(self):
|
|
for _ in range(5):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
event1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "floored_epm()", "epm()"],
|
|
"query": "event.type:transaction",
|
|
"orderby": ["transaction"],
|
|
"start": self.eleven_mins_ago_iso,
|
|
"end": iso_format(self.nine_mins_ago),
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event1.transaction
|
|
assert data[0]["floored_epm()"] == 1
|
|
assert data[0]["epm()"] == 2.5
|
|
|
|
def test_floored_epm_more_events(self):
|
|
for _ in range(25):
|
|
data = self.load_data(
|
|
timestamp=self.ten_mins_ago,
|
|
duration=timedelta(seconds=5),
|
|
)
|
|
data["transaction"] = "/aggregates/1"
|
|
event1 = self.store_event(data, project_id=self.project.id)
|
|
|
|
query = {
|
|
"field": ["transaction", "floored_epm()", "epm()"],
|
|
"query": "event.type:transaction",
|
|
"orderby": ["transaction"],
|
|
"start": self.eleven_mins_ago_iso,
|
|
"end": iso_format(self.nine_mins_ago),
|
|
}
|
|
response = self.do_request(query)
|
|
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
data = response.data["data"]
|
|
assert data[0]["transaction"] == event1.transaction
|
|
assert data[0]["epm()"] == 12.5
|
|
assert data[0]["floored_epm()"] == 10
|
|
|
|
|
|
@region_silo_test
|
|
class OrganizationEventsProfilesDatasetEndpointTest(OrganizationEventsEndpointTestBase):
|
|
@mock.patch("sentry.search.events.builder.discover.raw_snql_query")
|
|
def test_profiles_dataset_simple(self, mock_snql_query):
|
|
mock_snql_query.side_effect = [
|
|
{
|
|
"data": [
|
|
{
|
|
"project": self.project.id,
|
|
"transaction": "foo",
|
|
"last_seen": "2022-10-20T16:41:22+00:00",
|
|
"latest_event": "a" * 32,
|
|
"count": 1,
|
|
"count_unique_transaction": 1,
|
|
"percentile_profile_duration_0_25": 1,
|
|
"p50_profile_duration": 1,
|
|
"p75_profile_duration": 1,
|
|
"p95_profile_duration": 1,
|
|
"p99_profile_duration": 1,
|
|
"p100_profile_duration": 1,
|
|
"min_profile_duration": 1,
|
|
"max_profile_duration": 1,
|
|
"avg_profile_duration": 1,
|
|
"sum_profile_duration": 1,
|
|
},
|
|
],
|
|
"meta": [
|
|
{
|
|
"name": "project",
|
|
"type": "UInt64",
|
|
},
|
|
{
|
|
"name": "transaction",
|
|
"type": "LowCardinality(String)",
|
|
},
|
|
{
|
|
"name": "last_seen",
|
|
"type": "DateTime",
|
|
},
|
|
{
|
|
"name": "latest_event",
|
|
"type": "String",
|
|
},
|
|
{
|
|
"name": "count",
|
|
"type": "UInt64",
|
|
},
|
|
{
|
|
"name": "count_unique_transaction",
|
|
"type": "UInt64",
|
|
},
|
|
{
|
|
"name": "percentile_profile_duration_0_25",
|
|
"type": "Float64",
|
|
},
|
|
*[
|
|
{
|
|
"name": f"{fn}_profile_duration",
|
|
"type": "Float64",
|
|
}
|
|
for fn in ["p50", "p75", "p95", "p99", "p100", "min", "max", "avg", "sum"]
|
|
],
|
|
],
|
|
},
|
|
]
|
|
|
|
fields = [
|
|
"project",
|
|
"transaction",
|
|
"last_seen()",
|
|
"latest_event()",
|
|
"count()",
|
|
"count_unique(transaction)",
|
|
"percentile(profile.duration, 0.25)",
|
|
"p50(profile.duration)",
|
|
"p75(profile.duration)",
|
|
"p95(profile.duration)",
|
|
"p99(profile.duration)",
|
|
"p100(profile.duration)",
|
|
"min(profile.duration)",
|
|
"max(profile.duration)",
|
|
"avg(profile.duration)",
|
|
"sum(profile.duration)",
|
|
]
|
|
|
|
query = {
|
|
"field": fields,
|
|
"project": [self.project.id],
|
|
"dataset": "profiles",
|
|
}
|
|
response = self.do_request(query, features={"organizations:profiling": True})
|
|
assert response.status_code == 200, response.content
|
|
|
|
# making sure the response keys are in the form we expect and not aliased
|
|
data_keys = {key for row in response.data["data"] for key in row}
|
|
field_keys = {key for key in response.data["meta"]["fields"]}
|
|
unit_keys = {key for key in response.data["meta"]["units"]}
|
|
assert set(fields) == data_keys
|
|
assert set(fields) == field_keys
|
|
assert set(fields) == unit_keys
|
|
|
|
|
|
@region_silo_test
|
|
class OrganizationEventsProfileFunctionsDatasetEndpointTest(
|
|
OrganizationEventsEndpointTestBase, ProfilesSnubaTestCase
|
|
):
|
|
def test_functions_dataset_simple(self):
|
|
self.store_functions(
|
|
[
|
|
{
|
|
"self_times_ns": [100 for _ in range(100)],
|
|
"package": "foo",
|
|
"function": "bar",
|
|
"in_app": True,
|
|
},
|
|
],
|
|
project=self.project,
|
|
timestamp=before_now(hours=3),
|
|
)
|
|
self.store_functions(
|
|
[
|
|
{
|
|
"self_times_ns": [150 for _ in range(100)],
|
|
"package": "foo",
|
|
"function": "bar",
|
|
"in_app": True,
|
|
},
|
|
],
|
|
project=self.project,
|
|
timestamp=before_now(hours=1),
|
|
)
|
|
|
|
mid = before_now(hours=2)
|
|
|
|
fields = [
|
|
"transaction",
|
|
"project",
|
|
"function",
|
|
"package",
|
|
"is_application",
|
|
"platform.name",
|
|
"environment",
|
|
"release",
|
|
"count()",
|
|
"examples()",
|
|
"p50()",
|
|
"p75()",
|
|
"p95()",
|
|
"p99()",
|
|
"avg()",
|
|
"sum()",
|
|
f"regression_score(function.duration, 0.95, {int(mid.timestamp())})",
|
|
]
|
|
|
|
response = self.do_request(
|
|
{
|
|
"field": fields,
|
|
"statsPeriod": "4h",
|
|
"project": [self.project.id],
|
|
"dataset": "profileFunctions",
|
|
},
|
|
features={"organizations:profiling": True},
|
|
)
|
|
assert response.status_code == 200, response.content
|
|
|
|
# making sure the response keys are in the form we expect and not aliased
|
|
data_keys = {key for row in response.data["data"] for key in row}
|
|
field_keys = {key for key in response.data["meta"]["fields"]}
|
|
unit_keys = {key for key in response.data["meta"]["units"]}
|
|
assert set(fields) == data_keys
|
|
assert set(fields) == field_keys
|
|
assert set(fields) == unit_keys
|
|
assert response.data["meta"]["units"] == {
|
|
"transaction": None,
|
|
"project": None,
|
|
"function": None,
|
|
"package": None,
|
|
"is_application": None,
|
|
"platform.name": None,
|
|
"environment": None,
|
|
"release": None,
|
|
"count()": None,
|
|
"examples()": None,
|
|
"p50()": "nanosecond",
|
|
"p75()": "nanosecond",
|
|
"p95()": "nanosecond",
|
|
"p99()": "nanosecond",
|
|
"avg()": "nanosecond",
|
|
"sum()": "nanosecond",
|
|
f"regression_score(function.duration, 0.95, {int(mid.timestamp())})": None,
|
|
}
|
|
|
|
|
|
@region_silo_test
|
|
class OrganizationEventsIssuePlatformDatasetEndpointTest(
|
|
OrganizationEventsEndpointTestBase, SearchIssueTestMixin, PerformanceIssueTestCase
|
|
):
|
|
def test_performance_issue_id_filter(self):
|
|
event = self.create_performance_issue()
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "2h",
|
|
"query": f"issue.id:{event.group.id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_generic_issue_ids_filter(self):
|
|
user_data = {
|
|
"id": self.user.id,
|
|
"username": "user",
|
|
"email": "[email protected]",
|
|
"ip_address": "127.0.0.1",
|
|
}
|
|
event, _, group_info = self.store_search_issue(
|
|
self.project.id,
|
|
self.user.id,
|
|
[f"{ProfileFileIOGroupType.type_id}-group1"],
|
|
"prod",
|
|
before_now(hours=1).replace(tzinfo=timezone.utc),
|
|
user=user_data,
|
|
)
|
|
event, _, group_info = self.store_search_issue(
|
|
self.project.id,
|
|
self.user.id,
|
|
[f"{ProfileFileIOGroupType.type_id}-group2"],
|
|
"prod",
|
|
before_now(hours=1).replace(tzinfo=timezone.utc),
|
|
user=user_data,
|
|
)
|
|
assert group_info is not None
|
|
|
|
query = {
|
|
"field": ["title", "release", "environment", "user.display", "timestamp"],
|
|
"statsPeriod": "90d",
|
|
"query": f"issue.id:{group_info.group.id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
with self.feature(["organizations:profiling"]):
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["title"] == group_info.group.title
|
|
assert response.data["data"][0]["environment"] == "prod"
|
|
assert response.data["data"][0]["user.display"] == user_data["email"]
|
|
assert response.data["data"][0]["timestamp"] == event.timestamp
|
|
|
|
query = {
|
|
"field": ["title", "release", "environment", "user.display", "timestamp"],
|
|
"statsPeriod": "90d",
|
|
"query": f"issue:{group_info.group.qualified_short_id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
with self.feature(["organizations:profiling"]):
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0]["title"] == group_info.group.title
|
|
assert response.data["data"][0]["environment"] == "prod"
|
|
assert response.data["data"][0]["user.display"] == user_data["email"]
|
|
assert response.data["data"][0]["timestamp"] == event.timestamp
|
|
|
|
def test_performance_short_group_id(self):
|
|
event = self.create_performance_issue()
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{event.group.project.slug} issue:{event.group.qualified_short_id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_multiple_performance_short_group_ids_filter(self):
|
|
event1 = self.create_performance_issue()
|
|
event2 = self.create_performance_issue()
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{event1.group.project.slug} issue:[{event1.group.qualified_short_id},{event2.group.qualified_short_id}]",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 2
|
|
|
|
def test_user_display_issue_platform(self):
|
|
project1 = self.create_project()
|
|
user_data = {
|
|
"id": self.user.id,
|
|
"username": "user",
|
|
"email": "[email protected]",
|
|
"ip_address": "127.0.0.1",
|
|
}
|
|
_, _, group_info = self.store_search_issue(
|
|
project1.id,
|
|
1,
|
|
["group1-fingerprint"],
|
|
None,
|
|
before_now(hours=1).replace(tzinfo=timezone.utc),
|
|
user=user_data,
|
|
)
|
|
assert group_info is not None
|
|
|
|
features = {
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
"organizations:profiling": True,
|
|
}
|
|
query = {
|
|
"field": ["user.display"],
|
|
"query": f"user.display:hell* issue.id:{group_info.group.id}",
|
|
"statsPeriod": "24h",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
result = {r["user.display"] for r in data}
|
|
assert result == {user_data["email"]}
|
|
|
|
def test_all_events_fields(self):
|
|
user_data = {
|
|
"id": self.user.id,
|
|
"username": "user",
|
|
"email": "[email protected]",
|
|
"ip_address": "127.0.0.1",
|
|
}
|
|
replay_id = str(uuid.uuid4())
|
|
profile_id = str(uuid.uuid4())
|
|
event = self.create_performance_issue(
|
|
contexts={
|
|
"trace": {
|
|
"trace_id": str(uuid.uuid4().hex),
|
|
"span_id": "933e5c9a8e464da9",
|
|
"type": "trace",
|
|
},
|
|
"replay": {"replay_id": replay_id},
|
|
"profile": {"profile_id": profile_id},
|
|
},
|
|
user_data=user_data,
|
|
)
|
|
|
|
query = {
|
|
"field": [
|
|
"id",
|
|
"transaction",
|
|
"title",
|
|
"release",
|
|
"environment",
|
|
"user.display",
|
|
"device",
|
|
"os",
|
|
"url",
|
|
"runtime",
|
|
"replayId",
|
|
"profile.id",
|
|
"transaction.duration",
|
|
"timestamp",
|
|
],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{event.group.project.slug} issue:{event.group.qualified_short_id}",
|
|
"dataset": "issuePlatform",
|
|
}
|
|
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
|
|
data = response.data["data"][0]
|
|
|
|
assert data == {
|
|
"id": event.event_id,
|
|
"transaction": event.transaction,
|
|
"project.name": event.project.name.lower(),
|
|
"title": event.group.title,
|
|
"release": event.release,
|
|
"environment": event.get_environment().name,
|
|
"user.display": user_data["email"],
|
|
"device": "Mac",
|
|
"os": "",
|
|
"url": event.interfaces.data["request"].full_url,
|
|
"runtime": dict(event.get_raw_data()["tags"])["runtime"],
|
|
"replayId": replay_id.replace("-", ""),
|
|
"profile.id": profile_id.replace("-", ""),
|
|
"transaction.duration": 3000,
|
|
"timestamp": event.datetime.replace(microsecond=0).isoformat(),
|
|
}
|
|
|
|
|
|
class OrganizationEventsErrorsDatasetEndpointTest(OrganizationEventsEndpointTestBase):
|
|
def test_status(self):
|
|
with self.options({"issues.group_attributes.send_kafka": True}):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
group_2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group2"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
group_3 = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group3"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "2h",
|
|
"query": "status:unresolved",
|
|
"dataset": "errors",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 3
|
|
group_2.status = GroupStatus.IGNORED
|
|
group_2.substatus = GroupSubStatus.FOREVER
|
|
group_2.save(update_fields=["status", "substatus"])
|
|
group_3.status = GroupStatus.IGNORED
|
|
group_3.substatus = GroupSubStatus.FOREVER
|
|
group_3.save(update_fields=["status", "substatus"])
|
|
# XXX: Snuba caches query results, so change the time period so that the query
|
|
# changes enough to bust the cache.
|
|
query["statsPeriod"] = "3h"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_is_status(self):
|
|
with self.options({"issues.group_attributes.send_kafka": True}):
|
|
self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
group_2 = self.store_event(
|
|
data={
|
|
"event_id": "b" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group2"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
group_3 = self.store_event(
|
|
data={
|
|
"event_id": "c" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group3"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "2h",
|
|
"query": "is:unresolved",
|
|
"dataset": "errors",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 3
|
|
group_2.status = GroupStatus.IGNORED
|
|
group_2.substatus = GroupSubStatus.FOREVER
|
|
group_2.save(update_fields=["status", "substatus"])
|
|
group_3.status = GroupStatus.IGNORED
|
|
group_3.substatus = GroupSubStatus.FOREVER
|
|
group_3.save(update_fields=["status", "substatus"])
|
|
# XXX: Snuba caches query results, so change the time period so that the query
|
|
# changes enough to bust the cache.
|
|
query["statsPeriod"] = "3h"
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_short_group_id(self):
|
|
group_1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
query = {
|
|
"field": ["count()"],
|
|
"statsPeriod": "1h",
|
|
"query": f"project:{group_1.project.slug} issue:{group_1.qualified_short_id}",
|
|
"dataset": "errors",
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert response.data["data"][0]["count()"] == 1
|
|
|
|
def test_user_display(self):
|
|
with self.options({"issues.group_attributes.send_kafka": True}):
|
|
group_1 = self.store_event(
|
|
data={
|
|
"event_id": "a" * 32,
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
"user": {
|
|
"email": "[email protected]",
|
|
},
|
|
},
|
|
project_id=self.project.id,
|
|
).group
|
|
|
|
features = {
|
|
"organizations:discover-basic": True,
|
|
"organizations:global-views": True,
|
|
}
|
|
query = {
|
|
"field": ["user.display"],
|
|
"query": f"user.display:hell* issue.id:{group_1.id}",
|
|
"statsPeriod": "24h",
|
|
"dataset": "errors",
|
|
}
|
|
response = self.do_request(query, features=features)
|
|
assert response.status_code == 200, response.content
|
|
data = response.data["data"]
|
|
assert len(data) == 1
|
|
result = {r["user.display"] for r in data}
|
|
assert result == {"[email protected]"}
|
|
|
|
def test_performance_score(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 0.03},
|
|
"score.weight.lcp": {"value": 0.3},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 1.0},
|
|
"score.weight.lcp": {"value": 1.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"performance_score(measurements.score.lcp)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0] == {
|
|
"performance_score(measurements.score.lcp)": 0.7923076923076923,
|
|
}
|
|
|
|
def test_weighted_performance_score(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 0.03},
|
|
"score.weight.lcp": {"value": 0.3},
|
|
"score.total": {"value": 0.03},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 1.0},
|
|
"score.weight.lcp": {"value": 1.0},
|
|
"score.total": {"value": 1.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 0.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"weighted_performance_score(measurements.score.lcp)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0] == {
|
|
"weighted_performance_score(measurements.score.lcp)": 0.3433333333333333,
|
|
}
|
|
|
|
def test_invalid_performance_score_column(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 0.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"performance_score(measurements.score.fp)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
def test_invalid_weighted_performance_score_column(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 0.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"weighted_performance_score(measurements.score.fp)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 400, response.content
|
|
|
|
def test_all_events_fields(self):
|
|
user_data = {
|
|
"id": self.user.id,
|
|
"username": "user",
|
|
"email": "[email protected]",
|
|
"ip_address": "127.0.0.1",
|
|
}
|
|
replay_id = str(uuid.uuid4())
|
|
with self.options({"issues.group_attributes.send_kafka": True}):
|
|
event = self.store_event(
|
|
data={
|
|
"timestamp": self.ten_mins_ago_iso,
|
|
"fingerprint": ["group1"],
|
|
"contexts": {
|
|
"trace": {
|
|
"trace_id": str(uuid.uuid4().hex),
|
|
"span_id": "933e5c9a8e464da9",
|
|
"type": "trace",
|
|
},
|
|
"replay": {"replay_id": replay_id},
|
|
},
|
|
"tags": {"device": "Mac"},
|
|
"user": user_data,
|
|
},
|
|
project_id=self.project.id,
|
|
)
|
|
|
|
query = {
|
|
"field": [
|
|
"id",
|
|
"transaction",
|
|
"title",
|
|
"release",
|
|
"environment",
|
|
"user.display",
|
|
"device",
|
|
"os",
|
|
"replayId",
|
|
"timestamp",
|
|
],
|
|
"statsPeriod": "2d",
|
|
"query": "is:unresolved",
|
|
"dataset": "errors",
|
|
"sort": "-title",
|
|
}
|
|
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
|
|
data = response.data["data"][0]
|
|
|
|
assert data == {
|
|
"id": event.event_id,
|
|
"events.transaction": "",
|
|
"project.name": event.project.name.lower(),
|
|
"events.title": event.group.title,
|
|
"release": event.release,
|
|
"events.environment": None,
|
|
"user.display": user_data["email"],
|
|
"device": "Mac",
|
|
"os": "",
|
|
"replayId": replay_id,
|
|
"events.timestamp": event.datetime.replace(microsecond=0).isoformat(),
|
|
}
|
|
|
|
def test_opportunity_score(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 0.03},
|
|
"score.weight.lcp": {"value": 0.3},
|
|
"score.fcp": {"value": 0.4},
|
|
"score.weight.fcp": {"value": 0.7},
|
|
"score.total": {"value": 0.43},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 1.0},
|
|
"score.weight.lcp": {"value": 1.0},
|
|
"score.total": {"value": 1.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 0.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"opportunity_score(measurements.score.lcp)",
|
|
"opportunity_score(measurements.score.total)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0] == {
|
|
"opportunity_score(measurements.score.lcp)": 0.27,
|
|
"opportunity_score(measurements.score.total)": 1.57,
|
|
}
|
|
|
|
def test_count_scores(self):
|
|
self.transaction_data["measurements"] = {
|
|
"score.lcp": {"value": 0.03},
|
|
"score.total": {"value": 0.43},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 1.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
self.transaction_data["measurements"] = {
|
|
"score.total": {"value": 0.0},
|
|
}
|
|
self.store_event(self.transaction_data, self.project.id)
|
|
query = {
|
|
"field": [
|
|
"count_scores(measurements.score.lcp)",
|
|
"count_scores(measurements.score.total)",
|
|
]
|
|
}
|
|
response = self.do_request(query)
|
|
assert response.status_code == 200, response.content
|
|
assert len(response.data["data"]) == 1
|
|
assert response.data["data"][0] == {
|
|
"count_scores(measurements.score.lcp)": 1,
|
|
"count_scores(measurements.score.total)": 3,
|
|
}
|