Skip to content

Commit

Permalink
Merge branch 'master' into fix/backend-issues-annotations-structured
Browse files Browse the repository at this point in the history
  • Loading branch information
oioki authored Jul 22, 2024
2 parents 34637af + f7390a6 commit 0119690
Show file tree
Hide file tree
Showing 86 changed files with 1,105 additions and 687 deletions.
6 changes: 2 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
"papaparse": "^5.3.2",
"pegjs": "^0.10.0",
"pegjs-loader": "^0.5.6",
"platformicons": "^5.10.9",
"platformicons": "^6.0.1",
"po-catalog-loader": "2.0.0",
"prettier": "3.3.2",
"prismjs": "^1.29.0",
Expand Down Expand Up @@ -263,9 +263,7 @@
"last 3 iOS major versions",
"Firefox ESR"
],
"test": [
"current node"
]
"test": ["current node"]
},
"volta": {
"extends": ".volta.json"
Expand Down
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ module = [
"sentry.plugins.bases.notify",
"sentry.plugins.config",
"sentry.plugins.endpoints",
"sentry.plugins.providers.repository",
"sentry.receivers.releases",
"sentry.release_health.metrics_sessions_v2",
"sentry.replays.endpoints.project_replay_clicks_index",
Expand Down Expand Up @@ -530,7 +529,6 @@ module = [
"sentry.issues.constants",
"sentry.issues.endpoints",
"sentry.issues.endpoints.group_events",
"sentry.issues.endpoints.organization_activity",
"sentry.issues.endpoints.organization_group_search_views",
"sentry.issues.endpoints.organization_release_previous_commits",
"sentry.issues.endpoints.organization_searches",
Expand Down Expand Up @@ -626,7 +624,6 @@ module = [
"tests.sentry.issues",
"tests.sentry.issues.endpoints",
"tests.sentry.issues.endpoints.test_actionable_items",
"tests.sentry.issues.endpoints.test_organization_activity",
"tests.sentry.issues.endpoints.test_organization_group_search_views",
"tests.sentry.issues.endpoints.test_organization_searches",
"tests.sentry.issues.endpoints.test_project_stacktrace_link",
Expand Down
14 changes: 6 additions & 8 deletions src/sentry/api/endpoints/project_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,12 @@ def __init__(
"""
rule.data will supersede rule_data if passed in
"""
self._project_id: int = project_id
self._rule_data: dict[Any, Any] = rule.data if rule else rule_data
self._rule_id: int | None = rule_id
self._rule: Rule | None = rule
self._project_id = project_id
self._rule_data = rule.data if rule else rule_data or {}
self._rule_id = rule_id
self._rule = rule

self._keys_to_check: set[str] = self._get_keys_to_check()
self._keys_to_check = self._get_keys_to_check()

self._matcher_funcs_by_key: dict[str, Callable[[Rule, str], MatcherResult]] = {
self.ENVIRONMENT_KEY: self._environment_matcher,
Expand All @@ -99,9 +99,7 @@ def _get_keys_to_check(self) -> set[str]:
Some keys are ignored as they are not part of the logic.
Some keys are required to check, and are added on top.
"""
keys_to_check: set[str] = {
key for key in list(self._rule_data.keys()) if key not in self.EXCLUDED_FIELDS
}
keys_to_check = {key for key in self._rule_data if key not in self.EXCLUDED_FIELDS}
keys_to_check.update(self.SPECIAL_FIELDS)

return keys_to_check
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/api/paginator.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@ def _sort_combined_querysets(item):
sort_keys = []
sort_keys.append(self.get_item_key(item))
if len(self.model_key_map.get(type(item))) > 1:
# XXX: This doesn't do anything - it just uses a column name as the sort key. It should be pulling the
# value of the other keys out instead.
sort_keys.extend(iter(self.model_key_map.get(type(item))[1:]))
sort_keys.append(type(item).__name__)
return tuple(sort_keys)
Expand Down
19 changes: 17 additions & 2 deletions src/sentry/api/serializers/models/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import orjson

from sentry import features
from sentry.api.serializers import Serializer, register, serialize
from sentry.constants import ALL_ACCESS_PROJECTS
from sentry.models.dashboard import Dashboard
Expand Down Expand Up @@ -36,6 +37,21 @@ def get_attrs(self, item_list, user):
return result

def serialize(self, obj, attrs, user, **kwargs):
widget_type = (
DashboardWidgetTypes.get_type_name(obj.widget_type)
or DashboardWidgetTypes.TYPE_NAMES[0]
)

if (
features.has(
"organizations:performance-discover-dataset-selector",
obj.dashboard.organization,
actor=user,
)
and obj.discover_widget_split is not None
):
widget_type = DashboardWidgetTypes.get_type_name(obj.discover_widget_split)

return {
"id": str(obj.id),
"title": obj.title,
Expand All @@ -49,8 +65,7 @@ def serialize(self, obj, attrs, user, **kwargs):
"queries": attrs["queries"],
"limit": obj.limit,
# Default to discover type if null
"widgetType": DashboardWidgetTypes.get_type_name(obj.widget_type)
or DashboardWidgetTypes.TYPE_NAMES[0],
"widgetType": widget_type,
"layout": obj.detail.get("layout") if obj.detail else None,
}

Expand Down
5 changes: 4 additions & 1 deletion src/sentry/data_export/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from __future__ import annotations

import logging
from typing import Any

import orjson
from django.conf import settings
Expand Down Expand Up @@ -39,7 +42,7 @@ class ExportedData(Model):
date_finished = models.DateTimeField(null=True)
date_expired = models.DateTimeField(null=True, db_index=True)
query_type = BoundedPositiveIntegerField(choices=ExportQueryType.as_choices())
query_info = JSONField()
query_info: models.Field[dict[str, Any], dict[str, Any]] = JSONField()

@property
def status(self) -> ExportStatus:
Expand Down
6 changes: 4 additions & 2 deletions src/sentry/discover/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from enum import Enum
from typing import ClassVar
from typing import Any, ClassVar

from django.db import models, router, transaction
from django.db.models import Q, UniqueConstraint
Expand Down Expand Up @@ -89,7 +91,7 @@ class DiscoverSavedQuery(Model):
organization = FlexibleForeignKey("sentry.Organization")
created_by_id = HybridCloudForeignKey("sentry.User", null=True, on_delete="SET_NULL")
name = models.CharField(max_length=255)
query = JSONField()
query: models.Field[dict[str, Any], dict[str, Any]] = JSONField()
version = models.IntegerField(null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
Expand Down
4 changes: 4 additions & 0 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ def register_temporary_features(manager: FeatureManager):
manager.add("organizations:uptime-automatic-hostname-detection", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
# Enables automatic subscription creation in uptime
manager.add("organizations:uptime-automatic-subscription-creation", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
# Enabled returning uptime monitors from the rule api
manager.add("organizations:uptime-rule-api", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
# Enable creating issues via the issue platform
manager.add("organizations:uptime-create-issues", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
# Enables uptime related settings for projects and orgs
manager.add('organizations:uptime-settings', OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE)
manager.add("organizations:use-metrics-layer", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
Expand Down
37 changes: 25 additions & 12 deletions src/sentry/incidents/endpoints/organization_alert_rule_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import UTC, datetime

from django.conf import settings
from django.db.models import DateTimeField, IntegerField, OuterRef, Q, Subquery, Value
from django.db.models import Case, DateTimeField, IntegerField, OuterRef, Q, Subquery, Value, When
from django.db.models.functions import Coalesce
from drf_spectacular.utils import extend_schema, extend_schema_serializer
from rest_framework import serializers, status
Expand Down Expand Up @@ -35,9 +35,10 @@
AlertRuleSerializerResponse,
CombinedRuleSerializer,
)
from sentry.incidents.endpoints.utils import parse_team_params
from sentry.incidents.logic import get_slack_actions_with_async_lookups
from sentry.incidents.models.alert_rule import AlertRule
from sentry.incidents.models.incident import Incident
from sentry.incidents.models.incident import Incident, IncidentStatus
from sentry.incidents.serializers import AlertRuleSerializer as DrfAlertRuleSerializer
from sentry.incidents.utils.sentry_apps import trigger_sentry_app_action_creators_for_incidents
from sentry.integrations.slack.utils import RedisRuleStatus
Expand All @@ -49,10 +50,9 @@
from sentry.signals import alert_rule_created
from sentry.snuba.dataset import Dataset
from sentry.tasks.integrations.slack import find_channel_id_for_alert_rule
from sentry.uptime.models import ProjectUptimeSubscription, UptimeStatus
from sentry.utils.cursors import Cursor, StringCursor

from .utils import parse_team_params


class AlertRuleIndexMixin(Endpoint):
def fetch_metric_alert(self, request, organization, project=None):
Expand Down Expand Up @@ -152,7 +152,7 @@ class OrganizationCombinedRuleIndexEndpoint(OrganizationEndpoint):

def get(self, request: Request, organization) -> Response:
"""
Fetches (metric) alert rules and legacy (issue alert) rules for an organization
Fetches metric, issue and uptime alert rules for an organization
"""
project_ids = self.get_requested_project_ids_unchecked(request) or None
if project_ids == {-1}: # All projects for org:
Expand Down Expand Up @@ -196,6 +196,11 @@ def get(self, request: Request, organization) -> Response:
project__in=projects,
)

uptime_rules = ProjectUptimeSubscription.objects.filter(project__in=projects)

if not features.has("organizations:uptime-rule-api", organization):
uptime_rules = ProjectUptimeSubscription.objects.none()

if not features.has("organizations:performance-view", organization):
# Filter to only error alert rules
alert_rules = alert_rules.filter(snuba_query__dataset=Dataset.Events.value)
Expand All @@ -208,8 +213,9 @@ def get(self, request: Request, organization) -> Response:

name = request.GET.get("name", None)
if name:
alert_rules = alert_rules.filter(Q(name__icontains=name))
issue_rules = issue_rules.filter(Q(label__icontains=name))
alert_rules = alert_rules.filter(name__icontains=name)
issue_rules = issue_rules.filter(label__icontains=name)
uptime_rules = uptime_rules.filter(name__icontains=name)

if teams_query is not None:
team_ids = teams_query.values_list("id", flat=True)
Expand All @@ -220,6 +226,7 @@ def get(self, request: Request, organization) -> Response:
team_rule_condition = team_rule_condition | Q(owner_team_id__isnull=True)
alert_rules = alert_rules.filter(team_alert_condition)
issue_rules = issue_rules.filter(team_rule_condition)
uptime_rules = uptime_rules.filter(team_rule_condition)

expand = request.GET.getlist("expand", [])
if "latestIncident" in expand:
Expand Down Expand Up @@ -255,6 +262,14 @@ def get(self, request: Request, organization) -> Response:
issue_rules = issue_rules.annotate(
incident_status=Value(-2, output_field=IntegerField())
)
uptime_rules = uptime_rules.annotate(
incident_status=Case(
# If an uptime monitor is failing we want to treat it the same as if an alert is failing, so sort
# by the critical status
When(uptime_status=UptimeStatus.FAILED, then=IncidentStatus.CRITICAL.value),
default=-2,
)
)

if "date_triggered" in sort_key:
far_past_date = Value(datetime.min.replace(tzinfo=UTC), output_field=DateTimeField())
Expand All @@ -269,22 +284,20 @@ def get(self, request: Request, organization) -> Response:
),
)
issue_rules = issue_rules.annotate(date_triggered=far_past_date)
alert_rules_count = alert_rules.count()
issue_rules_count = issue_rules.count()
uptime_rules = uptime_rules.annotate(date_triggered=far_past_date)
alert_rule_intermediary = CombinedQuerysetIntermediary(alert_rules, sort_key)
rule_intermediary = CombinedQuerysetIntermediary(issue_rules, rule_sort_key)
uptime_intermediary = CombinedQuerysetIntermediary(uptime_rules, rule_sort_key)
response = self.paginate(
request,
paginator_cls=CombinedQuerysetPaginator,
on_results=lambda x: serialize(x, request.user, CombinedRuleSerializer(expand=expand)),
default_per_page=25,
intermediaries=[alert_rule_intermediary, rule_intermediary],
intermediaries=[alert_rule_intermediary, rule_intermediary, uptime_intermediary],
desc=not is_asc,
cursor_cls=StringCursor if case_insensitive else Cursor,
case_insensitive=case_insensitive,
)
response["X-Sentry-Issue-Rule-Hits"] = issue_rules_count
response[ALERT_RULES_COUNT_HEADER] = alert_rules_count
response[MAX_QUERY_SUBSCRIPTIONS_HEADER] = settings.MAX_QUERY_SUBSCRIPTIONS_PER_ORG
return response

Expand Down
26 changes: 19 additions & 7 deletions src/sentry/incidents/endpoints/serializers/alert_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from sentry.sentry_apps.services.app import app_service
from sentry.sentry_apps.services.app.model import RpcSentryAppComponentContext
from sentry.snuba.models import SnubaQueryEventType
from sentry.uptime.models import ProjectUptimeSubscription
from sentry.users.services.user import RpcUser
from sentry.users.services.user.service import user_service

Expand Down Expand Up @@ -395,6 +396,14 @@ def get_attrs(
serialized_rule["id"]: serialized_rule for serialized_rule in serialized_issue_rules
}

serialized_uptime_monitors = serialize(
[x for x in item_list if isinstance(x, ProjectUptimeSubscription)],
user=user,
)
serialized_uptime_monitor_map_by_id = {
item["id"]: item for item in serialized_uptime_monitors
}

for item in item_list:
item_id = str(item.id)
if item_id in serialized_alert_rule_map_by_id:
Expand All @@ -418,6 +427,9 @@ def get_attrs(
elif item_id in serialized_issue_rule_map_by_id:
# This is an issue alert rule
results[item] = serialized_issue_rule_map_by_id[item_id]
elif item_id in serialized_uptime_monitor_map_by_id:
# This is an uptime monitor
results[item] = serialized_uptime_monitor_map_by_id[item_id]
else:
logger.error(
"Alert Rule found but dropped during serialization",
Expand All @@ -432,18 +444,18 @@ def get_attrs(

def serialize(
self,
obj: Rule | AlertRule,
obj: Rule | AlertRule | ProjectUptimeSubscription,
attrs: Mapping[Any, Any],
user: User | RpcUser,
**kwargs: Any,
) -> MutableMapping[Any, Any]:
updated_attrs = {**attrs}
if isinstance(obj, AlertRule):
alert_rule_attrs: MutableMapping[Any, Any] = {**attrs}
alert_rule_attrs["type"] = "alert_rule"
return alert_rule_attrs
updated_attrs["type"] = "alert_rule"
elif isinstance(obj, Rule):
rule_attrs: MutableMapping[Any, Any] = {**attrs}
rule_attrs["type"] = "rule"
return rule_attrs
updated_attrs["type"] = "rule"
elif isinstance(obj, ProjectUptimeSubscription):
updated_attrs["type"] = "uptime"
else:
raise AssertionError(f"Invalid rule to serialize: {type(obj)}")
return updated_attrs
2 changes: 1 addition & 1 deletion src/sentry/models/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class Activity(Model):
# if the user is not set, it's assumed to be the system
user_id = HybridCloudForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete="SET_NULL")
datetime = models.DateTimeField(default=timezone.now)
data: models.Field[dict[str, Any], dict[str, Any]] = GzippedDictField(null=True)
data: models.Field[dict[str, Any] | None, dict[str, Any]] = GzippedDictField(null=True)

objects: ClassVar[ActivityManager] = ActivityManager()

Expand Down
4 changes: 3 additions & 1 deletion src/sentry/models/authidentity.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from collections.abc import Collection
from typing import Any

Expand Down Expand Up @@ -25,7 +27,7 @@ class AuthIdentity(ReplicatedControlModel):
user = FlexibleForeignKey(settings.AUTH_USER_MODEL)
auth_provider = FlexibleForeignKey("sentry.AuthProvider")
ident = models.CharField(max_length=128)
data = JSONField()
data: models.Field[dict[str, Any], dict[str, Any]] = JSONField()
last_verified = models.DateTimeField(default=timezone.now)
last_synced = models.DateTimeField(default=timezone.now)
date_added = models.DateTimeField(default=timezone.now)
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/authprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class AuthProvider(ReplicatedControlModel):

organization_id = HybridCloudForeignKey("sentry.Organization", on_delete="cascade", unique=True)
provider = models.CharField(max_length=128)
config = JSONField()
config: models.Field[dict[str, Any], dict[str, Any]] = JSONField()

date_added = models.DateTimeField(default=timezone.now)
sync_time = BoundedPositiveIntegerField(null=True)
Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Dashboard(Model):
visits = BoundedBigIntegerField(null=True, default=1)
last_visited = models.DateTimeField(null=True, default=timezone.now)
projects = models.ManyToManyField("sentry.Project", through=DashboardProject)
filters = JSONField(null=True)
filters: models.Field[dict[str, Any] | None, dict[str, Any] | None] = JSONField(null=True)

MAX_WIDGETS = 30

Expand Down
2 changes: 1 addition & 1 deletion src/sentry/models/debugfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ class ProjectDebugFile(Model):
project_id = BoundedBigIntegerField(null=True)
debug_id = models.CharField(max_length=64, db_column="uuid")
code_id = models.CharField(max_length=64, null=True)
data = JSONField(null=True)
data: models.Field[dict[str, Any] | None, dict[str, Any] | None] = JSONField(null=True)
date_accessed = models.DateTimeField(default=timezone.now)

objects: ClassVar[ProjectDebugFileManager] = ProjectDebugFileManager()
Expand Down
Loading

0 comments on commit 0119690

Please sign in to comment.