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