Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
/static/app/utils/timeSeries/ @getsentry/data-browsing
## Endof Visibility

## Data Browsing Migration Tests
/tests/sentry/migrations/test_1064_discover_to_explore_queries_self_hosted.py @getsentry/data-browsing
## End of Data Browsing Migration Tests


## Profiling
/static/app/components/profiling/ @getsentry/profiling
Expand Down
2 changes: 1 addition & 1 deletion migrations_lockfile.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ replays: 0007_organizationmember_replay_access

seer: 0005_delete_seerorganizationsettings

sentry: 1063_remove_customdynamicsamplingrule
sentry: 1064_discover_to_explore_queries_self_hosted

social_auth: 0003_social_auth_json_field

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Generated by Django 5.2.8 on 2025-12-01 16:35


import sentry_sdk
from django.db import migrations
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps

from sentry.discover.models import DiscoverSavedQuery, DiscoverSavedQueryProject
from sentry.explore.models import ExploreSavedQueryProject
from sentry.explore.translation.discover_translation import (
translate_discover_query_to_explore_query,
)
from sentry.models.dashboard_widget import TypesClass
from sentry.new_migrations.migrations import CheckedMigration
from sentry.utils.query import RangeQuerySetWrapperWithProgressBar


class DiscoverSavedQueryTypes(TypesClass):
DISCOVER = 0
ERROR_EVENTS = 1
"""
Error side of the split from Discover.
"""
TRANSACTION_LIKE = 2
"""
This targets transaction-like data from the split from discover.
"""

TYPES = [
(DISCOVER, "discover"),
(ERROR_EVENTS, "error-events"),
(TRANSACTION_LIKE, "transaction-like"),
]
TYPE_NAMES = [t[1] for t in TYPES]


def migrate_transactions_discover_queries_self_hosted(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
queryset = DiscoverSavedQuery.objects.filter(
dataset=DiscoverSavedQueryTypes.TRANSACTION_LIKE
).select_related("organization")

for discover_query in RangeQuerySetWrapperWithProgressBar(queryset):
try:
new_explore_query = translate_discover_query_to_explore_query(discover_query)
if not new_explore_query.projects.exists():
discover_projects_qs = DiscoverSavedQueryProject.objects.filter(
discover_saved_query_id=discover_query.id
)
projects_to_create = [
ExploreSavedQueryProject(
explore_saved_query_id=new_explore_query.id,
project_id=discover_project.project_id,
)
for discover_project in discover_projects_qs
]
ExploreSavedQueryProject.objects.bulk_create(projects_to_create)
new_explore_query.save()
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
except Exception as e:
sentry_sdk.capture_exception(e)


class Migration(CheckedMigration):
# This flag is used to mark that a migration shouldn't be automatically run in production.
# This should only be used for operations where it's safe to run the migration after your
# code has deployed. So this should not be used for most operations that alter the schema
# of a table.
# Here are some things that make sense to mark as post deployment:
# - Large data migrations. Typically we want these to be run manually so that they can be
# monitored and not block the deploy for a long period of time while they run.
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
# run this outside deployments so that we don't block them. Note that while adding an index
# is a schema change, it's completely safe to run the operation after the code has deployed.
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment

is_post_deployment = True

dependencies = [
("sentry", "1063_remove_customdynamicsamplingrule"),
("discover", "0003_discover_json_field"),
("explore", "0006_add_changed_reason_field_explore"),
]

operations = [
migrations.RunPython(
migrate_transactions_discover_queries_self_hosted,
reverse_code=migrations.RunPython.noop,
hints={
"tables": [
"sentry_discoversavedquery",
"sentry_discoversavedqueryproject",
"explore_exploresavedqueryproject",
"explpre_exploresavedquery",
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
]
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
},
)
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from sentry.discover.models import (
DiscoverSavedQuery,
DiscoverSavedQueryProject,
DiscoverSavedQueryTypes,
)
from sentry.explore.models import ExploreSavedQueryDataset
from sentry.testutils.cases import SnubaTestCase, TestMigrations


class MigrateDiscoverQueriesToExploreQueriesSelfHostedTest(TestMigrations, SnubaTestCase):
migrate_from = "1063_remove_customdynamicsamplingrule"
migrate_to = "1064_discover_to_explore_queries_self_hosted"

def setup_before_migration(self, apps):
User = apps.get_model("sentry", "User")

self.user = User.objects.create(email="test@sentry.io")
self.org = self.create_organization(name="Test org", slug="test-org")
self.project = self.create_project(organization=self.org)
self.project_ids = [self.project.id]

self.query = {"fields": ["title", "count()"], "query": ""}
self.discover_query_with_fields_to_translate = DiscoverSavedQuery.objects.create(
organization=self.org,
name="Test query",
query=self.query,
dataset=DiscoverSavedQueryTypes.TRANSACTION_LIKE,
)
for project_id in self.project_ids:
DiscoverSavedQueryProject.objects.create(
discover_saved_query=self.discover_query_with_fields_to_translate,
project_id=project_id,
)

self.discover_query_without_projects = DiscoverSavedQuery.objects.create(
organization=self.org,
name="Test query",
query=self.query,
dataset=DiscoverSavedQueryTypes.TRANSACTION_LIKE,
)

self.dropped_fields_query = {
"fields": ["id", "title", "count_web_vitals(measurements.lcp,good)"]
}
self.discover_query_dropped_fields = DiscoverSavedQuery.objects.create(
organization=self.org,
name="Test query",
query=self.dropped_fields_query,
dataset=DiscoverSavedQueryTypes.TRANSACTION_LIKE,
)

self.discover_query_errors = DiscoverSavedQuery.objects.create(
organization=self.org,
name="Test query",
query={},
dataset=DiscoverSavedQueryTypes.ERROR_EVENTS,
)

def test(self):
self.discover_query_with_fields_to_translate.refresh_from_db()
self.discover_query_without_projects.refresh_from_db()
self.discover_query_dropped_fields.refresh_from_db()
self.discover_query_errors.refresh_from_db()

# All TRANSACTION_LIKE queries should get explore queries, even without projects
# (no projects means "all projects")
assert self.discover_query_with_fields_to_translate.explore_query is not None
assert self.discover_query_without_projects.explore_query is not None
assert self.discover_query_dropped_fields.explore_query is not None
# ERRORS queries should NOT get explore queries
assert self.discover_query_errors.explore_query is None

explore_query_with_translated_fields = (
self.discover_query_with_fields_to_translate.explore_query
)
explore_query_dropped_fields = self.discover_query_dropped_fields.explore_query
explore_query_no_projects = self.discover_query_without_projects.explore_query

assert (
explore_query_with_translated_fields.dataset == ExploreSavedQueryDataset.SEGMENT_SPANS
)
assert explore_query_dropped_fields.dataset == ExploreSavedQueryDataset.SEGMENT_SPANS
assert explore_query_no_projects.dataset == ExploreSavedQueryDataset.SEGMENT_SPANS

assert explore_query_with_translated_fields.query["query"][0]["fields"] == [
"id",
"transaction",
]
assert explore_query_with_translated_fields.query["query"][0]["aggregateField"] == [
{"yAxes": ["count(span.duration)"], "chartType": 2}
]
assert explore_query_with_translated_fields.query["query"][0]["query"] == "is_transaction:1"
assert explore_query_with_translated_fields.projects.count() == len(self.project_ids)

assert explore_query_dropped_fields.query["query"][0]["fields"] == ["id", "transaction"]
assert explore_query_dropped_fields.query["query"][0]["aggregateField"] == []
assert explore_query_dropped_fields.query["query"][0]["query"] == "is_transaction:1"

assert explore_query_no_projects.projects.count() == 0
Loading