nimbus/
metrics.rs

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