Skip to main content

trustformers_debug/
collaboration.rs

1//! Collaboration features for sharing and annotating debugging reports
2//!
3//! This module provides tools for team collaboration on debugging sessions,
4//! including report sharing, annotation systems, and collaborative analysis.
5
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use uuid::Uuid;
11
12/// Collaboration system for debugging reports and analysis
13#[derive(Debug, Clone)]
14pub struct CollaborationManager {
15    /// Shared reports
16    shared_reports: HashMap<Uuid, SharedReport>,
17    /// Active annotation sessions
18    annotation_sessions: HashMap<Uuid, AnnotationSession>,
19    /// Team members
20    team_members: HashMap<Uuid, TeamMember>,
21    /// Comment threads
22    comment_threads: HashMap<Uuid, CommentThread>,
23}
24
25/// A shared debugging report with collaboration metadata
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct SharedReport {
28    /// Unique identifier
29    pub id: Uuid,
30    /// Report title
31    pub title: String,
32    /// Report content (JSON or Markdown)
33    pub content: String,
34    /// Content format
35    pub format: ReportFormat,
36    /// Author information
37    pub author: TeamMember,
38    /// Creation timestamp
39    pub created_at: DateTime<Utc>,
40    /// Last modified timestamp
41    pub last_modified: DateTime<Utc>,
42    /// Access permissions
43    pub permissions: ReportPermissions,
44    /// Associated tags
45    pub tags: Vec<String>,
46    /// Report version
47    pub version: u32,
48    /// Sharing status
49    pub sharing_status: SharingStatus,
50    /// Associated annotations
51    pub annotations: Vec<Uuid>,
52    /// View count
53    pub view_count: u64,
54    /// Collaborators
55    pub collaborators: Vec<Uuid>,
56}
57
58/// Team member information
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct TeamMember {
61    /// Unique identifier
62    pub id: Uuid,
63    /// Display name
64    pub name: String,
65    /// Email address
66    pub email: String,
67    /// Avatar URL
68    pub avatar_url: Option<String>,
69    /// Role in the team
70    pub role: TeamRole,
71    /// Timezone
72    pub timezone: String,
73    /// Last active timestamp
74    pub last_active: DateTime<Utc>,
75    /// Notification preferences
76    pub notification_preferences: NotificationPreferences,
77}
78
79/// Annotation session for collaborative analysis
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AnnotationSession {
82    /// Session identifier
83    pub id: Uuid,
84    /// Report being annotated
85    pub report_id: Uuid,
86    /// Session participants
87    pub participants: Vec<Uuid>,
88    /// Session creator
89    pub creator: Uuid,
90    /// Session start time
91    pub started_at: DateTime<Utc>,
92    /// Session end time (if ended)
93    pub ended_at: Option<DateTime<Utc>>,
94    /// Session status
95    pub status: SessionStatus,
96    /// Annotations in this session
97    pub annotations: Vec<Annotation>,
98    /// Session type
99    pub session_type: AnnotationType,
100}
101
102/// Individual annotation within a session
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct Annotation {
105    /// Annotation identifier
106    pub id: Uuid,
107    /// Author of the annotation
108    pub author: Uuid,
109    /// Timestamp
110    pub timestamp: DateTime<Utc>,
111    /// Annotation type
112    pub annotation_type: AnnotationType,
113    /// Target location in the report
114    pub target: AnnotationTarget,
115    /// Annotation content
116    pub content: String,
117    /// Importance level
118    pub importance: ImportanceLevel,
119    /// Resolution status
120    pub status: AnnotationStatus,
121    /// Related annotations
122    pub related_annotations: Vec<Uuid>,
123    /// Attachments
124    pub attachments: Vec<Attachment>,
125}
126
127/// Comment thread for discussions
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct CommentThread {
130    /// Thread identifier
131    pub id: Uuid,
132    /// Associated report
133    pub report_id: Uuid,
134    /// Thread subject
135    pub subject: String,
136    /// Thread creator
137    pub creator: Uuid,
138    /// Creation timestamp
139    pub created_at: DateTime<Utc>,
140    /// Thread status
141    pub status: ThreadStatus,
142    /// Comments in the thread
143    pub comments: Vec<Comment>,
144    /// Thread participants
145    pub participants: Vec<Uuid>,
146    /// Thread tags
147    pub tags: Vec<String>,
148}
149
150/// Individual comment in a thread
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct Comment {
153    /// Comment identifier
154    pub id: Uuid,
155    /// Comment author
156    pub author: Uuid,
157    /// Comment content
158    pub content: String,
159    /// Creation timestamp
160    pub timestamp: DateTime<Utc>,
161    /// Edit timestamp (if edited)
162    pub edited_at: Option<DateTime<Utc>>,
163    /// Parent comment (for replies)
164    pub parent_id: Option<Uuid>,
165    /// Comment reactions
166    pub reactions: HashMap<String, Vec<Uuid>>,
167    /// Attachments
168    pub attachments: Vec<Attachment>,
169}
170
171/// File or media attachment
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct Attachment {
174    /// Attachment identifier
175    pub id: Uuid,
176    /// File name
177    pub filename: String,
178    /// Content type
179    pub content_type: String,
180    /// File size in bytes
181    pub size: u64,
182    /// Storage URL or path
183    pub url: String,
184    /// Upload timestamp
185    pub uploaded_at: DateTime<Utc>,
186    /// Uploader
187    pub uploader: Uuid,
188}
189
190/// Report format types
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub enum ReportFormat {
193    Json,
194    Markdown,
195    Html,
196    Pdf,
197    Custom(String),
198}
199
200/// Access permissions for shared reports
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ReportPermissions {
203    /// Public visibility
204    pub is_public: bool,
205    /// Team visibility
206    pub team_access: bool,
207    /// Specific user access
208    pub user_access: Vec<Uuid>,
209    /// Edit permissions
210    pub edit_permissions: Vec<Uuid>,
211    /// Comment permissions
212    pub comment_permissions: Vec<Uuid>,
213}
214
215/// Sharing status
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub enum SharingStatus {
218    Private,
219    TeamShared,
220    PublicShared,
221    LinkShared(String),
222}
223
224/// Team roles
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub enum TeamRole {
227    Owner,
228    Admin,
229    Developer,
230    Reviewer,
231    Viewer,
232}
233
234/// Notification preferences
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct NotificationPreferences {
237    /// Email notifications
238    pub email_enabled: bool,
239    /// New comment notifications
240    pub comment_notifications: bool,
241    /// New annotation notifications
242    pub annotation_notifications: bool,
243    /// Report share notifications
244    pub share_notifications: bool,
245    /// Digest frequency
246    pub digest_frequency: DigestFrequency,
247}
248
249/// Notification digest frequency
250#[derive(Debug, Clone, Serialize, Deserialize)]
251pub enum DigestFrequency {
252    Immediate,
253    Hourly,
254    Daily,
255    Weekly,
256    Disabled,
257}
258
259/// Annotation session status
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub enum SessionStatus {
262    Active,
263    Paused,
264    Completed,
265    Cancelled,
266}
267
268/// Annotation types
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub enum AnnotationType {
271    Note,
272    Issue,
273    Question,
274    Improvement,
275    Bug,
276    Performance,
277    Security,
278    Documentation,
279}
280
281/// Target location for annotations
282#[derive(Debug, Clone, Serialize, Deserialize)]
283pub enum AnnotationTarget {
284    /// Line number in report
285    Line(u32),
286    /// Range of lines
287    LineRange(u32, u32),
288    /// Section identifier
289    Section(String),
290    /// Specific element
291    Element(String),
292    /// Custom location
293    Custom(HashMap<String, String>),
294}
295
296/// Importance levels
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum ImportanceLevel {
299    Low,
300    Medium,
301    High,
302    Critical,
303}
304
305/// Annotation status
306#[derive(Debug, Clone, Serialize, Deserialize)]
307pub enum AnnotationStatus {
308    Open,
309    InProgress,
310    Resolved,
311    Dismissed,
312}
313
314/// Comment thread status
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub enum ThreadStatus {
317    Open,
318    Resolved,
319    Locked,
320    Archived,
321}
322
323impl CollaborationManager {
324    /// Create a new collaboration manager
325    pub fn new() -> Self {
326        Self {
327            shared_reports: HashMap::new(),
328            annotation_sessions: HashMap::new(),
329            team_members: HashMap::new(),
330            comment_threads: HashMap::new(),
331        }
332    }
333
334    /// Share a debugging report
335    pub fn share_report(
336        &mut self,
337        title: String,
338        content: String,
339        format: ReportFormat,
340        author: TeamMember,
341        permissions: ReportPermissions,
342        tags: Vec<String>,
343    ) -> Result<Uuid> {
344        let report_id = Uuid::new_v4();
345        let now = Utc::now();
346
347        let shared_report = SharedReport {
348            id: report_id,
349            title,
350            content,
351            format,
352            author: author.clone(),
353            created_at: now,
354            last_modified: now,
355            permissions,
356            tags,
357            version: 1,
358            sharing_status: SharingStatus::Private,
359            annotations: Vec::new(),
360            view_count: 0,
361            collaborators: vec![author.id],
362        };
363
364        self.shared_reports.insert(report_id, shared_report);
365        self.team_members.insert(author.id, author);
366
367        Ok(report_id)
368    }
369
370    /// Get a shared report
371    pub fn get_report(&mut self, report_id: Uuid) -> Option<&mut SharedReport> {
372        if let Some(report) = self.shared_reports.get_mut(&report_id) {
373            report.view_count += 1;
374            Some(report)
375        } else {
376            None
377        }
378    }
379
380    /// Start an annotation session
381    pub fn start_annotation_session(
382        &mut self,
383        report_id: Uuid,
384        creator: Uuid,
385        participants: Vec<Uuid>,
386        session_type: AnnotationType,
387    ) -> Result<Uuid> {
388        let session_id = Uuid::new_v4();
389        let now = Utc::now();
390
391        let session = AnnotationSession {
392            id: session_id,
393            report_id,
394            participants,
395            creator,
396            started_at: now,
397            ended_at: None,
398            status: SessionStatus::Active,
399            annotations: Vec::new(),
400            session_type,
401        };
402
403        self.annotation_sessions.insert(session_id, session);
404        Ok(session_id)
405    }
406
407    /// Add an annotation to a session
408    pub fn add_annotation(
409        &mut self,
410        session_id: Uuid,
411        author: Uuid,
412        annotation_type: AnnotationType,
413        target: AnnotationTarget,
414        content: String,
415        importance: ImportanceLevel,
416    ) -> Result<Uuid> {
417        let annotation_id = Uuid::new_v4();
418        let now = Utc::now();
419
420        let annotation = Annotation {
421            id: annotation_id,
422            author,
423            timestamp: now,
424            annotation_type,
425            target,
426            content,
427            importance,
428            status: AnnotationStatus::Open,
429            related_annotations: Vec::new(),
430            attachments: Vec::new(),
431        };
432
433        if let Some(session) = self.annotation_sessions.get_mut(&session_id) {
434            session.annotations.push(annotation);
435
436            // Add annotation to the report as well
437            if let Some(report) = self.shared_reports.get_mut(&session.report_id) {
438                report.annotations.push(annotation_id);
439                report.last_modified = now;
440            }
441
442            Ok(annotation_id)
443        } else {
444            Err(anyhow::anyhow!("Annotation session not found"))
445        }
446    }
447
448    /// Create a comment thread
449    pub fn create_comment_thread(
450        &mut self,
451        report_id: Uuid,
452        subject: String,
453        creator: Uuid,
454        tags: Vec<String>,
455    ) -> Result<Uuid> {
456        let thread_id = Uuid::new_v4();
457        let now = Utc::now();
458
459        let thread = CommentThread {
460            id: thread_id,
461            report_id,
462            subject,
463            creator,
464            created_at: now,
465            status: ThreadStatus::Open,
466            comments: Vec::new(),
467            participants: vec![creator],
468            tags,
469        };
470
471        self.comment_threads.insert(thread_id, thread);
472        Ok(thread_id)
473    }
474
475    /// Add a comment to a thread
476    pub fn add_comment(
477        &mut self,
478        thread_id: Uuid,
479        author: Uuid,
480        content: String,
481        parent_id: Option<Uuid>,
482    ) -> Result<Uuid> {
483        let comment_id = Uuid::new_v4();
484        let now = Utc::now();
485
486        let comment = Comment {
487            id: comment_id,
488            author,
489            content,
490            timestamp: now,
491            edited_at: None,
492            parent_id,
493            reactions: HashMap::new(),
494            attachments: Vec::new(),
495        };
496
497        if let Some(thread) = self.comment_threads.get_mut(&thread_id) {
498            thread.comments.push(comment);
499
500            // Add author as participant if not already present
501            if !thread.participants.contains(&author) {
502                thread.participants.push(author);
503            }
504
505            Ok(comment_id)
506        } else {
507            Err(anyhow::anyhow!("Comment thread not found"))
508        }
509    }
510
511    /// Add a team member
512    pub fn add_team_member(&mut self, member: TeamMember) -> Uuid {
513        let member_id = member.id;
514        self.team_members.insert(member_id, member);
515        member_id
516    }
517
518    /// Update report sharing status
519    pub fn update_sharing_status(&mut self, report_id: Uuid, status: SharingStatus) -> Result<()> {
520        if let Some(report) = self.shared_reports.get_mut(&report_id) {
521            report.sharing_status = status;
522            report.last_modified = Utc::now();
523            Ok(())
524        } else {
525            Err(anyhow::anyhow!("Report not found"))
526        }
527    }
528
529    /// Get reports accessible to a user
530    pub fn get_user_reports(&self, user_id: Uuid) -> Vec<&SharedReport> {
531        self.shared_reports
532            .values()
533            .filter(|report| {
534                // Check if user has access
535                report.author.id == user_id
536                    || report.collaborators.contains(&user_id)
537                    || report.permissions.user_access.contains(&user_id)
538                    || report.permissions.is_public
539                    || (report.permissions.team_access && self.team_members.contains_key(&user_id))
540            })
541            .collect()
542    }
543
544    /// Get annotations for a report
545    pub fn get_report_annotations(&self, report_id: Uuid) -> Vec<&Annotation> {
546        let mut annotations = Vec::new();
547
548        for session in self.annotation_sessions.values() {
549            if session.report_id == report_id {
550                annotations.extend(session.annotations.iter());
551            }
552        }
553
554        annotations
555    }
556
557    /// Get comment threads for a report
558    pub fn get_report_threads(&self, report_id: Uuid) -> Vec<&CommentThread> {
559        self.comment_threads
560            .values()
561            .filter(|thread| thread.report_id == report_id)
562            .collect()
563    }
564
565    /// Export collaboration data
566    pub fn export_collaboration_data(&self) -> Result<String> {
567        #[derive(Serialize)]
568        struct CollaborationExport<'a> {
569            reports: Vec<&'a SharedReport>,
570            sessions: Vec<&'a AnnotationSession>,
571            threads: Vec<&'a CommentThread>,
572            members: Vec<&'a TeamMember>,
573            export_timestamp: DateTime<Utc>,
574        }
575
576        let export_data = CollaborationExport {
577            reports: self.shared_reports.values().collect(),
578            sessions: self.annotation_sessions.values().collect(),
579            threads: self.comment_threads.values().collect(),
580            members: self.team_members.values().collect(),
581            export_timestamp: Utc::now(),
582        };
583
584        serde_json::to_string_pretty(&export_data)
585            .map_err(|e| anyhow::anyhow!("Failed to export collaboration data: {}", e))
586    }
587
588    /// Generate collaboration statistics
589    pub fn get_collaboration_stats(&self) -> CollaborationStats {
590        let total_reports = self.shared_reports.len();
591        let total_annotations =
592            self.annotation_sessions.values().map(|s| s.annotations.len()).sum();
593        let total_comments = self.comment_threads.values().map(|t| t.comments.len()).sum();
594        let active_sessions = self
595            .annotation_sessions
596            .values()
597            .filter(|s| matches!(s.status, SessionStatus::Active))
598            .count();
599        let team_size = self.team_members.len();
600
601        CollaborationStats {
602            total_reports,
603            total_annotations,
604            total_comments,
605            active_sessions,
606            team_size,
607            reports_per_member: if team_size > 0 {
608                total_reports as f64 / team_size as f64
609            } else {
610                0.0
611            },
612            annotations_per_report: if total_reports > 0 {
613                total_annotations as f64 / total_reports as f64
614            } else {
615                0.0
616            },
617        }
618    }
619}
620
621/// Collaboration statistics
622#[derive(Debug, Clone, Serialize, Deserialize)]
623pub struct CollaborationStats {
624    pub total_reports: usize,
625    pub total_annotations: usize,
626    pub total_comments: usize,
627    pub active_sessions: usize,
628    pub team_size: usize,
629    pub reports_per_member: f64,
630    pub annotations_per_report: f64,
631}
632
633impl Default for CollaborationManager {
634    fn default() -> Self {
635        Self::new()
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    fn create_test_member() -> TeamMember {
644        TeamMember {
645            id: Uuid::new_v4(),
646            name: "Test User".to_string(),
647            email: "test@example.com".to_string(),
648            avatar_url: None,
649            role: TeamRole::Developer,
650            timezone: "UTC".to_string(),
651            last_active: Utc::now(),
652            notification_preferences: NotificationPreferences {
653                email_enabled: true,
654                comment_notifications: true,
655                annotation_notifications: true,
656                share_notifications: true,
657                digest_frequency: DigestFrequency::Daily,
658            },
659        }
660    }
661
662    #[test]
663    fn test_share_report() {
664        let mut manager = CollaborationManager::new();
665        let author = create_test_member();
666
667        let permissions = ReportPermissions {
668            is_public: false,
669            team_access: true,
670            user_access: vec![],
671            edit_permissions: vec![author.id],
672            comment_permissions: vec![author.id],
673        };
674
675        let report_id = manager
676            .share_report(
677                "Test Report".to_string(),
678                "Report content".to_string(),
679                ReportFormat::Markdown,
680                author,
681                permissions,
682                vec!["test".to_string()],
683            )
684            .expect("operation failed in test");
685
686        assert!(manager.shared_reports.contains_key(&report_id));
687    }
688
689    #[test]
690    fn test_annotation_session() {
691        let mut manager = CollaborationManager::new();
692        let author = create_test_member();
693
694        // First create a report
695        let permissions = ReportPermissions {
696            is_public: false,
697            team_access: true,
698            user_access: vec![],
699            edit_permissions: vec![author.id],
700            comment_permissions: vec![author.id],
701        };
702
703        let report_id = manager
704            .share_report(
705                "Test Report".to_string(),
706                "Report content".to_string(),
707                ReportFormat::Markdown,
708                author.clone(),
709                permissions,
710                vec![],
711            )
712            .expect("operation failed in test");
713
714        // Start annotation session
715        let session_id = manager
716            .start_annotation_session(report_id, author.id, vec![author.id], AnnotationType::Note)
717            .expect("operation failed in test");
718
719        // Add annotation
720        let _annotation_id = manager
721            .add_annotation(
722                session_id,
723                author.id,
724                AnnotationType::Issue,
725                AnnotationTarget::Line(10),
726                "This looks like a bug".to_string(),
727                ImportanceLevel::High,
728            )
729            .expect("operation failed in test");
730
731        assert!(manager.annotation_sessions.contains_key(&session_id));
732
733        // Verify annotation was added to report
734        let report = manager.shared_reports.get(&report_id).expect("expected value not found");
735        assert!(!report.annotations.is_empty());
736    }
737
738    #[test]
739    fn test_comment_thread() {
740        let mut manager = CollaborationManager::new();
741        let author = create_test_member();
742
743        // Create a report first
744        let permissions = ReportPermissions {
745            is_public: false,
746            team_access: true,
747            user_access: vec![],
748            edit_permissions: vec![author.id],
749            comment_permissions: vec![author.id],
750        };
751
752        let report_id = manager
753            .share_report(
754                "Test Report".to_string(),
755                "Report content".to_string(),
756                ReportFormat::Markdown,
757                author.clone(),
758                permissions,
759                vec![],
760            )
761            .expect("operation failed in test");
762
763        // Create comment thread
764        let thread_id = manager
765            .create_comment_thread(
766                report_id,
767                "Discussion about results".to_string(),
768                author.id,
769                vec!["discussion".to_string()],
770            )
771            .expect("operation failed in test");
772
773        // Add comment
774        let _comment_id = manager
775            .add_comment(thread_id, author.id, "Great analysis!".to_string(), None)
776            .expect("operation failed in test");
777
778        assert!(manager.comment_threads.contains_key(&thread_id));
779
780        let thread = manager.comment_threads.get(&thread_id).expect("expected value not found");
781        assert_eq!(thread.comments.len(), 1);
782    }
783
784    #[test]
785    fn test_collaboration_stats() {
786        let mut manager = CollaborationManager::new();
787        let author = create_test_member();
788
789        // Add some test data
790        manager.add_team_member(author.clone());
791
792        let permissions = ReportPermissions {
793            is_public: false,
794            team_access: true,
795            user_access: vec![],
796            edit_permissions: vec![author.id],
797            comment_permissions: vec![author.id],
798        };
799
800        manager
801            .share_report(
802                "Test Report".to_string(),
803                "Content".to_string(),
804                ReportFormat::Markdown,
805                author,
806                permissions,
807                vec![],
808            )
809            .expect("operation failed in test");
810
811        let stats = manager.get_collaboration_stats();
812        assert_eq!(stats.total_reports, 1);
813        assert_eq!(stats.team_size, 1);
814        assert_eq!(stats.reports_per_member, 1.0);
815    }
816}