ricecoder_github/managers/
code_review_operations.rs

1//! Code Review Operations - Additional code review functionality
2
3use crate::errors::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use tracing::{debug, info};
7
8/// Code review metrics
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CodeReviewMetrics {
11    /// Total PRs reviewed
12    pub total_prs_reviewed: u32,
13    /// PRs approved
14    pub prs_approved: u32,
15    /// PRs with changes requested
16    pub prs_changes_requested: u32,
17    /// Average review time (in minutes)
18    pub average_review_time: u32,
19    /// Average quality score
20    pub average_quality_score: f32,
21}
22
23impl Default for CodeReviewMetrics {
24    fn default() -> Self {
25        Self {
26            total_prs_reviewed: 0,
27            prs_approved: 0,
28            prs_changes_requested: 0,
29            average_review_time: 0,
30            average_quality_score: 0.0,
31        }
32    }
33}
34
35impl CodeReviewMetrics {
36    /// Create new metrics
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    /// Update metrics with new review
42    pub fn update_with_review(
43        &mut self,
44        approved: bool,
45        quality_score: u32,
46        review_time_minutes: u32,
47    ) {
48        self.total_prs_reviewed += 1;
49
50        if approved {
51            self.prs_approved += 1;
52        } else {
53            self.prs_changes_requested += 1;
54        }
55
56        // Update average review time
57        self.average_review_time = (self.average_review_time * (self.total_prs_reviewed - 1)
58            + review_time_minutes)
59            / self.total_prs_reviewed;
60
61        // Update average quality score
62        self.average_quality_score = (self.average_quality_score * (self.total_prs_reviewed - 1) as f32
63            + quality_score as f32)
64            / self.total_prs_reviewed as f32;
65    }
66
67    /// Get approval rate
68    pub fn approval_rate(&self) -> f32 {
69        if self.total_prs_reviewed == 0 {
70            0.0
71        } else {
72            (self.prs_approved as f32 / self.total_prs_reviewed as f32) * 100.0
73        }
74    }
75}
76
77/// Approval condition
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ApprovalCondition {
80    /// Condition name
81    pub name: String,
82    /// Condition description
83    pub description: String,
84    /// Is met
85    pub is_met: bool,
86}
87
88impl ApprovalCondition {
89    /// Create new condition
90    pub fn new(name: impl Into<String>, description: impl Into<String>, is_met: bool) -> Self {
91        Self {
92            name: name.into(),
93            description: description.into(),
94            is_met,
95        }
96    }
97}
98
99/// Conditional approval result
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ConditionalApprovalResult {
102    /// PR number
103    pub pr_number: u32,
104    /// Approval conditions
105    pub conditions: Vec<ApprovalCondition>,
106    /// Overall approval
107    pub approved: bool,
108    /// Approval reason
109    pub reason: String,
110}
111
112impl ConditionalApprovalResult {
113    /// Create new result
114    pub fn new(pr_number: u32) -> Self {
115        Self {
116            pr_number,
117            conditions: Vec::new(),
118            approved: false,
119            reason: String::new(),
120        }
121    }
122
123    /// Add condition
124    pub fn with_condition(mut self, condition: ApprovalCondition) -> Self {
125        self.conditions.push(condition);
126        self
127    }
128
129    /// Add conditions
130    pub fn with_conditions(mut self, conditions: Vec<ApprovalCondition>) -> Self {
131        self.conditions.extend(conditions);
132        self
133    }
134
135    /// Set approval
136    pub fn set_approved(mut self, approved: bool, reason: impl Into<String>) -> Self {
137        self.approved = approved;
138        self.reason = reason.into();
139        self
140    }
141
142    /// Check if all conditions are met
143    pub fn all_conditions_met(&self) -> bool {
144        self.conditions.iter().all(|c| c.is_met)
145    }
146
147    /// Get unmet conditions
148    pub fn unmet_conditions(&self) -> Vec<&ApprovalCondition> {
149        self.conditions.iter().filter(|c| !c.is_met).collect()
150    }
151}
152
153/// Code Review Operations
154pub struct CodeReviewOperations;
155
156impl CodeReviewOperations {
157    /// Create a new code review operations instance
158    pub fn new() -> Self {
159        Self
160    }
161
162    /// Post code review suggestion as comment
163    pub fn post_suggestion_comment(
164        &self,
165        pr_number: u32,
166        file_path: &str,
167        line_number: Option<u32>,
168        suggestion: &str,
169    ) -> Result<String> {
170        debug!(
171            pr_number = pr_number,
172            file_path = file_path,
173            line_number = line_number,
174            "Posting code review suggestion"
175        );
176
177        let mut comment = "**Code Review Suggestion**\n\n".to_string();
178        comment.push_str(&format!("File: `{}`\n", file_path));
179
180        if let Some(line) = line_number {
181            comment.push_str(&format!("Line: {}\n\n", line));
182        }
183
184        comment.push_str(&format!("**Suggestion:**\n{}\n", suggestion));
185
186        info!(
187            pr_number = pr_number,
188            comment_length = comment.len(),
189            "Suggestion comment created"
190        );
191
192        Ok(comment)
193    }
194
195    /// Generate code review summary report
196    pub fn generate_summary_report(
197        &self,
198        pr_number: u32,
199        quality_score: u32,
200        issues_count: usize,
201        suggestions_count: usize,
202        approved: bool,
203    ) -> Result<String> {
204        debug!(
205            pr_number = pr_number,
206            quality_score = quality_score,
207            "Generating code review summary report"
208        );
209
210        let mut report = format!("## Code Review Report - PR #{}\n\n", pr_number);
211
212        // Quality score
213        report.push_str(&format!("**Quality Score:** {}/100\n\n", quality_score));
214
215        // Status
216        let status = if approved { "✅ APPROVED" } else { "❌ NEEDS REVIEW" };
217        report.push_str(&format!("**Status:** {}\n\n", status));
218
219        // Issues and suggestions
220        report.push_str(&format!("**Issues Found:** {}\n", issues_count));
221        report.push_str(&format!("**Suggestions:** {}\n\n", suggestions_count));
222
223        // Recommendation
224        if approved {
225            report.push_str("This PR meets all quality standards and is ready for merge.\n");
226        } else {
227            report.push_str("This PR requires attention before merging. Please address the issues and suggestions above.\n");
228        }
229
230        info!(
231            pr_number = pr_number,
232            report_length = report.len(),
233            "Summary report generated"
234        );
235
236        Ok(report)
237    }
238
239    /// Track review metrics
240    pub fn track_metrics(
241        &self,
242        metrics: &mut CodeReviewMetrics,
243        approved: bool,
244        quality_score: u32,
245        review_time_minutes: u32,
246    ) -> Result<()> {
247        debug!(
248            approved = approved,
249            quality_score = quality_score,
250            review_time_minutes = review_time_minutes,
251            "Tracking review metrics"
252        );
253
254        metrics.update_with_review(approved, quality_score, review_time_minutes);
255
256        info!(
257            total_reviewed = metrics.total_prs_reviewed,
258            approval_rate = metrics.approval_rate(),
259            "Metrics updated"
260        );
261
262        Ok(())
263    }
264
265    /// Evaluate conditional approval
266    pub fn evaluate_conditional_approval(
267        &self,
268        pr_number: u32,
269        conditions: HashMap<String, bool>,
270    ) -> Result<ConditionalApprovalResult> {
271        debug!(
272            pr_number = pr_number,
273            condition_count = conditions.len(),
274            "Evaluating conditional approval"
275        );
276
277        let mut result = ConditionalApprovalResult::new(pr_number);
278
279        for (name, is_met) in conditions {
280            let condition = ApprovalCondition::new(
281                &name,
282                format!("Condition: {}", name),
283                is_met,
284            );
285            result = result.with_condition(condition);
286        }
287
288        // Determine overall approval
289        let all_met = result.all_conditions_met();
290        let reason = if all_met {
291            "All approval conditions are met".to_string()
292        } else {
293            format!(
294                "{} condition(s) not met",
295                result.unmet_conditions().len()
296            )
297        };
298
299        result = result.set_approved(all_met, reason);
300
301        info!(
302            pr_number = pr_number,
303            approved = result.approved,
304            "Conditional approval evaluated"
305        );
306
307        Ok(result)
308    }
309
310    /// Generate approval checklist
311    pub fn generate_approval_checklist(
312        &self,
313        pr_number: u32,
314        conditions: &[ApprovalCondition],
315    ) -> Result<String> {
316        debug!(
317            pr_number = pr_number,
318            condition_count = conditions.len(),
319            "Generating approval checklist"
320        );
321
322        let mut checklist = format!("## Approval Checklist - PR #{}\n\n", pr_number);
323
324        for condition in conditions {
325            let checkbox = if condition.is_met { "✅" } else { "❌" };
326            checklist.push_str(&format!(
327                "{} **{}**: {}\n",
328                checkbox, condition.name, condition.description
329            ));
330        }
331
332        info!(
333            pr_number = pr_number,
334            checklist_length = checklist.len(),
335            "Approval checklist generated"
336        );
337
338        Ok(checklist)
339    }
340}
341
342impl Default for CodeReviewOperations {
343    fn default() -> Self {
344        Self::new()
345    }
346}
347
348#[cfg(test)]
349mod tests {
350    use super::*;
351
352    #[test]
353    fn test_code_review_metrics_default() {
354        let metrics = CodeReviewMetrics::default();
355        assert_eq!(metrics.total_prs_reviewed, 0);
356        assert_eq!(metrics.prs_approved, 0);
357    }
358
359    #[test]
360    fn test_code_review_metrics_update() {
361        let mut metrics = CodeReviewMetrics::new();
362        metrics.update_with_review(true, 85, 30);
363        assert_eq!(metrics.total_prs_reviewed, 1);
364        assert_eq!(metrics.prs_approved, 1);
365        assert_eq!(metrics.average_quality_score, 85.0);
366    }
367
368    #[test]
369    fn test_code_review_metrics_approval_rate() {
370        let mut metrics = CodeReviewMetrics::new();
371        metrics.update_with_review(true, 85, 30);
372        metrics.update_with_review(false, 60, 45);
373        assert_eq!(metrics.approval_rate(), 50.0);
374    }
375
376    #[test]
377    fn test_approval_condition_creation() {
378        let condition = ApprovalCondition::new("Test", "Test condition", true);
379        assert_eq!(condition.name, "Test");
380        assert!(condition.is_met);
381    }
382
383    #[test]
384    fn test_conditional_approval_result_creation() {
385        let result = ConditionalApprovalResult::new(123);
386        assert_eq!(result.pr_number, 123);
387        assert!(result.conditions.is_empty());
388    }
389
390    #[test]
391    fn test_conditional_approval_result_with_conditions() {
392        let condition1 = ApprovalCondition::new("Test1", "Description1", true);
393        let condition2 = ApprovalCondition::new("Test2", "Description2", false);
394        let result = ConditionalApprovalResult::new(123)
395            .with_condition(condition1)
396            .with_condition(condition2);
397        assert_eq!(result.conditions.len(), 2);
398    }
399
400    #[test]
401    fn test_conditional_approval_result_all_conditions_met() {
402        let condition = ApprovalCondition::new("Test", "Description", true);
403        let result = ConditionalApprovalResult::new(123)
404            .with_condition(condition);
405        assert!(result.all_conditions_met());
406    }
407
408    #[test]
409    fn test_conditional_approval_result_unmet_conditions() {
410        let condition1 = ApprovalCondition::new("Test1", "Description1", true);
411        let condition2 = ApprovalCondition::new("Test2", "Description2", false);
412        let result = ConditionalApprovalResult::new(123)
413            .with_condition(condition1)
414            .with_condition(condition2);
415        assert_eq!(result.unmet_conditions().len(), 1);
416    }
417
418    #[test]
419    fn test_code_review_operations_creation() {
420        let ops = CodeReviewOperations::new();
421        assert_eq!(std::mem::size_of_val(&ops), 0); // Zero-sized type
422    }
423
424    #[test]
425    fn test_post_suggestion_comment() {
426        let ops = CodeReviewOperations::new();
427        let comment = ops
428            .post_suggestion_comment(123, "src/main.rs", Some(42), "Use better variable name")
429            .unwrap();
430        assert!(comment.contains("Code Review Suggestion"));
431        assert!(comment.contains("src/main.rs"));
432        assert!(comment.contains("42"));
433    }
434
435    #[test]
436    fn test_generate_summary_report() {
437        let ops = CodeReviewOperations::new();
438        let report = ops
439            .generate_summary_report(123, 85, 2, 3, true)
440            .unwrap();
441        assert!(report.contains("PR #123"));
442        assert!(report.contains("85/100"));
443        assert!(report.contains("APPROVED"));
444    }
445
446    #[test]
447    fn test_track_metrics() {
448        let ops = CodeReviewOperations::new();
449        let mut metrics = CodeReviewMetrics::new();
450        ops.track_metrics(&mut metrics, true, 85, 30).unwrap();
451        assert_eq!(metrics.total_prs_reviewed, 1);
452    }
453
454    #[test]
455    fn test_evaluate_conditional_approval() {
456        let ops = CodeReviewOperations::new();
457        let mut conditions = HashMap::new();
458        conditions.insert("Quality".to_string(), true);
459        conditions.insert("Tests".to_string(), true);
460        let result = ops.evaluate_conditional_approval(123, conditions).unwrap();
461        assert!(result.approved);
462    }
463
464    #[test]
465    fn test_evaluate_conditional_approval_not_met() {
466        let ops = CodeReviewOperations::new();
467        let mut conditions = HashMap::new();
468        conditions.insert("Quality".to_string(), true);
469        conditions.insert("Tests".to_string(), false);
470        let result = ops.evaluate_conditional_approval(123, conditions).unwrap();
471        assert!(!result.approved);
472    }
473
474    #[test]
475    fn test_generate_approval_checklist() {
476        let ops = CodeReviewOperations::new();
477        let conditions = vec![
478            ApprovalCondition::new("Quality", "Quality check", true),
479            ApprovalCondition::new("Tests", "Test coverage", false),
480        ];
481        let checklist = ops.generate_approval_checklist(123, &conditions).unwrap();
482        assert!(checklist.contains("PR #123"));
483        assert!(checklist.contains("✅"));
484        assert!(checklist.contains("❌"));
485    }
486}