leptos_next_metadata/analytics/
integration.rs

1//! Analytics Integration
2//!
3//! Provides integration between the analytics system and the main
4//! metadata library, automatically tracking metadata operations.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::time::{SystemTime, UNIX_EPOCH};
9
10use crate::analytics::{AnalyticsConfig, AnalyticsEventType, AnalyticsManager, PerformanceMetrics};
11use crate::canvas_types::CanvasOgParams;
12use crate::error::{ErrorKind, MetadataError};
13use crate::themes::Theme;
14
15/// Analytics integration for metadata operations
16pub struct MetadataAnalyticsIntegration {
17    /// Analytics manager
18    manager: AnalyticsManager,
19    /// Operation start times
20    operation_timers: HashMap<String, u64>,
21}
22
23/// Analytics integration configuration
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct IntegrationConfig {
26    /// Enable automatic tracking
27    pub auto_track: bool,
28    /// Track metadata generation
29    pub track_metadata: bool,
30    /// Track OG image generation
31    pub track_og_images: bool,
32    /// Track theme applications
33    pub track_themes: bool,
34    /// Track validation
35    pub track_validation: bool,
36    /// Track errors
37    pub track_errors: bool,
38    /// Include performance metrics
39    pub include_performance: bool,
40    /// Include user context
41    pub include_user_context: bool,
42}
43
44impl Default for IntegrationConfig {
45    fn default() -> Self {
46        Self {
47            auto_track: true,
48            track_metadata: true,
49            track_og_images: true,
50            track_themes: true,
51            track_validation: true,
52            track_errors: true,
53            include_performance: true,
54            include_user_context: true,
55        }
56    }
57}
58
59impl MetadataAnalyticsIntegration {
60    /// Create a new analytics integration
61    pub fn new(config: AnalyticsConfig, _integration_config: IntegrationConfig) -> Self {
62        let mut manager = AnalyticsManager::new(config);
63
64        // Add default handlers based on environment
65        #[cfg(target_arch = "wasm32")]
66        {
67            use crate::analytics::handlers::AnalyticsHandlerFactory;
68            manager.add_handler(AnalyticsHandlerFactory::console(None, None));
69            manager.add_handler(AnalyticsHandlerFactory::local_storage(None, None, None));
70        }
71
72        #[cfg(not(target_arch = "wasm32"))]
73        {
74            use crate::analytics::handlers::AnalyticsHandlerFactory;
75            manager.add_handler(AnalyticsHandlerFactory::console(None, None));
76            manager.add_handler(AnalyticsHandlerFactory::file(None, None, None));
77        }
78
79        Self {
80            manager,
81            operation_timers: HashMap::new(),
82        }
83    }
84
85    /// Create a default analytics integration
86    pub fn default() -> Self {
87        let analytics_config = AnalyticsConfig::default();
88        let integration_config = IntegrationConfig::default();
89        Self::new(analytics_config, integration_config)
90    }
91
92    /// Start tracking an operation
93    pub fn start_operation(&mut self, operation_id: String) -> Result<(), MetadataError> {
94        let start_time = SystemTime::now()
95            .duration_since(UNIX_EPOCH)
96            .map_err(|e| MetadataError::new(ErrorKind::Unknown, e.to_string()))?
97            .as_millis() as u64;
98
99        self.operation_timers.insert(operation_id, start_time);
100        Ok(())
101    }
102
103    /// End tracking an operation
104    pub fn end_operation(
105        &mut self,
106        operation_id: String,
107        operation_type: &str,
108        success: bool,
109        properties: HashMap<String, serde_json::Value>,
110    ) -> Result<u64, MetadataError> {
111        let end_time = SystemTime::now()
112            .duration_since(UNIX_EPOCH)
113            .map_err(|e| MetadataError::new(ErrorKind::Unknown, e.to_string()))?
114            .as_millis() as u64;
115
116        let duration_ms = if let Some(start_time) = self.operation_timers.remove(&operation_id) {
117            end_time - start_time
118        } else {
119            0
120        };
121
122        // Track performance
123        self.manager
124            .track_performance(operation_type, duration_ms, success, None)?;
125
126        // Track as event
127        let mut event_properties = properties;
128        event_properties.insert(
129            "operation_type".to_string(),
130            serde_json::Value::String(operation_type.to_string()),
131        );
132        event_properties.insert("success".to_string(), serde_json::Value::Bool(success));
133
134        self.manager.track_event(
135            AnalyticsEventType::PerformanceMeasured,
136            event_properties,
137            Some(duration_ms),
138        )?;
139
140        Ok(duration_ms)
141    }
142
143    /// Track metadata generation
144    pub fn track_metadata_generation(
145        &mut self,
146        metadata_type: &str,
147        success: bool,
148        properties: HashMap<String, serde_json::Value>,
149    ) -> Result<(), MetadataError> {
150        let mut event_properties = properties;
151        event_properties.insert(
152            "metadata_type".to_string(),
153            serde_json::Value::String(metadata_type.to_string()),
154        );
155        event_properties.insert("success".to_string(), serde_json::Value::Bool(success));
156
157        self.manager.track_event(
158            AnalyticsEventType::MetadataGenerated,
159            event_properties,
160            None,
161        )?;
162
163        Ok(())
164    }
165
166    /// Track OG image generation
167    pub fn track_og_image_generation(
168        &mut self,
169        params: &CanvasOgParams,
170        success: bool,
171        generation_time_ms: u64,
172        image_size: Option<(u32, u32)>,
173    ) -> Result<(), MetadataError> {
174        let mut properties = HashMap::new();
175        properties.insert(
176            "title".to_string(),
177            serde_json::Value::String(params.title.clone()),
178        );
179        properties.insert("success".to_string(), serde_json::Value::Bool(success));
180        properties.insert(
181            "generation_time_ms".to_string(),
182            serde_json::Value::Number(serde_json::Number::from(generation_time_ms)),
183        );
184
185        if let Some(description) = &params.description {
186            properties.insert(
187                "description".to_string(),
188                serde_json::Value::String(description.clone()),
189            );
190        }
191
192        if let Some(width) = params.width {
193            properties.insert(
194                "width".to_string(),
195                serde_json::Value::Number(serde_json::Number::from(width)),
196            );
197        }
198
199        if let Some(height) = params.height {
200            properties.insert(
201                "height".to_string(),
202                serde_json::Value::Number(serde_json::Number::from(height)),
203            );
204        }
205
206        if let Some(background_color) = &params.background_color {
207            properties.insert(
208                "background_color".to_string(),
209                serde_json::Value::String(background_color.clone()),
210            );
211        }
212
213        if let Some(font_family) = &params.font_family {
214            properties.insert(
215                "font_family".to_string(),
216                serde_json::Value::String(font_family.clone()),
217            );
218        }
219
220        if let Some((width, height)) = image_size {
221            properties.insert(
222                "generated_width".to_string(),
223                serde_json::Value::Number(serde_json::Number::from(width)),
224            );
225            properties.insert(
226                "generated_height".to_string(),
227                serde_json::Value::Number(serde_json::Number::from(height)),
228            );
229        }
230
231        // Check if theme was used
232        if params.text_gradient.is_some()
233            || params.text_shadow.is_some()
234            || params.text_outline.is_some()
235        {
236            properties.insert(
237                "has_theme_effects".to_string(),
238                serde_json::Value::Bool(true),
239            );
240        }
241
242        if params.layers.is_some() {
243            properties.insert(
244                "has_custom_layers".to_string(),
245                serde_json::Value::Bool(true),
246            );
247        }
248
249        self.manager.track_event(
250            AnalyticsEventType::OgImageGenerated,
251            properties,
252            Some(generation_time_ms),
253        )?;
254
255        // Also track as performance event
256        self.manager
257            .track_performance("og_image_generation", generation_time_ms, success, None)?;
258
259        Ok(())
260    }
261
262    /// Track theme application
263    pub fn track_theme_application(
264        &mut self,
265        theme: &Theme,
266        success: bool,
267        application_time_ms: u64,
268    ) -> Result<(), MetadataError> {
269        let mut properties = HashMap::new();
270        properties.insert(
271            "theme_id".to_string(),
272            serde_json::Value::String(theme.id.clone()),
273        );
274        properties.insert(
275            "theme_name".to_string(),
276            serde_json::Value::String(theme.name.clone()),
277        );
278        properties.insert(
279            "theme_category".to_string(),
280            serde_json::Value::String(format!("{:?}", theme.category)),
281        );
282        properties.insert("success".to_string(), serde_json::Value::Bool(success));
283        properties.insert(
284            "application_time_ms".to_string(),
285            serde_json::Value::Number(serde_json::Number::from(application_time_ms)),
286        );
287
288        // Track theme features
289        properties.insert(
290            "has_text_shadow".to_string(),
291            serde_json::Value::Bool(theme.effects.text.shadow.is_some()),
292        );
293        properties.insert(
294            "has_text_outline".to_string(),
295            serde_json::Value::Bool(theme.effects.text.outline.is_some()),
296        );
297        properties.insert(
298            "has_text_gradient".to_string(),
299            serde_json::Value::Bool(theme.effects.text.gradient.is_some()),
300        );
301        properties.insert(
302            "has_background_gradient".to_string(),
303            serde_json::Value::Bool(theme.effects.background.gradient.is_some()),
304        );
305        properties.insert(
306            "has_background_pattern".to_string(),
307            serde_json::Value::Bool(theme.effects.background.pattern.is_some()),
308        );
309        properties.insert(
310            "has_border".to_string(),
311            serde_json::Value::Bool(theme.effects.border.width > 0.0),
312        );
313
314        self.manager.track_event(
315            AnalyticsEventType::ThemeApplied,
316            properties,
317            Some(application_time_ms),
318        )?;
319
320        Ok(())
321    }
322
323    /// Track metadata validation
324    pub fn track_metadata_validation(
325        &mut self,
326        metadata_type: &str,
327        is_valid: bool,
328        validation_time_ms: u64,
329        errors: Vec<String>,
330    ) -> Result<(), MetadataError> {
331        let mut properties = HashMap::new();
332        properties.insert(
333            "metadata_type".to_string(),
334            serde_json::Value::String(metadata_type.to_string()),
335        );
336        properties.insert("is_valid".to_string(), serde_json::Value::Bool(is_valid));
337        properties.insert(
338            "validation_time_ms".to_string(),
339            serde_json::Value::Number(serde_json::Number::from(validation_time_ms)),
340        );
341        properties.insert(
342            "error_count".to_string(),
343            serde_json::Value::Number(serde_json::Number::from(errors.len())),
344        );
345
346        if !errors.is_empty() {
347            properties.insert(
348                "errors".to_string(),
349                serde_json::Value::Array(
350                    errors
351                        .into_iter()
352                        .map(|e| serde_json::Value::String(e))
353                        .collect(),
354                ),
355            );
356        }
357
358        self.manager.track_event(
359            AnalyticsEventType::MetadataValidated,
360            properties,
361            Some(validation_time_ms),
362        )?;
363
364        Ok(())
365    }
366
367    /// Track error
368    pub fn track_error(
369        &mut self,
370        error: MetadataError,
371        context: HashMap<String, serde_json::Value>,
372    ) -> Result<(), MetadataError> {
373        self.manager.track_error(error, context)?;
374        Ok(())
375    }
376
377    /// Track custom event
378    pub fn track_custom_event(
379        &mut self,
380        event_name: &str,
381        properties: HashMap<String, serde_json::Value>,
382        duration_ms: Option<u64>,
383    ) -> Result<(), MetadataError> {
384        self.manager.track_event(
385            AnalyticsEventType::Custom(event_name.to_string()),
386            properties,
387            duration_ms,
388        )?;
389
390        Ok(())
391    }
392
393    /// Get performance metrics
394    pub fn get_performance_metrics(&self) -> &PerformanceMetrics {
395        self.manager.get_performance_metrics()
396    }
397
398    /// Get analytics configuration
399    pub fn get_config(&self) -> &AnalyticsConfig {
400        self.manager.get_config()
401    }
402
403    /// Generate analytics insights
404    pub fn generate_insights(&self) -> Result<crate::analytics::AnalyticsInsights, MetadataError> {
405        self.manager.generate_insights()
406    }
407
408    /// Get analytics manager
409    pub fn get_manager(&self) -> &AnalyticsManager {
410        &self.manager
411    }
412
413    /// Get mutable analytics manager
414    pub fn get_manager_mut(&mut self) -> &mut AnalyticsManager {
415        &mut self.manager
416    }
417
418    /// Start analytics session
419    pub fn start_session(
420        &mut self,
421        session_id: String,
422        user_agent: Option<String>,
423    ) -> Result<(), MetadataError> {
424        self.manager.start_session(session_id, user_agent)
425    }
426
427    /// End analytics session
428    pub fn end_session(&mut self) -> Result<(), MetadataError> {
429        self.manager.end_session()
430    }
431}
432
433/// Analytics wrapper for automatic tracking
434pub struct AnalyticsWrapper<T> {
435    /// Inner value
436    inner: T,
437    /// Analytics integration
438    analytics: MetadataAnalyticsIntegration,
439}
440
441impl<T> AnalyticsWrapper<T> {
442    /// Create a new analytics wrapper
443    pub fn new(inner: T, analytics: MetadataAnalyticsIntegration) -> Self {
444        Self { inner, analytics }
445    }
446
447    /// Get the inner value
448    pub fn inner(&self) -> &T {
449        &self.inner
450    }
451
452    /// Get mutable reference to inner value
453    pub fn inner_mut(&mut self) -> &mut T {
454        &mut self.inner
455    }
456
457    /// Get analytics integration
458    pub fn analytics(&self) -> &MetadataAnalyticsIntegration {
459        &self.analytics
460    }
461
462    /// Get mutable analytics integration
463    pub fn analytics_mut(&mut self) -> &mut MetadataAnalyticsIntegration {
464        &mut self.analytics
465    }
466
467    /// Consume and return inner value and analytics
468    pub fn into_parts(self) -> (T, MetadataAnalyticsIntegration) {
469        (self.inner, self.analytics)
470    }
471}
472
473/// Trait for objects that can be wrapped with analytics
474pub trait AnalyticsTrackable {
475    /// Wrap with analytics
476    fn with_analytics(self, analytics: MetadataAnalyticsIntegration) -> AnalyticsWrapper<Self>
477    where
478        Self: Sized,
479    {
480        AnalyticsWrapper::new(self, analytics)
481    }
482}
483
484impl<T> AnalyticsTrackable for T {}
485
486/// Analytics context for tracking operations
487pub struct AnalyticsContext {
488    /// Operation ID
489    operation_id: String,
490    /// Analytics integration
491    analytics: MetadataAnalyticsIntegration,
492}
493
494impl AnalyticsContext {
495    /// Create a new analytics context
496    pub fn new(operation_id: String, analytics: MetadataAnalyticsIntegration) -> Self {
497        Self {
498            operation_id,
499            analytics,
500        }
501    }
502
503    /// Track operation completion
504    pub fn complete_operation(
505        mut self,
506        operation_type: &str,
507        success: bool,
508        properties: HashMap<String, serde_json::Value>,
509    ) -> Result<u64, MetadataError> {
510        self.analytics
511            .end_operation(self.operation_id, operation_type, success, properties)
512    }
513
514    /// Track error and complete operation
515    pub fn complete_with_error(
516        mut self,
517        operation_type: &str,
518        error: MetadataError,
519        context: HashMap<String, serde_json::Value>,
520    ) -> Result<u64, MetadataError> {
521        // Track the error
522        self.analytics.track_error(error.clone(), context.clone())?;
523
524        // Complete the operation as failed
525        self.analytics
526            .end_operation(self.operation_id, operation_type, false, context)
527    }
528
529    /// Get analytics integration
530    pub fn analytics(&self) -> &MetadataAnalyticsIntegration {
531        &self.analytics
532    }
533
534    /// Get mutable analytics integration
535    pub fn analytics_mut(&mut self) -> &mut MetadataAnalyticsIntegration {
536        &mut self.analytics
537    }
538}
539
540/// Helper macro for tracking operations
541#[macro_export]
542macro_rules! track_operation {
543    ($analytics:expr, $operation_type:expr, $operation:block) => {{
544        let operation_id = format!(
545            "{}_{}",
546            $operation_type,
547            std::time::SystemTime::now()
548                .duration_since(std::time::UNIX_EPOCH)
549                .unwrap()
550                .as_millis()
551        );
552        $analytics.start_operation(operation_id.clone())?;
553
554        let result = $operation;
555
556        let success = result.is_ok();
557        let properties = std::collections::HashMap::new();
558        $analytics.end_operation(operation_id, $operation_type, success, properties)?;
559
560        result
561    }};
562}