Skip to content

Commit e472db4

Browse files
authored
Merge branch 'master' into nm/scraps/timeline-tokens
2 parents a872b01 + fa01ed4 commit e472db4

17 files changed

Lines changed: 1403 additions & 224 deletions

File tree

migrations_lockfile.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ will then be regenerated, and you should be able to merge without conflicts.
77

88
discover: 0003_discover_json_field
99

10-
explore: 0008_add_trace_item_attribute_context
10+
explore: 0009_add_trace_item_attribute_value_context
1111

1212
feedback: 0007_cleanup_failed_safe_deletes
1313

src/sentry/backup/comparators.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -996,6 +996,9 @@ def get_default_comparators() -> dict[str, list[JSONScrubbingComparator]]:
996996
"explore.traceitemattributecontext": [
997997
DateUpdatedComparator("date_updated", "date_added")
998998
],
999+
"explore.traceitemattributevaluecontext": [
1000+
DateUpdatedComparator("date_updated", "date_added")
1001+
],
9991002
"insights.insightsstarredsegment": [
10001003
DateUpdatedComparator("date_updated", "date_added")
10011004
],
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Generated by Django 5.2.14 on 2026-06-17 18:15
2+
3+
import django.db.models.deletion
4+
import sentry.db.models.fields.bounded
5+
import sentry.db.models.fields.foreignkey
6+
import sentry.db.models.fields.hybrid_cloud_foreign_key
7+
from django.db import migrations, models
8+
9+
from sentry.new_migrations.migrations import CheckedMigration
10+
11+
12+
class Migration(CheckedMigration):
13+
# This flag is used to mark that a migration shouldn't be automatically run in production.
14+
# This should only be used for operations where it's safe to run the migration after your
15+
# code has deployed. So this should not be used for most operations that alter the schema
16+
# of a table.
17+
# Here are some things that make sense to mark as post deployment:
18+
# - Large data migrations. Typically we want these to be run manually so that they can be
19+
# monitored and not block the deploy for a long period of time while they run.
20+
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
21+
# run this outside deployments so that we don't block them. Note that while adding an index
22+
# is a schema change, it's completely safe to run the operation after the code has deployed.
23+
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment
24+
25+
is_post_deployment = False
26+
27+
dependencies = [
28+
("explore", "0008_add_trace_item_attribute_context"),
29+
("sentry", "1117_drop_organizationmapping_codecov_access_delete"),
30+
]
31+
32+
operations = [
33+
migrations.CreateModel(
34+
name="TraceItemAttributeValueContext",
35+
fields=[
36+
(
37+
"id",
38+
sentry.db.models.fields.bounded.BoundedBigAutoField(
39+
primary_key=True, serialize=False
40+
),
41+
),
42+
("date_updated", models.DateTimeField(auto_now=True)),
43+
("date_added", models.DateTimeField(auto_now_add=True)),
44+
("attribute_name", models.CharField()),
45+
("attribute_value", models.CharField()),
46+
(
47+
"attribute_type",
48+
sentry.db.models.fields.bounded.BoundedPositiveIntegerField(),
49+
),
50+
(
51+
"item_type",
52+
sentry.db.models.fields.bounded.BoundedPositiveIntegerField(),
53+
),
54+
("brief", models.CharField(max_length=280, null=True)),
55+
("additional_context", models.TextField(null=True)),
56+
(
57+
"created_by_id",
58+
sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey(
59+
"sentry.User", db_index=True, null=True, on_delete="SET_NULL"
60+
),
61+
),
62+
(
63+
"updated_by_id",
64+
sentry.db.models.fields.hybrid_cloud_foreign_key.HybridCloudForeignKey(
65+
"sentry.User", db_index=True, null=True, on_delete="SET_NULL"
66+
),
67+
),
68+
("last_received", models.DateTimeField(null=True)),
69+
(
70+
"organization",
71+
sentry.db.models.fields.foreignkey.FlexibleForeignKey(
72+
on_delete=django.db.models.deletion.CASCADE,
73+
to="sentry.organization",
74+
),
75+
),
76+
(
77+
"project",
78+
sentry.db.models.fields.foreignkey.FlexibleForeignKey(
79+
null=True,
80+
on_delete=django.db.models.deletion.CASCADE,
81+
to="sentry.project",
82+
),
83+
),
84+
],
85+
options={
86+
"db_table": "explore_traceitemattributevaluecontext",
87+
"indexes": [
88+
models.Index(
89+
fields=[
90+
"organization",
91+
"item_type",
92+
"attribute_name",
93+
"attribute_value",
94+
],
95+
name="explore_tra_organiz_a8eb06_idx",
96+
)
97+
],
98+
"constraints": [
99+
models.UniqueConstraint(
100+
condition=models.Q(("project__isnull", False)),
101+
fields=(
102+
"organization",
103+
"project",
104+
"item_type",
105+
"attribute_name",
106+
"attribute_value",
107+
"attribute_type",
108+
),
109+
name="explore_traceitemvalue_unique_project_scoped",
110+
),
111+
models.UniqueConstraint(
112+
condition=models.Q(("project__isnull", True)),
113+
fields=(
114+
"organization",
115+
"item_type",
116+
"attribute_name",
117+
"attribute_value",
118+
"attribute_type",
119+
),
120+
name="explore_traceitemvalue_unique_org_scoped",
121+
),
122+
],
123+
},
124+
),
125+
]

src/sentry/explore/models.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,28 @@ class TraceItemAttributeTypes(TypesClass):
400400
TYPE_NAMES = [t[1] for t in TYPES]
401401

402402

403+
class TraceMetricTypes(TypesClass):
404+
"""
405+
Integer-backed mirror of ``sentry.search.eap.trace_metrics.types.TraceMetricType``,
406+
the metric type of a trace metric value (counter / gauge / distribution).
407+
408+
The integer ids are stable identifiers and must never be reused or reordered. A
409+
test in ``tests/sentry/explore/test_models.py`` guards against drift from
410+
``ALLOWED_METRIC_TYPES``.
411+
"""
412+
413+
COUNTER = 0
414+
GAUGE = 1
415+
DISTRIBUTION = 2
416+
417+
TYPES = [
418+
(COUNTER, "counter"),
419+
(GAUGE, "gauge"),
420+
(DISTRIBUTION, "distribution"),
421+
]
422+
TYPE_NAMES = [t[1] for t in TYPES]
423+
424+
403425
@cell_silo_model
404426
class TraceItemAttributeContext(DefaultFieldsModel):
405427
"""
@@ -455,3 +477,77 @@ class Meta:
455477
]
456478

457479
__repr__ = sane_repr("organization_id", "project_id", "item_type", "attribute_key")
480+
481+
482+
@cell_silo_model
483+
class TraceItemAttributeValueContext(DefaultFieldsModel):
484+
"""
485+
Human (and agent) authored context for a trace item attribute *value* (e.g. a
486+
specific custom metric). Used to surface descriptions of individual values when
487+
building queries.
488+
489+
Under the hood metric names are attribute values, so for v0 ``attribute_name`` is
490+
typically ``metric.name`` and ``attribute_value`` is the metric's name. Context is
491+
scoped to an organization and, optionally, a project (a null project means the
492+
context applies org-wide).
493+
"""
494+
495+
__relocation_scope__ = RelocationScope.Organization
496+
497+
organization = FlexibleForeignKey("sentry.Organization")
498+
# A null project means the context applies to the whole organization.
499+
project = FlexibleForeignKey("sentry.Project", null=True)
500+
501+
# The attribute and value this context is for, e.g. "metric.name" / "my.counter".
502+
attribute_name = models.CharField()
503+
attribute_value = models.CharField()
504+
# For metrics this is the metric type (counter / gauge / distribution).
505+
attribute_type = BoundedPositiveIntegerField(choices=TraceMetricTypes.as_choices())
506+
item_type = BoundedPositiveIntegerField(choices=TraceItemTypes.as_choices())
507+
508+
# A short, one-line description of the attribute value.
509+
brief = models.CharField(max_length=280, null=True)
510+
# Longer markdown notes / additional context about the attribute value.
511+
additional_context = models.TextField(null=True)
512+
513+
created_by_id = HybridCloudForeignKey("sentry.User", null=True, on_delete="SET_NULL")
514+
updated_by_id = HybridCloudForeignKey("sentry.User", null=True, on_delete="SET_NULL")
515+
# When the value was last seen in storage. Used to prune stale entries.
516+
last_received = models.DateTimeField(null=True)
517+
518+
class Meta:
519+
app_label = "explore"
520+
db_table = "explore_traceitemattributevaluecontext"
521+
# project is nullable, so unique_together would treat each null project as
522+
# distinct and allow duplicate org-wide rows. Use partial unique constraints
523+
# to enforce uniqueness for both the project-scoped and org-wide cases.
524+
constraints = [
525+
UniqueConstraint(
526+
fields=[
527+
"organization",
528+
"project",
529+
"item_type",
530+
"attribute_name",
531+
"attribute_value",
532+
"attribute_type",
533+
],
534+
name="explore_traceitemvalue_unique_project_scoped",
535+
condition=Q(project__isnull=False),
536+
),
537+
UniqueConstraint(
538+
fields=[
539+
"organization",
540+
"item_type",
541+
"attribute_name",
542+
"attribute_value",
543+
"attribute_type",
544+
],
545+
name="explore_traceitemvalue_unique_org_scoped",
546+
condition=Q(project__isnull=True),
547+
),
548+
]
549+
indexes = [
550+
models.Index(fields=["organization", "item_type", "attribute_name", "attribute_value"]),
551+
]
552+
553+
__repr__ = sane_repr("organization_id", "project_id", "item_type", "attribute_name")

0 commit comments

Comments
 (0)