1#[cfg(feature = "stateful")]
6use crate::enrollment::PreviousGeckoPrefState;
7
8use crate::{enrollment::ExperimentEnrollment, EnrolledFeature, EnrollmentStatus};
9use serde_derive::{Deserialize, Serialize};
10
11pub trait MetricsHandler: Send + Sync {
12 fn record_enrollment_statuses(&self, enrollment_status_extras: Vec<EnrollmentStatusExtraDef>);
13
14 #[cfg(feature = "stateful")]
15 fn record_feature_activation(&self, event: FeatureExposureExtraDef);
16
17 #[cfg(feature = "stateful")]
18 fn record_feature_exposure(&self, event: FeatureExposureExtraDef);
19
20 #[cfg(feature = "stateful")]
21 fn record_malformed_feature_config(&self, event: MalformedFeatureConfigExtraDef);
22}
23
24#[derive(Serialize, Deserialize, Clone)]
25pub struct EnrollmentStatusExtraDef {
26 pub branch: Option<String>,
27 pub conflict_slug: Option<String>,
28 pub error_string: Option<String>,
29 pub reason: Option<String>,
30 pub slug: Option<String>,
31 pub status: Option<String>,
32 #[cfg(not(feature = "stateful"))]
33 pub user_id: Option<String>,
34 #[cfg(feature = "stateful")]
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub prev_gecko_pref_states: Option<Vec<PreviousGeckoPrefState>>,
37}
38
39#[cfg(test)]
40impl EnrollmentStatusExtraDef {
41 pub fn branch(&self) -> &str {
42 self.branch.as_ref().unwrap()
43 }
44
45 pub fn conflict_slug(&self) -> &str {
46 self.conflict_slug.as_ref().unwrap()
47 }
48
49 pub fn error_string(&self) -> &str {
50 self.error_string.as_ref().unwrap()
51 }
52
53 pub fn reason(&self) -> &str {
54 self.reason.as_ref().unwrap()
55 }
56
57 pub fn slug(&self) -> &str {
58 self.slug.as_ref().unwrap()
59 }
60
61 pub fn status(&self) -> &str {
62 self.status.as_ref().unwrap()
63 }
64
65 #[cfg(not(feature = "stateful"))]
66 pub fn user_id(&self) -> &str {
67 self.user_id.as_ref().unwrap()
68 }
69}
70
71impl From<ExperimentEnrollment> for EnrollmentStatusExtraDef {
72 fn from(enrollment: ExperimentEnrollment) -> Self {
73 let mut branch_value: Option<String> = None;
74 let mut reason_value: Option<String> = None;
75 let mut error_value: Option<String> = None;
76 match &enrollment.status {
77 EnrollmentStatus::Enrolled { reason, branch, .. } => {
78 branch_value = Some(branch.to_owned());
79 reason_value = Some(reason.to_string());
80 }
81 EnrollmentStatus::Disqualified { reason, branch, .. } => {
82 branch_value = Some(branch.to_owned());
83 reason_value = Some(reason.to_string());
84 }
85 EnrollmentStatus::NotEnrolled { reason } => {
86 reason_value = Some(reason.to_string());
87 }
88 EnrollmentStatus::WasEnrolled { branch, .. } => branch_value = Some(branch.to_owned()),
89 EnrollmentStatus::Error { reason } => {
90 error_value = Some(reason.to_owned());
91 }
92 }
93 EnrollmentStatusExtraDef {
94 branch: branch_value,
95 conflict_slug: None,
96 error_string: error_value,
97 reason: reason_value,
98 slug: Some(enrollment.slug),
99 status: Some(enrollment.status.name()),
100 #[cfg(not(feature = "stateful"))]
101 user_id: None,
102 #[cfg(feature = "stateful")]
103 prev_gecko_pref_states: None,
104 }
105 }
106}
107
108#[derive(Clone)]
109pub struct FeatureExposureExtraDef {
110 pub branch: Option<String>,
111 pub slug: String,
112 pub feature_id: String,
113}
114
115impl From<EnrolledFeature> for FeatureExposureExtraDef {
116 fn from(value: EnrolledFeature) -> Self {
117 Self {
118 feature_id: value.feature_id,
119 branch: value.branch,
120 slug: value.slug,
121 }
122 }
123}
124
125#[derive(Clone, Debug, Default, PartialEq)]
126pub struct MalformedFeatureConfigExtraDef {
127 pub slug: Option<String>,
128 pub branch: Option<String>,
129 pub feature_id: String,
130 pub part: String,
131}
132
133#[cfg(feature = "stateful")]
134impl MalformedFeatureConfigExtraDef {
135 pub(crate) fn from(value: EnrolledFeature, part: String) -> Self {
136 Self {
137 slug: Some(value.slug),
138 branch: value.branch,
139 feature_id: value.feature_id,
140 part,
141 }
142 }
143
144 pub(crate) fn new(feature_id: String, part: String) -> Self {
145 Self {
146 feature_id,
147 part,
148 ..Default::default()
149 }
150 }
151}