Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(feedback): add User Feedbacks as Events #2604

Merged
merged 25 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 23 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Changelog

## Unrelease
## Unreleased

**Features**:

- Add inbound filters option to filter legacy Edge browsers (i.e. versions 12-18 ) ([#2650](https://github.com/getsentry/relay/pull/2650))
- Add User Feedback Ingestion. ([#2604](https://github.com/getsentry/relay/pull/2604))
- Group resource spans by scrubbed domain and filename. ([#2654](https://github.com/getsentry/relay/pull/2654))

**Internal**:
Expand Down
1 change: 1 addition & 0 deletions py/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Remove event breadcrumbs dating before January 1, 1970 UTC. ([#2635](https://github.com/getsentry/relay/pull/2635))
- Add `PerformanceScoreConfig` config and performance score calculations to measurements for frontend events. ([#2632](https://github.com/getsentry/relay/pull/2632))
- Add `locale` ,`screen_width_pixels`, `screen_height_pixels`, and `uuid` to the device context. ([#2640](https://github.com/getsentry/relay/pull/2640))
- Add feedback DataCategory. ([#2604](https://github.com/getsentry/relay/pull/2604))

## 0.8.31

Expand Down
1 change: 1 addition & 0 deletions py/sentry_relay/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class DataCategory(IntEnum):
PROFILE_INDEXED = 11
SPAN = 12
MONITOR_SEAT = 13
USER_REPORT_V2 = 14
UNKNOWN = -1
# end generated

Expand Down
8 changes: 8 additions & 0 deletions relay-base-schema/src/data_category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ pub enum DataCategory {
/// but we define it here to prevent clashing values since this data category enumeration
/// is also used outside of Relay via the Python package.
MonitorSeat = 13,
/// User Feedback
///
/// Represents a User Feedback processed.
/// Currently standardized on name UserReportV2 to avoid clashing with the old UserReport.
/// TODO(jferg): Rename this to UserFeedback once old UserReport is deprecated.
UserReportV2 = 14,
Comment on lines +63 to +65
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on when UserReport -> UserFeedback transition happens, we may need to support both alternatives to avoid future breaking changes. Have we considered this? Is there any ETA on the deprecation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use UserFeedback right away? I agree with Iker that renaming later could bring extra complexity and possible compatibility issues.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when discussing with @jan-auer over initial call, he called out to not have it so there are "synonyms" in the code. I can change to UserFeedback if that is preferred.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think more important than the internal naming is making sure that the serialized string representations in the public interface do not have to be changed, e.g "user_report_v2" for the data category.

The internal names of the types are less important, but I did a case insensitive search for "feedback" and found only a few references in comments, so I would personally go for the new naming straight away, but I'll leave it up to you. cc @jan-auer

jjbayer marked this conversation as resolved.
Show resolved Hide resolved
//
// IMPORTANT: After adding a new entry to DataCategory, go to the `relay-cabi` subfolder and run
// `make header` to regenerate the C-binding. This allows using the data category from Python.
Expand Down Expand Up @@ -108,6 +114,7 @@ impl DataCategory {
Self::Monitor => "monitor",
Self::Span => "span",
Self::MonitorSeat => "monitor_seat",
Self::UserReportV2 => "feedback",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this string to the from_name above before merging, otherwise Relay won't recognize the data category when deserializing.

Self::Unknown => "unknown",
}
}
Expand Down Expand Up @@ -157,6 +164,7 @@ impl From<EventType> for DataCategory {
EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
Self::Security
}
EventType::UserReportV2 => Self::UserReportV2,
}
}
}
6 changes: 6 additions & 0 deletions relay-base-schema/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ pub enum EventType {
ExpectStaple,
/// Performance monitoring transactions carrying spans.
Transaction,
/// User feedback payload.
///
/// TODO(Jferg): Change this to UserFeedback once old UserReport logic is deprecated.
UserReportV2,
/// All events that do not qualify as any other type.
#[serde(other)]
#[default]
Expand Down Expand Up @@ -71,6 +75,7 @@ impl FromStr for EventType {
"expectct" => EventType::ExpectCt,
"expectstaple" => EventType::ExpectStaple,
"transaction" => EventType::Transaction,
"feedback" => EventType::UserReportV2,
jjbayer marked this conversation as resolved.
Show resolved Hide resolved
_ => return Err(ParseEventTypeError),
})
}
Expand All @@ -86,6 +91,7 @@ impl fmt::Display for EventType {
EventType::ExpectCt => write!(f, "expectct"),
EventType::ExpectStaple => write!(f, "expectstaple"),
EventType::Transaction => write!(f, "transaction"),
EventType::UserReportV2 => write!(f, "feedback"),
}
}
}
Expand Down
20 changes: 9 additions & 11 deletions relay-cabi/include/relay.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#ifndef RELAY_H_INCLUDED
#define RELAY_H_INCLUDED

/* Generated with cbindgen:0.25.0 */
/* Generated with cbindgen:0.26.0 */

/* Warning, this file is autogenerated. Do not modify this manually. */

Expand Down Expand Up @@ -88,6 +88,14 @@ enum RelayDataCategory {
* is also used outside of Relay via the Python package.
*/
RELAY_DATA_CATEGORY_MONITOR_SEAT = 13,
/**
* User Feedback
*
* Represents a User Feedback processed.
* Currently standardized on name UserReportV2 to avoid clashing with the old UserReport.
* TODO(jferg): Rename this to UserFeedback once old UserReport is deprecated.
*/
RELAY_DATA_CATEGORY_USER_REPORT_V2 = 14,
/**
* Any other data category not known by this Relay.
*/
Expand Down Expand Up @@ -616,14 +624,4 @@ struct RelayStr relay_validate_project_config(const struct RelayStr *value,
*/
struct RelayStr normalize_global_config(const struct RelayStr *value);

/**
* Runs dynamic sampling given the sampling config, root sampling config, DSC and event.
*
* Returns the sampling decision containing the sample_rate and the list of matched rule ids.
*/
struct RelayStr run_dynamic_sampling(const struct RelayStr *sampling_config,
const struct RelayStr *root_sampling_config,
const struct RelayStr *dsc,
const struct RelayStr *event);

#endif /* RELAY_H_INCLUDED */
5 changes: 5 additions & 0 deletions relay-dynamic-config/src/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ pub enum Feature {
/// Enables data scrubbing of replay recording payloads.
#[serde(rename = "organizations:session-replay-recording-scrubbing")]
SessionReplayRecordingScrubbing,
/// Enables new User Feedback ingest.
///
/// TODO(jferg): rename to UserFeedbackIngest once old UserReport logic is deprecated.
#[serde(rename = "organizations:user-feedback-ingest")]
UserReportV2Ingest,
/// Enables device.class synthesis
///
/// Enables device.class tag synthesis on mobile events.
Expand Down
3 changes: 3 additions & 0 deletions relay-event-normalization/src/normalize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ impl<'a> NormalizeProcessor<'a> {
if event.ty.value() == Some(&EventType::Transaction) {
return EventType::Transaction;
}
if event.ty.value() == Some(&EventType::UserReportV2) {
return EventType::UserReportV2;
}

// The SDKs do not describe event types, and we must infer them from available attributes.
let has_exceptions = event
Expand Down
6 changes: 5 additions & 1 deletion relay-event-schema/src/protocol/contexts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ mod reprocessing;
mod response;
mod runtime;
mod trace;

mod user_report_v2;
pub use app::*;
pub use browser::*;
pub use cloud_resource::*;
Expand All @@ -27,6 +27,7 @@ pub use reprocessing::*;
pub use response::*;
pub use runtime::*;
pub use trace::*;
pub use user_report_v2::*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* exports above aren't ideal, could we only export the types we need?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should i include that refactor in this PR, or could I follow up with that? not sure how you all like to split changes like that into separate PRs or not.


#[cfg(feature = "jsonschema")]
use relay_jsonschema_derive::JsonSchema;
Expand Down Expand Up @@ -67,6 +68,9 @@ pub enum Context {
Profile(Box<ProfileContext>),
/// Information related to Replay.
Replay(Box<ReplayContext>),
/// Information related to User Report V2. TODO:(jferg): rename to UserFeedbackContext
#[metastructure(tag = "feedback")]
UserReportV2(Box<UserReportV2Context>),
/// Information related to Monitors feature.
Monitor(Box<MonitorContext>),
/// Auxilliary information for reprocessing.
Expand Down
2 changes: 1 addition & 1 deletion relay-event-schema/src/protocol/contexts/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ mod tests {
use crate::protocol::Context;

#[test]
fn test_trace_context_roundtrip() {
fn test_replay_context() {
let json = r#"{
"replay_id": "4c79f60c11214eb38604f4ae0781bfb2",
"type": "replay"
Expand Down
78 changes: 78 additions & 0 deletions relay-event-schema/src/protocol/contexts/user_report_v2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#[cfg(feature = "jsonschema")]
use relay_jsonschema_derive::JsonSchema;
use relay_protocol::{Annotated, Empty, FromValue, IntoValue, Object, Value};

use crate::processor::ProcessValue;

/// Feedback context.
///
/// This contexts contains user feedback specific attributes.
/// We don't PII scrub contact_email as that is provided by the user.
/// TODO(jferg): rename to FeedbackContext once old UserReport logic is deprecated.
#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
#[cfg_attr(feature = "jsonschema", derive(JsonSchema))]
pub struct UserReportV2Context {
/// The feedback message which contains what the user has to say.
pub message: Annotated<String>,

/// an email optionally provided by the user, which can be different from user.email
#[metastructure(pii = "false")]
pub contact_email: Annotated<String>,
/// Additional arbitrary fields for forwards compatibility.
#[metastructure(additional_properties, retain = "true")]
pub other: Object<Value>,
}

impl super::DefaultContext for UserReportV2Context {
fn default_key() -> &'static str {
"feedback"
}

fn from_context(context: super::Context) -> Option<Self> {
match context {
super::Context::UserReportV2(c) => Some(*c),
_ => None,
}
}

fn cast(context: &super::Context) -> Option<&Self> {
match context {
super::Context::UserReportV2(c) => Some(c),
_ => None,
}
}

fn cast_mut(context: &mut super::Context) -> Option<&mut Self> {
match context {
super::Context::UserReportV2(c) => Some(c),
_ => None,
}
}

fn into_context(self) -> super::Context {
super::Context::UserReportV2(Box::new(self))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::Context;

#[test]
fn test_feedback_context() {
let json = r#"{
"message": "test message",
"contact_email": "test@test.com",
"type": "feedback"
}"#;
let context = Annotated::new(Context::UserReportV2(Box::new(UserReportV2Context {
message: Annotated::new("test message".to_string()),
contact_email: Annotated::new("test@test.com".to_string()),
other: Object::default(),
})));

assert_eq!(context, Annotated::from_json(json).unwrap());
assert_eq!(json, context.to_json_pretty().unwrap());
}
}
3 changes: 2 additions & 1 deletion relay-quotas/src/quota.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ impl CategoryUnit {
| DataCategory::TransactionIndexed
| DataCategory::Span
| DataCategory::MonitorSeat
| DataCategory::Monitor => Some(Self::Count),
| DataCategory::Monitor
| DataCategory::UserReportV2 => Some(Self::Count),
DataCategory::Attachment => Some(Self::Bytes),
DataCategory::Session => Some(Self::Batched),

Expand Down
12 changes: 12 additions & 0 deletions relay-server/src/actors/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,7 @@ impl EnvelopeProcessorService {
ItemType::Security => true,
ItemType::FormData => true,
ItemType::RawSecurity => true,
ItemType::UserReportV2 => true,

// These should be removed conditionally:
ItemType::UnrealReport => self.inner.config.processing_enabled(),
Expand Down Expand Up @@ -1687,6 +1688,9 @@ impl EnvelopeProcessorService {
let transaction_item = envelope.take_item_by(|item| item.ty() == &ItemType::Transaction);
let security_item = envelope.take_item_by(|item| item.ty() == &ItemType::Security);
let raw_security_item = envelope.take_item_by(|item| item.ty() == &ItemType::RawSecurity);
let user_report_v2_item =
envelope.take_item_by(|item| item.ty() == &ItemType::UserReportV2);

let form_item = envelope.take_item_by(|item| item.ty() == &ItemType::FormData);
let attachment_item = envelope
.take_item_by(|item| item.attachment_type() == Some(&AttachmentType::EventPayload));
Expand Down Expand Up @@ -1718,6 +1722,14 @@ impl EnvelopeProcessorService {
// hint to normalization that we're dealing with a transaction now.
self.event_from_json_payload(item, Some(EventType::Transaction))?
})
} else if let Some(item) = user_report_v2_item {
relay_log::trace!("processing user_report_v2");
let project_state = &state.project_state;
let user_report_v2_ingest = project_state.has_feature(Feature::UserReportV2Ingest);
if !user_report_v2_ingest {
return Err(ProcessingError::NoEventPayload);
}
self.event_from_json_payload(item, Some(EventType::UserReportV2))?
JoshFerge marked this conversation as resolved.
Show resolved Hide resolved
} else if let Some(mut item) = raw_security_item {
relay_log::trace!("processing security report");
sample_rates = item.take_sample_rates();
Expand Down
5 changes: 4 additions & 1 deletion relay-server/src/actors/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ impl StoreService {
let event_item = envelope.get_item_by(|item| {
matches!(
item.ty(),
ItemType::Event | ItemType::Transaction | ItemType::Security
ItemType::Event
| ItemType::Transaction
| ItemType::Security
| ItemType::UserReportV2
)
});

Expand Down
11 changes: 10 additions & 1 deletion relay-server/src/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ pub enum ItemType {
CheckIn,
/// A standalone span.
Span,
/// UserReport as an Event
UserReportV2,
/// A new item type that is yet unknown by this version of Relay.
///
/// By default, items of this type are forwarded without modification. Processing Relays and
Expand All @@ -127,6 +129,7 @@ impl ItemType {
match event_type {
EventType::Default | EventType::Error => ItemType::Event,
EventType::Transaction => ItemType::Transaction,
EventType::UserReportV2 => ItemType::UserReportV2,
EventType::Csp | EventType::Hpkp | EventType::ExpectCt | EventType::ExpectStaple => {
ItemType::Security
}
Expand All @@ -145,6 +148,7 @@ impl fmt::Display for ItemType {
Self::RawSecurity => write!(f, "raw_security"),
Self::UnrealReport => write!(f, "unreal_report"),
Self::UserReport => write!(f, "user_report"),
Self::UserReportV2 => write!(f, "feedback"),
Self::Session => write!(f, "session"),
Self::Sessions => write!(f, "sessions"),
Self::Statsd => write!(f, "statsd"),
Expand Down Expand Up @@ -173,6 +177,7 @@ impl std::str::FromStr for ItemType {
"raw_security" => Self::RawSecurity,
"unreal_report" => Self::UnrealReport,
"user_report" => Self::UserReport,
"feedback" => Self::UserReportV2,
"session" => Self::Session,
"sessions" => Self::Sessions,
"statsd" => Self::Statsd,
Expand Down Expand Up @@ -556,6 +561,7 @@ impl Item {
ItemType::Statsd | ItemType::MetricBuckets => None,
ItemType::FormData => None,
ItemType::UserReport => None,
ItemType::UserReportV2 => None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means no outcomes will be generated. Is this intended?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for now, yes. will change this once we have data flowing through system and we're closer to alpha etc.

ItemType::Profile => Some(if indexed {
DataCategory::ProfileIndexed
} else {
Expand Down Expand Up @@ -700,7 +706,8 @@ impl Item {
| ItemType::Transaction
| ItemType::Security
| ItemType::RawSecurity
| ItemType::UnrealReport => true,
| ItemType::UnrealReport
| ItemType::UserReportV2 => true,

// Attachments are only event items if they are crash reports or if they carry partial
// event payloads. Plain attachments never create event payloads.
Expand Down Expand Up @@ -758,6 +765,8 @@ impl Item {
ItemType::RawSecurity => true,
ItemType::UnrealReport => true,
ItemType::UserReport => true,
ItemType::UserReportV2 => true,

ItemType::ReplayEvent => true,
ItemType::Session => false,
ItemType::Sessions => false,
Expand Down
1 change: 1 addition & 0 deletions relay-server/src/utils/rate_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fn infer_event_category(item: &Item) -> Option<DataCategory> {
ItemType::Transaction => Some(DataCategory::Transaction),
ItemType::Security | ItemType::RawSecurity => Some(DataCategory::Security),
ItemType::UnrealReport => Some(DataCategory::Error),
ItemType::UserReportV2 => Some(DataCategory::UserReportV2),
ItemType::Attachment if item.creates_event() => Some(DataCategory::Error),
ItemType::Attachment => None,
ItemType::Session => None,
Expand Down
1 change: 1 addition & 0 deletions relay-server/src/utils/sizes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub fn check_envelope_size_limits(config: &Config, envelope: &Envelope) -> bool
| ItemType::Security
| ItemType::ReplayEvent
| ItemType::RawSecurity
| ItemType::UserReportV2
| ItemType::FormData => {
event_size += item.len();
}
Expand Down
Loading
Loading