Skip to content

Commit

Permalink
Merge branch 'master' into abdk/explore-table-styles
Browse files Browse the repository at this point in the history
  • Loading branch information
Abdkhan14 authored Oct 7, 2024
2 parents 8e7ad80 + bcf7cbe commit deefe11
Show file tree
Hide file tree
Showing 56 changed files with 2,164 additions and 1,338 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ module = [
"sentry.tagstore.types",
"sentry.tasks.auth",
"sentry.tasks.base",
"sentry.tasks.process_buffer",
"sentry.testutils.cases",
"sentry.testutils.fixtures",
"sentry.testutils.helpers.notifications",
Expand Down
22 changes: 19 additions & 3 deletions src/sentry/api/endpoints/organization_dashboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,25 @@ def has_object_permission(self, request: Request, view, obj):
return super().has_object_permission(request, view, obj)

if isinstance(obj, Dashboard):
for project in obj.projects.all():
if not request.access.has_project_access(project):
return False
# 1. Dashboard contains certain projects
if obj.projects.exists():
return request.access.has_projects_access(obj.projects.all())

# 2. Dashboard covers all projects or all my projects

# allow when Open Membership
if obj.organization.flags.allow_joinleave:
return True

# allow for Managers and Owners
if request.access.has_scope("org:write"):
return True

# allow for creator
if request.user.id == obj.created_by_id:
return True

return False

return True

Expand Down
28 changes: 27 additions & 1 deletion src/sentry/api/endpoints/organization_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,36 @@ class DiscoverDatasetSplitException(Exception):
Referrer.API_DASHBOARDS_BIGNUMBERWIDGET.value,
Referrer.API_DISCOVER_TRANSACTIONS_LIST.value,
Referrer.API_DISCOVER_QUERY_TABLE.value,
Referrer.API_PERFORMANCE_BROWSER_RESOURCE_MAIN_TABLE.value,
Referrer.API_PERFORMANCE_BROWSER_RESOURCES_PAGE_SELECTOR.value,
Referrer.API_PERFORMANCE_BROWSER_WEB_VITALS_PROJECT.value,
Referrer.API_PERFORMANCE_BROWSER_WEB_VITALS_PROJECT_SCORES.value,
Referrer.API_PERFORMANCE_BROWSER_WEB_VITALS_TRANSACTION.value,
Referrer.API_PERFORMANCE_BROWSER_WEB_VITALS_TRANSACTIONS_SCORES.value,
Referrer.API_PERFORMANCE_CACHE_LANDING_CACHE_TRANSACTION_LIST.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_APDEX_AREA.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_HIGHEST_CACHE_MISS_RATE_TRANSACTIONS.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_MOST_FROZEN_FRAMES.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_MOST_SLOW_FRAMES.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_MOST_TIME_CONSUMING_DOMAINS.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_MOST_TIME_CONSUMING_RESOURCES.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_MOST_TIME_SPENT_DB_QUERIES.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_SLOW_DB_OPS.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_SLOW_HTTP_OPS.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_SLOW_RESOURCE_OPS.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_SLOW_SCREENS_BY_TTID.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_TPM_AREA.value,
Referrer.API_PERFORMANCE_GENERIC_WIDGET_CHART_USER_MISERY_AREA.value,
Referrer.API_PERFORMANCE_VITALS_CARDS.value,
Referrer.API_PERFORMANCE_LANDING_TABLE.value,
Referrer.API_PERFORMANCE_TRANSACTION_SUMMARY.value,
Referrer.API_PERFORMANCE_TRANSACTION_EVENTS.value,
Referrer.API_PERFORMANCE_TRANSACTION_NAME_SEARCH_BAR.value,
Referrer.API_PERFORMANCE_TRANSACTION_SPANS.value,
Referrer.API_PERFORMANCE_TRANSACTION_SUMMARY.value,
Referrer.API_PERFORMANCE_STATUS_BREAKDOWN.value,
Referrer.API_PERFORMANCE_VITAL_DETAIL.value,
Referrer.API_PERFORMANCE_DURATIONPERCENTILECHART.value,
Referrer.API_PERFORMANCE_TRACE_TRACE_DRAWER_TRANSACTION_CACHE_METRICS.value,
Referrer.API_PERFORMANCE_TRANSACTIONS_STATISTICAL_DETECTOR_ROOT_CAUSE_ANALYSIS.value,
Referrer.API_PROFILING_LANDING_TABLE.value,
Referrer.API_PROFILING_LANDING_FUNCTIONS_CARD.value,
Expand All @@ -83,12 +106,15 @@ class DiscoverDatasetSplitException(Exception):
Referrer.API_TRACE_VIEW_ERRORS_VIEW.value,
Referrer.API_TRACE_VIEW_HOVER_CARD.value,
Referrer.API_ISSUES_ISSUE_EVENTS.value,
Referrer.API_STARFISH_DATABASE_SYSTEM_SELECTOR.value,
Referrer.API_STARFISH_ENDPOINT_LIST.value,
Referrer.API_STARFISH_FULL_SPAN_FROM_TRACE.value,
Referrer.API_STARFISH_GET_SPAN_ACTIONS.value,
Referrer.API_STARFISH_GET_SPAN_DOMAINS.value,
Referrer.API_STARFISH_GET_SPAN_OPERATIONS.value,
Referrer.API_STARFISH_SIDEBAR_SPAN_METRICS.value,
Referrer.API_STARFISH_SPAN_CATEGORY_BREAKDOWN.value,
Referrer.API_STARFISH_SPAN_DESCRIPTION.value,
Referrer.API_STARFISH_SPAN_LIST.value,
Referrer.API_STARFISH_SPAN_SUMMARY_P95.value,
Referrer.API_STARFISH_SPAN_SUMMARY_PAGE.value,
Expand Down
4 changes: 2 additions & 2 deletions src/sentry/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
[
# basic tasks that must be passed models still
"sentry.tasks.process_buffer.process_incr",
"sentry.tasks.process_resource_change_bound",
"sentry.tasks.sentry_apps.send_alert_event",
"sentry.sentry_apps.tasks.sentry_apps.process_resource_change_bound",
"sentry.sentry_apps.tasks.sentry_apps.send_alert_event",
"sentry.tasks.unmerge",
"src.sentry.notifications.utils.async_send_notification",
# basic tasks that can already deal with primary keys passed
Expand Down
2 changes: 2 additions & 0 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ def register_temporary_features(manager: FeatureManager):
manager.add("organizations:issue-search-snuba", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
# Enable the new issue stream search bar UI
manager.add("organizations:issue-stream-search-query-builder", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable issue stream table layout changes
manager.add("organizations:issue-stream-table-layout", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
manager.add("organizations:large-debug-files", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
# Enabled latest adopted release filter for issue alerts
manager.add("organizations:latest-adopted-release-filter", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
Expand Down
101 changes: 7 additions & 94 deletions src/sentry/flags/endpoints/hooks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import datetime
from typing import Any, TypedDict

from rest_framework import serializers
from rest_framework.request import Request
from rest_framework.response import Response

Expand All @@ -11,7 +7,12 @@
from sentry.api.base import Endpoint, region_silo_endpoint
from sentry.api.bases.organization import OrganizationPermission
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.flags.models import ACTION_MAP, CREATED_BY_TYPE_MAP, FlagAuditLogModel
from sentry.flags.providers import (
DeserializationError,
InvalidProvider,
handle_provider_event,
write,
)
from sentry.models.organization import Organization
from sentry.utils.sdk import bind_organization_context

Expand Down Expand Up @@ -65,97 +66,9 @@ def convert_args(

def post(self, request: Request, organization: Organization, provider: str) -> Response:
try:
rows_data = handle_provider_event(provider, request.data, organization.id)
FlagAuditLogModel.objects.bulk_create(FlagAuditLogModel(**row) for row in rows_data)
write(handle_provider_event(provider, request.data, organization.id))
return Response(status=200)
except InvalidProvider:
raise ResourceDoesNotExist
except DeserializationError as exc:
return Response(exc.errors, status=400)


"""Provider definitions.
Provider definitions are pure functions. They accept data and return data. Providers do not
initiate any IO operations. Instead they return commands in the form of the return type or
an exception. These commands inform the caller (the endpoint defintion) what IO must be
emitted to satisfy the request. This is done primarily to improve testability and test
performance but secondarily to allow easy extension of the endpoint without knowledge of
the underlying systems.
"""


class FlagAuditLogRow(TypedDict):
"""A complete flag audit log row instance."""

action: int
created_at: datetime.datetime
created_by: str
created_by_type: int
flag: str
organization_id: int
tags: dict[str, Any]


class DeserializationError(Exception):
"""The request body could not be deserialized."""

def __init__(self, errors):
self.errors = errors


class InvalidProvider(Exception):
"""An unsupported provider type was specified."""

...


def handle_provider_event(
provider: str,
request_data: dict[str, Any],
organization_id: int,
) -> list[FlagAuditLogRow]:
if provider == "flag-pole":
return handle_flag_pole_event(request_data, organization_id)
else:
raise InvalidProvider(provider)


"""Flag pole provider definition.
If you are not Sentry you will not ever use this driver. Metadata provider by flag pole is
limited to what we can extract from the git repository on merge.
"""


class FlagPoleItemSerializer(serializers.Serializer):
action = serializers.ChoiceField(choices=("created", "updated"), required=True)
created_at = serializers.DateTimeField(required=True)
created_by = serializers.CharField(required=True)
flag = serializers.CharField(max_length=100, required=True)
tags = serializers.DictField(required=True)


class FlagPoleSerializer(serializers.Serializer):
data = FlagPoleItemSerializer(many=True, required=True) # type: ignore[assignment]


def handle_flag_pole_event(
request_data: dict[str, Any], organization_id: int
) -> list[FlagAuditLogRow]:
serializer = FlagPoleSerializer(data=request_data)
if not serializer.is_valid():
raise DeserializationError(serializer.errors)

return [
dict(
action=ACTION_MAP[validated_item["action"]],
created_at=validated_item["created_at"],
created_by=validated_item["created_by"],
created_by_type=CREATED_BY_TYPE_MAP["email"],
flag=validated_item["flag"],
organization_id=organization_id,
tags=validated_item["tags"],
)
for validated_item in serializer.validated_data["data"]
]
85 changes: 85 additions & 0 deletions src/sentry/flags/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import datetime
from typing import Any, TypedDict

from sentry.flags.models import ACTION_MAP, CREATED_BY_TYPE_MAP, FlagAuditLogModel


def write(rows: list["FlagAuditLogRow"]) -> None:
FlagAuditLogModel.objects.bulk_create(FlagAuditLogModel(**row) for row in rows)


"""Provider definitions.
Provider definitions are pure functions. They accept data and return data. Providers do not
initiate any IO operations. Instead they return commands in the form of the return type or
an exception. These commands inform the caller (the endpoint defintion) what IO must be
emitted to satisfy the request. This is done primarily to improve testability and test
performance but secondarily to allow easy extension of the endpoint without knowledge of
the underlying systems.
"""


class FlagAuditLogRow(TypedDict):
"""A complete flag audit log row instance."""

action: int
created_at: datetime.datetime
created_by: str
created_by_type: int
flag: str
organization_id: int
tags: dict[str, Any]


class DeserializationError(Exception):
"""The request body could not be deserialized."""

def __init__(self, errors):
self.errors = errors


class InvalidProvider(Exception):
"""An unsupported provider type was specified."""

...


def handle_provider_event(
provider: str,
request_data: dict[str, Any],
organization_id: int,
) -> list[FlagAuditLogRow]:
raise InvalidProvider(provider)


"""Internal flag-pole provider.
Allows us to skip the HTTP endpoint.
"""


class FlagAuditLogItem(TypedDict):
"""A simplified type which is easier to work with than the row definition."""

action: str
flag: str
created_at: datetime.datetime
created_by: str
tags: dict[str, str]


def handle_flag_pole_event_internal(items: list[FlagAuditLogItem], organization_id: int) -> None:
write(
[
{
"action": ACTION_MAP[item["action"]],
"created_at": item["created_at"],
"created_by": item["created_by"],
"created_by_type": CREATED_BY_TYPE_MAP["name"],
"flag": item["flag"],
"organization_id": organization_id,
"tags": item["tags"],
}
for item in items
]
)
12 changes: 12 additions & 0 deletions src/sentry/incidents/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,18 @@ def delete_alert_rule(

incidents = Incident.objects.filter(alert_rule=alert_rule)
if incidents.exists():
# if this was a dynamic rule, delete the data in Seer
if alert_rule.detection_type == AlertRuleDetectionType.DYNAMIC:
success = delete_rule_in_seer(
alert_rule=alert_rule,
)
if not success:
logger.error(
"Call to delete rule data in Seer failed",
extra={
"rule_id": alert_rule.id,
},
)
AlertRuleActivity.objects.create(
alert_rule=alert_rule,
user_id=user.id if user else None,
Expand Down
13 changes: 13 additions & 0 deletions src/sentry/integrations/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from sentry.shared_integrations.exceptions import IntegrationError, IntegrationProviderError
from sentry.silo.base import SiloMode
from sentry.users.models.identity import Identity, IdentityProvider, IdentityStatus
from sentry.utils import metrics
from sentry.web.helpers import render_to_response

__all__ = ["IntegrationPipeline"]
Expand Down Expand Up @@ -85,6 +86,13 @@ def get_analytics_entry(self) -> PipelineAnalyticsEntry | None:
pipeline_type = "reauth" if self.fetch_state("integration_id") else "install"
return PipelineAnalyticsEntry("integrations.pipeline_step", pipeline_type)

def initialize(self) -> None:
super().initialize()

metrics.incr(
"sentry.integrations.installation_attempt", tags={"integration": self.provider.key}
)

def finish_pipeline(self):
try:
data = self.provider.build_integration(self.state.data)
Expand Down Expand Up @@ -118,6 +126,11 @@ def finish_pipeline(self):
)
self.provider.post_install(self.integration, self.organization, extra=extra)
self.clear_session()

metrics.incr(
"sentry.integrations.installation_finished", tags={"integration": self.provider.key}
)

return response

def _finish_pipeline(self, data):
Expand Down
14 changes: 14 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,20 @@
flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE,
)

# Flag Options
register(
"flags:options-audit-log-is-enabled",
default=True,
flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE,
type=Bool,
)
register(
"flags:options-audit-log-organization-id",
default=None,
flags=FLAG_ALLOW_EMPTY | FLAG_PRIORITIZE_DISK | FLAG_AUTOMATOR_MODIFIABLE,
type=Int,
)

# Replay Options
#
# Replay storage backend configuration (only applicable if the direct-storage driver is used)
Expand Down
Loading

0 comments on commit deefe11

Please sign in to comment.