Skip to content

Commit

Permalink
Merge branch 'master' into evanpurkhiser/feat-uptime-call-quotas-disa…
Browse files Browse the repository at this point in the history
…ble-seat-when-deleting-monitors
  • Loading branch information
JoshFerge authored Jan 30, 2025
2 parents 2437d8c + 4a02836 commit 8c6d1e2
Show file tree
Hide file tree
Showing 135 changed files with 1,322 additions and 793 deletions.
5 changes: 1 addition & 4 deletions babel.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@ const config: TransformOptions = {
overrides: [],
plugins: ['@emotion/babel-plugin', '@babel/plugin-transform-runtime'],
env: {
production: {
plugins: [['babel-plugin-add-react-displayname']],
},
production: {},
development: {
plugins: [
'@emotion/babel-plugin',
'@babel/plugin-transform-react-jsx-source',
...(process.env.SENTRY_UI_HOT_RELOAD ? ['react-refresh/babel'] : []),
],
},
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
"dependencies": {
"@acemarke/react-prod-sourcemaps": "^0.2.1",
"@babel/core": "~7.25.9",
"@babel/plugin-transform-react-jsx": "^7.25.9",
"@babel/plugin-transform-react-jsx-source": "^7.25.9",
"@babel/plugin-transform-runtime": "~7.25.9",
"@babel/preset-env": "~7.25.9",
"@babel/preset-react": "^7.25.9",
Expand Down Expand Up @@ -94,7 +92,6 @@
"@types/webpack-env": "^1.18.5",
"ansi-to-react": "^6.1.6",
"babel-loader": "^9.2.1",
"babel-plugin-add-react-displayname": "^0.0.5",
"browserslist": "^4.22.2",
"buffer": "^6.0.3",
"cbor-web": "^8.1.0",
Expand Down Expand Up @@ -147,6 +144,7 @@
"query-string": "7.0.1",
"react": "18.2.0",
"react-date-range": "^1.4.0",
"react-docgen-typescript": "^2.2.2",
"react-dom": "18.2.0",
"react-grid-layout": "^1.3.4",
"react-lazyload": "^3.2.1",
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,6 @@ module = [
"sentry.snuba.errors",
"sentry.snuba.issue_platform",
"sentry.snuba.metrics.datasource",
"sentry.snuba.metrics.fields.base",
"sentry.snuba.metrics.query_builder",
"sentry.snuba.spans_metrics",
"sentry.tagstore.snuba.backend",
Expand Down
9 changes: 8 additions & 1 deletion src/sentry/api/endpoints/group_integration_details.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
from sentry.models.activity import Activity
from sentry.models.group import Group
from sentry.models.grouplink import GroupLink
from sentry.shared_integrations.exceptions import IntegrationError, IntegrationFormError
from sentry.shared_integrations.exceptions import (
IntegrationError,
IntegrationFormError,
IntegrationInstallationConfigurationError,
)
from sentry.signals import integration_issue_created, integration_issue_linked
from sentry.types.activity import ActivityType
from sentry.users.models.user import User
Expand Down Expand Up @@ -280,6 +284,9 @@ def post(self, request: Request, group, integration_id) -> Response:

try:
data = installation.create_issue(request.data)
except IntegrationInstallationConfigurationError as exc:
lifecycle.record_halt(exc)
return Response({"non_field_errors": [str(exc)]}, status=400)
except IntegrationFormError as exc:
lifecycle.record_halt(exc)
return Response(exc.field_errors, status=400)
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@
GroupHashesEndpoint,
GroupNotesDetailsEndpoint,
GroupNotesEndpoint,
GroupOpenPeriodsEndpoint,
GroupSimilarIssuesEmbeddingsEndpoint,
GroupSimilarIssuesEndpoint,
GroupTombstoneDetailsEndpoint,
Expand Down Expand Up @@ -697,6 +698,11 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
GroupEventsEndpoint.as_view(),
name=f"{name_prefix}-group-events",
),
re_path(
r"^(?P<issue_id>[^\/]+)/open-periods/$",
GroupOpenPeriodsEndpoint.as_view(),
name=f"{name_prefix}-group-open-periods",
),
re_path(
r"^(?P<issue_id>[^\/]+)/events/(?P<event_id>(?:latest|oldest|recommended|\d+|[A-Fa-f0-9-]{32,36}))/$",
GroupEventDetailsEndpoint.as_view(),
Expand Down
6 changes: 3 additions & 3 deletions src/sentry/backup/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
# each entry in this dict, please leave a TODO comment pointed to a github issue for removing
# the shim, noting in the comment which self-hosted release will trigger the removal.
DELETED_FIELDS: dict[str, set[str]] = {
# These fields were removed in 2023 but we need them to support exports from older sentry versions.
"sentry.team": {"actor"},
# These fields were removed in 2024 but we need them to support exports from older sentry versions.
"sentry.team": {"actor", "org_role"},
"sentry.rule": {"owner"},
"sentry.alertrule": {"owner"},
"sentry.alertrule": {"owner", "include_all_projects"},
"sentry.grouphistory": {"actor"},
}

Expand Down
20 changes: 18 additions & 2 deletions src/sentry/integrations/bitbucket/issues.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
from __future__ import annotations

from collections.abc import Sequence
from typing import Any
from typing import Any, NoReturn

from django.urls import reverse

from sentry.integrations.source_code_management.issues import SourceCodeIssueIntegration
from sentry.models.group import Group
from sentry.organizations.services.organization.service import organization_service
from sentry.shared_integrations.exceptions import ApiError, IntegrationFormError
from sentry.shared_integrations.exceptions import (
ApiError,
IntegrationFormError,
IntegrationInstallationConfigurationError,
)
from sentry.silo.base import all_silo_function
from sentry.users.models.identity import Identity
from sentry.users.models.user import User

# Generated based on the response from the Bitbucket API
# Example: {"type": "error", "error": {"message": "Repository has no issue tracker."}}
BITBUCKET_HALT_ERROR_CODES = ["Repository has no issue tracker."]


ISSUE_TYPES = (
("bug", "Bug"),
("enhancement", "Enhancement"),
Expand Down Expand Up @@ -129,6 +139,12 @@ def get_link_issue_config(self, group: Group, **kwargs) -> list[dict[str, Any]]:
},
]

def raise_error(self, exc: Exception, identity: Identity | None = None) -> NoReturn:
if isinstance(exc, ApiError) and exc.json:
if (message := exc.json.get("error", {}).get("message")) in BITBUCKET_HALT_ERROR_CODES:
raise IntegrationInstallationConfigurationError(message)
super().raise_error(exc, identity)

def create_issue(self, data, **kwargs):
client = self.get_client()
if not data.get("repo"):
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/issues/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .group_hashes import GroupHashesEndpoint
from .group_notes import GroupNotesEndpoint
from .group_notes_details import GroupNotesDetailsEndpoint
from .group_open_periods import GroupOpenPeriodsEndpoint
from .group_similar_issues import GroupSimilarIssuesEndpoint
from .group_similar_issues_embeddings import GroupSimilarIssuesEmbeddingsEndpoint
from .group_tombstone import GroupTombstoneEndpoint
Expand Down Expand Up @@ -40,6 +41,7 @@
"GroupHashesEndpoint",
"GroupNotesDetailsEndpoint",
"GroupNotesEndpoint",
"GroupOpenPeriodsEndpoint",
"GroupSimilarIssuesEmbeddingsEndpoint",
"GroupSimilarIssuesEndpoint",
"GroupTombstoneDetailsEndpoint",
Expand Down
65 changes: 4 additions & 61 deletions src/sentry/issues/endpoints/group_details.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import functools
import logging
from collections.abc import Sequence
from datetime import datetime, timedelta
from datetime import timedelta

from django.utils import timezone
from rest_framework.exceptions import ValidationError
Expand All @@ -24,16 +24,14 @@
from sentry.api.serializers.models.group_stream import get_actions, get_available_issue_plugins
from sentry.api.serializers.models.plugin import PluginSerializer
from sentry.api.serializers.models.team import TeamSerializer
from sentry.incidents.models.alert_rule import AlertRule
from sentry.incidents.utils.metric_issue_poc import OpenPeriod
from sentry.integrations.api.serializers.models.external_issue import ExternalIssueSerializer
from sentry.integrations.models.external_issue import ExternalIssue
from sentry.issues.constants import get_issue_tsdb_group_model
from sentry.issues.escalating_group_forecast import EscalatingGroupForecast
from sentry.issues.grouptype import GroupCategory
from sentry.models.activity import Activity
from sentry.models.eventattachment import EventAttachment
from sentry.models.group import Group
from sentry.models.group import Group, get_open_periods_for_group
from sentry.models.groupinbox import get_inbox_details
from sentry.models.grouplink import GroupLink
from sentry.models.groupowner import get_owner_details
Expand All @@ -47,12 +45,12 @@
)
from sentry.sentry_apps.models.platformexternalissue import PlatformExternalIssue
from sentry.tasks.post_process import fetch_buffered_group_stats
from sentry.types.activity import ActivityType
from sentry.types.ratelimit import RateLimit, RateLimitCategory
from sentry.users.services.user.service import user_service
from sentry.utils import metrics

delete_logger = logging.getLogger("sentry.deletions.api")
OPEN_PERIOD_LIMIT = 50


def get_group_global_count(group: Group) -> str:
Expand Down Expand Up @@ -90,61 +88,6 @@ def _get_seen_by(self, request: Request, group: Group):
seen_by = list(GroupSeen.objects.filter(group=group).order_by("-last_seen"))
return [seen for seen in serialize(seen_by, request.user) if seen is not None]

def _get_open_periods_for_group(self, group: Group) -> list[OpenPeriod]:
if not features.has("organizations:issue-open-periods", group.organization):
return []

activities = Activity.objects.filter(
group=group,
type__in=[ActivityType.SET_UNRESOLVED.value, ActivityType.SET_RESOLVED.value],
datetime__gte=timezone.now() - timedelta(days=90),
).order_by("datetime")

open_periods = []
start: datetime | None = group.first_seen

for activity in activities:
if activity.type == ActivityType.SET_RESOLVED.value and start:
open_periods.append(
OpenPeriod(
start=start,
end=activity.datetime,
duration=activity.datetime - start,
is_open=False,
last_checked=activity.datetime,
)
)
start = None
elif activity.type == ActivityType.SET_UNRESOLVED.value and not start:
start = activity.datetime

if start:
event = group.get_latest_event()
last_checked = group.last_seen
if event:
alert_rule_id = (
event.data.get("contexts", {}).get("metric_alert", {}).get("alert_rule_id")
)
if alert_rule_id:
try:
alert_rule = AlertRule.objects.get(id=alert_rule_id)
now = timezone.now()
last_checked = now - timedelta(seconds=alert_rule.snuba_query.time_window)
except AlertRule.DoesNotExist:
pass

open_periods.append(
OpenPeriod(
start=start,
end=None,
duration=None,
is_open=True,
last_checked=last_checked,
)
)

return open_periods[::-1]

def _get_context_plugins(self, request: Request, group: Group):
project = group.project
return serialize(
Expand Down Expand Up @@ -222,7 +165,7 @@ def get(self, request: Request, group: Group) -> Response:

# TODO: these probably should be another endpoint
activity = Activity.objects.get_activities_for_group(group, 100)
open_periods = self._get_open_periods_for_group(group)
open_periods = get_open_periods_for_group(group, limit=OPEN_PERIOD_LIMIT)
seen_by = self._get_seen_by(request, group)

if "release" not in collapse:
Expand Down
45 changes: 45 additions & 0 deletions src/sentry/issues/endpoints/group_open_periods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from rest_framework.exceptions import ParseError
from rest_framework.request import Request
from rest_framework.response import Response

from sentry.api.api_owners import ApiOwner
from sentry.api.api_publish_status import ApiPublishStatus
from sentry.api.base import region_silo_endpoint
from sentry.api.bases import GroupEndpoint
from sentry.api.paginator import GenericOffsetPaginator
from sentry.api.utils import get_date_range_from_params
from sentry.exceptions import InvalidParams
from sentry.models.group import get_open_periods_for_group

if TYPE_CHECKING:
from sentry.models.group import Group


@region_silo_endpoint
class GroupOpenPeriodsEndpoint(GroupEndpoint):
publish_status = {
"GET": ApiPublishStatus.PRIVATE,
}
owner = ApiOwner.ISSUES

def get(self, request: Request, group: Group) -> Response:
"""
Return a list of open periods for an issue
"""
try:
start, end = get_date_range_from_params(request.GET, optional=True)
except InvalidParams:
raise ParseError(detail="Invalid date range")

def data_fn(offset: int, limit: int) -> Any:
return get_open_periods_for_group(group, start, end, offset, limit)

return self.paginate(
request=request,
on_results=lambda results: [result.to_dict() for result in results],
paginator=GenericOffsetPaginator(data_fn=data_fn),
)
Loading

0 comments on commit 8c6d1e2

Please sign in to comment.