1use crate::errors::Result;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use tracing::{debug, info};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct CodeReviewMetrics {
11 pub total_prs_reviewed: u32,
13 pub prs_approved: u32,
15 pub prs_changes_requested: u32,
17 pub average_review_time: u32,
19 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 pub fn new() -> Self {
38 Self::default()
39 }
40
41 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 self.average_review_time = (self.average_review_time * (self.total_prs_reviewed - 1)
58 + review_time_minutes)
59 / self.total_prs_reviewed;
60
61 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 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#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct ApprovalCondition {
80 pub name: String,
82 pub description: String,
84 pub is_met: bool,
86}
87
88impl ApprovalCondition {
89 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#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct ConditionalApprovalResult {
102 pub pr_number: u32,
104 pub conditions: Vec<ApprovalCondition>,
106 pub approved: bool,
108 pub reason: String,
110}
111
112impl ConditionalApprovalResult {
113 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 pub fn with_condition(mut self, condition: ApprovalCondition) -> Self {
125 self.conditions.push(condition);
126 self
127 }
128
129 pub fn with_conditions(mut self, conditions: Vec<ApprovalCondition>) -> Self {
131 self.conditions.extend(conditions);
132 self
133 }
134
135 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 pub fn all_conditions_met(&self) -> bool {
144 self.conditions.iter().all(|c| c.is_met)
145 }
146
147 pub fn unmet_conditions(&self) -> Vec<&ApprovalCondition> {
149 self.conditions.iter().filter(|c| !c.is_met).collect()
150 }
151}
152
153pub struct CodeReviewOperations;
155
156impl CodeReviewOperations {
157 pub fn new() -> Self {
159 Self
160 }
161
162 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 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 report.push_str(&format!("**Quality Score:** {}/100\n\n", quality_score));
214
215 let status = if approved { "✅ APPROVED" } else { "❌ NEEDS REVIEW" };
217 report.push_str(&format!("**Status:** {}\n\n", status));
218
219 report.push_str(&format!("**Issues Found:** {}\n", issues_count));
221 report.push_str(&format!("**Suggestions:** {}\n\n", suggestions_count));
222
223 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 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 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 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 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); }
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}