Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
nvsmirnov authored Sep 5, 2024
2 parents 912e251 + 7870515 commit 6348d83
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 119 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/openapi-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ env:

jobs:
check-diff:
name: build api
name: (Optional) Shows the difference between the prod and dev schema
runs-on: ubuntu-22.04
timeout-minutes: 90
steps:
Expand Down
60 changes: 58 additions & 2 deletions src/sentry/api/endpoints/project_rule_actions.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import logging

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

from sentry import features
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 ProjectAlertRulePermission, ProjectEndpoint
from sentry.api.serializers.rest_framework import RuleActionSerializer
from sentry.eventstore.models import GroupEvent
from sentry.models.rule import Rule
from sentry.rules.processing.processor import activate_downstream_actions
from sentry.shared_integrations.exceptions import IntegrationError
from sentry.utils.safe import safe_execute
from sentry.utils.samples import create_sample_event

Expand Down Expand Up @@ -60,7 +65,58 @@ def post(self, request: Request, project) -> Response:
project, platform=project.platform, default="javascript", tagged=True
)

if features.has(
"projects:verbose-test-alert-reporting", project=project, user=request.user
):
return self.execute_future_on_test_event(test_event, rule)
else:
# Old existing behavior to wrap this in a handler which buries exceptions
for callback, futures in activate_downstream_actions(rule, test_event).values():
safe_execute(callback, test_event, futures)
return Response()

def execute_future_on_test_event(
self,
test_event: GroupEvent,
rule: Rule,
) -> Response:
"""
A slightly modified version of utils.safe.safe_execute that handles
IntegrationFormErrors, and returns a body with `{ actions: [<error info>] }`.
This is used in our Alert Rule UI to display errors to the user.
"""
action_exceptions = []
for callback, futures in activate_downstream_actions(rule, test_event).values():
safe_execute(callback, test_event, futures)
try:
callback(test_event, futures)
except Exception as exc:
callback_name = getattr(callback, "__name__", str(callback))
cls_name = callback.__class__.__name__
logger = logging.getLogger(f"sentry.test_rule.{cls_name.lower()}")

# safe_execute logs these as exceptions, which can result in
# noisy sentry issues, so log with a warning instead.
if isinstance(exc, IntegrationError):
logger.warning(
"%s.test_alert.integration_error", callback_name, extra={"exc": exc}
)

# IntegrationFormErrors should be safe to propagate via the API
action_exceptions.append(str(exc))
else:
# If we encounter some unexpected exception, we probably
# don't want to continue executing more callbacks.
logger.warning(
"%s.test_alert.unexpected_exception", callback_name, extra={"exc": exc}
)
break

status = None
data = None
# Presence of "actions" here means we have exceptions to surface to the user
if len(action_exceptions) > 0:
status = 400
data = {"actions": action_exceptions}

return Response()
return Response(status=status, data=data)
6 changes: 0 additions & 6 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,12 +1128,6 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
"schedule": crontab(minute="0", hour="12", day_of_week="sat"),
"options": {"expires": 60 * 60 * 3},
},
# "schedule-daily-organization-reports": {
# "task": "sentry.tasks.summaries.daily_summary.schedule_organizations",
# # Run every 1 hour on business days
# "schedule": crontab(minute=0, hour="*/1", day_of_week="mon-fri"),
# "options": {"expires": 60 * 60 * 3},
# },
"schedule-hybrid-cloud-foreign-key-jobs": {
"task": "sentry.tasks.deletion.hybrid_cloud.schedule_hybrid_cloud_foreign_key_jobs",
# Run every 15 minutes
Expand Down
7 changes: 6 additions & 1 deletion src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ def register_temporary_features(manager: FeatureManager):
manager.add("organizations:invite-members", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)
# Enable rate limits for inviting members.
manager.add("organizations:invite-members-rate-limits", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=False)
# Enable displaying the trace view on issue details
manager.add("organizations:issue-details-always-show-trace", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enables the UI for Autofix in issue details
manager.add("organizations:issue-details-autofix-ui", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enables a toggle for entering the new issue details UI
Expand Down Expand Up @@ -501,7 +503,7 @@ def register_temporary_features(manager: FeatureManager):
# User Feedback Error Link Ingestion Changes
manager.add("organizations:user-feedback-event-link-ingestion-changes", OrganizationFeature, FeatureHandlerStrategy.OPTIONS, api_expose=False)
# Enable display of a trace data section in feedback details
manager.add("organizations:user-feedback-trace-section", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
manager.add("organizations:user-feedback-trace-section", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable view hierarchies options
manager.add("organizations:view-hierarchies-options-dev", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable the new explore page
Expand Down Expand Up @@ -548,6 +550,9 @@ def register_temporary_features(manager: FeatureManager):
# EAP: extremely experimental flag that makes DDM page use EAP tables
manager.add("projects:use-eap-spans-for-metrics-explorer", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)

# Ecosystem: Enable verbose alert reporting when triggering test alerts
manager.add("projects:verbose-test-alert-reporting", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE,
api_expose=False)
# Project plugin features
manager.add("projects:plugins", ProjectPluginFeature, FeatureHandlerStrategy.INTERNAL, default=True, api_expose=True)

Expand Down
156 changes: 79 additions & 77 deletions src/sentry/middleware/ratelimit.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,90 +49,92 @@ def process_view(
) -> HttpResponseBase | None:
"""Check if the endpoint call will violate."""

try:
with sentry_sdk.start_span(op="ratelimit.early_return"):
# TODO: put these fields into their own object
request.will_be_rate_limited = False
if settings.SENTRY_SELF_HOSTED:
return None
request.rate_limit_category = None
request.rate_limit_uid = uuid.uuid4().hex
view_class = getattr(view_func, "view_class", None)
if not view_class:
return None

enforce_rate_limit = getattr(view_class, "enforce_rate_limit", False)
if enforce_rate_limit is False:
return None

with sentry_sdk.start_span(op="ratelimit.determine_limit_config"):
rate_limit_config = get_rate_limit_config(
view_class, view_args, {**view_kwargs, "request": request}
)
rate_limit_group = (
rate_limit_config.group if rate_limit_config else RateLimitConfig().group
)
request.rate_limit_key = get_rate_limit_key(
view_func, request, rate_limit_group, rate_limit_config
)
if request.rate_limit_key is None:
return None

category_str = request.rate_limit_key.split(":", 1)[0]
request.rate_limit_category = category_str

rate_limit = get_rate_limit_value(
http_method=request.method,
category=RateLimitCategory(category_str),
rate_limit_config=rate_limit_config,
)
if rate_limit is None:
return None

with sentry_sdk.start_span(op="ratelimit.above_rate_limit_check"):
request.rate_limit_metadata = above_rate_limit_check(
request.rate_limit_key, rate_limit, request.rate_limit_uid, rate_limit_group
)

# TODO: also limit by concurrent window once we have the data
rate_limit_cond = (
request.rate_limit_metadata.rate_limit_type != RateLimitType.NOT_LIMITED
if settings.ENFORCE_CONCURRENT_RATE_LIMITS
else request.rate_limit_metadata.rate_limit_type == RateLimitType.FIXED_WINDOW
)
if rate_limit_cond:
request.will_be_rate_limited = True
logger.info(
"sentry.api.rate-limit.exceeded",
extra={
"key": request.rate_limit_key,
"url": request.build_absolute_uri(),
"limit": request.rate_limit_metadata.limit,
"window": request.rate_limit_metadata.window,
},
)
response = HttpResponse(
orjson.dumps(
DEFAULT_ERROR_MESSAGE.format(
limit=request.rate_limit_metadata.limit,
window=request.rate_limit_metadata.window,
)
),
status=429,
with metrics.timer("middleware.ratelimit.process_view", sample_rate=0.01):
try:
with sentry_sdk.start_span(op="ratelimit.early_return"):
# TODO: put these fields into their own object
request.will_be_rate_limited = False
if settings.SENTRY_SELF_HOSTED:
return None
request.rate_limit_category = None
request.rate_limit_uid = uuid.uuid4().hex
view_class = getattr(view_func, "view_class", None)
if not view_class:
return None

enforce_rate_limit = getattr(view_class, "enforce_rate_limit", False)
if enforce_rate_limit is False:
return None

with sentry_sdk.start_span(op="ratelimit.determine_limit_config"):
rate_limit_config = get_rate_limit_config(
view_class, view_args, {**view_kwargs, "request": request}
)
rate_limit_group = (
rate_limit_config.group if rate_limit_config else RateLimitConfig().group
)
request.rate_limit_key = get_rate_limit_key(
view_func, request, rate_limit_group, rate_limit_config
)
if request.rate_limit_key is None:
return None

category_str = request.rate_limit_key.split(":", 1)[0]
request.rate_limit_category = category_str

rate_limit = get_rate_limit_value(
http_method=request.method,
category=RateLimitCategory(category_str),
rate_limit_config=rate_limit_config,
)
if rate_limit is None:
return None

with sentry_sdk.start_span(op="ratelimit.above_rate_limit_check"):
request.rate_limit_metadata = above_rate_limit_check(
request.rate_limit_key, rate_limit, request.rate_limit_uid, rate_limit_group
)

# TODO: also limit by concurrent window once we have the data
rate_limit_cond = (
request.rate_limit_metadata.rate_limit_type != RateLimitType.NOT_LIMITED
if settings.ENFORCE_CONCURRENT_RATE_LIMITS
else request.rate_limit_metadata.rate_limit_type == RateLimitType.FIXED_WINDOW
)
assert request.method is not None
return apply_cors_headers(
request=request, response=response, allowed_methods=[request.method]
if rate_limit_cond:
request.will_be_rate_limited = True
logger.info(
"sentry.api.rate-limit.exceeded",
extra={
"key": request.rate_limit_key,
"url": request.build_absolute_uri(),
"limit": request.rate_limit_metadata.limit,
"window": request.rate_limit_metadata.window,
},
)
response = HttpResponse(
orjson.dumps(
DEFAULT_ERROR_MESSAGE.format(
limit=request.rate_limit_metadata.limit,
window=request.rate_limit_metadata.window,
)
),
status=429,
)
assert request.method is not None
return apply_cors_headers(
request=request, response=response, allowed_methods=[request.method]
)
except Exception:
logging.exception(
"Error during rate limiting, failing open. THIS SHOULD NOT HAPPEN"
)
except Exception:
logging.exception("Error during rate limiting, failing open. THIS SHOULD NOT HAPPEN")

return None

def process_response(
self, request: HttpRequest, response: HttpResponseBase
) -> HttpResponseBase:
with metrics.timer("middleware.ratelimit.process_response"):
with metrics.timer("middleware.ratelimit.process_response", sample_rate=0.01):
try:
rate_limit_metadata: RateLimitMeta | None = getattr(
request, "rate_limit_metadata", None
Expand Down
4 changes: 4 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2682,3 +2682,7 @@
default=True,
flags=FLAG_AUTOMATOR_MODIFIABLE,
)

register(
"ecosystem:enable_integration_form_error_raise", default=False, flags=FLAG_AUTOMATOR_MODIFIABLE
)
2 changes: 1 addition & 1 deletion src/sentry/profiles/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def increment(
status_forcelist={502},
allowed_methods={"GET", "POST"},
),
timeout=10,
timeout=15,
maxsize=10,
headers={"Accept-Encoding": "br, gzip"},
)
Expand Down
9 changes: 8 additions & 1 deletion src/sentry/rules/actions/integrations/create_ticket/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from rest_framework.response import Response

from sentry import options
from sentry.constants import ObjectStatus
from sentry.eventstore.models import GroupEvent
from sentry.integrations.base import IntegrationInstallation
Expand Down Expand Up @@ -134,7 +135,13 @@ def create_issue(event: GroupEvent, futures: Sequence[RuleFuture]) -> None:
"provider": provider,
},
)
return

# Testing out if this results in a lot of noisy sentry issues
# when enabled.
if options.get("ecosystem:enable_integration_form_error_raise"):
raise
else:
return

if not event.get_tag("sample_event") == "yes":
create_link(integration, installation, event, response)
5 changes: 3 additions & 2 deletions static/app/bootstrap/initializeSdk.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,15 @@ export function initializeSdk(config: Config, {routes}: {routes?: Function} = {}
addExtraMeasurements(event);
addUIElementTag(event);

event.spans = event.spans?.filter(span => {
const filteredSpans = event.spans?.filter(span => {
return IGNORED_SPANS_BY_DESCRIPTION.every(
partialDesc => !span.description?.includes(partialDesc)
);
});

// If we removed any spans at the end above, the end timestamp needs to be adjusted again.
if (event.spans) {
if (filteredSpans && filteredSpans?.length !== event.spans?.length) {
event.spans = filteredSpans;
const newEndTimestamp = Math.max(...event.spans.map(span => span.timestamp ?? 0));
event.timestamp = newEndTimestamp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
MAX_MENU_HEIGHT,
MAX_SEARCH_ITEMS,
} from 'sentry/views/dashboards/widgetBuilder/utils';
import ResultsSearchQueryBuilder from 'sentry/views/discover/resultsSearchQueryBuilder';

interface Props {
getFilterWarning: SearchBarProps['getFilterWarning'];
Expand Down Expand Up @@ -42,7 +43,20 @@ export function EventsSearchBar({
? generateAggregateFields(organization, eventView.fields)
: eventView.fields;

return (
return organization.features.includes('search-query-builder-discover') ? (
<ResultsSearchQueryBuilder
projectIds={eventView.project}
query={widgetQuery.conditions}
fields={fields}
onChange={(query, state) => {
onClose?.(query, {validSearch: state.queryIsValid});
}}
customMeasurements={customMeasurements}
dataset={dataset}
includeTransactions={hasDatasetSelector(organization) ? false : true}
searchSource="widget_builder"
/>
) : (
<Search
searchSource="widget_builder"
organization={organization}
Expand Down
Loading

0 comments on commit 6348d83

Please sign in to comment.