1use relay_conventions::attributes::{SENTRY__DESCRIPTION, SENTRY__OP, SENTRY__ORIGIN};
2use relay_conventions::name::name_for_op_and_attributes;
3use relay_event_schema::protocol::Attributes;
4use relay_protocol::{Getter, Val};
5
6pub fn name_for_attributes(attributes: &Attributes) -> Option<String> {
17 let origin = attributes
18 .get_value(SENTRY__ORIGIN)
19 .and_then(|o| o.as_str());
20 let description = attributes
21 .get_value(SENTRY__DESCRIPTION)
22 .and_then(|d| d.as_str());
23
24 if let Some(description) = description
25 && origin == Some("manual")
26 {
27 return Some(description.to_owned());
28 }
29
30 let op = attributes.get_value(SENTRY__OP)?.as_str()?;
31 Some(name_for_op_and_attributes(op, &AttributeGetter(attributes)).unwrap_or(op.to_owned()))
32}
33
34struct AttributeGetter<'a>(&'a Attributes);
39
40impl<'a> Getter for AttributeGetter<'a> {
41 fn get_value(&self, path: &str) -> Option<Val<'_>> {
42 self.0.get_value(path).map(|value| value.into())
43 }
44}
45
46#[cfg(test)]
47mod tests {
48 use relay_protocol::Annotated;
49
50 use super::*;
51
52 #[test]
53 fn test_attributes_falls_back_to_op_when_no_templates_defined() {
54 let attributes = Attributes::from([(
55 "sentry.op".to_owned(),
56 Annotated::new("foo".to_owned().into()),
57 )]);
58
59 assert_eq!(name_for_attributes(&attributes), Some("foo".to_owned()));
60 }
61
62 #[test]
63 fn test_attributes_uses_the_first_matching_template() {
64 let attributes = Attributes::from([
65 (
66 "sentry.op".to_owned(),
67 Annotated::new("db".to_owned().into()),
68 ),
69 (
70 "db.query.summary".to_owned(),
71 Annotated::new("SELECT users".to_owned().into()),
72 ),
73 (
74 "db.operation.name".to_owned(),
75 Annotated::new("INSERT".to_owned().into()),
76 ),
77 (
78 "db.collection.name".to_owned(),
79 Annotated::new("widgets".to_owned().into()),
80 ),
81 ]);
82
83 assert_eq!(
84 name_for_attributes(&attributes),
85 Some("SELECT users".to_owned())
86 );
87 }
88
89 #[test]
90 fn test_attributes_uses_fallback_templates_when_data_is_missing() {
91 let attributes = Attributes::from([
92 (
93 "sentry.op".to_owned(),
94 Annotated::new("db".to_owned().into()),
95 ),
96 (
97 "db.operation.name".to_owned(),
98 Annotated::new("INSERT".to_owned().into()),
99 ),
100 (
101 "db.collection.name".to_owned(),
102 Annotated::new("widgets".to_owned().into()),
103 ),
104 ]);
105
106 assert_eq!(
107 name_for_attributes(&attributes),
108 Some("INSERT widgets".to_owned())
109 );
110 }
111
112 #[test]
113 fn test_attributes_falls_back_to_hardcoded_name_when_nothing_matches() {
114 let attributes = Attributes::from([(
115 "sentry.op".to_owned(),
116 Annotated::new("db".to_owned().into()),
117 )]);
118
119 assert_eq!(
120 name_for_attributes(&attributes),
121 Some("Database operation".to_owned())
122 );
123 }
124
125 #[test]
126 fn test_manual_spans_use_description_v2() {
127 let attributes = Attributes::from([
128 (
129 "sentry.origin".to_owned(),
130 Annotated::new("manual".to_owned().into()),
131 ),
132 (
133 "sentry.description".to_owned(),
134 Annotated::new("Custom name".to_owned().into()),
135 ),
136 (
137 "sentry.op".to_owned(),
138 Annotated::new("db".to_owned().into()),
139 ),
140 (
141 "db.query.summary".to_owned(),
142 Annotated::new("SELECT users".to_owned().into()),
143 ),
144 (
145 "db.operation.name".to_owned(),
146 Annotated::new("INSERT".to_owned().into()),
147 ),
148 (
149 "db.collection.name".to_owned(),
150 Annotated::new("widgets".to_owned().into()),
151 ),
152 ]);
153
154 assert_eq!(
155 name_for_attributes(&attributes),
156 Some("Custom name".to_owned())
157 );
158 }
159}