|
@@ -1,5 +1,6 @@
|
|
|
-import sentry_sdk
|
|
|
-from django.db import router, transaction
|
|
|
+from collections.abc import Sequence
|
|
|
+from typing import Any
|
|
|
+
|
|
|
from rest_framework.request import Request
|
|
|
from rest_framework.response import Response
|
|
|
|
|
@@ -10,16 +11,15 @@ from sentry.api.base import region_silo_endpoint
|
|
|
from sentry.api.bases import ProjectEndpoint
|
|
|
from sentry.api.paginator import OffsetPaginator
|
|
|
from sentry.api.serializers import serialize
|
|
|
-from sentry.api.serializers.models.metrics_extraction_rules import (
|
|
|
- SpanAttributeExtractionRuleConfigSerializer,
|
|
|
-)
|
|
|
+from sentry.api.serializers.models.metrics_extraction_rules import MetricsExtractionRuleSerializer
|
|
|
from sentry.models.project import Project
|
|
|
-from sentry.sentry_metrics.configuration import HARD_CODED_UNITS
|
|
|
-from sentry.sentry_metrics.models import (
|
|
|
- SpanAttributeExtractionRuleCondition,
|
|
|
- SpanAttributeExtractionRuleConfig,
|
|
|
+from sentry.sentry_metrics.extraction_rules import (
|
|
|
+ MetricsExtractionRule,
|
|
|
+ create_metrics_extraction_rules,
|
|
|
+ delete_metrics_extraction_rules,
|
|
|
+ get_metrics_extraction_rules,
|
|
|
+ update_metrics_extraction_rules,
|
|
|
)
|
|
|
-from sentry.tasks.relay import schedule_invalidate_project_config
|
|
|
|
|
|
|
|
|
@region_silo_endpoint
|
|
@@ -42,22 +42,15 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
|
|
|
if not self.has_feature(project.organization, request):
|
|
|
return Response(status=404)
|
|
|
|
|
|
- config_update = request.data.get("metricsExtractionRules") or []
|
|
|
- if len(config_update) == 0:
|
|
|
+ rules_update = request.data.get("metricsExtractionRules") or []
|
|
|
+ if len(rules_update) == 0:
|
|
|
return Response(status=204)
|
|
|
|
|
|
try:
|
|
|
- with transaction.atomic(router.db_for_write(SpanAttributeExtractionRuleConfig)):
|
|
|
- for obj in config_update:
|
|
|
- SpanAttributeExtractionRuleConfig.objects.filter(
|
|
|
- project=project, span_attribute=obj["spanAttribute"]
|
|
|
- ).delete()
|
|
|
- schedule_invalidate_project_config(
|
|
|
- project_id=project.id, trigger="span_attribute_extraction_configs"
|
|
|
- )
|
|
|
+ state_update = self._generate_deleted_rule_objects(rules_update)
|
|
|
+ delete_metrics_extraction_rules(project, state_update)
|
|
|
except Exception as e:
|
|
|
- sentry_sdk.capture_exception()
|
|
|
- return Response(status=400, data={"detail": str(e)})
|
|
|
+ return Response(status=500, data={"detail": str(e)})
|
|
|
|
|
|
return Response(status=204)
|
|
|
|
|
@@ -67,18 +60,16 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
|
|
|
return Response(status=404)
|
|
|
|
|
|
try:
|
|
|
- configs = SpanAttributeExtractionRuleConfig.objects.filter(project=project)
|
|
|
-
|
|
|
+ extraction_rules = get_metrics_extraction_rules(project)
|
|
|
except Exception as e:
|
|
|
return Response(status=500, data={"detail": str(e)})
|
|
|
|
|
|
- # TODO(metrics): do real pagination using the database
|
|
|
return self.paginate(
|
|
|
request,
|
|
|
- queryset=list(configs),
|
|
|
+ queryset=extraction_rules,
|
|
|
paginator_cls=OffsetPaginator,
|
|
|
on_results=lambda x: serialize(
|
|
|
- x, user=request.user, serializer=SpanAttributeExtractionRuleConfigSerializer()
|
|
|
+ x, user=request.user, serializer=MetricsExtractionRuleSerializer()
|
|
|
),
|
|
|
default_per_page=1000,
|
|
|
max_per_page=1000,
|
|
@@ -90,88 +81,62 @@ class ProjectMetricsExtractionRulesEndpoint(ProjectEndpoint):
|
|
|
if not self.has_feature(project.organization, request):
|
|
|
return Response(status=404)
|
|
|
|
|
|
- config_update = request.data.get("metricsExtractionRules")
|
|
|
+ rules_update = request.data.get("metricsExtractionRules")
|
|
|
|
|
|
- if not config_update:
|
|
|
+ if not rules_update or len(rules_update) == 0:
|
|
|
return Response(
|
|
|
status=400,
|
|
|
data={"detail": "Please specify the metric extraction rule to be created."},
|
|
|
)
|
|
|
+
|
|
|
try:
|
|
|
- configs = []
|
|
|
- with transaction.atomic(router.db_for_write(SpanAttributeExtractionRuleConfig)):
|
|
|
- for obj in config_update:
|
|
|
- configs.append(
|
|
|
- SpanAttributeExtractionRuleConfig.from_dict(obj, request.user.id, project)
|
|
|
- )
|
|
|
- schedule_invalidate_project_config(
|
|
|
- project_id=project.id, trigger="span_attribute_extraction_configs"
|
|
|
- )
|
|
|
-
|
|
|
- except Exception:
|
|
|
- sentry_sdk.capture_exception()
|
|
|
- return Response(
|
|
|
- status=400,
|
|
|
+ state_update = self._generate_updated_rule_objects(rules_update)
|
|
|
+ persisted_rules = create_metrics_extraction_rules(project, state_update)
|
|
|
+ updated_rules = serialize(
|
|
|
+ persisted_rules, request.user, MetricsExtractionRuleSerializer()
|
|
|
)
|
|
|
+ except Exception as e:
|
|
|
+ return Response(status=400, data={"detail": str(e)})
|
|
|
|
|
|
- persisted_config = serialize(
|
|
|
- configs,
|
|
|
- request.user,
|
|
|
- SpanAttributeExtractionRuleConfigSerializer(),
|
|
|
- )
|
|
|
- return Response(data=persisted_config, status=200)
|
|
|
+ return Response(status=200, data=updated_rules)
|
|
|
|
|
|
def put(self, request: Request, project: Project) -> Response:
|
|
|
"""PUT to modify an existing extraction rule."""
|
|
|
if not self.has_feature(project.organization, request):
|
|
|
return Response(status=404)
|
|
|
|
|
|
- config_update = request.data.get("metricsExtractionRules")
|
|
|
- if not config_update:
|
|
|
+ rules_update = request.data.get("metricsExtractionRules")
|
|
|
+
|
|
|
+ if not rules_update or len(rules_update) == 0:
|
|
|
return Response(status=200)
|
|
|
|
|
|
try:
|
|
|
- with transaction.atomic(router.db_for_write(SpanAttributeExtractionRuleConfig)):
|
|
|
- configs = []
|
|
|
- for obj in config_update:
|
|
|
- config = SpanAttributeExtractionRuleConfig.objects.get(
|
|
|
- project=project, span_attribute=obj["spanAttribute"]
|
|
|
- )
|
|
|
- config.aggregates = obj["aggregates"]
|
|
|
- config.unit = HARD_CODED_UNITS.get(obj["spanAttribute"], obj["unit"])
|
|
|
- config.tags = obj["tags"]
|
|
|
- config.save()
|
|
|
- config.refresh_from_db()
|
|
|
- configs.append(config)
|
|
|
-
|
|
|
- # delete conditions not present in update
|
|
|
- included_conditions = [x["id"] for x in obj["conditions"]]
|
|
|
- SpanAttributeExtractionRuleCondition.objects.filter(config=config).exclude(
|
|
|
- id__in=included_conditions
|
|
|
- ).delete()
|
|
|
-
|
|
|
- for condition in obj["conditions"]:
|
|
|
- condition_id = condition["id"] if "id" in condition else None
|
|
|
- SpanAttributeExtractionRuleCondition.objects.update_or_create(
|
|
|
- id=condition_id,
|
|
|
- config=config,
|
|
|
- defaults={
|
|
|
- "value": condition["value"],
|
|
|
- "created_by_id": request.user.id,
|
|
|
- },
|
|
|
- )
|
|
|
- schedule_invalidate_project_config(
|
|
|
- project_id=project.id, trigger="span_attribute_extraction_configs"
|
|
|
- )
|
|
|
-
|
|
|
- except Exception:
|
|
|
- sentry_sdk.capture_exception()
|
|
|
- return Response(status=400)
|
|
|
-
|
|
|
- persisted_config = serialize(
|
|
|
- configs,
|
|
|
- request.user,
|
|
|
- SpanAttributeExtractionRuleConfigSerializer(),
|
|
|
- )
|
|
|
+ state_update = self._generate_updated_rule_objects(rules_update)
|
|
|
+ persisted_rules = update_metrics_extraction_rules(project, state_update)
|
|
|
+ updated_rules = serialize(
|
|
|
+ persisted_rules, request.user, MetricsExtractionRuleSerializer()
|
|
|
+ )
|
|
|
+ except Exception as e:
|
|
|
+ return Response(status=400, data={"detail": str(e)})
|
|
|
+
|
|
|
+ return Response(status=200, data=updated_rules)
|
|
|
+
|
|
|
+ def _generate_updated_rule_objects(
|
|
|
+ self, updated_rules: list[dict[str, Any]]
|
|
|
+ ) -> dict[str, MetricsExtractionRule]:
|
|
|
+ state_update = {}
|
|
|
+ for updated_rule in updated_rules:
|
|
|
+ rule = MetricsExtractionRule.from_dict(updated_rule)
|
|
|
+ mri = rule.generate_mri()
|
|
|
+ state_update[mri] = rule
|
|
|
+
|
|
|
+ return state_update
|
|
|
+
|
|
|
+ def _generate_deleted_rule_objects(
|
|
|
+ self, updated_rules: list[dict[str, Any]]
|
|
|
+ ) -> Sequence[MetricsExtractionRule]:
|
|
|
+ state_update = []
|
|
|
+ for updated_rule in updated_rules:
|
|
|
+ state_update.append(MetricsExtractionRule.from_dict(updated_rule))
|
|
|
|
|
|
- return Response(data=persisted_config, status=200)
|
|
|
+ return state_update
|