statsig_rust/event_logging/event_queue/
queued_layer_param_expo.rs

1use crate::{
2    evaluation::evaluation_types::ExtraExposureInfo,
3    event_logging::{
4        event_logger::ExposureTrigger,
5        exposure_sampling::{EvtSamplingDecision, ExposureSamplingKey},
6        exposure_utils::{get_metadata_with_details, get_statsig_metadata_with_sampling_decision},
7        statsig_event::StatsigEvent,
8        statsig_event_internal::{StatsigEventInternal, LAYER_EXPOSURE_EVENT_NAME},
9    },
10    hashing::ahash_str,
11    interned_string::InternedString,
12    statsig_types::Layer,
13    user::StatsigUserLoggable,
14    EvaluationDetails, SecondaryExposure,
15};
16
17use super::queued_event::{EnqueueOperation, QueuedEvent, QueuedExposure};
18use crate::event_logging::statsig_event::string_metadata_to_value_metadata;
19
20pub enum EnqueueLayerParamExpoOp<'a> {
21    LayerRef(u64, &'a Layer, &'a str, ExposureTrigger),
22    LayerOwned(u64, Box<Layer>, String, ExposureTrigger),
23}
24
25impl<'a> EnqueueLayerParamExpoOp<'a> {
26    fn get_layer_ref(&'a self) -> &'a Layer {
27        match self {
28            EnqueueLayerParamExpoOp::LayerRef(_, layer, _, _) => layer,
29            EnqueueLayerParamExpoOp::LayerOwned(_, layer, _, _) => layer,
30        }
31    }
32
33    fn get_parameter_name_ref(&'a self) -> &'a str {
34        match self {
35            EnqueueLayerParamExpoOp::LayerRef(_, _, parameter_name, _) => parameter_name,
36            EnqueueLayerParamExpoOp::LayerOwned(_, _, parameter_name, _) => parameter_name.as_str(),
37        }
38    }
39}
40
41impl EnqueueOperation for EnqueueLayerParamExpoOp<'_> {
42    fn as_exposure(&self) -> Option<&impl QueuedExposure<'_>> {
43        Some(self)
44    }
45
46    fn into_queued_event(self, sampling_decision: EvtSamplingDecision) -> QueuedEvent {
47        let event = match self {
48            EnqueueLayerParamExpoOp::LayerRef(exposure_time, layer, parameter_name, trigger) => {
49                extract_from_layer_ref(
50                    exposure_time,
51                    layer,
52                    parameter_name,
53                    trigger,
54                    sampling_decision,
55                )
56            }
57            EnqueueLayerParamExpoOp::LayerOwned(exposure_time, layer, parameter_name, trigger) => {
58                extract_from_layer_owned(
59                    exposure_time,
60                    layer,
61                    parameter_name,
62                    trigger,
63                    sampling_decision,
64                )
65            }
66        };
67
68        QueuedEvent::LayerParamExposure(event)
69    }
70}
71
72impl<'a> QueuedExposure<'a> for EnqueueLayerParamExpoOp<'a> {
73    fn create_exposure_sampling_key(&self) -> ExposureSamplingKey {
74        let layer = self.get_layer_ref();
75
76        let user_data = &layer.__user.data;
77        let evaluation = layer.__evaluation.as_ref().map(|e| &e.base);
78
79        // todo: use Cow and pre-hash the parameter name
80        let pname = self.get_parameter_name_ref();
81        let pname_hash = ahash_str(pname);
82
83        ExposureSamplingKey::new(evaluation, user_data.as_ref(), pname_hash)
84    }
85
86    fn get_rule_id_ref(&'a self) -> &'a str {
87        &self.get_layer_ref().rule_id
88    }
89
90    fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
91        self.get_layer_ref()
92            .__evaluation
93            .as_ref()?
94            .base
95            .exposure_info
96            .as_ref()
97    }
98}
99
100pub struct QueuedLayerParamExposureEvent {
101    pub user: StatsigUserLoggable,
102    pub layer_name: String,
103    pub rule_id: String,
104    pub parameter_name: String,
105    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
106    pub evaluation_details: EvaluationDetails,
107    pub version: Option<u32>,
108    pub exposure_trigger: ExposureTrigger,
109    pub sampling_decision: EvtSamplingDecision,
110    pub override_config_name: Option<InternedString>,
111    pub is_explicit: bool,
112    pub allocated_experiment: Option<InternedString>,
113    pub exposure_time: u64,
114}
115
116impl QueuedLayerParamExposureEvent {
117    pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
118        let mut metadata = get_metadata_with_details(self.evaluation_details);
119        metadata.insert("config".into(), self.layer_name);
120        metadata.insert("ruleID".into(), self.rule_id);
121        metadata.insert(
122            "allocatedExperiment".into(),
123            self.allocated_experiment
124                .unwrap_or_default()
125                .unperformant_to_string(),
126        );
127        metadata.insert("parameterName".into(), self.parameter_name);
128        metadata.insert("isExplicitParameter".into(), self.is_explicit.to_string());
129
130        if let Some(version) = self.version {
131            metadata.insert("configVersion".into(), version.to_string());
132        }
133
134        if self.exposure_trigger == ExposureTrigger::Manual {
135            metadata.insert("isManualExposure".into(), "true".into());
136        }
137
138        if let Some(override_config_name) = self.override_config_name {
139            metadata.insert(
140                "overrideConfigName".into(),
141                override_config_name.unperformant_to_string(),
142            );
143        }
144
145        let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
146
147        let event = StatsigEvent {
148            event_name: LAYER_EXPOSURE_EVENT_NAME.into(),
149            value: None,
150            metadata: Some(string_metadata_to_value_metadata(metadata)),
151            statsig_metadata: Some(statsig_metadata),
152        };
153
154        StatsigEventInternal::new(
155            self.exposure_time,
156            self.user,
157            event,
158            Some(self.secondary_exposures.unwrap_or_default()),
159        )
160    }
161}
162
163type ExtractFromEvaluationResult = (
164    bool,
165    Option<InternedString>,
166    Option<Vec<SecondaryExposure>>,
167    Option<u32>,
168    Option<InternedString>,
169);
170
171fn extract_exposure_info(layer: &Layer, parameter_name: &str) -> ExtractFromEvaluationResult {
172    let evaluation = match layer.__evaluation.as_ref() {
173        Some(eval) => eval,
174        None => return (false, None, None, None, None),
175    };
176
177    let is_explicit = evaluation.explicit_parameters.contains(parameter_name);
178    let secondary_exposures;
179    let mut allocated_experiment = None;
180
181    if is_explicit {
182        allocated_experiment = evaluation.allocated_experiment_name.clone();
183        secondary_exposures = Some(evaluation.base.secondary_exposures.clone());
184    } else {
185        secondary_exposures = evaluation.undelegated_secondary_exposures.clone();
186    }
187
188    // version might be on the top level or the exposure info
189    let mut version = layer.__version;
190    let mut override_config_name = None;
191
192    if let Some(exposure_info) = evaluation.base.exposure_info.as_ref() {
193        version = exposure_info.version;
194        override_config_name = exposure_info.override_config_name.clone();
195    }
196
197    (
198        is_explicit,
199        allocated_experiment,
200        secondary_exposures,
201        version,
202        override_config_name,
203    )
204}
205
206fn extract_from_layer_ref(
207    exposure_time: u64,
208    layer: &Layer,
209    param_name: &str,
210    trigger: ExposureTrigger,
211    sampling_decision: EvtSamplingDecision,
212) -> QueuedLayerParamExposureEvent {
213    let parameter_name = param_name.to_string();
214    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
215        extract_exposure_info(layer, &parameter_name);
216
217    let rule_id = match layer.__parameter_rule_ids {
218        Some(ref rule_ids) => rule_ids
219            .get(&InternedString::from_str_ref(param_name))
220            .map(|s| s.unperformant_to_string())
221            .unwrap_or_else(|| layer.rule_id.clone()),
222        None => layer.rule_id.clone(),
223    };
224
225    QueuedLayerParamExposureEvent {
226        exposure_time,
227        user: layer.__user.clone(),
228        layer_name: layer.name.clone(),
229        rule_id,
230        parameter_name,
231        exposure_trigger: trigger,
232        evaluation_details: layer.details.clone(),
233        version,
234        sampling_decision,
235        override_config_name,
236        secondary_exposures,
237        is_explicit,
238        allocated_experiment,
239    }
240}
241
242fn extract_from_layer_owned(
243    exposure_time: u64,
244    layer: Box<Layer>,
245    parameter_name: String,
246    trigger: ExposureTrigger,
247    sampling_decision: EvtSamplingDecision,
248) -> QueuedLayerParamExposureEvent {
249    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
250        extract_exposure_info(&layer, &parameter_name);
251
252    let rule_id = match layer.__parameter_rule_ids {
253        Some(ref rule_ids) => rule_ids
254            .get(&InternedString::from_str_ref(parameter_name.as_str()))
255            .map(|s| s.unperformant_to_string())
256            .unwrap_or_else(|| layer.rule_id.clone()),
257        None => layer.rule_id.clone(),
258    };
259
260    QueuedLayerParamExposureEvent {
261        exposure_time,
262        user: layer.__user,
263        layer_name: layer.name,
264        rule_id,
265        parameter_name,
266        exposure_trigger: trigger,
267        evaluation_details: layer.details,
268        version,
269        sampling_decision,
270        override_config_name,
271        secondary_exposures,
272        is_explicit,
273        allocated_experiment,
274    }
275}