|
@@ -1,20 +1,32 @@
|
|
|
-from typing import Callable, List, Mapping, Set
|
|
|
+from typing import Callable, List, Mapping, Optional, Set
|
|
|
|
|
|
+import sentry_sdk
|
|
|
from django.utils.functional import cached_property
|
|
|
from snuba_sdk.column import Column
|
|
|
from snuba_sdk.function import CurriedFunction, Function
|
|
|
from snuba_sdk.orderby import OrderBy
|
|
|
|
|
|
+from sentry.exceptions import InvalidSearchQuery
|
|
|
from sentry.models import Project
|
|
|
+from sentry.models.transaction_threshold import (
|
|
|
+ TRANSACTION_METRICS,
|
|
|
+ ProjectTransactionThreshold,
|
|
|
+ ProjectTransactionThresholdOverride,
|
|
|
+)
|
|
|
from sentry.search.events.constants import (
|
|
|
+ DEFAULT_PROJECT_THRESHOLD,
|
|
|
+ DEFAULT_PROJECT_THRESHOLD_METRIC,
|
|
|
ERROR_HANDLED_ALIAS,
|
|
|
ERROR_UNHANDLED_ALIAS,
|
|
|
ISSUE_ALIAS,
|
|
|
ISSUE_ID_ALIAS,
|
|
|
KEY_TRANSACTION_ALIAS,
|
|
|
+ MAX_QUERYABLE_TRANSACTION_THRESHOLDS,
|
|
|
PROJECT_ALIAS,
|
|
|
PROJECT_NAME_ALIAS,
|
|
|
PROJECT_THRESHOLD_CONFIG_ALIAS,
|
|
|
+ PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
|
+ PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
|
SNQL_FIELD_ALLOWLIST,
|
|
|
TEAM_KEY_TRANSACTION_ALIAS,
|
|
|
TIMESTAMP_TO_DAY_ALIAS,
|
|
@@ -23,6 +35,7 @@ from sentry.search.events.constants import (
|
|
|
USER_DISPLAY_ALIAS,
|
|
|
)
|
|
|
from sentry.search.events.types import ParamsType, SelectType, WhereType
|
|
|
+from sentry.utils.numbers import format_grouped_length
|
|
|
from sentry.utils.snuba import Dataset, resolve_column
|
|
|
|
|
|
|
|
@@ -54,12 +67,12 @@ class QueryBase:
|
|
|
TIMESTAMP_TO_DAY_ALIAS: self._resolve_timestamp_to_day_alias,
|
|
|
USER_DISPLAY_ALIAS: self._resolve_user_display_alias,
|
|
|
TRANSACTION_STATUS_ALIAS: self._resolve_transaction_status,
|
|
|
+ PROJECT_THRESHOLD_CONFIG_ALIAS: self._resolve_project_threshold_config,
|
|
|
ERROR_UNHANDLED_ALIAS: self._resolve_error_unhandled_alias,
|
|
|
ERROR_HANDLED_ALIAS: self._resolve_error_handled_alias,
|
|
|
# TODO: implement these
|
|
|
KEY_TRANSACTION_ALIAS: self._resolve_unimplemented_alias,
|
|
|
TEAM_KEY_TRANSACTION_ALIAS: self._resolve_unimplemented_alias,
|
|
|
- PROJECT_THRESHOLD_CONFIG_ALIAS: self._resolve_unimplemented_alias,
|
|
|
}
|
|
|
|
|
|
@cached_property
|
|
@@ -133,6 +146,140 @@ class QueryBase:
|
|
|
"toUInt8", [self.column(TRANSACTION_STATUS_ALIAS)], TRANSACTION_STATUS_ALIAS
|
|
|
)
|
|
|
|
|
|
+ def _resolve_project_threshold_config(self, _: str) -> SelectType:
|
|
|
+ org_id = self.params.get("organization_id")
|
|
|
+ project_ids = self.params.get("project_id")
|
|
|
+
|
|
|
+ project_threshold_configs = (
|
|
|
+ ProjectTransactionThreshold.objects.filter(
|
|
|
+ organization_id=org_id,
|
|
|
+ project_id__in=project_ids,
|
|
|
+ )
|
|
|
+ .order_by("project_id")
|
|
|
+ .values_list("project_id", "threshold", "metric")
|
|
|
+ )
|
|
|
+
|
|
|
+ transaction_threshold_configs = (
|
|
|
+ ProjectTransactionThresholdOverride.objects.filter(
|
|
|
+ organization_id=org_id,
|
|
|
+ project_id__in=project_ids,
|
|
|
+ )
|
|
|
+ .order_by("project_id")
|
|
|
+ .values_list("transaction", "project_id", "threshold", "metric")
|
|
|
+ )
|
|
|
+
|
|
|
+ num_project_thresholds = project_threshold_configs.count()
|
|
|
+ sentry_sdk.set_tag("project_threshold.count", num_project_thresholds)
|
|
|
+ sentry_sdk.set_tag(
|
|
|
+ "project_threshold.count.grouped",
|
|
|
+ format_grouped_length(num_project_thresholds, [10, 100, 250, 500]),
|
|
|
+ )
|
|
|
+
|
|
|
+ num_transaction_thresholds = transaction_threshold_configs.count()
|
|
|
+ sentry_sdk.set_tag("txn_threshold.count", num_transaction_thresholds)
|
|
|
+ sentry_sdk.set_tag(
|
|
|
+ "txn_threshold.count.grouped",
|
|
|
+ format_grouped_length(num_transaction_thresholds, [10, 100, 250, 500]),
|
|
|
+ )
|
|
|
+
|
|
|
+ if (
|
|
|
+ num_project_thresholds + num_transaction_thresholds
|
|
|
+ > MAX_QUERYABLE_TRANSACTION_THRESHOLDS
|
|
|
+ ):
|
|
|
+ raise InvalidSearchQuery(
|
|
|
+ f"Exceeded {MAX_QUERYABLE_TRANSACTION_THRESHOLDS} configured transaction thresholds limit, try with fewer Projects."
|
|
|
+ )
|
|
|
+
|
|
|
+ project_threshold_config_keys = []
|
|
|
+ project_threshold_config_values = []
|
|
|
+ for project_id, threshold, metric in project_threshold_configs:
|
|
|
+ project_threshold_config_keys.append(Function("toUInt64", [project_id]))
|
|
|
+ project_threshold_config_values.append((TRANSACTION_METRICS[metric], threshold))
|
|
|
+
|
|
|
+ project_threshold_override_config_keys = []
|
|
|
+ project_threshold_override_config_values = []
|
|
|
+ for transaction, project_id, threshold, metric in transaction_threshold_configs:
|
|
|
+ project_threshold_override_config_keys.append(
|
|
|
+ (Function("toUInt64", [project_id]), transaction)
|
|
|
+ )
|
|
|
+ project_threshold_override_config_values.append(
|
|
|
+ (TRANSACTION_METRICS[metric], threshold)
|
|
|
+ )
|
|
|
+
|
|
|
+ project_threshold_config_index: SelectType = Function(
|
|
|
+ "indexOf",
|
|
|
+ [
|
|
|
+ project_threshold_config_keys,
|
|
|
+ self.column("project_id"),
|
|
|
+ ],
|
|
|
+ PROJECT_THRESHOLD_CONFIG_INDEX_ALIAS,
|
|
|
+ )
|
|
|
+
|
|
|
+ project_threshold_override_config_index: SelectType = Function(
|
|
|
+ "indexOf",
|
|
|
+ [
|
|
|
+ project_threshold_override_config_keys,
|
|
|
+ (self.column("project_id"), self.column("transaction")),
|
|
|
+ ],
|
|
|
+ PROJECT_THRESHOLD_OVERRIDE_CONFIG_INDEX_ALIAS,
|
|
|
+ )
|
|
|
+
|
|
|
+ def _project_threshold_config(alias: Optional[str] = None) -> SelectType:
|
|
|
+ return (
|
|
|
+ Function(
|
|
|
+ "if",
|
|
|
+ [
|
|
|
+ Function(
|
|
|
+ "equals",
|
|
|
+ [
|
|
|
+ project_threshold_config_index,
|
|
|
+ 0,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ (DEFAULT_PROJECT_THRESHOLD_METRIC, DEFAULT_PROJECT_THRESHOLD),
|
|
|
+ Function(
|
|
|
+ "arrayElement",
|
|
|
+ [
|
|
|
+ project_threshold_config_values,
|
|
|
+ project_threshold_config_index,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ alias,
|
|
|
+ )
|
|
|
+ if project_threshold_configs
|
|
|
+ else Function(
|
|
|
+ "tuple",
|
|
|
+ [DEFAULT_PROJECT_THRESHOLD_METRIC, DEFAULT_PROJECT_THRESHOLD],
|
|
|
+ alias,
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ if transaction_threshold_configs:
|
|
|
+ return Function(
|
|
|
+ "if",
|
|
|
+ [
|
|
|
+ Function(
|
|
|
+ "equals",
|
|
|
+ [
|
|
|
+ project_threshold_override_config_index,
|
|
|
+ 0,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ _project_threshold_config(),
|
|
|
+ Function(
|
|
|
+ "arrayElement",
|
|
|
+ [
|
|
|
+ project_threshold_override_config_values,
|
|
|
+ project_threshold_override_config_index,
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ PROJECT_THRESHOLD_CONFIG_ALIAS,
|
|
|
+ )
|
|
|
+
|
|
|
+ return _project_threshold_config(PROJECT_THRESHOLD_CONFIG_ALIAS)
|
|
|
+
|
|
|
def _resolve_error_unhandled_alias(self, _: str) -> SelectType:
|
|
|
return Function("notHandled", [], ERROR_UNHANDLED_ALIAS)
|
|
|
|