statsig_rust/event_logging/event_queue/
queued_gate_expo.rs1use std::borrow::Cow;
2
3use crate::{
4 evaluation::evaluation_types::{ExtraExposureInfo, GateEvaluation},
5 event_logging::{
6 event_logger::ExposureTrigger,
7 exposure_sampling::{EvtSamplingDecision, ExposureSamplingKey},
8 exposure_utils::{get_metadata_with_details, get_statsig_metadata_with_sampling_decision},
9 statsig_event::StatsigEvent,
10 statsig_event_internal::{StatsigEventInternal, GATE_EXPOSURE_EVENT_NAME},
11 },
12 interned_string::InternedString,
13 user::{StatsigUserInternal, StatsigUserLoggable},
14 EvaluationDetails, SecondaryExposure,
15};
16
17use super::queued_event::{EnqueueOperation, QueuedEvent, QueuedExposure};
18
19pub struct EnqueueGateExpoOp<'a> {
20 pub user: &'a StatsigUserInternal<'a, 'a>,
21 pub queried_gate_name: &'a str,
22 pub evaluation: Option<Cow<'a, GateEvaluation>>,
23 pub details: EvaluationDetails,
24 pub trigger: ExposureTrigger,
25 pub exposure_time: u64,
26}
27
28impl EnqueueOperation for EnqueueGateExpoOp<'_> {
29 fn as_exposure(&self) -> Option<&impl QueuedExposure<'_>> {
30 Some(self)
31 }
32
33 fn into_queued_event(self, sampling_decision: EvtSamplingDecision) -> QueuedEvent {
34 let (
35 gate_name, rule_id,
37 value,
38 version,
39 override_config_name,
40 secondary_exposures,
41 ) = extract_from_cow(self.evaluation);
42
43 QueuedEvent::GateExposure(QueuedGateExposureEvent {
44 user: self.user.to_loggable(),
45 gate_name,
46 value,
47 exposure_time: self.exposure_time,
48 version,
49 rule_id,
50 secondary_exposures,
51 evaluation_details: self.details,
52 override_config_name,
53 exposure_trigger: self.trigger,
54 sampling_decision,
55 })
56 }
57}
58
59impl<'a> QueuedExposure<'a> for EnqueueGateExpoOp<'a> {
60 fn create_exposure_sampling_key(&self) -> ExposureSamplingKey {
61 let user_data = &self.user.user_ref.data;
62 let evaluation = self.evaluation.as_ref().map(|e| &e.base);
63 let value = self.evaluation.as_ref().is_some_and(|e| e.value);
64
65 ExposureSamplingKey::new(evaluation, user_data.as_ref(), value as u64)
66 }
67
68 fn get_rule_id_ref(&'a self) -> &'a str {
69 rule_id_ref(&self.evaluation)
70 }
71
72 fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
73 self.evaluation.as_ref()?.base.exposure_info.as_ref()
74 }
75}
76
77pub struct QueuedGateExposureEvent {
78 pub user: StatsigUserLoggable,
79 pub gate_name: InternedString,
80 pub value: bool,
81 pub rule_id: InternedString,
82 pub secondary_exposures: Option<Vec<SecondaryExposure>>,
83 pub evaluation_details: EvaluationDetails,
84 pub version: Option<u32>,
85 pub exposure_trigger: ExposureTrigger,
86 pub sampling_decision: EvtSamplingDecision,
87 pub override_config_name: Option<String>,
88 pub exposure_time: u64,
89}
90
91impl QueuedGateExposureEvent {
92 pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
93 let mut metadata = get_metadata_with_details(self.evaluation_details);
94 metadata.insert("gate".into(), self.gate_name.unperformant_to_string());
95 metadata.insert("gateValue".into(), self.value.to_string());
96
97 metadata.insert("ruleID".into(), self.rule_id.unperformant_to_string());
100
101 if self.exposure_trigger == ExposureTrigger::Manual {
102 metadata.insert("isManualExposure".into(), "true".into());
103 }
104
105 if let Some(version) = self.version {
106 metadata.insert("configVersion".into(), version.to_string());
107 }
108
109 if let Some(override_config_name) = self.override_config_name {
110 metadata.insert("overrideConfigName".into(), override_config_name);
111 }
112
113 let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
114
115 let event = StatsigEvent {
116 event_name: GATE_EXPOSURE_EVENT_NAME.into(),
117 value: None,
118 metadata: Some(metadata),
119 statsig_metadata: Some(statsig_metadata),
120 };
121
122 StatsigEventInternal::new(
123 self.exposure_time,
124 self.user,
125 event,
126 self.secondary_exposures,
127 )
128 }
129}
130
131type ExtractInfoResult = (
132 InternedString,
133 InternedString,
134 bool,
135 Option<u32>,
136 Option<String>,
137 Option<Vec<SecondaryExposure>>,
138);
139
140fn extract_from_cow(moo: Option<Cow<'_, GateEvaluation>>) -> ExtractInfoResult {
141 let moo = match moo {
142 Some(m) => m,
143 None => {
144 return (
145 InternedString::default(),
146 InternedString::default(),
147 false,
148 None,
149 None,
150 None,
151 )
152 }
153 };
154
155 match moo {
156 Cow::Borrowed(evaluation) => {
157 let name = evaluation.base.name.clone();
158 let rule_id = evaluation.base.rule_id.clone();
159 let value = evaluation.value;
160 let expo_info = evaluation.base.exposure_info.clone();
161 let secondary_exposures = evaluation.base.secondary_exposures.clone();
162
163 let version = expo_info.as_ref().and_then(|info| info.version);
164 let override_config_name = expo_info
165 .as_ref()
166 .and_then(|info| info.override_config_name.clone());
167
168 (
169 name,
170 rule_id,
171 value,
172 version,
173 override_config_name,
174 Some(secondary_exposures),
175 )
176 }
177 Cow::Owned(evaluation) => {
178 let name = evaluation.base.name;
179 let rule_id = evaluation.base.rule_id;
180 let value = evaluation.value;
181 let expo_info = evaluation.base.exposure_info;
182 let secondary_exposures = evaluation.base.secondary_exposures;
183
184 let version = expo_info.as_ref().and_then(|info| info.version);
185 let override_config_name = expo_info.and_then(|info| info.override_config_name.clone());
186
187 (
188 name,
189 rule_id,
190 value,
191 version,
192 override_config_name,
193 Some(secondary_exposures),
194 )
195 }
196 }
197}
198
199fn rule_id_ref<'a>(moo: &'a Option<Cow<'a, GateEvaluation>>) -> &'a str {
200 moo.as_ref().map_or("", |x| x.base.rule_id.as_str())
201}