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