diff --git a/jest.config.snapshots.ts b/jest.config.snapshots.ts index 9175ca209126b5..e2d6cca557fe3e 100644 --- a/jest.config.snapshots.ts +++ b/jest.config.snapshots.ts @@ -41,6 +41,7 @@ const swcConfig: SwcOptions = { const ESM_NODE_MODULES = ['screenfull', 'cbor2', 'nuqs', 'color']; const config: Config.InitialOptions = { + testTimeout: 30_000, cacheDirectory: '.cache/jest-snapshots', // testEnvironment and testMatch are the core differences between this and the main config testEnvironment: 'node', diff --git a/pyproject.toml b/pyproject.toml index cbf0f462485b2a..b8882810da83d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1286,19 +1286,6 @@ module = [ "sentry.release_health.tasks", "sentry.releases", "sentry.releases.endpoints.*", - "sentry.relocation.api.endpoints", - "sentry.relocation.api.endpoints.abort", - "sentry.relocation.api.endpoints.cancel", - "sentry.relocation.api.endpoints.details", - "sentry.relocation.api.endpoints.index", - "sentry.relocation.api.endpoints.pause", - "sentry.relocation.api.endpoints.public_key", - "sentry.relocation.api.endpoints.recover", - "sentry.relocation.api.endpoints.retry", - "sentry.relocation.api.endpoints.unpause", - "sentry.relocation.models.*", - "sentry.relocation.tasks.*", - "sentry.relocation.utils", "sentry.remote_subscriptions.*", "sentry.replays", "sentry.replays._case_studies.*", diff --git a/src/sentry/dynamic_sampling/per_org/tasks/configuration.py b/src/sentry/dynamic_sampling/per_org/tasks/configuration.py index 24c91aace8f12f..a7635681759279 100644 --- a/src/sentry/dynamic_sampling/per_org/tasks/configuration.py +++ b/src/sentry/dynamic_sampling/per_org/tasks/configuration.py @@ -2,16 +2,20 @@ from abc import ABC, abstractmethod from collections.abc import Mapping +from datetime import timedelta from django.core.exceptions import ObjectDoesNotExist from sentry import options, quotas from sentry.constants import SAMPLING_MODE_DEFAULT, TARGET_SAMPLE_RATE_DEFAULT, ObjectStatus +from sentry.dynamic_sampling.per_org.tasks.queries import get_eap_organization_volume from sentry.dynamic_sampling.per_org.tasks.telemetry import ( DynamicSamplingException, DynamicSamplingStatus, ) from sentry.dynamic_sampling.rules.utils import ProjectId +from sentry.dynamic_sampling.tasks.common import compute_sliding_window_sample_rate +from sentry.dynamic_sampling.tasks.helpers.sliding_window import FALLBACK_SLIDING_WINDOW_SIZE from sentry.dynamic_sampling.types import DynamicSamplingMode, SamplingMeasure from sentry.dynamic_sampling.utils import has_custom_dynamic_sampling from sentry.models.options.project_option import ProjectOption @@ -50,12 +54,17 @@ class BaseDynamicSamplingConfiguration(ABC): def __init__(self, organization: Organization) -> None: self.organization = organization + self.sliding_window_sample_rate: TargetSampleRate = None @property @abstractmethod def is_enabled(self) -> bool: raise NotImplementedError + @abstractmethod + def get_sample_rate(self) -> TargetSampleRate: + raise NotImplementedError + @property def is_span_based(self) -> bool: return self.measure == SamplingMeasure.SPANS @@ -79,12 +88,15 @@ def _get_projects(self) -> list[Project]: class NoDynamicSamplingConfiguration(BaseDynamicSamplingConfiguration): def __init__(self) -> None: - pass + self.sliding_window_sample_rate: TargetSampleRate = None @property def is_enabled(self) -> bool: return False + def get_sample_rate(self) -> TargetSampleRate: + return None + class AutomaticDynamicSamplingConfiguration(BaseDynamicSamplingConfiguration): sample_rate: TargetSampleRate @@ -98,12 +110,37 @@ def __init__(self, organization: Organization) -> None: ) except ObjectDoesNotExist as exc: raise DynamicSamplingException(DynamicSamplingStatus.NO_SUBSCRIPTION) from exc + if not self.is_enabled: + return self.projects = self._get_projects() + self.sliding_window_sample_rate = self._get_sliding_window_sample_rate() @property def is_enabled(self) -> bool: return self.sample_rate is not None + def get_sample_rate(self) -> TargetSampleRate: + if self.sliding_window_sample_rate is not None: + return self.sliding_window_sample_rate + return self.sample_rate + + def _get_sliding_window_sample_rate(self) -> TargetSampleRate: + if not self.projects: + return None + + org_volume_24h = get_eap_organization_volume( + self, time_interval=timedelta(hours=FALLBACK_SLIDING_WINDOW_SIZE) + ) + if org_volume_24h is None: + return None + + return compute_sliding_window_sample_rate( + org_id=self.organization.id, + project_id=None, + total_root_count=org_volume_24h.total, + window_size=FALLBACK_SLIDING_WINDOW_SIZE, + ) + class CustomDynamicSamplingOrganizationConfiguration(BaseDynamicSamplingConfiguration): sample_rate: TargetSampleRate @@ -121,6 +158,9 @@ def __init__(self, organization: Organization) -> None: def is_enabled(self) -> bool: return True + def get_sample_rate(self) -> TargetSampleRate: + return self.sample_rate + class CustomDynamicSamplingProjectConfiguration(BaseDynamicSamplingConfiguration): project_target_sample_rates: ProjectTargetSampleRates @@ -138,6 +178,9 @@ def is_enabled(self) -> bool: sample_rate is not None for sample_rate in self.project_target_sample_rates.values() ) + def get_sample_rate(self) -> TargetSampleRate: + return None + def _get_project_target_sample_rates(self) -> ProjectTargetSampleRates: project_sample_rates = ProjectOption.objects.get_value_bulk( self.projects, "sentry:target_sample_rate" diff --git a/src/sentry/dynamic_sampling/per_org/tasks/queries.py b/src/sentry/dynamic_sampling/per_org/tasks/queries.py index af77a229a97332..7b18516249105a 100644 --- a/src/sentry/dynamic_sampling/per_org/tasks/queries.py +++ b/src/sentry/dynamic_sampling/per_org/tasks/queries.py @@ -5,17 +5,18 @@ from dataclasses import dataclass, field from datetime import UTC, datetime, timedelta from enum import StrEnum -from typing import Any, Literal +from typing import Any, Literal, Protocol from sentry_protos.snuba.v1.trace_item_attribute_pb2 import ExtrapolationMode -from sentry.dynamic_sampling.per_org.tasks.configuration import BaseDynamicSamplingConfiguration from sentry.dynamic_sampling.rules.utils import ProjectId from sentry.dynamic_sampling.tasks.boost_low_volume_transactions import ProjectTransactions from sentry.dynamic_sampling.tasks.common import ( ACTIVE_ORGS_VOLUMES_DEFAULT_TIME_INTERVAL, OrganizationDataVolume, ) +from sentry.models.organization import Organization +from sentry.models.project import Project from sentry.search.eap.constants import SAMPLING_MODE_HIGHEST_ACCURACY from sentry.search.eap.types import SearchResolverConfig from sentry.search.events.types import SnubaParams @@ -23,6 +24,11 @@ from sentry.snuba.spans_rpc import Spans +class OrganizationVolumeConfig(Protocol): + organization: Organization + projects: list[Project] + + class DynamicSamplingQueryFilters(StrEnum): IS_SEGMENT = "sentry.is_segment:true" @@ -86,7 +92,7 @@ def run_eap_spans_table_query_in_chunks( def get_eap_organization_volume( - config: BaseDynamicSamplingConfiguration, + config: OrganizationVolumeConfig, time_interval: timedelta = ACTIVE_ORGS_VOLUMES_DEFAULT_TIME_INTERVAL, ) -> OrganizationDataVolume | None: end_time = datetime.now(UTC) @@ -128,7 +134,7 @@ def get_eap_organization_volume( def get_eap_project_volumes( - config: BaseDynamicSamplingConfiguration, + config: OrganizationVolumeConfig, time_interval: timedelta = timedelta(hours=1), ) -> list[ProjectVolume]: end_time = datetime.now(UTC) @@ -177,7 +183,7 @@ def get_eap_project_volumes( def get_eap_transaction_volumes( - config: BaseDynamicSamplingConfiguration, + config: OrganizationVolumeConfig, time_interval: timedelta = ACTIVE_ORGS_VOLUMES_DEFAULT_TIME_INTERVAL, order_by_volume: Literal["asc", "desc"] = "asc", max_transactions: int = 100, diff --git a/src/sentry/dynamic_sampling/per_org/tasks/scheduler.py b/src/sentry/dynamic_sampling/per_org/tasks/scheduler.py index 86b92bcaa5de1c..6a710e6178292b 100644 --- a/src/sentry/dynamic_sampling/per_org/tasks/scheduler.py +++ b/src/sentry/dynamic_sampling/per_org/tasks/scheduler.py @@ -108,8 +108,8 @@ def run_calculations_per_org_task(org_id: OrganizationId) -> DynamicSamplingStat if not config.projects: return DynamicSamplingStatus.ORG_HAS_NO_PROJECTS - org_volume = get_eap_organization_volume(config) - if org_volume is None: + org_volume_5m = get_eap_organization_volume(config) + if org_volume_5m is None: return DynamicSamplingStatus.NO_ORG_VOLUME if config.should_balance_projects: diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index 25c6aa355cbe8e..3bd5327635ed7b 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -289,8 +289,6 @@ def register_temporary_features(manager: FeatureManager) -> None: manager.add("organizations:seer-wizard", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable the Seer issues view manager.add("organizations:seer-issue-view", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) - # Enable Autofix to use Seer Agent instead of legacy Celery pipeline - manager.add("organizations:autofix-on-explorer", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable autofix introspection for early stopping of autofix runs manager.add("organizations:seer-autofix-introspection", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) # Enable Seer Workflows in Slack (released, kept until overrides are removed) diff --git a/src/sentry/integrations/discord/integration.py b/src/sentry/integrations/discord/integration.py index 06430ac58c1671..0acb6959223f6a 100644 --- a/src/sentry/integrations/discord/integration.py +++ b/src/sentry/integrations/discord/integration.py @@ -148,6 +148,19 @@ class DiscordOAuthApiSerializer(CamelSnakeSerializer): guild_id = CharField(required=True) +class DiscordInitialDataSerializer(CamelSnakeSerializer): + """Initial pipeline data for App Directory-originated Discord installs. + + When a user installs from Discord's App Directory, Discord initiates OAuth + and redirects back to Sentry with `code` and `guild_id`. The frontend + forwards them here so the pipeline can skip its own OAuth step. + """ + + code = CharField(required=False) + guild_id = CharField(required=False) + use_configure = CharField(required=False) + + class DiscordOAuthApiStep: """API-mode OAuth step for Discord integration setup. @@ -170,7 +183,18 @@ def __init__( self.scopes = scopes self.redirect_url = redirect_url - def get_step_data(self, pipeline: IntegrationPipeline, request: HttpRequest) -> dict[str, str]: + def get_step_data(self, pipeline: IntegrationPipeline, request: HttpRequest) -> dict[str, Any]: + # App Directory installs arrive with OAuth already complete: code and + # guild_id are bound to state via initialData. Signal the frontend to + # advance immediately using those values instead of opening a popup. + if pipeline.fetch_state("use_configure"): + return { + "appDirectoryInstall": True, + "code": pipeline.fetch_state("code"), + "guildId": pipeline.fetch_state("guild_id"), + "state": pipeline.signature, + } + params = urlencode( { "client_id": self.client_id, @@ -245,6 +269,9 @@ def get_pipeline_api_steps(self) -> ApiPipelineSteps[IntegrationPipeline]: ), ] + def get_initial_data_serializer_cls(self) -> type[DiscordInitialDataSerializer]: + return DiscordInitialDataSerializer + def build_integration(self, state: Mapping[str, Any]) -> IntegrationData: guild_id = str(state.get("guild_id")) @@ -258,11 +285,9 @@ def build_integration(self, state: Mapping[str, Any]) -> IntegrationData: except (ApiError, AttributeError): guild_name = guild_id - discord_config = state.get(IntegrationProviderSlug.DISCORD.value, {}) - if isinstance(discord_config, dict): - use_configure = discord_config.get("use_configure") == "1" - else: - use_configure = False + # App Directory installs initiated OAuth with configure_url as the + # redirect_uri, so token exchange must echo it back. + use_configure = state.get("use_configure") == "1" url = self.configure_url if use_configure else self.setup_url auth_code = str(state.get("code")) diff --git a/src/sentry/integrations/discord/urls.py b/src/sentry/integrations/discord/urls.py index 2e6657bc57758c..7ae5b50b64ac70 100644 --- a/src/sentry/integrations/discord/urls.py +++ b/src/sentry/integrations/discord/urls.py @@ -1,9 +1,7 @@ from django.urls import re_path from sentry.integrations.discord.spec import DiscordMessagingSpec -from sentry.integrations.web.discord_extension_configuration import ( - DiscordExtensionConfigurationView, -) +from sentry.integrations.discord.views.configure_redirect import DiscordConfigureRedirectView from .webhooks.base import DiscordInteractionsEndpoint @@ -13,10 +11,12 @@ DiscordInteractionsEndpoint.as_view(), name="sentry-integration-discord-interactions", ), - # Discord App Directory extension install flow + # Discord App Directory's redirect_uri lands here after the user authorizes + # in Discord. We forward the OAuth params to the link view, which opens the + # install pipeline modal to finish the install. re_path( r"^configure/$", - DiscordExtensionConfigurationView.as_view(), + DiscordConfigureRedirectView.as_view(), name="discord-extension-configuration", ), ] diff --git a/src/sentry/integrations/discord/views/configure_redirect.py b/src/sentry/integrations/discord/views/configure_redirect.py new file mode 100644 index 00000000000000..d9f7145fa9324e --- /dev/null +++ b/src/sentry/integrations/discord/views/configure_redirect.py @@ -0,0 +1,16 @@ +from django.views.generic.base import RedirectView + +from sentry.web.frontend.base import control_silo_view + + +@control_silo_view +class DiscordConfigureRedirectView(RedirectView): + """OAuth redirect target for Discord App Directory installs. + + Forwards `code` and `guild_id` from Discord's OAuth callback to the + integration link view, which picks an org and opens the install pipeline. + """ + + url = "/extensions/discord/link/" + query_string = True + permanent = False diff --git a/src/sentry/integrations/web/discord_extension_configuration.py b/src/sentry/integrations/web/discord_extension_configuration.py deleted file mode 100644 index 854cabf32982af..00000000000000 --- a/src/sentry/integrations/web/discord_extension_configuration.py +++ /dev/null @@ -1,13 +0,0 @@ -from sentry.integrations.types import IntegrationProviderSlug -from sentry.web.frontend.base import control_silo_view - -from .integration_extension_configuration import IntegrationExtensionConfigurationView - - -@control_silo_view -class DiscordExtensionConfigurationView(IntegrationExtensionConfigurationView): - provider = IntegrationProviderSlug.DISCORD.value - external_provider_key = IntegrationProviderSlug.DISCORD.value - - def map_params_to_state(self, params): - return {"use_configure": "1", **params} diff --git a/src/sentry/middleware/integrations/parsers/discord.py b/src/sentry/middleware/integrations/parsers/discord.py index 390e84ff8c1d38..041a45540c62aa 100644 --- a/src/sentry/middleware/integrations/parsers/discord.py +++ b/src/sentry/middleware/integrations/parsers/discord.py @@ -12,6 +12,7 @@ from sentry.hybridcloud.outbox.category import WebhookProviderIdentifier from sentry.integrations.discord.message_builder.base.flags import EPHEMERAL_FLAG from sentry.integrations.discord.requests.base import DiscordRequest, DiscordRequestError +from sentry.integrations.discord.views.configure_redirect import DiscordConfigureRedirectView from sentry.integrations.discord.views.link_identity import DiscordLinkIdentityView from sentry.integrations.discord.views.unlink_identity import DiscordUnlinkIdentityView from sentry.integrations.discord.webhooks.base import DiscordInteractionsEndpoint @@ -22,9 +23,6 @@ ) from sentry.integrations.models.integration import Integration from sentry.integrations.types import EXTERNAL_PROVIDERS, ExternalProviders -from sentry.integrations.web.discord_extension_configuration import ( - DiscordExtensionConfigurationView, -) from sentry.middleware.integrations.tasks import convert_to_async_discord_response from sentry.types.cell import Cell @@ -38,7 +36,7 @@ class DiscordRequestParser(BaseRequestParser): control_classes = [ DiscordLinkIdentityView, DiscordUnlinkIdentityView, - DiscordExtensionConfigurationView, + DiscordConfigureRedirectView, ] # Dynamically set to avoid RawPostDataException from double reads diff --git a/src/sentry/relocation/api/endpoints/index.py b/src/sentry/relocation/api/endpoints/index.py index e6a44d5cbcdbc2..f9e5c6d573e477 100644 --- a/src/sentry/relocation/api/endpoints/index.py +++ b/src/sentry/relocation/api/endpoints/index.py @@ -3,6 +3,7 @@ from datetime import timedelta from functools import reduce from string import Template +from typing import Any from django.db import router from django.db.models import Q @@ -53,7 +54,7 @@ RELOCATION_FILE_SIZE_MEDIUM = 100 * 1024**2 -def get_relocation_size_category(size) -> str: +def get_relocation_size_category(size: int) -> str: if size < RELOCATION_FILE_SIZE_SMALL: return "small" elif size < RELOCATION_FILE_SIZE_MEDIUM: @@ -81,7 +82,7 @@ def should_throttle_relocation(relocation_bucket_size: str) -> bool: return True -class RelocationsPostSerializer(serializers.Serializer): +class RelocationsPostSerializer(serializers.Serializer[dict[str, Any]]): file = serializers.FileField(required=True) orgs = serializers.CharField(required=True, allow_blank=False, allow_null=False) owner = serializers.CharField( diff --git a/src/sentry/relocation/models/relocation.py b/src/sentry/relocation/models/relocation.py index 3e9e9891ecb84b..42406e23fab43b 100644 --- a/src/sentry/relocation/models/relocation.py +++ b/src/sentry/relocation/models/relocation.py @@ -12,7 +12,7 @@ from sentry.db.models.fields.uuid import UUIDField -def default_guid(): +def default_guid() -> str: return uuid4().hex @@ -57,7 +57,7 @@ def get_in_progress_choices(cls) -> list[tuple[int, str]]: return [(key.value, key.name) for key in cls if key.name != "COMPLETED"] @classmethod - def max_value(cls): + def max_value(cls) -> int: return max(item.value for item in cls) class Status(Enum): @@ -242,7 +242,7 @@ def __str__(self) -> str: else: raise ValueError("Cannot extract a filename from `RelocationFile.Kind.UNKNOWN`.") - def to_filename(self, ext: str): + def to_filename(self, ext: str) -> str: return str(self) + "." + ext relocation = FlexibleForeignKey("sentry.Relocation") diff --git a/src/sentry/relocation/tasks/process.py b/src/sentry/relocation/tasks/process.py index 9b7341f075cea6..72db7295c9cdb7 100644 --- a/src/sentry/relocation/tasks/process.py +++ b/src/sentry/relocation/tasks/process.py @@ -971,11 +971,11 @@ class NextTask: the task to be scheduled at some later point in the execution. """ - task: Task + task: Task[..., Any] args: list[Any] countdown: int | None = None - def schedule(self): + def schedule(self) -> None: """ Run the `.apply_async()` call defined by this future. """ @@ -1126,7 +1126,7 @@ def validating_start(uuid: str) -> None: ): cb_client = CloudBuildClient() - def camel_to_snake_keep_underscores(value): + def camel_to_snake_keep_underscores(value: str) -> str: match = re.search(r"(_++)$", value) converted = camel_to_snake_case(value) return converted + (match.group(0) if match else "") @@ -1708,11 +1708,11 @@ def completed(uuid: str) -> None: processing_deadline_duration=FAST_TIME_LIMIT, silo_mode=SiloMode.CELL, ) -def noop(): +def noop() -> None: pass -TASK_MAP: dict[OrderedTask, Task] = { +TASK_MAP: dict[OrderedTask, Task[..., Any]] = { OrderedTask.NONE: noop, OrderedTask.UPLOADING_START: uploading_start, OrderedTask.UPLOADING_COMPLETE: uploading_complete, @@ -1735,7 +1735,7 @@ def noop(): assert set(OrderedTask._member_map_.keys()) == {k.name for k in TASK_MAP.keys()} -def get_first_task_for_step(target_step: Relocation.Step) -> Task | None: +def get_first_task_for_step(target_step: Relocation.Step) -> Task[..., Any] | None: min_task: OrderedTask | None = None for ordered_task, step in TASK_TO_STEP.items(): if step == target_step: diff --git a/src/sentry/relocation/tasks/transfer.py b/src/sentry/relocation/tasks/transfer.py index 6e6b314b4e3e4e..c1e0f25c11a791 100644 --- a/src/sentry/relocation/tasks/transfer.py +++ b/src/sentry/relocation/tasks/transfer.py @@ -1,4 +1,5 @@ import logging +from typing import Any from django.db.models import Subquery from django.utils import timezone @@ -46,7 +47,7 @@ def find_relocation_transfer_region() -> None: def _find_relocation_transfer( model_cls: type[BaseRelocationTransfer], - process_task: Task, + process_task: Task[..., Any], ) -> None: """ Advance the scheduled_for time for all transfers that are diff --git a/src/sentry/relocation/utils.py b/src/sentry/relocation/utils.py index 0b42c92dd7b882..2191085490efad 100644 --- a/src/sentry/relocation/utils.py +++ b/src/sentry/relocation/utils.py @@ -650,7 +650,7 @@ def make_cloudbuild_step_args(indent: int, args: list[str]) -> str: # The set of arguments to invoke a "docker compose" in a cloudbuild step is tedious and repetitive - # better to just handle it here. @lru_cache(maxsize=1) -def get_docker_compose_cmd(): +def get_docker_compose_cmd() -> str: return make_cloudbuild_step_args( 3, [ @@ -666,7 +666,7 @@ def get_docker_compose_cmd(): # The set of arguments to invoke a "docker compose run" in a cloudbuild step is tedious and # repetitive - better to just handle it here. @lru_cache(maxsize=1) -def get_docker_compose_run(): +def get_docker_compose_run() -> str: return make_cloudbuild_step_args( 3, [ @@ -678,7 +678,7 @@ def get_docker_compose_run(): @lru_cache(maxsize=1) -def get_relocations_bucket_name(): +def get_relocations_bucket_name() -> str: """ When using the local FileSystemStorage (ie, in tests), we use a contrived bucket name, since this is really just an alias for a bespoke local directory in that case. diff --git a/src/sentry/search/snuba/backend.py b/src/sentry/search/snuba/backend.py index 5d54321b08d33a..79f0ea594fafcf 100644 --- a/src/sentry/search/snuba/backend.py +++ b/src/sentry/search/snuba/backend.py @@ -12,7 +12,7 @@ from django.utils import timezone from django.utils.functional import SimpleLazyObject -from sentry import features, quotas +from sentry import quotas from sentry.api.event_search import SearchFilter from sentry.db.models.manager.base_query_set import BaseQuerySet from sentry.exceptions import InvalidSearchQuery @@ -595,11 +595,7 @@ def _get_queryset_conditions( "issue.type": QCallbackCondition(lambda types: Q(type__in=types)), "issue.priority": QCallbackCondition(lambda priorities: Q(priority__in=priorities)), "issue.seer_actionability": QCallbackCondition(seer_actionability_filter), - "issue.seer_last_run": ScalarCondition( - "seer_explorer_autofix_last_triggered" - if features.has("organizations:autofix-on-explorer", organization) - else "seer_autofix_last_triggered" - ), + "issue.seer_last_run": ScalarCondition("seer_explorer_autofix_last_triggered"), "issue.id": QCallbackCondition( lambda ids: Q(id__in=[int(v) for v in (ids if isinstance(ids, list) else [ids])]) ), diff --git a/src/sentry/seer/agent/client_utils.py b/src/sentry/seer/agent/client_utils.py index 07b9e3d2eb584d..591297a8312a26 100644 --- a/src/sentry/seer/agent/client_utils.py +++ b/src/sentry/seer/agent/client_utils.py @@ -208,24 +208,7 @@ def has_seer_agent_access_with_detail( if not has_access: return False, error - feature_names = [ - # Access to seer agent - "organizations:seer-explorer", - # Access to seer agent powered autofix - "organizations:autofix-on-explorer", - ] - - batch_features = features.batch_has( - feature_names, - organization=organization, - actor=actor, - ) - - if batch_features is None: - return False, "Feature flag not enabled" - - org_features = batch_features.get(f"organization:{organization.id}", {}) - if not any(bool(org_features.get(feature_name)) for feature_name in feature_names): + if not features.has("organizations:seer-explorer", organization, actor=actor): return False, "Feature flag not enabled" # Check open team membership (the agent requires this for context) diff --git a/src/sentry/seer/autofix/issue_summary.py b/src/sentry/seer/autofix/issue_summary.py index ff6ce342973c88..af3e22daff94cd 100644 --- a/src/sentry/seer/autofix/issue_summary.py +++ b/src/sentry/seer/autofix/issue_summary.py @@ -18,7 +18,7 @@ from sentry.locks import locks from sentry.models.group import Group from sentry.net.http import connection_from_url -from sentry.seer.autofix.autofix import _get_trace_tree_for_event, trigger_legacy_autofix +from sentry.seer.autofix.autofix import _get_trace_tree_for_event from sentry.seer.autofix.autofix_agent import ( AutofixStep, NoSeerQuotaException, @@ -52,7 +52,6 @@ from sentry.taskworker.namespaces import seer_tasks from sentry.users.models.user import User from sentry.users.services.user.model import RpcUser -from sentry.users.services.user.service import user_service from sentry.utils.cache import cache from sentry.utils.locking import UnableToAcquireLock @@ -176,41 +175,17 @@ def _trigger_autofix_task( } ) - user: User | AnonymousUser | RpcUser | None = None - if user_id: - user = user_service.get_user(user_id=user_id) - if user is None: - logger.warning( - "_trigger_autofix_task.user_not_found", - extra={"group_id": group_id, "user_id": user_id}, - ) - user = AnonymousUser() - else: - user = AnonymousUser() - - # Route to agent-based autofix if both feature flags are enabled run_id: int | None = None - if features.has("organizations:autofix-on-explorer", group.organization): - try: - run_id = trigger_autofix_agent( - group=group, - step=AutofixStep.ROOT_CAUSE, - referrer=referrer, - run_id=None, - stopping_point=stopping_point, - ) - except NoSeerQuotaException: - pass - else: - response = trigger_legacy_autofix( + try: + run_id = trigger_autofix_agent( group=group, - event_id=event_id, - user=user, + step=AutofixStep.ROOT_CAUSE, referrer=referrer, - auto_run_source=auto_run_source, + run_id=None, stopping_point=stopping_point, ) - run_id = response.data.get("run_id") + except NoSeerQuotaException: + pass if run_id and SeerAutofixOperator.has_access(organization=group.project.organization): SeerOperatorAutofixCache.migrate(from_group_id=group_id, to_run_id=run_id) diff --git a/src/sentry/seer/entrypoints/operator.py b/src/sentry/seer/entrypoints/operator.py index 2eee7b381a3ba8..739b1eb9033199 100644 --- a/src/sentry/seer/entrypoints/operator.py +++ b/src/sentry/seer/entrypoints/operator.py @@ -1,8 +1,6 @@ import logging from typing import Any -from rest_framework.response import Response - from sentry import features from sentry.constants import DataCategory from sentry.models.activity import Activity @@ -13,20 +11,8 @@ from sentry.seer.agent.client_models import CodingAgentState, SeerRunState from sentry.seer.agent.client_utils import fetch_run_status from sentry.seer.agent.on_completion_hook import AgentOnCompletionHook -from sentry.seer.autofix.autofix import trigger_legacy_autofix, update_legacy_autofix -from sentry.seer.autofix.constants import AutofixReferrer, AutofixStatus -from sentry.seer.autofix.types import ( - AutofixCreatePRPayload, - AutofixSelectRootCausePayload, - AutofixSelectSolutionPayload, -) -from sentry.seer.autofix.utils import ( - AutofixState, - AutofixStoppingPoint, - get_autofix_state, - get_automation_handoff, -) -from sentry.seer.autofix.utils import CodingAgentState as LegacyCodingAgentState +from sentry.seer.autofix.constants import AutofixReferrer +from sentry.seer.autofix.utils import AutofixStoppingPoint, get_automation_handoff from sentry.seer.entrypoints.cache import SeerOperatorAgentCache, SeerOperatorAutofixCache from sentry.seer.entrypoints.metrics import ( SeerOperatorEventLifecycleMetric, @@ -67,7 +53,6 @@ # entrypoint's ability to receive updates from those triggers. So 12 is plenty, even accounting for # incidents, since a run should not take nearly that long to complete. PROCESS_AUTOFIX_TIMEOUT_SECONDS = 60 * 5 # 5 minutes -AUTOFIX_FALLBACK_CAUSE_ID = 0 def has_seer_autofix_entrypoint_access( @@ -149,22 +134,13 @@ def trigger_autofix( instruction: str | None = None, run_id: int | None = None, ) -> None: - if features.has("organizations:autofix-on-explorer", group.organization): - self.trigger_autofix_agent( - group=group, - user=user, - stopping_point=stopping_point, - instruction=instruction, - run_id=run_id, - ) - else: - self.trigger_autofix_legacy( - group=group, - user=user, - stopping_point=stopping_point, - instruction=instruction, - run_id=run_id, - ) + self.trigger_autofix_agent( + group=group, + user=user, + stopping_point=stopping_point, + instruction=instruction, + run_id=run_id, + ) def trigger_autofix_agent( self, @@ -352,17 +328,8 @@ def trigger_handoff( ) try: - coding_agents: list[CodingAgentState] | list[LegacyCodingAgentState] - if features.has("organizations:autofix-on-explorer", group.organization): - agent_state = fetch_run_status(run_id=run_id, organization=group.organization) - coding_agents = list(agent_state.coding_agents.values()) - else: - autofix_state = get_autofix_state( - run_id=run_id, organization_id=group.organization.id - ) - coding_agents = ( - list(autofix_state.coding_agents.values()) if autofix_state else [] - ) + agent_state = fetch_run_status(run_id=run_id, organization=group.organization) + coding_agents: list[CodingAgentState] = list(agent_state.coding_agents.values()) except Exception as e: with SeerOperatorEventLifecycleMetric( interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_HANDOFF_ERROR, @@ -419,162 +386,6 @@ def trigger_handoff( ).capture(): self.entrypoint.on_trigger_handoff_success(run_id=run_id, target=target) - def trigger_autofix_legacy( - self, - *, - group: Group, - user: User | RpcUser, - stopping_point: AutofixStoppingPoint, - instruction: str | None = None, - run_id: int | None = None, - ) -> None: - event_lifecyle = SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.OPERATOR_TRIGGER_AUTOFIX, - entrypoint_key=self.entrypoint.key, - ) - - raw_response: Response | None = None - with event_lifecyle.capture() as lifecycle: - lifecycle.add_extras( - { - "group_id": str(group.id), - "user_id": str(user.id), - "stopping_point": str(stopping_point), - } - ) - try: - existing_state = get_autofix_state( - group_id=group.id, organization_id=group.organization.id - ) - except Exception as e: - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR, - entrypoint_key=self.entrypoint.key, - ).capture(): - self.entrypoint.on_trigger_autofix_error( - error="Encountered an error while talking to Seer" - ) - lifecycle.record_failure(failure_reason=e) - return - if existing_state: - stopping_point_step = get_stopping_point_status(stopping_point, existing_state) - lifecycle.add_extras( - { - "existing_run_id": str(existing_state.run_id), - "existing_run_status": str(existing_state.status), - } - ) - # For now, we don't support re-runs over slack -- it causes a confusing UX without - # reliably being able to edit messages. - if stopping_point_step: - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ALREADY_EXISTS, - entrypoint_key=self.entrypoint.key, - ).capture(): - has_complete_stage = ( - False - if stopping_point_step.get("key") - in {"root_cause_analysis_processing", "solution_processing"} - else stopping_point_step.get("status") == AutofixStatus.COMPLETED - ) - self.entrypoint.on_trigger_autofix_already_exists( - run_id=existing_state.run_id, - has_complete_stage=has_complete_stage, - ) - return - - if not run_id: - raw_response = trigger_legacy_autofix( - group=group, - user=user, - referrer=AutofixReferrer.SLACK, - instruction=instruction, - stopping_point=stopping_point, - ) - else: - payload: ( - AutofixSelectRootCausePayload - | AutofixSelectSolutionPayload - | AutofixCreatePRPayload - | None - ) = None - if stopping_point == AutofixStoppingPoint.SOLUTION: - payload = AutofixSelectRootCausePayload( - type="select_root_cause", - cause_id=get_latest_cause_id(existing_state), - ) - elif stopping_point == AutofixStoppingPoint.CODE_CHANGES: - payload = AutofixSelectSolutionPayload(type="select_solution") - elif stopping_point == AutofixStoppingPoint.OPEN_PR: - payload = AutofixCreatePRPayload(type="create_pr") - else: - lifecycle.record_failure(failure_reason="invalid_stopping_point") - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR, - entrypoint_key=self.entrypoint.key, - ).capture(): - self.entrypoint.on_trigger_autofix_error( - error="Invalid stopping point provided" - ) - return - - raw_response = update_legacy_autofix( - organization_id=group.organization.id, - run_id=run_id, - payload=payload, - ) - - error_message = raw_response.data.get("detail") - - # Let the entrypoint signal to the external service that no run was started :/ - if error_message: - lifecycle.record_failure(failure_reason=error_message) - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR, - entrypoint_key=self.entrypoint.key, - ).capture(): - self.entrypoint.on_trigger_autofix_error(error=error_message) - return - - run_id = raw_response.data.get("run_id") if not run_id else run_id - if not run_id: - lifecycle.record_failure(failure_reason="no_run_id") - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_ERROR, - entrypoint_key=self.entrypoint.key, - ).capture(): - self.entrypoint.on_trigger_autofix_error(error="An unknown error has occurred") - return - lifecycle.add_extra("run_id", str(run_id)) - - # Let the entrypoint signal to the external service that the run started - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_ON_TRIGGER_AUTOFIX_SUCCESS, - entrypoint_key=self.entrypoint.key, - ).capture(): - self.entrypoint.on_trigger_autofix_success(run_id=run_id) - - # Create a cache payload that will be picked up for subsequent updates - with SeerOperatorEventLifecycleMetric( - interaction_type=SeerOperatorInteractionType.ENTRYPOINT_CREATE_AUTOFIX_CACHE_PAYLOAD, - entrypoint_key=self.entrypoint.key, - ).capture(): - cache_payload = self.entrypoint.create_autofix_cache_payload() - - if not cache_payload: - return - cache_result = SeerOperatorAutofixCache.populate_post_autofix_cache( - entrypoint_key=str(self.entrypoint.key), - cache_payload=cache_payload, - run_id=run_id, - ) - lifecycle.add_extras( - { - "cache_key": cache_result["key"], - "cache_source": cache_result["source"], - } - ) - def has_seer_agent_entrypoint_access( *, @@ -879,44 +690,6 @@ def process_autofix_updates( ept_lifecycle.record_failure(failure_reason=e) -def get_stopping_point_status( - stopping_point: AutofixStoppingPoint, autofix_state: AutofixState -) -> dict | None: - """ - Gets the most recent matching step state from a given stopping point. - """ - # The most recent of a repeated step is at the end of the list, that's what we want to surface - steps = reversed(autofix_state.steps) - match stopping_point: - case AutofixStoppingPoint.ROOT_CAUSE: - step = next( - ( - step - for step in steps - if step.get("key") in {"root_cause_analysis", "root_cause_analysis_processing"} - ), - None, - ) - case AutofixStoppingPoint.SOLUTION: - step = next( - (step for step in steps if step.get("key") in {"solution", "solution_processing"}), - None, - ) - case AutofixStoppingPoint.CODE_CHANGES: - step = next((step for step in steps if step.get("key") == "changes"), None) - case AutofixStoppingPoint.OPEN_PR: - step = next( - ( - step - for step in steps - if step.get("key") == "changes" - and any(change.get("pull_request") for change in step.get("changes", [])) - ), - None, - ) - return step - - def get_autofix_explorer_status( stopping_point: AutofixStoppingPoint, autofix_state: SeerRunState ) -> bool | None: @@ -975,32 +748,6 @@ def get_autofix_explorer_status( return None -def get_latest_cause_id(autofix_state: AutofixState | None) -> int: - """ - Gets the latest cause_id from a given autofix state. - """ - if not autofix_state: - return AUTOFIX_FALLBACK_CAUSE_ID - root_cause_step = next( - ( - step - # If there are multiple RCA steps, we want the latest, so we reverse the list - for step in reversed(autofix_state.steps) - if step.get("key") == "root_cause_analysis" - ), - None, - ) - if not root_cause_step: - return AUTOFIX_FALLBACK_CAUSE_ID - - root_causes = root_cause_step.get("causes", []) - if not root_causes: - return AUTOFIX_FALLBACK_CAUSE_ID - - # The most recent cause is at the end of the list - return root_causes[-1].get("id", AUTOFIX_FALLBACK_CAUSE_ID) - - class SeerOperatorCompletionHook(AgentOnCompletionHook): """Completion hook that notifies all entrypoints when a Seer Agent run finishes. diff --git a/static/app/components/charts/chartWidgetLoader.tsx b/static/app/components/charts/chartWidgetLoader.tsx index eeb3c1be5fab86..4ba7a2730c036b 100644 --- a/static/app/components/charts/chartWidgetLoader.tsx +++ b/static/app/components/charts/chartWidgetLoader.tsx @@ -5,7 +5,7 @@ import {Placeholder} from 'sentry/components/placeholder'; import {t} from 'sentry/locale'; import type {ReactEchartsRef} from 'sentry/types/echarts'; import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types'; -import {EVENT_GRAPH_WIDGET_ID} from 'sentry/views/issueDetails/streamline/eventGraphWidget'; +import {EVENT_GRAPH_WIDGET_ID} from 'sentry/views/issueDetails/eventGraphWidget'; export type ChartId = keyof typeof CHART_MAP; interface Props extends LoadableChartWidgetProps { @@ -18,8 +18,7 @@ interface Props extends LoadableChartWidgetProps { } // We need to map the widget id to the dynamic import because we want the import paths to be statically analyzable. const CHART_MAP = { - [EVENT_GRAPH_WIDGET_ID]: () => - import('sentry/views/issueDetails/streamline/eventGraphWidget'), + [EVENT_GRAPH_WIDGET_ID]: () => import('sentry/views/issueDetails/eventGraphWidget'), } satisfies Record Promise<{default: React.FC}>>; /** diff --git a/static/app/components/events/autofix/v3/content.tsx b/static/app/components/events/autofix/v3/content.tsx index 6a0d138fab09f5..37c5412400e19a 100644 --- a/static/app/components/events/autofix/v3/content.tsx +++ b/static/app/components/events/autofix/v3/content.tsx @@ -24,7 +24,7 @@ import {Placeholder} from 'sentry/components/placeholder'; import {IconClose} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {Group} from 'sentry/types/group'; -import type {useAiConfig} from 'sentry/views/issueDetails/streamline/hooks/useAiConfig'; +import type {useAiConfig} from 'sentry/views/issueDetails/hooks/useAiConfig'; interface SeerDrawerContentProps { aiConfig: ReturnType; diff --git a/static/app/components/events/autofix/v3/drawer.tsx b/static/app/components/events/autofix/v3/drawer.tsx index b3952c1671f96c..da89552d27bcc0 100644 --- a/static/app/components/events/autofix/v3/drawer.tsx +++ b/static/app/components/events/autofix/v3/drawer.tsx @@ -19,7 +19,7 @@ import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; import {useAutoScroll} from 'sentry/utils/useAutoScroll'; import {useCopyToClipboard} from 'sentry/utils/useCopyToClipboard'; -import {useAiConfig} from 'sentry/views/issueDetails/streamline/hooks/useAiConfig'; +import {useAiConfig} from 'sentry/views/issueDetails/hooks/useAiConfig'; interface SeerDrawerProps { group: Group; diff --git a/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx b/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx index c293bd388cbe4e..a1685c6ed6d655 100644 --- a/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx +++ b/static/app/components/events/breadcrumbs/breadcrumbsDataSection.tsx @@ -32,8 +32,8 @@ import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useLocalStorageState} from 'sentry/utils/useLocalStorageState'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface BreadcrumbsDataSectionProps { event: Event; diff --git a/static/app/components/events/contexts/contextDataSection.tsx b/static/app/components/events/contexts/contextDataSection.tsx index dc1941696ca1b3..f08ad447b59cde 100644 --- a/static/app/components/events/contexts/contextDataSection.tsx +++ b/static/app/components/events/contexts/contextDataSection.tsx @@ -5,8 +5,8 @@ import {KeyValueData} from 'sentry/components/keyValueData'; import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Project} from 'sentry/types/project'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface ContextDataSectionProps { event: Event; diff --git a/static/app/components/events/device.tsx b/static/app/components/events/device.tsx index fb180f41ff83f1..40bd38765782c1 100644 --- a/static/app/components/events/device.tsx +++ b/static/app/components/events/device.tsx @@ -1,8 +1,8 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {KeyValueList} from './interfaces/keyValueList'; diff --git a/static/app/components/events/eventAttachments.tsx b/static/app/components/events/eventAttachments.tsx index 5d0dff420e4a53..9345f675e1dba6 100644 --- a/static/app/components/events/eventAttachments.tsx +++ b/static/app/components/events/eventAttachments.tsx @@ -18,9 +18,9 @@ import type {Group, IssueAttachment} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {InlineEventAttachment} from 'sentry/views/issueDetails/groupEventAttachments/inlineEventAttachment'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {Tab, TabPaths} from 'sentry/views/issueDetails/types'; import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute'; diff --git a/static/app/components/events/eventDrawer.tsx b/static/app/components/events/eventDrawer.tsx index 99678c507bd7dd..51b4be494c7833 100644 --- a/static/app/components/events/eventDrawer.tsx +++ b/static/app/components/events/eventDrawer.tsx @@ -6,7 +6,7 @@ import {InputGroup} from '@sentry/scraps/input'; import {Flex, type FlexProps} from '@sentry/scraps/layout'; import {Breadcrumbs as NavigationBreadcrumbs} from 'sentry/components/breadcrumbs'; -import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/streamline/eventTitle'; +import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/eventTitle'; import { NAVIGATION_MOBILE_TOPBAR_HEIGHT_WITH_PAGE_FRAME, PRIMARY_HEADER_HEIGHT, diff --git a/static/app/components/events/eventEvidence.tsx b/static/app/components/events/eventEvidence.tsx index 6a623292ae3a93..63dd4da126e83f 100644 --- a/static/app/components/events/eventEvidence.tsx +++ b/static/app/components/events/eventEvidence.tsx @@ -8,8 +8,8 @@ import { getConfigForIssueType, getIssueCategoryAndTypeFromOccurrenceType, } from 'sentry/utils/issueTypeConfig'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type EvidenceProps = { event: Event; diff --git a/static/app/components/events/eventExtraData/index.tsx b/static/app/components/events/eventExtraData/index.tsx index 90bf932a23137e..284bdb2d877de9 100644 --- a/static/app/components/events/eventExtraData/index.tsx +++ b/static/app/components/events/eventExtraData/index.tsx @@ -11,8 +11,8 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {getEventExtraDataKnownDataDetails} from './getEventExtraDataKnownDataDetails'; import type {EventExtraDataType, EventExtraData as TEventExtraData} from './types'; diff --git a/static/app/components/events/eventHydrationDiff/replayDiffContent.tsx b/static/app/components/events/eventHydrationDiff/replayDiffContent.tsx index cc9d285311ccac..ae7577cd5de303 100644 --- a/static/app/components/events/eventHydrationDiff/replayDiffContent.tsx +++ b/static/app/components/events/eventHydrationDiff/replayDiffContent.tsx @@ -18,8 +18,8 @@ import type {Group} from 'sentry/types/group'; import {getReplayDiffOffsetsFromEvent} from 'sentry/utils/replays/getDiffTimestamps'; import {useLoadReplayReader} from 'sentry/utils/replays/hooks/useLoadReplayReader'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface Props { event: Event; diff --git a/static/app/components/events/eventHydrationDiff/replayDiffSection.tsx b/static/app/components/events/eventHydrationDiff/replayDiffSection.tsx index 83207eda032e97..6c2f887e1f9e21 100644 --- a/static/app/components/events/eventHydrationDiff/replayDiffSection.tsx +++ b/static/app/components/events/eventHydrationDiff/replayDiffSection.tsx @@ -11,9 +11,9 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {SectionDivider} from 'sentry/views/issueDetails/streamline/foldSection'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {SectionDivider} from 'sentry/views/issueDetails/foldSection'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface Props { event: Event; diff --git a/static/app/components/events/eventInsightDiff.tsx b/static/app/components/events/eventInsightDiff.tsx index 740c02f7a466c3..c183733f11c968 100644 --- a/static/app/components/events/eventInsightDiff.tsx +++ b/static/app/components/events/eventInsightDiff.tsx @@ -11,8 +11,8 @@ import { type MetricIds, useSizeAnalysisComparison, } from 'sentry/utils/preprod/useSizeAnalysisComparison'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {InsightComparisonSection} from 'sentry/views/preprod/buildComparison/main/insightComparisonSection'; type SectionProps = MetricIds & {project: Project}; diff --git a/static/app/components/events/eventProcessingErrors.tsx b/static/app/components/events/eventProcessingErrors.tsx index 2f98ebf555b188..950d45f82bfe23 100644 --- a/static/app/components/events/eventProcessingErrors.tsx +++ b/static/app/components/events/eventProcessingErrors.tsx @@ -12,8 +12,8 @@ import {QuestionTooltip} from 'sentry/components/questionTooltip'; import {t, tct} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Project} from 'sentry/types/project'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type Props = { event: Event; diff --git a/static/app/components/events/eventReplay/replayClipSection.tsx b/static/app/components/events/eventReplay/replayClipSection.tsx index feb5c63f924fbe..b445e7dd94f909 100644 --- a/static/app/components/events/eventReplay/replayClipSection.tsx +++ b/static/app/components/events/eventReplay/replayClipSection.tsx @@ -16,8 +16,8 @@ import type {Group} from 'sentry/types/group'; import {getAnalyticsDataForEvent, getAnalyticsDataForGroup} from 'sentry/utils/events'; import {useReplayCountForIssues} from 'sentry/utils/replayCount/useReplayCountForIssues'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {Tab, TabPaths} from 'sentry/views/issueDetails/types'; import {useGroupDetailsRoute} from 'sentry/views/issueDetails/useGroupDetailsRoute'; diff --git a/static/app/components/events/eventReplay/replayInlineOnboardingPanel.tsx b/static/app/components/events/eventReplay/replayInlineOnboardingPanel.tsx index 6dc866c087b46b..ee11551bef1e60 100644 --- a/static/app/components/events/eventReplay/replayInlineOnboardingPanel.tsx +++ b/static/app/components/events/eventReplay/replayInlineOnboardingPanel.tsx @@ -16,8 +16,8 @@ import {trackAnalytics} from 'sentry/utils/analytics'; import {useReplayOnboardingSidebarPanel} from 'sentry/utils/replays/hooks/useReplayOnboarding'; import {useMedia} from 'sentry/utils/useMedia'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type OnboardingCTAProps = { platform: PlatformKey; diff --git a/static/app/components/events/eventSdk.tsx b/static/app/components/events/eventSdk.tsx index 6c01f6fdca0ae7..c70491afe24f41 100644 --- a/static/app/components/events/eventSdk.tsx +++ b/static/app/components/events/eventSdk.tsx @@ -1,8 +1,8 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {KeyValueList} from './interfaces/keyValueList'; import {AnnotatedText} from './meta/annotatedText'; diff --git a/static/app/components/events/eventStatisticalDetector/aggregateSpanDiff.tsx b/static/app/components/events/eventStatisticalDetector/aggregateSpanDiff.tsx index fa1fc9f20ac3a6..dfbaf9c2e7d3f5 100644 --- a/static/app/components/events/eventStatisticalDetector/aggregateSpanDiff.tsx +++ b/static/app/components/events/eventStatisticalDetector/aggregateSpanDiff.tsx @@ -14,8 +14,8 @@ import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useSpans} from 'sentry/views/insights/common/queries/useDiscover'; import {SpanFields} from 'sentry/views/insights/types'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {makeTracesPathname} from 'sentry/views/traces/pathnames'; import {EventRegressionTable, type EventRegressionTableRow} from './eventRegressionTable'; diff --git a/static/app/components/events/eventStatisticalDetector/breakpointChart.tsx b/static/app/components/events/eventStatisticalDetector/breakpointChart.tsx index 983072fdedee8f..77917c28cb3eff 100644 --- a/static/app/components/events/eventStatisticalDetector/breakpointChart.tsx +++ b/static/app/components/events/eventStatisticalDetector/breakpointChart.tsx @@ -17,8 +17,8 @@ import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; import {Mode} from 'sentry/views/explore/queryParams/mode'; import {getExploreUrl} from 'sentry/views/explore/utils'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import type {BreakpointEvidenceData} from './breakpointChartOptions'; import {RELATIVE_DAYS_WINDOW} from './consts'; diff --git a/static/app/components/events/eventStatisticalDetector/eventComparison/index.tsx b/static/app/components/events/eventStatisticalDetector/eventComparison/index.tsx index e68cf4e6db02df..074ce80440dd5f 100644 --- a/static/app/components/events/eventStatisticalDetector/eventComparison/index.tsx +++ b/static/app/components/events/eventStatisticalDetector/eventComparison/index.tsx @@ -6,8 +6,8 @@ import {EventDisplay} from 'sentry/components/events/eventStatisticalDetector/ev import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Project} from 'sentry/types/project'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; const COMPARISON_DESCRIPTION = t( 'To better understand what happened before and after this regression, compare a baseline event with a regressed event. Look for any significant shape changes, operation percentage changes, and tag differences.' diff --git a/static/app/components/events/eventStatisticalDetector/eventRegressionSummary.tsx b/static/app/components/events/eventStatisticalDetector/eventRegressionSummary.tsx index b04dff7fdbe286..138caa6474a6b4 100644 --- a/static/app/components/events/eventStatisticalDetector/eventRegressionSummary.tsx +++ b/static/app/components/events/eventStatisticalDetector/eventRegressionSummary.tsx @@ -14,8 +14,8 @@ import {getFormat, getFormattedDate} from 'sentry/utils/dates'; import {getDuration} from 'sentry/utils/duration/getDuration'; import {formatPercentage} from 'sentry/utils/number/formatPercentage'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import { DisplayModes, transactionSummaryRouteWithQuery, diff --git a/static/app/components/events/eventStatisticalDetector/functionBreakpointChart.tsx b/static/app/components/events/eventStatisticalDetector/functionBreakpointChart.tsx index 8bfb74bab1f4af..71e6db983bcd20 100644 --- a/static/app/components/events/eventStatisticalDetector/functionBreakpointChart.tsx +++ b/static/app/components/events/eventStatisticalDetector/functionBreakpointChart.tsx @@ -9,8 +9,8 @@ import type {Event} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {useProfileEventsStats} from 'sentry/utils/profiling/hooks/useProfileEventsStats'; import {useRelativeDateTime} from 'sentry/utils/profiling/hooks/useRelativeDateTime'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {RELATIVE_DAYS_WINDOW} from './consts'; diff --git a/static/app/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection.tsx b/static/app/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection.tsx index aa4204f13f0602..85f6bc29254c32 100644 --- a/static/app/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection.tsx +++ b/static/app/components/events/eventTagsAndScreenshot/screenshot/screenshotDataSection.tsx @@ -19,9 +19,9 @@ import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {EventAttachmentFilter} from 'sentry/views/issueDetails/groupEventAttachments/groupEventAttachmentsFilter'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; import {Tab, TabPaths} from 'sentry/views/issueDetails/types'; interface ScreenshotDataSectionProps { diff --git a/static/app/components/events/eventTagsAndScreenshot/tags.tsx b/static/app/components/events/eventTagsAndScreenshot/tags.tsx index db78ed223d2ecc..f5a8fdd7ed1fda 100644 --- a/static/app/components/events/eventTagsAndScreenshot/tags.tsx +++ b/static/app/components/events/eventTagsAndScreenshot/tags.tsx @@ -14,8 +14,8 @@ import { import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Project} from 'sentry/types/project'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type Props = { event: Event; diff --git a/static/app/components/events/eventViewHierarchy.tsx b/static/app/components/events/eventViewHierarchy.tsx index 928a02b13437a5..190f32972aac18 100644 --- a/static/app/components/events/eventViewHierarchy.tsx +++ b/static/app/components/events/eventViewHierarchy.tsx @@ -15,8 +15,8 @@ import {defined} from 'sentry/utils'; import type {getApiUrl} from 'sentry/utils/api/getApiUrl'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import type {ViewHierarchyData} from './viewHierarchy'; import {ViewHierarchy} from './viewHierarchy'; diff --git a/static/app/components/events/eventXrayDiff.tsx b/static/app/components/events/eventXrayDiff.tsx index 6832694a17d87e..5abeb1538a04c3 100644 --- a/static/app/components/events/eventXrayDiff.tsx +++ b/static/app/components/events/eventXrayDiff.tsx @@ -10,8 +10,8 @@ import { type MetricIds, useSizeAnalysisComparison, } from 'sentry/utils/preprod/useSizeAnalysisComparison'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {TreemapDiffSection} from 'sentry/views/preprod/buildComparison/main/treemapDiffSection'; type SectionProps = MetricIds & {project: Project}; diff --git a/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx b/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx index 346dc0c249ad73..fc2fc3da7b11b8 100644 --- a/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx +++ b/static/app/components/events/featureFlags/eventFeatureFlagSection.tsx @@ -36,9 +36,9 @@ import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import {useLocation} from 'sentry/utils/useLocation'; import {useMedia} from 'sentry/utils/useMedia'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; -import {useIssueDetailsEventView} from 'sentry/views/issueDetails/streamline/hooks/useIssueDetailsDiscoverQuery'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; +import {useIssueDetailsEventView} from 'sentry/views/issueDetails/hooks/useIssueDetailsDiscoverQuery'; export function EventFeatureFlagSection(props: EventFeatureFlagSectionProps) { return ( diff --git a/static/app/components/events/groupingInfo/groupingInfoSection.tsx b/static/app/components/events/groupingInfo/groupingInfoSection.tsx index 05df74bfbd10af..260c04563ea7e1 100644 --- a/static/app/components/events/groupingInfo/groupingInfoSection.tsx +++ b/static/app/components/events/groupingInfo/groupingInfoSection.tsx @@ -4,8 +4,8 @@ import {LazyLoad} from 'sentry/components/lazyLoad'; import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface EventGroupingInfoSectionProps { event: Event; diff --git a/static/app/components/events/highlights/highlightsDataSection.tsx b/static/app/components/events/highlights/highlightsDataSection.tsx index dfb8debc94939d..bcab1cc70efe05 100644 --- a/static/app/components/events/highlights/highlightsDataSection.tsx +++ b/static/app/components/events/highlights/highlightsDataSection.tsx @@ -33,8 +33,8 @@ import {useDetailedProject} from 'sentry/utils/project/useDetailedProject'; import {useReplayData} from 'sentry/utils/replays/hooks/useReplayData'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; interface HighlightsDataSectionProps { event: Event; diff --git a/static/app/components/events/highlights/highlightsIconSummary.tsx b/static/app/components/events/highlights/highlightsIconSummary.tsx index 6dc1f3e5f0bf01..7cc5dfb25e6066 100644 --- a/static/app/components/events/highlights/highlightsIconSummary.tsx +++ b/static/app/components/events/highlights/highlightsIconSummary.tsx @@ -31,7 +31,7 @@ import type {Organization} from 'sentry/types/organization'; import {isMobilePlatform, isNativePlatform} from 'sentry/utils/platform'; import {useOrganization} from 'sentry/utils/useOrganization'; import {Divider} from 'sentry/views/issueDetails/divider'; -import {SectionDivider} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionDivider} from 'sentry/views/issueDetails/foldSection'; interface HighlightsIconSummaryProps { event: Event; diff --git a/static/app/components/events/interfaces/crashContent/exception/content.tsx b/static/app/components/events/interfaces/crashContent/exception/content.tsx index de1fab3ad99383..7c3f945b7011ad 100644 --- a/static/app/components/events/interfaces/crashContent/exception/content.tsx +++ b/static/app/components/events/interfaces/crashContent/exception/content.tsx @@ -22,11 +22,8 @@ import {StackType} from 'sentry/types/stacktrace'; import {defined} from 'sentry/utils'; import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; import {useProjects} from 'sentry/utils/useProjects'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import { - FoldSection, - SectionDivider, -} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection, SectionDivider} from 'sentry/views/issueDetails/foldSection'; import {useIsSampleEvent} from 'sentry/views/issueDetails/utils'; import {Mechanism} from './mechanism'; diff --git a/static/app/components/events/interfaces/csp/index.tsx b/static/app/components/events/interfaces/csp/index.tsx index 2c7d93aa47a9ad..029658ace16a2d 100644 --- a/static/app/components/events/interfaces/csp/index.tsx +++ b/static/app/components/events/interfaces/csp/index.tsx @@ -6,8 +6,8 @@ import {KeyValueList} from 'sentry/components/events/interfaces/keyValueList'; import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {EntryType} from 'sentry/types/event'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import type {HelpProps} from './help'; import {CSPHelp as Help} from './help'; diff --git a/static/app/components/events/interfaces/debugMeta/index.tsx b/static/app/components/events/interfaces/debugMeta/index.tsx index f5e0a6212e0095..6791cc7ae63ad2 100644 --- a/static/app/components/events/interfaces/debugMeta/index.tsx +++ b/static/app/components/events/interfaces/debugMeta/index.tsx @@ -32,8 +32,8 @@ import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {Status} from './debugImage/status'; import {DebugImage} from './debugImage'; diff --git a/static/app/components/events/interfaces/exception.tsx b/static/app/components/events/interfaces/exception.tsx index 5d99cb27c37e65..fcf3be5f7ef122 100644 --- a/static/app/components/events/interfaces/exception.tsx +++ b/static/app/components/events/interfaces/exception.tsx @@ -9,7 +9,7 @@ import type {Event, ExceptionType} from 'sentry/types/event'; import {EntryType} from 'sentry/types/event'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; -import {SectionDivider} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionDivider} from 'sentry/views/issueDetails/foldSection'; import {ExceptionContent} from './crashContent/exception'; import {NoStackTraceMessage} from './noStackTraceMessage'; diff --git a/static/app/components/events/interfaces/frame/deprecatedLine.tsx b/static/app/components/events/interfaces/frame/deprecatedLine.tsx index 5bf851989d8246..a4e22566a91476 100644 --- a/static/app/components/events/interfaces/frame/deprecatedLine.tsx +++ b/static/app/components/events/interfaces/frame/deprecatedLine.tsx @@ -26,7 +26,7 @@ import type {StacktraceType} from 'sentry/types/stacktrace'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useSentryAppComponentsStore} from 'sentry/utils/useSentryAppComponentsStore'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; +import {SectionKey} from 'sentry/views/issueDetails/context'; import {Context} from './context'; import {DefaultTitle} from './defaultTitle'; diff --git a/static/app/components/events/interfaces/generic.spec.tsx b/static/app/components/events/interfaces/generic.spec.tsx index 6b9601f48f4eae..1add23387a3a32 100644 --- a/static/app/components/events/interfaces/generic.spec.tsx +++ b/static/app/components/events/interfaces/generic.spec.tsx @@ -5,7 +5,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {textWithMarkupMatcher} from 'sentry-test/utils'; import {Generic} from 'sentry/components/events/interfaces/generic'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; +import {SectionKey} from 'sentry/views/issueDetails/context'; describe('Generic entry', () => { it('display redacted data', async () => { diff --git a/static/app/components/events/interfaces/generic.tsx b/static/app/components/events/interfaces/generic.tsx index bccb0f2853ef86..0fbcd8a385fd18 100644 --- a/static/app/components/events/interfaces/generic.tsx +++ b/static/app/components/events/interfaces/generic.tsx @@ -5,7 +5,7 @@ import {SegmentedControl} from '@sentry/scraps/segmentedControl'; import {KeyValueList} from 'sentry/components/events/interfaces/keyValueList'; import {AnnotatedText} from 'sentry/components/events/meta/annotatedText'; import {t} from 'sentry/locale'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; function getView({ data, diff --git a/static/app/components/events/interfaces/message.tsx b/static/app/components/events/interfaces/message.tsx index fce148da8abd38..95077281e42b84 100644 --- a/static/app/components/events/interfaces/message.tsx +++ b/static/app/components/events/interfaces/message.tsx @@ -7,8 +7,8 @@ import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {EntryType} from 'sentry/types/event'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type Props = { data: { diff --git a/static/app/components/events/interfaces/nativeFrame.tsx b/static/app/components/events/interfaces/nativeFrame.tsx index e180402b5d805b..3e1a4e775aace4 100644 --- a/static/app/components/events/interfaces/nativeFrame.tsx +++ b/static/app/components/events/interfaces/nativeFrame.tsx @@ -40,8 +40,8 @@ import {StackView, type StacktraceType} from 'sentry/types/stacktrace'; import {defined} from 'sentry/utils'; import {useSentryAppComponentsStore} from 'sentry/utils/useSentryAppComponentsStore'; import {useSyncedLocalStorageState} from 'sentry/utils/useSyncedLocalStorageState'; -import {SectionKey, useIssueDetails} from 'sentry/views/issueDetails/streamline/context'; -import {getFoldSectionKey} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey, useIssueDetails} from 'sentry/views/issueDetails/context'; +import {getFoldSectionKey} from 'sentry/views/issueDetails/foldSection'; import {useOptionalDebugMetaSearch} from './debugMeta/debugMetaSearchContext'; import {combineStatus} from './debugMeta/utils'; diff --git a/static/app/components/events/interfaces/performance/anrRootCause.tsx b/static/app/components/events/interfaces/performance/anrRootCause.tsx index 577708c1632e17..4000ee3d4bea46 100644 --- a/static/app/components/events/interfaces/performance/anrRootCause.tsx +++ b/static/app/components/events/interfaces/performance/anrRootCause.tsx @@ -23,8 +23,8 @@ import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {useLocation} from 'sentry/utils/useLocation'; import {useProjects} from 'sentry/utils/useProjects'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {useIssuesTraceTree} from 'sentry/views/performance/newTraceDetails/traceApi/useIssuesTraceTree'; import {useTrace} from 'sentry/views/performance/newTraceDetails/traceApi/useTrace'; import {useTraceStateAnalytics} from 'sentry/views/performance/newTraceDetails/useTraceStateAnalytics'; diff --git a/static/app/components/events/interfaces/performance/eventTraceView.tsx b/static/app/components/events/interfaces/performance/eventTraceView.tsx index 2dc5d2d6e05bbe..e5eebcf9423ff6 100644 --- a/static/app/components/events/interfaces/performance/eventTraceView.tsx +++ b/static/app/components/events/interfaces/performance/eventTraceView.tsx @@ -19,8 +19,8 @@ import type {Organization} from 'sentry/types/organization'; import {getConfigForIssueType} from 'sentry/utils/issueTypeConfig'; import {useRouteAnalyticsParams} from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; import {useLocation} from 'sentry/utils/useLocation'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {TraceIssueEvent} from 'sentry/views/issueDetails/traceTimeline/traceIssue'; import {useTraceTimelineEvents} from 'sentry/views/issueDetails/traceTimeline/useTraceTimelineEvents'; import {IssuesTraceWaterfall} from 'sentry/views/performance/newTraceDetails/issuesTraceWaterfall'; diff --git a/static/app/components/events/interfaces/performance/spanEvidence.tsx b/static/app/components/events/interfaces/performance/spanEvidence.tsx index 840c932f78af96..75e7190ad60469 100644 --- a/static/app/components/events/interfaces/performance/spanEvidence.tsx +++ b/static/app/components/events/interfaces/performance/spanEvidence.tsx @@ -12,8 +12,8 @@ import { } from 'sentry/types/group'; import type {Organization} from 'sentry/types/organization'; import {sanitizeQuerySelector} from 'sentry/utils/sanitizeQuerySelector'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {SpanEvidenceKeyValueList} from './spanEvidenceKeyValueList'; diff --git a/static/app/components/events/interfaces/request/index.tsx b/static/app/components/events/interfaces/request/index.tsx index 6684623b48906d..1c4e918703f01d 100644 --- a/static/app/components/events/interfaces/request/index.tsx +++ b/static/app/components/events/interfaces/request/index.tsx @@ -24,8 +24,8 @@ import type {EntryRequest, Event} from 'sentry/types/event'; import {EntryType} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {isValidUrl} from 'sentry/utils/string/isValidUrl'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {getTransformedData} from './getTransformedData'; diff --git a/static/app/components/events/interfaces/template.tsx b/static/app/components/events/interfaces/template.tsx index 25f171eb10663c..e6486bb3e18d86 100644 --- a/static/app/components/events/interfaces/template.tsx +++ b/static/app/components/events/interfaces/template.tsx @@ -1,8 +1,8 @@ import {t} from 'sentry/locale'; import type {Event, Frame} from 'sentry/types/event'; import {EntryType} from 'sentry/types/event'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {DeprecatedLine} from './frame/deprecatedLine'; diff --git a/static/app/components/events/interfaces/threads.tsx b/static/app/components/events/interfaces/threads.tsx index 8a50b30e458d01..96d3dff02a820b 100644 --- a/static/app/components/events/interfaces/threads.tsx +++ b/static/app/components/events/interfaces/threads.tsx @@ -36,9 +36,9 @@ import type {Group} from 'sentry/types/group'; import type {PlatformKey, Project} from 'sentry/types/project'; import {StackType, StackView} from 'sentry/types/stacktrace'; import {defined} from 'sentry/utils'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; -import {setActiveThreadId} from 'sentry/views/issueDetails/streamline/hooks/useCopyIssueDetails'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; +import {setActiveThreadId} from 'sentry/views/issueDetails/hooks/useCopyIssueDetails'; import {ExceptionContent} from './crashContent/exception'; import {StackTraceContent} from './crashContent/stackTrace'; diff --git a/static/app/components/events/interfaces/uptime/uptimeAssertionsSection.tsx b/static/app/components/events/interfaces/uptime/uptimeAssertionsSection.tsx index ad0cd019cb45cd..00a7dd26605fce 100644 --- a/static/app/components/events/interfaces/uptime/uptimeAssertionsSection.tsx +++ b/static/app/components/events/interfaces/uptime/uptimeAssertionsSection.tsx @@ -2,8 +2,8 @@ import {KeyValueList} from 'sentry/components/events/interfaces/keyValueList'; import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {AssertionFailureTree} from 'sentry/views/alerts/rules/uptime/assertions/assertionFailure/assertionFailureTree'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; export function UptimeAssertionsSection({event}: {event: Event}) { const evidenceData = event.occurrence?.evidenceData; diff --git a/static/app/components/events/metrics/metricsSection.tsx b/static/app/components/events/metrics/metricsSection.tsx index 47408d35ba56b6..da41162f5657b4 100644 --- a/static/app/components/events/metrics/metricsSection.tsx +++ b/static/app/components/events/metrics/metricsSection.tsx @@ -20,8 +20,8 @@ import {useOrganization} from 'sentry/utils/useOrganization'; import {METRICS_DRAWER_QUERY_PARAM} from 'sentry/views/explore/metrics/constants'; import {MetricsSamplesTable} from 'sentry/views/explore/metrics/metricInfoTabs/metricsSamplesTable'; import {canUseMetricsUI} from 'sentry/views/explore/metrics/metricsFlags'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; import {TraceViewMetricsProviderWrapper} from 'sentry/views/performance/newTraceDetails/traceMetrics'; import {NUMBER_ABBREVIATED_METRICS} from './useMetricsIssueSection'; diff --git a/static/app/components/events/ourlogs/ourlogsSection.tsx b/static/app/components/events/ourlogs/ourlogsSection.tsx index 10b5fa33cf0af3..2da8aa313d2f83 100644 --- a/static/app/components/events/ourlogs/ourlogsSection.tsx +++ b/static/app/components/events/ourlogs/ourlogsSection.tsx @@ -28,8 +28,8 @@ import {LOGS_DRAWER_QUERY_PARAM} from 'sentry/views/explore/logs/constants'; import {LogsQueryParamsProvider} from 'sentry/views/explore/logs/logsQueryParamsProvider'; import {LogRowContent} from 'sentry/views/explore/logs/tables/logsTableRow'; import {useQueryParamsSearch} from 'sentry/views/explore/queryParams/context'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; export function OurlogsSection({ event, diff --git a/static/app/components/events/packageData.tsx b/static/app/components/events/packageData.tsx index 3453744fd5b577..c284dd2cd47f7f 100644 --- a/static/app/components/events/packageData.tsx +++ b/static/app/components/events/packageData.tsx @@ -6,8 +6,8 @@ import {KeyValueData} from 'sentry/components/keyValueData'; import {t} from 'sentry/locale'; import type {Event} from 'sentry/types/event'; import {isEmptyObject} from 'sentry/utils/object/isEmptyObject'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type Props = { event: Event; diff --git a/static/app/components/events/profileEventEvidence.tsx b/static/app/components/events/profileEventEvidence.tsx index be0ede36f78813..4e7c2bb2ed517a 100644 --- a/static/app/components/events/profileEventEvidence.tsx +++ b/static/app/components/events/profileEventEvidence.tsx @@ -8,8 +8,8 @@ import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import {generateProfileFlamechartRouteWithHighlightFrame} from 'sentry/utils/profiling/routes'; import {useLocation} from 'sentry/utils/useLocation'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type ProfileEvidenceProps = {event: Event; projectSlug: string}; diff --git a/static/app/components/events/rrwebIntegration.tsx b/static/app/components/events/rrwebIntegration.tsx index 33144c637ae319..0b577be09ec0be 100644 --- a/static/app/components/events/rrwebIntegration.tsx +++ b/static/app/components/events/rrwebIntegration.tsx @@ -10,8 +10,8 @@ import type {Project} from 'sentry/types/project'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {SectionKey} from 'sentry/views/issueDetails/streamline/context'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {SectionKey} from 'sentry/views/issueDetails/context'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; type Props = { event: Event; diff --git a/static/app/components/events/traceEventDataSection.tsx b/static/app/components/events/traceEventDataSection.tsx index 3d1e7913829341..f04f7adf14ce2d 100644 --- a/static/app/components/events/traceEventDataSection.tsx +++ b/static/app/components/events/traceEventDataSection.tsx @@ -19,7 +19,7 @@ import {trackAnalytics} from 'sentry/utils/analytics'; import {isMobilePlatform, isNativePlatform} from 'sentry/utils/platform'; import {useApi} from 'sentry/utils/useApi'; import {useOrganization} from 'sentry/utils/useOrganization'; -import {FoldSection} from 'sentry/views/issueDetails/streamline/foldSection'; +import {FoldSection} from 'sentry/views/issueDetails/foldSection'; const sortByOptions = { 'recent-first': t('Newest'), diff --git a/static/app/components/feedback/feedbackItem/feedbackActivitySection.spec.tsx b/static/app/components/feedback/feedbackItem/feedbackActivitySection.spec.tsx index 0d00c36b77d6a4..69451e4b294cba 100644 --- a/static/app/components/feedback/feedbackItem/feedbackActivitySection.spec.tsx +++ b/static/app/components/feedback/feedbackItem/feedbackActivitySection.spec.tsx @@ -27,7 +27,7 @@ describe('FeedbackActivitySection', () => { }); }); - it('renders feedback activity with the streamlined activity feed', async () => { + it('renders feedback activity with the issue details activity feed', async () => { const feedbackItem = GroupFixture({ id: '1337', activity: [ diff --git a/static/app/components/feedback/feedbackItem/feedbackAssignedTo.tsx b/static/app/components/feedback/feedbackItem/feedbackAssignedTo.tsx index 87f1aad4b6777b..5707aa4cdc1664 100644 --- a/static/app/components/feedback/feedbackItem/feedbackAssignedTo.tsx +++ b/static/app/components/feedback/feedbackItem/feedbackAssignedTo.tsx @@ -8,8 +8,8 @@ import {getApiUrl} from 'sentry/utils/api/getApiUrl'; import type {FeedbackEvent} from 'sentry/utils/feedback/types'; import {useApiQuery} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; -import type {EventOwners} from 'sentry/views/issueDetails/streamline/header/getOwnerList'; -import {getOwnerList} from 'sentry/views/issueDetails/streamline/header/getOwnerList'; +import type {EventOwners} from 'sentry/views/issueDetails/header/getOwnerList'; +import {getOwnerList} from 'sentry/views/issueDetails/header/getOwnerList'; interface Props { feedbackEvent: FeedbackEvent; diff --git a/static/app/components/groupPreviewTooltip/utils.tsx b/static/app/components/groupPreviewTooltip/utils.tsx index 15510de48a9e18..3c223445815475 100644 --- a/static/app/components/groupPreviewTooltip/utils.tsx +++ b/static/app/components/groupPreviewTooltip/utils.tsx @@ -5,7 +5,7 @@ import type {Event} from 'sentry/types/event'; import {defined} from 'sentry/utils'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useTimeout} from 'sentry/utils/useTimeout'; -import {getEventSearchFromIssueQuery} from 'sentry/views/issueDetails/streamline/hooks/useEventQuery'; +import {getEventSearchFromIssueQuery} from 'sentry/views/issueDetails/hooks/useEventQuery'; import {useGroup} from 'sentry/views/issueDetails/useGroup'; import { groupEventApiOptions, diff --git a/static/app/components/searchQueryBuilder/askSeer/askSeer.tsx b/static/app/components/searchQueryBuilder/askSeer/askSeer.tsx index b1cf2b84fa530c..3467f365ba13c1 100644 --- a/static/app/components/searchQueryBuilder/askSeer/askSeer.tsx +++ b/static/app/components/searchQueryBuilder/askSeer/askSeer.tsx @@ -11,13 +11,13 @@ import { AskSeerListItem, AskSeerPane, } from 'sentry/components/searchQueryBuilder/askSeer/components'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderAI} from 'sentry/components/searchQueryBuilder/context'; import {t} from 'sentry/locale'; import {useOrganization} from 'sentry/utils/useOrganization'; export function AskSeer({state}: {state: ComboBoxState}) { const organization = useOrganization(); - const {displayAskSeerFeedback} = useSearchQueryBuilder(); + const {displayAskSeerFeedback} = useSearchQueryBuilderAI(); const isMutating = useIsMutating({ mutationKey: [setupCheckQueryKey(organization.slug)], diff --git a/static/app/components/searchQueryBuilder/askSeer/askSeerFeedback.tsx b/static/app/components/searchQueryBuilder/askSeer/askSeerFeedback.tsx index 0e2e82b9d5da9c..4933a8efa82f9a 100644 --- a/static/app/components/searchQueryBuilder/askSeer/askSeerFeedback.tsx +++ b/static/app/components/searchQueryBuilder/askSeer/askSeerFeedback.tsx @@ -5,7 +5,7 @@ import {Text} from '@sentry/scraps/text'; import {useAnalyticsArea} from 'sentry/components/analyticsArea'; import {AskSeerLabel} from 'sentry/components/searchQueryBuilder/askSeer/components'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderAI} from 'sentry/components/searchQueryBuilder/context'; import {IconSeer, IconThumb} from 'sentry/icons'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; @@ -15,7 +15,7 @@ export function AskSeerFeedback() { const organization = useOrganization(); const analyticsArea = useAnalyticsArea(); const {setDisplayAskSeerFeedback, askSeerNLQueryRef, askSeerSuggestedQueryRef} = - useSearchQueryBuilder(); + useSearchQueryBuilderAI(); const handleClick = (type: 'positive' | 'negative') => { trackAnalytics('ai_query.feedback', { diff --git a/static/app/components/searchQueryBuilder/askSeer/askSeerOption.tsx b/static/app/components/searchQueryBuilder/askSeer/askSeerOption.tsx index 0729dae919811b..f45ca54121a8a4 100644 --- a/static/app/components/searchQueryBuilder/askSeer/askSeerOption.tsx +++ b/static/app/components/searchQueryBuilder/askSeer/askSeerOption.tsx @@ -11,7 +11,7 @@ import { AskSeerLabel, AskSeerListItem, } from 'sentry/components/searchQueryBuilder/askSeer/components'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderAI} from 'sentry/components/searchQueryBuilder/context'; import {IconSeer} from 'sentry/icons'; import {t} from 'sentry/locale'; import {trackAnalytics} from 'sentry/utils/analytics'; @@ -21,7 +21,7 @@ export const ASK_SEER_ITEM_KEY = 'ask_seer'; export function AskSeerOption({state}: {state: ComboBoxState}) { const ref = useRef(null); - const {setDisplayAskSeer, aiSearchBadgeType} = useSearchQueryBuilder(); + const {setDisplayAskSeer, aiSearchBadgeType} = useSearchQueryBuilderAI(); const analyticsArea = useAnalyticsArea(); const organization = useOrganization(); diff --git a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx index de70de119131f0..6d6b2a89521e41 100644 --- a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx +++ b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox.spec.tsx @@ -7,7 +7,7 @@ import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrar import {AskSeerComboBox} from 'sentry/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox'; import { SearchQueryBuilderProvider, - useSearchQueryBuilder, + useSearchQueryBuilderAI, } from 'sentry/components/searchQueryBuilder/context'; import {fetchMutation} from 'sentry/utils/queryClient'; @@ -116,7 +116,7 @@ describe('AskSeerComboBox', () => { it('closes seer search when close button is clicked', async () => { function TestComponent() { - const {displayAskSeer, setDisplayAskSeer} = useSearchQueryBuilder(); + const {displayAskSeer, setDisplayAskSeer} = useSearchQueryBuilderAI(); return displayAskSeer ? ( ({ autoSubmitSeer, setAutoSubmitSeer, enableAISearch, - } = useSearchQueryBuilder(); + } = useSearchQueryBuilderAI(); const analyticsArea = useAnalyticsArea(); diff --git a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerPollingComboBox.tsx b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerPollingComboBox.tsx index 362f04cd0967fd..a27df53b420ebc 100644 --- a/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerPollingComboBox.tsx +++ b/static/app/components/searchQueryBuilder/askSeerCombobox/askSeerPollingComboBox.tsx @@ -28,7 +28,7 @@ import { generateQueryTokensString, isNoneOfTheseItem, } from 'sentry/components/searchQueryBuilder/askSeerCombobox/utils'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderAI} from 'sentry/components/searchQueryBuilder/context'; import {useSearchTokenCombobox} from 'sentry/components/searchQueryBuilder/tokens/useSearchTokenCombobox'; import {IconClose, IconMegaphone, IconSearch} from 'sentry/icons'; import {t} from 'sentry/locale'; @@ -139,7 +139,7 @@ export function AskSeerPollingComboBox({ autoSubmitSeer, setAutoSubmitSeer, enableAISearch, - } = useSearchQueryBuilder(); + } = useSearchQueryBuilderAI(); const analyticsArea = useAnalyticsArea(); diff --git a/static/app/components/searchQueryBuilder/askSeerCombobox/queryTokens.tsx b/static/app/components/searchQueryBuilder/askSeerCombobox/queryTokens.tsx index c811b3ef5a7f48..c6dfc69b16c1c6 100644 --- a/static/app/components/searchQueryBuilder/askSeerCombobox/queryTokens.tsx +++ b/static/app/components/searchQueryBuilder/askSeerCombobox/queryTokens.tsx @@ -4,7 +4,7 @@ import {Flex} from '@sentry/scraps/layout'; import type {QueryTokensProps} from 'sentry/components/searchQueryBuilder/askSeerCombobox/types'; import {formatDateRange} from 'sentry/components/searchQueryBuilder/askSeerCombobox/utils'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderConfig} from 'sentry/components/searchQueryBuilder/context'; import {ProvidedFormattedQuery} from 'sentry/components/searchQueryBuilder/formattedQuery'; import {parseQueryBuilderValue} from 'sentry/components/searchQueryBuilder/utils'; import {t} from 'sentry/locale'; @@ -19,7 +19,7 @@ export function QueryTokens({ visualizations, }: QueryTokensProps) { const tokens = []; - const {getFieldDefinition} = useSearchQueryBuilder(); + const {getFieldDefinition} = useSearchQueryBuilderConfig(); const parsedQuery = query ? parseQueryBuilderValue(query, getFieldDefinition) : null; if (query && parsedQuery?.length) { tokens.push( diff --git a/static/app/components/searchQueryBuilder/context.tsx b/static/app/components/searchQueryBuilder/context.tsx index 07a6373cc2fe57..7a5c88e6bc4fda 100644 --- a/static/app/components/searchQueryBuilder/context.tsx +++ b/static/app/components/searchQueryBuilder/context.tsx @@ -35,72 +35,120 @@ import {useDimensions} from 'sentry/utils/useDimensions'; import {useOrganization} from 'sentry/utils/useOrganization'; import {usePrevious} from 'sentry/utils/usePrevious'; -interface SearchQueryBuilderContextData { - actionBarRef: React.RefObject; - aiSearchBadgeType: 'alpha' | 'beta'; - askSeerNLQueryRef: React.RefObject; - askSeerSuggestedQueryRef: React.RefObject; - autoSubmitSeer: boolean; +interface SearchQueryBuilderStateContextData { clearSearchQuery: (options?: {reopenDropdown?: boolean}) => void; committedQuery: string; - consumeReopenDropdownOnQueryClear: () => void; - currentInputValueRef: React.RefObject; + dispatch: Dispatch; + focusOverride: FocusOverride | null; + handleSearch: (query: string) => void; + parseQuery: (query: string) => ParseResult | null; + parsedQuery: ParseResult | null; + query: string; +} + +interface SearchQueryBuilderConfigContextData { + caseInsensitive: CaseInsensitive | undefined; disabled: boolean; disallowFreeText: boolean; disallowLogicalOperators: boolean; disallowWildcard: boolean; - dispatch: Dispatch; - displayAskSeer: boolean; - displayAskSeerFeedback: boolean; - enableAISearch: boolean; - filterKeyMenuWidth: number; + filterKeyAliases: TagCollection | undefined; filterKeySections: FilterKeySection[]; filterKeys: TagCollection; - focusOverride: FocusOverride | null; - // @deprecated: remove this, it's constant now - gaveSeerConsent: true; getFieldDefinition: (key: string, kind?: FieldKind) => FieldDefinition | null; getSuggestedFilterKey: (key: string) => string | null; + getTagKeys: GetTagKeys | undefined; getTagValues: GetTagValues; - handleSearch: (query: string) => void; invalidFilterKeys: string[]; - parseQuery: (query: string) => ParseResult | null; - parsedQuery: ParseResult | null; - query: string; - reopenDropdownOnQueryClear: boolean; + matchKeySuggestions: Array<{key: string; valuePattern: RegExp}> | undefined; + namespace: string | undefined; + onCaseInsensitiveClick: ((value: CaseInsensitive) => void) | undefined; + placeholder: string | undefined; + recentSearches: SavedSearchType | undefined; + replaceRawSearchKeys: string[] | undefined; searchSource: string; +} + +interface SearchQueryBuilderLayoutContextData { + actionBarRef: React.RefObject; + currentInputValueRef: React.RefObject; + filterKeyMenuWidth: number; + portalTarget: HTMLElement | null | undefined; + size: 'small' | 'normal'; + wrapperRef: React.RefObject; +} + +interface SearchQueryBuilderAIContextData { + aiSearchBadgeType: 'alpha' | 'beta'; + askSeerNLQueryRef: React.RefObject; + askSeerSuggestedQueryRef: React.RefObject; + autoSubmitSeer: boolean; + displayAskSeer: boolean; + displayAskSeerFeedback: boolean; + enableAISearch: boolean; setAutoSubmitSeer: (enabled: boolean) => void; setDisplayAskSeer: (enabled: boolean) => void; setDisplayAskSeerFeedback: (enabled: boolean) => void; - size: 'small' | 'normal'; - wrapperRef: React.RefObject; - caseInsensitive?: CaseInsensitive; - filterKeyAliases?: TagCollection; - getTagKeys?: GetTagKeys; - matchKeySuggestions?: Array<{key: string; valuePattern: RegExp}>; - namespace?: string; - onCaseInsensitiveClick?: (value: CaseInsensitive) => void; - placeholder?: string; - /** - * The element to render the combobox popovers into. - */ - portalTarget?: HTMLElement | null; - recentSearches?: SavedSearchType; - replaceRawSearchKeys?: string[]; } -export function useSearchQueryBuilder() { - const context = useContext(SearchQueryBuilderContext); - if (!context) { - throw new Error( - 'useSearchQueryBuilder must be used within a SearchQueryBuilderProvider' - ); +interface SearchQueryBuilderInteractionContextData { + consumeReopenDropdownOnQueryClear: () => void; + reopenDropdownOnQueryClear: boolean; +} + +function useRequiredContext(context: React.Context, hookName: string) { + const contextValue = useContext(context); + if (!contextValue) { + throw new Error(`${hookName} must be used within a SearchQueryBuilderProvider`); } - return context; + return contextValue; +} + +export function useSearchQueryBuilderState() { + return useRequiredContext(SearchQueryBuilderStateContext, 'useSearchQueryBuilderState'); +} + +export function useSearchQueryBuilderConfig() { + return useRequiredContext( + SearchQueryBuilderConfigContext, + 'useSearchQueryBuilderConfig' + ); +} + +export function useSearchQueryBuilderLayout() { + return useRequiredContext( + SearchQueryBuilderLayoutContext, + 'useSearchQueryBuilderLayout' + ); +} + +export function useSearchQueryBuilderAI() { + return useRequiredContext(SearchQueryBuilderAIContext, 'useSearchQueryBuilderAI'); } -export const SearchQueryBuilderContext = - createContext(null); +export function useSearchQueryBuilderInteraction() { + return useRequiredContext( + SearchQueryBuilderInteractionContext, + 'useSearchQueryBuilderInteraction' + ); +} + +export function useHasSearchQueryBuilderProvider() { + return useContext(SearchQueryBuilderProviderContext); +} + +const SearchQueryBuilderStateContext = + createContext(null); +const SearchQueryBuilderConfigContext = + createContext(null); +const SearchQueryBuilderLayoutContext = + createContext(null); +const SearchQueryBuilderAIContext = createContext( + null +); +const SearchQueryBuilderInteractionContext = + createContext(null); +const SearchQueryBuilderProviderContext = createContext(false); export function SearchQueryBuilderProvider({ children, @@ -267,96 +315,136 @@ export function SearchQueryBuilderProvider({ const size = searchBarWidth && searchBarWidth < 600 ? ('small' as const) : ('normal' as const); - const contextValue = useMemo((): SearchQueryBuilderContextData => { + const stateValue = useMemo((): SearchQueryBuilderStateContextData => { return { - ...state, - aiSearchBadgeType, - invalidFilterKeys: stableInvalidFilterKeys, + clearSearchQuery, + committedQuery: state.committedQuery, + dispatch, + focusOverride: state.focusOverride, + handleSearch, + parseQuery, + parsedQuery, + query: state.query, + }; + }, [ + clearSearchQuery, + dispatch, + handleSearch, + parseQuery, + parsedQuery, + state.committedQuery, + state.focusOverride, + state.query, + ]); + + const configValue = useMemo((): SearchQueryBuilderConfigContextData => { + return { + caseInsensitive, disabled, disallowFreeText: Boolean(disallowFreeText), disallowLogicalOperators: Boolean(disallowLogicalOperators), disallowWildcard: Boolean(disallowWildcard), - enableAISearch, - parseQuery, - parsedQuery, + filterKeyAliases, filterKeySections: filterKeySections ?? [], - filterKeyMenuWidth, filterKeys: stableFilterKeys, + getFieldDefinition: stableFieldDefinitionGetter, getSuggestedFilterKey: stableGetSuggestedFilterKey, - getTagValues, getTagKeys, - getFieldDefinition: stableFieldDefinitionGetter, - dispatch, - clearSearchQuery, - consumeReopenDropdownOnQueryClear, - wrapperRef, - actionBarRef, - handleSearch, + getTagValues, + invalidFilterKeys: stableInvalidFilterKeys, + matchKeySuggestions, + namespace, + onCaseInsensitiveClick, placeholder, recentSearches, - namespace, - searchSource, - size, - portalTarget, - autoSubmitSeer, - setAutoSubmitSeer, - displayAskSeer, - setDisplayAskSeer: setDisplayAskSeerState, replaceRawSearchKeys, - matchKeySuggestions, - filterKeyAliases, - gaveSeerConsent: true, - currentInputValueRef, - reopenDropdownOnQueryClear, - displayAskSeerFeedback, - setDisplayAskSeerFeedback, - askSeerNLQueryRef, - askSeerSuggestedQueryRef, - caseInsensitive, - onCaseInsensitiveClick, + searchSource, }; }, [ - aiSearchBadgeType, - autoSubmitSeer, caseInsensitive, - clearSearchQuery, - consumeReopenDropdownOnQueryClear, disabled, disallowFreeText, disallowLogicalOperators, disallowWildcard, - dispatch, - displayAskSeer, - displayAskSeerFeedback, - enableAISearch, filterKeyAliases, - filterKeyMenuWidth, filterKeySections, getTagKeys, getTagValues, - handleSearch, stableInvalidFilterKeys, matchKeySuggestions, + namespace, onCaseInsensitiveClick, - parseQuery, - parsedQuery, placeholder, - portalTarget, recentSearches, - reopenDropdownOnQueryClear, - namespace, replaceRawSearchKeys, searchSource, - size, stableFieldDefinitionGetter, stableFilterKeys, stableGetSuggestedFilterKey, - state, ]); + const layoutValue = useMemo((): SearchQueryBuilderLayoutContextData => { + return { + actionBarRef, + currentInputValueRef, + filterKeyMenuWidth, + portalTarget, + size, + wrapperRef, + }; + }, [ + actionBarRef, + currentInputValueRef, + filterKeyMenuWidth, + portalTarget, + size, + wrapperRef, + ]); + + const aiValue = useMemo((): SearchQueryBuilderAIContextData => { + return { + aiSearchBadgeType, + askSeerNLQueryRef, + askSeerSuggestedQueryRef, + autoSubmitSeer, + displayAskSeer, + displayAskSeerFeedback, + enableAISearch, + setAutoSubmitSeer, + setDisplayAskSeer: setDisplayAskSeerState, + setDisplayAskSeerFeedback, + }; + }, [ + aiSearchBadgeType, + askSeerNLQueryRef, + askSeerSuggestedQueryRef, + autoSubmitSeer, + displayAskSeer, + displayAskSeerFeedback, + enableAISearch, + setDisplayAskSeerFeedback, + ]); + + const interactionValue = useMemo((): SearchQueryBuilderInteractionContextData => { + return { + consumeReopenDropdownOnQueryClear, + reopenDropdownOnQueryClear, + }; + }, [consumeReopenDropdownOnQueryClear, reopenDropdownOnQueryClear]); + return ( - - {children} - + + + + + + + {children} + + + + + + ); } diff --git a/static/app/components/searchQueryBuilder/formattedQuery.spec.tsx b/static/app/components/searchQueryBuilder/formattedQuery.spec.tsx index 355ab1dd934468..d15a41aaa4343f 100644 --- a/static/app/components/searchQueryBuilder/formattedQuery.spec.tsx +++ b/static/app/components/searchQueryBuilder/formattedQuery.spec.tsx @@ -15,10 +15,12 @@ const FILTER_KEYS: TagCollection = { }; jest.mock('sentry/components/searchQueryBuilder/context', () => ({ - useSearchQueryBuilder: () => ({ - size: 'normal', + useSearchQueryBuilderConfig: () => ({ getFieldDefinition: () => null, }), + useSearchQueryBuilderLayout: () => ({ + size: 'normal', + }), })); describe('FormattedQuery', () => { diff --git a/static/app/components/searchQueryBuilder/formattedQuery.tsx b/static/app/components/searchQueryBuilder/formattedQuery.tsx index 0b6361b4a82066..c4c736a134dbf6 100644 --- a/static/app/components/searchQueryBuilder/formattedQuery.tsx +++ b/static/app/components/searchQueryBuilder/formattedQuery.tsx @@ -5,7 +5,7 @@ import {Text} from '@sentry/scraps/text'; import { SearchQueryBuilderProvider, - useSearchQueryBuilder, + useSearchQueryBuilderConfig, } from 'sentry/components/searchQueryBuilder/context'; import {AggregateKeyVisual} from 'sentry/components/searchQueryBuilder/tokens/filter/aggregateKey'; import {FilterValueText} from 'sentry/components/searchQueryBuilder/tokens/filter/filter'; @@ -54,7 +54,7 @@ function FilterKey({token}: {token: TokenResult}) { } function Filter({token}: {token: TokenResult}) { - const {getFieldDefinition} = useSearchQueryBuilder(); + const {getFieldDefinition} = useSearchQueryBuilderConfig(); const label = useMemo( () => getOperatorInfo({ diff --git a/static/app/components/searchQueryBuilder/hooks/useOnChange.tsx b/static/app/components/searchQueryBuilder/hooks/useOnChange.tsx index 9e6d9889847db3..4a513bcf37888a 100644 --- a/static/app/components/searchQueryBuilder/hooks/useOnChange.tsx +++ b/static/app/components/searchQueryBuilder/hooks/useOnChange.tsx @@ -1,11 +1,11 @@ import type {SearchQueryBuilderProps} from 'sentry/components/searchQueryBuilder'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderState} from 'sentry/components/searchQueryBuilder/context'; import {queryIsValid} from 'sentry/components/searchQueryBuilder/utils'; import {useEffectAfterFirstRender} from 'sentry/utils/useEffectAfterFirstRender'; import {usePrevious} from 'sentry/utils/usePrevious'; export function useOnChange({onChange}: Pick) { - const {committedQuery, handleSearch, parseQuery} = useSearchQueryBuilder(); + const {committedQuery, handleSearch, parseQuery} = useSearchQueryBuilderState(); const previousCommittedQuery = usePrevious(committedQuery); diff --git a/static/app/components/searchQueryBuilder/hooks/useQueryBuilderGridItem.tsx b/static/app/components/searchQueryBuilder/hooks/useQueryBuilderGridItem.tsx index 69cfc0fdf52b00..6910465a9db716 100644 --- a/static/app/components/searchQueryBuilder/hooks/useQueryBuilderGridItem.tsx +++ b/static/app/components/searchQueryBuilder/hooks/useQueryBuilderGridItem.tsx @@ -5,7 +5,7 @@ import {isMac} from '@react-aria/utils'; import type {ListState} from '@react-stately/list'; import type {FocusableElement, Node} from '@react-types/shared'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderLayout} from 'sentry/components/searchQueryBuilder/context'; import {useKeyboardSelection} from 'sentry/components/searchQueryBuilder/hooks/useKeyboardSelection'; import {findNearestFreeTextKey} from 'sentry/components/searchQueryBuilder/utils'; import type {ParseResultToken} from 'sentry/components/searchSyntax/parser'; @@ -94,7 +94,7 @@ export function useQueryBuilderGridItem( state: ListState, ref: RefObject ) { - const {wrapperRef} = useSearchQueryBuilder(); + const {wrapperRef} = useSearchQueryBuilderLayout(); const {rowProps, gridCellProps} = useGridListItem({node: item}, state, ref); const {selectInDirection} = useKeyboardSelection(); diff --git a/static/app/components/searchQueryBuilder/hooks/useSelectOnDrag.tsx b/static/app/components/searchQueryBuilder/hooks/useSelectOnDrag.tsx index 05c0416a7a25ac..84a65d33cb4d35 100644 --- a/static/app/components/searchQueryBuilder/hooks/useSelectOnDrag.tsx +++ b/static/app/components/searchQueryBuilder/hooks/useSelectOnDrag.tsx @@ -2,7 +2,7 @@ import {useCallback, useEffect, useRef} from 'react'; import type {ListState} from '@react-stately/list'; import type {Key} from '@react-types/shared'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderLayout} from 'sentry/components/searchQueryBuilder/context'; import {Token, type ParseResultToken} from 'sentry/components/searchSyntax/parser'; type DraggingState = { @@ -118,7 +118,7 @@ function getItemIndexAtPosition( * and should behave similarly to selection within a textarea. */ export function useSelectOnDrag(state: ListState) { - const {wrapperRef} = useSearchQueryBuilder(); + const {wrapperRef} = useSearchQueryBuilderLayout(); const dragState = useRef(null); const cachedTokenCoordinates = useRef(null); // Mouse move events fire more than once per frame, so we use this ref to diff --git a/static/app/components/searchQueryBuilder/hooks/useUndoStack.tsx b/static/app/components/searchQueryBuilder/hooks/useUndoStack.tsx index 388edd7c3c77bc..2c7db1f1a95e0b 100644 --- a/static/app/components/searchQueryBuilder/hooks/useUndoStack.tsx +++ b/static/app/components/searchQueryBuilder/hooks/useUndoStack.tsx @@ -2,7 +2,7 @@ import {useCallback, useRef} from 'react'; import type {ListState} from '@react-stately/list'; import type {Key} from '@react-types/shared'; -import {useSearchQueryBuilder} from 'sentry/components/searchQueryBuilder/context'; +import {useSearchQueryBuilderState} from 'sentry/components/searchQueryBuilder/context'; import type {FocusOverride} from 'sentry/components/searchQueryBuilder/types'; import type {ParseResultToken} from 'sentry/components/searchSyntax/parser'; import {defined} from 'sentry/utils'; @@ -77,7 +77,7 @@ function updateUndoStack({ * Hook that manages the undo stack for the search query builder. */ export function useUndoStack(state: ListState) { - const {query, focusOverride, dispatch} = useSearchQueryBuilder(); + const {query, focusOverride, dispatch} = useSearchQueryBuilderState(); const undoStackRef = useRef([]); const trimmedQuery = query.trim(); diff --git a/static/app/components/searchQueryBuilder/index.spec.tsx b/static/app/components/searchQueryBuilder/index.spec.tsx index ffdcb054166301..26e065245eb2f8 100644 --- a/static/app/components/searchQueryBuilder/index.spec.tsx +++ b/static/app/components/searchQueryBuilder/index.spec.tsx @@ -19,7 +19,8 @@ import { import {AskSeerComboBox} from 'sentry/components/searchQueryBuilder/askSeerCombobox/askSeerComboBox'; import { SearchQueryBuilderProvider, - useSearchQueryBuilder, + useSearchQueryBuilderAI, + useSearchQueryBuilderState, } from 'sentry/components/searchQueryBuilder/context'; import { QueryInterfaceType, @@ -5807,7 +5808,8 @@ describe('SearchQueryBuilder', () => { }); function AskSeerTestComponent({children}: {children: React.ReactNode}) { - const {displayAskSeer, query} = useSearchQueryBuilder(); + const {displayAskSeer} = useSearchQueryBuilderAI(); + const {query} = useSearchQueryBuilderState(); return displayAskSeer ? ( { story('SearchQueryBuilderProvider', () => { function OpenDropdownButton() { - const {dispatch} = useSearchQueryBuilder(); + const {dispatch} = useSearchQueryBuilderState(); return ( + {t('Send Request')} - + ); } diff --git a/static/app/views/settings/organizationMcpCli/index.tsx b/static/app/views/settings/organizationMcpCli/index.tsx index 3117f8f00eca3b..5bb112d9ff0578 100644 --- a/static/app/views/settings/organizationMcpCli/index.tsx +++ b/static/app/views/settings/organizationMcpCli/index.tsx @@ -43,7 +43,11 @@ export default function OrganizationMcpCli() { https://mcp.sentry.dev/mcp/your-org/your-project
- + {t('MCP Documentation')}
diff --git a/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.spec.tsx b/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.spec.tsx index 9b94148dc1d67b..dcfaef11940b16 100644 --- a/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.spec.tsx +++ b/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.spec.tsx @@ -6,7 +6,7 @@ import {ProjectFixture} from 'sentry-fixture/project'; import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; import {act, render, screen} from 'sentry-test/reactTestingLibrary'; -import type {useAiConfig} from 'sentry/views/issueDetails/streamline/hooks/useAiConfig'; +import type {useAiConfig} from 'sentry/views/issueDetails/hooks/useAiConfig'; import {SubscriptionStore} from 'getsentry/stores/subscriptionStore'; diff --git a/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.tsx b/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.tsx index 49ce514401029b..ad979cf24fdd7b 100644 --- a/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.tsx +++ b/static/gsApp/components/ai/aiConfigureSeerQuotaSidebar.tsx @@ -10,7 +10,7 @@ import {useOrganization} from 'sentry/utils/useOrganization'; import { AutofixContent, type AutofixContentProps, -} from 'sentry/views/issueDetails/streamline/sidebar/autofixSection'; +} from 'sentry/views/issueDetails/sidebar/autofixSection'; import {useSubscription} from 'getsentry/hooks/useSubscription'; import {hasAccessToSubscriptionOverview} from 'getsentry/utils/billing'; diff --git a/tests/sentry/dynamic_sampling/per_org/tasks/test_configuration.py b/tests/sentry/dynamic_sampling/per_org/tasks/test_configuration.py index 3648da8ef7e3a1..8fda8542883540 100644 --- a/tests/sentry/dynamic_sampling/per_org/tasks/test_configuration.py +++ b/tests/sentry/dynamic_sampling/per_org/tasks/test_configuration.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Callable +from datetime import timedelta from typing import NamedTuple from unittest.mock import patch @@ -18,6 +19,8 @@ DynamicSamplingException, DynamicSamplingStatus, ) +from sentry.dynamic_sampling.tasks.common import OrganizationDataVolume +from sentry.dynamic_sampling.tasks.helpers.sliding_window import FALLBACK_SLIDING_WINDOW_SIZE from sentry.dynamic_sampling.types import DynamicSamplingMode, SamplingMeasure from sentry.models.organization import Organization from sentry.testutils.cases import TestCase @@ -66,18 +69,85 @@ def test_subscription_backed_org_uses_blended_sample_rate(self) -> None: with pytest.raises(AttributeError): getattr(configuration, "project_target_sample_rates") get_blended_sample_rate.assert_called_once_with(organization_id=org.id) + assert configuration.get_sample_rate() == 0.5 + + def test_subscription_backed_org_uses_eap_sliding_window_sample_rate(self) -> None: + org = self.create_organization() + self.create_project(organization=org) + sliding_window_volume = OrganizationDataVolume(org_id=org.id, total=1000, indexed=250) + + with ( + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", + return_value=0.5, + ), + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.get_eap_organization_volume", + return_value=sliding_window_volume, + ) as get_volume, + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.compute_sliding_window_sample_rate", + return_value=0.25, + ) as compute_sample_rate, + ): + configuration = get_configuration(org.id) + + assert isinstance(configuration, AutomaticDynamicSamplingConfiguration) + assert configuration.get_sample_rate() == 0.25 + get_volume.assert_called_once() + assert get_volume.call_args.kwargs["time_interval"] == timedelta( + hours=FALLBACK_SLIDING_WINDOW_SIZE + ) + compute_sample_rate.assert_called_once_with( + org_id=org.id, + project_id=None, + total_root_count=1000, + window_size=FALLBACK_SLIDING_WINDOW_SIZE, + ) + + def test_subscription_backed_org_falls_back_to_blended_sample_rate_without_volume( + self, + ) -> None: + org = self.create_organization() + self.create_project(organization=org) + + with ( + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", + return_value=0.5, + ), + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.get_eap_organization_volume", + return_value=None, + ), + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.compute_sliding_window_sample_rate", + ) as compute_sample_rate, + ): + configuration = get_configuration(org.id) + + assert isinstance(configuration, AutomaticDynamicSamplingConfiguration) + assert configuration.get_sample_rate() == 0.5 + compute_sample_rate.assert_not_called() def test_subscription_backed_org_without_sample_rate_is_disabled(self) -> None: org = self.create_organization() + self.create_project(organization=org) - with patch( - "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", - return_value=None, + with ( + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", + return_value=None, + ), + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.get_eap_organization_volume", + ) as get_volume, ): configuration = get_configuration(org.id) assert isinstance(configuration, NoDynamicSamplingConfiguration) assert not configuration.is_enabled + get_volume.assert_not_called() with pytest.raises(AttributeError): getattr(configuration, "measure") with pytest.raises(AttributeError): @@ -142,6 +212,7 @@ def test_org_mode_custom_dynamic_sampling_uses_org_target_sample_rate(self) -> N measure_case.expected_measure == SamplingMeasure.SEGMENTS ) assert configuration.sample_rate == 0.3 + assert configuration.get_sample_rate() == 0.3 with pytest.raises(AttributeError): getattr(configuration, "project_target_sample_rates") get_blended_sample_rate.assert_not_called() @@ -182,6 +253,7 @@ def test_project_mode_custom_dynamic_sampling_stores_project_sample_rates(self) project.id: 0.2, project_without_rate.id: None, } + assert configuration.get_sample_rate() is None with pytest.raises(AttributeError): getattr(configuration, "sample_rate") get_blended_sample_rate.assert_not_called() diff --git a/tests/sentry/dynamic_sampling/per_org/tasks/test_queries.py b/tests/sentry/dynamic_sampling/per_org/tasks/test_queries.py index af2b7a4f30f575..7a0770184c6c16 100644 --- a/tests/sentry/dynamic_sampling/per_org/tasks/test_queries.py +++ b/tests/sentry/dynamic_sampling/per_org/tasks/test_queries.py @@ -84,9 +84,15 @@ def get_config( self, organization: Organization, ) -> BaseDynamicSamplingConfiguration: - with patch( - "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", - return_value=1.0, + with ( + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.quotas.backend.get_blended_sample_rate", + return_value=1.0, + ), + patch( + "sentry.dynamic_sampling.per_org.tasks.configuration.get_eap_organization_volume", + return_value=None, + ), ): return get_configuration(organization.id) @@ -207,8 +213,8 @@ def test_get_eap_project_volumes_without_traffic(self) -> None: self.create_project(organization=organization) with patch( - "sentry.dynamic_sampling.per_org.tasks.queries.Spans.run_table_query", - return_value={"data": []}, + "sentry.dynamic_sampling.per_org.tasks.queries.run_eap_spans_table_query_in_chunks", + return_value=[], ): project_volumes = get_eap_project_volumes( self.get_config(organization), time_interval=timedelta(hours=1) @@ -259,8 +265,8 @@ def test_get_eap_project_volumes_without_projects(self) -> None: organization = self.create_organization() with patch( - "sentry.dynamic_sampling.per_org.tasks.queries.Spans.run_table_query", - return_value={"data": []}, + "sentry.dynamic_sampling.per_org.tasks.queries.run_eap_spans_table_query_in_chunks", + return_value=[], ) as run_table_query: project_volumes = get_eap_project_volumes( self.get_config(organization), time_interval=timedelta(hours=1) @@ -268,7 +274,8 @@ def test_get_eap_project_volumes_without_projects(self) -> None: assert project_volumes == [] run_table_query.assert_called_once() - assert run_table_query.call_args.kwargs["params"].projects == [] + query = run_table_query.call_args.args[0] + assert query["params"].projects == [] class EAPTransactionVolumesTest(TestCase, SnubaTestCase, SpanTestCase): diff --git a/tests/sentry/integrations/discord/test_integration.py b/tests/sentry/integrations/discord/test_integration.py index 1605e4df8eb62b..68c0236e6a1a52 100644 --- a/tests/sentry/integrations/discord/test_integration.py +++ b/tests/sentry/integrations/discord/test_integration.py @@ -366,12 +366,11 @@ def _get_pipeline_url(self) -> str: args=[self.organization.slug, IntegrationPipeline.pipeline_name], ) - def _initialize_pipeline(self) -> Any: - return self.client.post( - self._get_pipeline_url(), - data={"action": "initialize", "provider": "discord"}, - format="json", - ) + def _initialize_pipeline(self, initial_data: dict[str, Any] | None = None) -> Any: + payload: dict[str, Any] = {"action": "initialize", "provider": "discord"} + if initial_data is not None: + payload["initialData"] = initial_data + return self.client.post(self._get_pipeline_url(), data=payload, format="json") def _advance_step(self, data: dict[str, Any]) -> Any: return self.client.post(self._get_pipeline_url(), data=data, format="json") @@ -459,3 +458,82 @@ def test_full_pipeline_flow(self, mock_set_application_command: mock.MagicMock) organization_id=self.organization.id, integration=integration, ).exists() + + @responses.activate + def test_app_directory_initialize_returns_auto_advance_data(self) -> None: + resp = self._initialize_pipeline( + initial_data={ + "code": "discord-auth-code", + "guildId": self.guild_id, + "useConfigure": "1", + } + ) + assert resp.status_code == 200 + assert resp.data["step"] == "oauth_login" + data = resp.data["data"] + assert data["appDirectoryInstall"] is True + assert data["code"] == "discord-auth-code" + assert data["guildId"] == self.guild_id + assert "state" in data + assert "oauthUrl" not in data + + @responses.activate + @mock.patch("sentry.integrations.discord.client.DiscordClient.set_application_command") + def test_app_directory_full_flow(self, mock_set_application_command: mock.MagicMock) -> None: + responses.add( + responses.GET, + url=f"{DiscordClient.base_url}{GUILD_URL.format(guild_id=self.guild_id)}", + match=[header_matcher({"Authorization": f"Bot {self.bot_token}"})], + json={"id": self.guild_id, "name": self.guild_name}, + ) + responses.add( + responses.GET, + url=f"{DiscordClient.base_url}{APPLICATION_COMMANDS_URL.format(application_id=self.application_id)}", + match=[header_matcher({"Authorization": f"Bot {self.bot_token}"})], + json=COMMANDS, + ) + responses.add( + responses.POST, + url=f"{DISCORD_BASE_URL}/oauth2/token", + json={"access_token": "access_token"}, + ) + responses.add( + responses.GET, + url=f"{DiscordClient.base_url}/users/@me", + json={"id": "user_1234"}, + ) + responses.add( + responses.GET, + url=f"{DiscordClient.base_url}/users/@me/guilds/{self.guild_id}/member", + json={}, + ) + + resp = self._initialize_pipeline( + initial_data={ + "code": "discord-auth-code", + "guildId": self.guild_id, + "useConfigure": "1", + } + ) + data = resp.data["data"] + assert data["appDirectoryInstall"] is True + pipeline_signature = data["state"] + + resp = self._advance_step( + { + "code": data["code"], + "state": pipeline_signature, + "guildId": data["guildId"], + } + ) + assert resp.status_code == 200 + assert resp.data["status"] == "complete" + + # Token exchange must echo `configure_url` as redirect_uri because OAuth + # was initiated from Discord's App Directory with that URL. + token_calls = [c for c in responses.calls if c.request.url.endswith("/oauth2/token")] + assert len(token_calls) == 1 + token_body = token_calls[0].request.body + if isinstance(token_body, bytes): + token_body = token_body.decode() + assert "configure" in token_body diff --git a/tests/sentry/issues/endpoints/test_organization_group_index.py b/tests/sentry/issues/endpoints/test_organization_group_index.py index 7039f5bd06573f..9c438eb823254a 100644 --- a/tests/sentry/issues/endpoints/test_organization_group_index.py +++ b/tests/sentry/issues/endpoints/test_organization_group_index.py @@ -531,7 +531,7 @@ def test_perf_issue(self) -> None: assert response.data[0]["id"] == str(perf_group.id) def test_has_seer_last_run(self) -> None: - """Test filtering issues by whether they have seer_autofix_last_triggered set.""" + """Test filtering issues by whether they have seer_explorer_autofix_last_triggered set.""" event1 = self.store_event( data={ "fingerprint": ["no-seer-group"], @@ -563,29 +563,17 @@ def test_has_seer_last_run(self) -> None: self.login_as(user=self.user) - # Query for issues that have seer_autofix_last_triggered set + # Query for issues that have seer_explorer_autofix_last_triggered set response = self.get_success_response(query="has:issue.seer_last_run") assert len(response.data) == 1 - assert response.data[0]["id"] == str(group_with_legacy_seer.id) + assert response.data[0]["id"] == str(group_with_explorer_seer.id) - # Query for issues that do NOT have seer_autofix_last_triggered set + # Query for issues that do NOT have seer_explorer_autofix_last_triggered set response = self.get_success_response(query="!has:issue.seer_last_run") assert len(response.data) == 2 - assert response.data[0]["id"] == str(group_with_explorer_seer.id) + assert response.data[0]["id"] == str(group_with_legacy_seer.id) assert response.data[1]["id"] == str(group_without_seer.id) - # Query for issues that have seer_explorer_autofix_last_triggered set - with self.feature("organizations:autofix-on-explorer"): - response = self.get_success_response(query="has:issue.seer_last_run") - assert len(response.data) == 1 - assert response.data[0]["id"] == str(group_with_explorer_seer.id) - - # Query for issues that do NOT have seer_explorer_autofix_last_triggered set - response = self.get_success_response(query="!has:issue.seer_last_run") - assert len(response.data) == 2 - assert response.data[0]["id"] == str(group_with_legacy_seer.id) - assert response.data[1]["id"] == str(group_without_seer.id) - def test_lookup_by_event_id(self) -> None: project = self.project project.update_option("sentry:resolve_age", 1) diff --git a/tests/sentry/seer/agent/test_client_utils.py b/tests/sentry/seer/agent/test_client_utils.py index 4b9f2bd7585f9b..a1f9e9c1830791 100644 --- a/tests/sentry/seer/agent/test_client_utils.py +++ b/tests/sentry/seer/agent/test_client_utils.py @@ -32,36 +32,18 @@ def test_hide_ai_features_option_set(self) -> None: result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (False, "AI features are disabled for this organization.") - def test_no_explorer_flags_enabled(self) -> None: + def test_no_explorer_flag_enabled(self) -> None: with self.feature("organizations:gen-ai-features"): result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (False, "Feature flag not enabled") - def test_only_seer_explorer_flag(self) -> None: + def test_seer_explorer_flag_enabled(self) -> None: with self.feature( {"organizations:gen-ai-features": True, "organizations:seer-explorer": True} ): result = has_seer_agent_access_with_detail(self.org, self.user) assert result == (True, None) - def test_only_autofix_on_explorer_flag(self) -> None: - with self.feature( - {"organizations:gen-ai-features": True, "organizations:autofix-on-explorer": True} - ): - result = has_seer_agent_access_with_detail(self.org, self.user) - assert result == (True, None) - - def test_all_explorer_flags_enabled(self) -> None: - with self.feature( - { - "organizations:gen-ai-features": True, - "organizations:seer-explorer": True, - "organizations:autofix-on-explorer": True, - } - ): - result = has_seer_agent_access_with_detail(self.org, self.user) - assert result == (True, None) - def test_allow_joinleave_disabled(self) -> None: self.org.flags.allow_joinleave = False self.org.save() @@ -69,7 +51,6 @@ def test_allow_joinleave_disabled(self) -> None: { "organizations:gen-ai-features": True, "organizations:seer-explorer": True, - "organizations:autofix-on-explorer": True, } ): result = has_seer_agent_access_with_detail(self.org, self.user) diff --git a/tests/sentry/seer/entrypoints/slack/test_tasks.py b/tests/sentry/seer/entrypoints/slack/test_tasks.py index 8f146bf18c1058..b8092855c75bd9 100644 --- a/tests/sentry/seer/entrypoints/slack/test_tasks.py +++ b/tests/sentry/seer/entrypoints/slack/test_tasks.py @@ -41,7 +41,6 @@ _SEER_SLACK_FEATURES = { "organizations:gen-ai-features": True, "organizations:seer-explorer": True, - "organizations:autofix-on-explorer": True, } diff --git a/tests/sentry/seer/entrypoints/test_operator.py b/tests/sentry/seer/entrypoints/test_operator.py index 6cc0fad8f1f15c..e96c3148c06fa9 100644 --- a/tests/sentry/seer/entrypoints/test_operator.py +++ b/tests/sentry/seer/entrypoints/test_operator.py @@ -3,8 +3,6 @@ from typing import Any, TypedDict, cast from unittest.mock import Mock, patch -from rest_framework.response import Response - from fixtures.seer.webhooks import MOCK_RUN_ID from sentry.models.activity import Activity from sentry.models.organization import Organization @@ -16,16 +14,12 @@ RepoPRState, SeerRunState, ) -from sentry.seer.autofix.constants import AutofixReferrer, AutofixStatus +from sentry.seer.autofix.constants import AutofixReferrer from sentry.seer.autofix.utils import ( - AutofixState, AutofixStoppingPoint, CodingAgentProviderType, - CodingAgentStatus, ) -from sentry.seer.autofix.utils import CodingAgentState as LegacyCodingAgentState from sentry.seer.entrypoints.operator import ( - AUTOFIX_FALLBACK_CAUSE_ID, SEER_EVENT_TO_ACTIVITY_TYPE, SeerAgentOperator, SeerAutofixOperator, @@ -113,22 +107,6 @@ def _set_automation_handoff( self.project.update_option("sentry:seer_automation_handoff_target", target.value) self.project.update_option("sentry:seer_automation_handoff_integration_id", 789) - def _build_autofix_state_with_agents( - self, agents: dict[str, LegacyCodingAgentState] - ) -> AutofixState: - return AutofixState( - run_id=MOCK_RUN_ID, - request={ - "organization_id": self.organization.id, - "project_id": self.project.id, - "issue": {"id": self.group.id, "title": "test"}, - "repos": [], - }, - updated_at=datetime.now(), - status=AutofixStatus.PROCESSING, - coding_agents=agents, - ) - @patch("sentry.seer.entrypoints.operator.has_seer_access", return_value=True) def test_has_access_with_seer(self, _mock_has_seer_access): MockNoAccessEntrypoint = Mock(spec=SeerAutofixEntrypoint) @@ -163,234 +141,6 @@ def test_has_access_without_seer(self, _mock_has_seer_access): entrypoint_key=cast(SeerEntrypointKey, entrypoint_key), ) - @patch( - "sentry.seer.entrypoints.operator.update_legacy_autofix", - return_value=Response({"run_id": MOCK_RUN_ID}, status=202), - ) - @patch( - "sentry.seer.entrypoints.operator.trigger_legacy_autofix", - return_value=Response({"run_id": MOCK_RUN_ID}, status=202), - ) - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - def test_trigger_autofix_pathway( - self, - mock_get_autofix_state, - mock_trigger_autofix_helper, - mock_update_autofix_helper, - ): - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - assert mock_trigger_autofix_helper.call_count == 1 - assert mock_update_autofix_helper.call_count == 0 - mock_trigger_autofix_helper.reset_mock() - - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.SOLUTION, - run_id=MOCK_RUN_ID, - ) - assert mock_trigger_autofix_helper.call_count == 0 - assert mock_update_autofix_helper.call_count == 1 - - @patch( - "sentry.seer.entrypoints.operator.trigger_legacy_autofix", - return_value=Response({"run_id": MOCK_RUN_ID}, status=202), - ) - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - def test_trigger_autofix_success(self, mock_get_autofix_state, mock_trigger_autofix_helper): - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - assert mock_trigger_autofix_helper.call_count == 1 - assert self.entrypoint.autofix_errors == [] - assert self.entrypoint.autofix_run_ids == [MOCK_RUN_ID] - - @patch("sentry.seer.entrypoints.operator.trigger_legacy_autofix") - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - def test_trigger_autofix_already_exists( - self, mock_get_autofix_state, mock_trigger_autofix_helper - ): - existing_rca_step_state = { - "key": "root_cause_analysis", - "status": AutofixStatus.COMPLETED, - } - existing_state = AutofixState( - run_id=MOCK_RUN_ID, - request={ - "organization_id": self.organization.id, - "project_id": self.project.id, - "issue": {"id": self.group.id, "title": "test"}, - "repos": [], - }, - updated_at=datetime.now(), - status=AutofixStatus.PROCESSING, - steps=[existing_rca_step_state], - ) - mock_get_autofix_state.return_value = existing_state - - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - - mock_trigger_autofix_helper.assert_not_called() - assert self.entrypoint.autofix_already_exists_states == [(existing_state.run_id, True)] - assert self.entrypoint.autofix_run_ids == [] - assert self.entrypoint.autofix_errors == [] - - @patch( - "sentry.seer.entrypoints.operator.trigger_legacy_autofix", - return_value=Response({"run_id": MOCK_RUN_ID}, status=202), - ) - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - def test_trigger_autofix_proceeds_when_completed( - self, mock_get_autofix_state, mock_trigger_autofix_helper - ): - existing_state = AutofixState( - run_id=MOCK_RUN_ID, - request={ - "organization_id": self.organization.id, - "project_id": self.project.id, - "issue": {"id": self.group.id, "title": "test"}, - "repos": [], - }, - updated_at=datetime.now(), - status=AutofixStatus.COMPLETED, - ) - mock_get_autofix_state.return_value = existing_state - - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - - mock_trigger_autofix_helper.assert_called_once() - assert self.entrypoint.autofix_already_exists_states == [] - assert self.entrypoint.autofix_run_ids == [MOCK_RUN_ID] - - @patch("sentry.seer.entrypoints.operator.trigger_legacy_autofix") - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - def test_trigger_autofix_error(self, mock_get_autofix_state, mock_trigger_autofix_helper): - mock_trigger_autofix_helper.return_value = Response( - {"detail": "Invalid request"}, status=400 - ) - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - mock_trigger_autofix_helper.return_value = Response({"run_id": None}, status=202) - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - assert mock_trigger_autofix_helper.call_count == 2 - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - run_id=MOCK_RUN_ID, - ) - assert self.entrypoint.autofix_errors == [ - "Invalid request", - "An unknown error has occurred", - "Invalid stopping point provided", - ] - assert self.entrypoint.autofix_run_ids == [] - - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_success(self, mock_trigger_handoff_helper, mock_get_state): - self._set_automation_handoff() - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) - mock_trigger_handoff_helper.assert_called_once() - assert mock_trigger_handoff_helper.call_args.kwargs["referrer"] == AutofixReferrer.SLACK - assert self.entrypoint.handoff_successes == [ - (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT) - ] - assert self.entrypoint.handoff_already_exists == [] - assert self.entrypoint.handoff_errors == [] - - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_already_exists_running( - self, mock_trigger_handoff_helper, mock_get_state - ): - self._set_automation_handoff() - mock_get_state.return_value = self._build_autofix_state_with_agents( - { - "agent-1": LegacyCodingAgentState( - id="agent-1", - status=CodingAgentStatus.RUNNING, - provider=CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, - name="Cursor", - started_at=datetime.now(), - ) - } - ) - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) - mock_trigger_handoff_helper.assert_not_called() - assert self.entrypoint.handoff_already_exists == [ - (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, False) - ] - assert self.entrypoint.handoff_successes == [] - - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_already_exists_completed( - self, mock_trigger_handoff_helper, mock_get_state - ): - self._set_automation_handoff() - mock_get_state.return_value = self._build_autofix_state_with_agents( - { - "agent-1": LegacyCodingAgentState( - id="agent-1", - status=CodingAgentStatus.COMPLETED, - provider=CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, - name="Cursor", - started_at=datetime.now(), - ) - } - ) - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) - mock_trigger_handoff_helper.assert_not_called() - assert self.entrypoint.handoff_already_exists == [ - (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, True) - ] - - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_proceeds_when_all_agents_failed( - self, mock_trigger_handoff_helper, mock_get_state - ): - self._set_automation_handoff() - mock_get_state.return_value = self._build_autofix_state_with_agents( - { - "agent-1": LegacyCodingAgentState( - id="agent-1", - status=CodingAgentStatus.FAILED, - provider=CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, - name="Cursor", - started_at=datetime.now(), - ) - } - ) - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) - mock_trigger_handoff_helper.assert_called_once() - assert self.entrypoint.handoff_successes == [ - (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT) - ] - assert self.entrypoint.handoff_already_exists == [] - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") def test_trigger_handoff_no_config_is_silent_halt(self, mock_trigger_handoff_helper): self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) @@ -399,29 +149,16 @@ def test_trigger_handoff_no_config_is_silent_halt(self, mock_trigger_handoff_hel assert self.entrypoint.handoff_already_exists == [] assert self.entrypoint.handoff_errors == [] - @patch( - "sentry.seer.entrypoints.operator.get_autofix_state", - side_effect=Exception("seer down"), - ) - @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_state_fetch_error_calls_error_hook( - self, mock_trigger_handoff_helper, mock_get_state - ): - self._set_automation_handoff() - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) - mock_trigger_handoff_helper.assert_not_called() - assert self.entrypoint.handoff_errors == ["Encountered an error while talking to Seer"] - assert self.entrypoint.handoff_successes == [] - - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) + @patch("sentry.seer.entrypoints.operator.fetch_run_status", return_value=None) @patch( "sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff", side_effect=RuntimeError("boom"), ) def test_trigger_handoff_launch_error_calls_error_hook( - self, mock_trigger_handoff_helper, mock_get_state + self, mock_trigger_handoff_helper, mock_fetch_status ): self._set_automation_handoff() + mock_fetch_status.return_value = self._build_explorer_state_with_agents({}) self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) assert self.entrypoint.handoff_errors == [ "Encountered an error while launching the coding agent" @@ -441,11 +178,10 @@ def _build_explorer_state_with_agents( @patch("sentry.seer.entrypoints.operator.fetch_run_status") @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_explorer_success(self, mock_trigger_handoff_helper, mock_fetch_status): + def test_trigger_handoff_success(self, mock_trigger_handoff_helper, mock_fetch_status): self._set_automation_handoff() mock_fetch_status.return_value = self._build_explorer_state_with_agents({}) - with self.feature("organizations:autofix-on-explorer"): - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) + self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) mock_trigger_handoff_helper.assert_called_once() assert mock_trigger_handoff_helper.call_args.kwargs["referrer"] == AutofixReferrer.SLACK assert self.entrypoint.handoff_successes == [ @@ -456,7 +192,7 @@ def test_trigger_handoff_explorer_success(self, mock_trigger_handoff_helper, moc @patch("sentry.seer.entrypoints.operator.fetch_run_status") @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_explorer_already_exists_running( + def test_trigger_handoff_already_exists_running( self, mock_trigger_handoff_helper, mock_fetch_status ): self._set_automation_handoff() @@ -471,8 +207,7 @@ def test_trigger_handoff_explorer_already_exists_running( ) } ) - with self.feature("organizations:autofix-on-explorer"): - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) + self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) mock_trigger_handoff_helper.assert_not_called() assert self.entrypoint.handoff_already_exists == [ (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, False) @@ -480,7 +215,7 @@ def test_trigger_handoff_explorer_already_exists_running( @patch("sentry.seer.entrypoints.operator.fetch_run_status") @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_explorer_already_exists_completed( + def test_trigger_handoff_already_exists_completed( self, mock_trigger_handoff_helper, mock_fetch_status ): self._set_automation_handoff() @@ -495,8 +230,7 @@ def test_trigger_handoff_explorer_already_exists_completed( ) } ) - with self.feature("organizations:autofix-on-explorer"): - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) + self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) mock_trigger_handoff_helper.assert_not_called() assert self.entrypoint.handoff_already_exists == [ (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT, True) @@ -504,7 +238,7 @@ def test_trigger_handoff_explorer_already_exists_completed( @patch("sentry.seer.entrypoints.operator.fetch_run_status") @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_explorer_proceeds_when_all_agents_failed( + def test_trigger_handoff_proceeds_when_all_agents_failed( self, mock_trigger_handoff_helper, mock_fetch_status ): self._set_automation_handoff() @@ -519,8 +253,7 @@ def test_trigger_handoff_explorer_proceeds_when_all_agents_failed( ) } ) - with self.feature("organizations:autofix-on-explorer"): - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) + self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) mock_trigger_handoff_helper.assert_called_once() assert self.entrypoint.handoff_successes == [ (MOCK_RUN_ID, CodingAgentProviderType.CURSOR_BACKGROUND_AGENT) @@ -532,39 +265,15 @@ def test_trigger_handoff_explorer_proceeds_when_all_agents_failed( side_effect=Exception("seer down"), ) @patch("sentry.seer.autofix.autofix_agent.trigger_coding_agent_handoff") - def test_trigger_handoff_explorer_state_fetch_error_calls_error_hook( + def test_trigger_handoff_state_fetch_error_calls_error_hook( self, mock_trigger_handoff_helper, mock_fetch_status ): self._set_automation_handoff() - with self.feature("organizations:autofix-on-explorer"): - self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) + self.operator.trigger_handoff(group=self.group, run_id=MOCK_RUN_ID) mock_trigger_handoff_helper.assert_not_called() assert self.entrypoint.handoff_errors == ["Encountered an error while talking to Seer"] assert self.entrypoint.handoff_successes == [] - @patch( - "sentry.seer.entrypoints.operator.trigger_legacy_autofix", - return_value=Response({"run_id": MOCK_RUN_ID}, status=202), - ) - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - @patch("sentry.seer.entrypoints.cache.SeerOperatorAutofixCache.populate_post_autofix_cache") - def test_trigger_autofix_creates_cache_payload( - self, - mock_populate_post_autofix_cache, - mock_get_autofix_state, - mock_trigger_autofix_helper, - ): - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.ROOT_CAUSE, - ) - mock_populate_post_autofix_cache.assert_called_with( - entrypoint_key=MockAutofixEntrypoint.key, - run_id=MOCK_RUN_ID, - cache_payload=self.entrypoint.create_autofix_cache_payload(), - ) - @patch.object(SeerAutofixOperator, "has_access", return_value=True) @patch.dict( "sentry.seer.entrypoints.operator.autofix_entrypoint_registry.registrations", @@ -693,66 +402,6 @@ def test_process_autofix_updates_skips_entrypoint_without_access( cache_payload=cache_payload, ) - @patch("sentry.seer.entrypoints.operator.update_legacy_autofix") - @patch("sentry.seer.entrypoints.operator.get_autofix_state", return_value=None) - def test_solution_stopping_point_sends_select_root_cause( - self, _mock_get_autofix_state, mock_update_autofix - ): - mock_update_autofix.return_value = Response({"run_id": MOCK_RUN_ID}, status=202) - - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.SOLUTION, - run_id=MOCK_RUN_ID, - ) - - mock_update_autofix.assert_called_once() - call_kwargs = mock_update_autofix.call_args.kwargs - assert call_kwargs["organization_id"] == self.group.organization.id - payload = call_kwargs["payload"] - assert payload["type"] == "select_root_cause" - assert payload["cause_id"] == AUTOFIX_FALLBACK_CAUSE_ID - - @patch("sentry.seer.entrypoints.operator.update_legacy_autofix") - @patch("sentry.seer.entrypoints.operator.get_autofix_state") - def test_solution_stopping_point_uses_cause_id_from_state( - self, mock_get_autofix_state, mock_update_autofix - ): - mock_update_autofix.return_value = Response({"run_id": MOCK_RUN_ID}, status=202) - existing_state = AutofixState( - run_id=MOCK_RUN_ID, - request={ - "organization_id": self.organization.id, - "project_id": self.project.id, - "issue": {"id": self.group.id, "title": "test"}, - "repos": [], - }, - updated_at=datetime.now(), - status=AutofixStatus.PROCESSING, - steps=[ - { - "key": "root_cause_analysis", - "status": AutofixStatus.COMPLETED, - "causes": [{"id": 12}, {"id": 34}], - }, - ], - ) - mock_get_autofix_state.return_value = existing_state - - self.operator.trigger_autofix( - group=self.group, - user=self.user, - stopping_point=AutofixStoppingPoint.SOLUTION, - run_id=MOCK_RUN_ID, - ) - - mock_update_autofix.assert_called_once() - call_kwargs = mock_update_autofix.call_args.kwargs - payload = call_kwargs["payload"] - assert payload["type"] == "select_root_cause" - assert payload["cause_id"] == 34 - def test_can_trigger_autofix_returns_false_without_seer_access(self) -> None: assert SeerAutofixOperator.can_trigger_autofix(group=self.group) is False