ci_manager/ci_provider/github/
util.rs1use octocrab::models::{
3 workflows::{Job, Step},
4 JobId,
5};
6
7use super::JobLog;
8
9#[derive(Debug)]
10pub struct JobErrorLog {
11 pub job_id: JobId,
12 pub job_name: String,
13 pub failed_step_logs: Vec<StepErrorLog>,
14}
15
16impl JobErrorLog {
17 pub fn new(job_id: JobId, job_name: String, logs: Vec<StepErrorLog>) -> Self {
18 JobErrorLog {
19 job_id,
20 job_name,
21 failed_step_logs: logs,
22 }
23 }
24
25 pub fn logs_as_str(&self) -> String {
27 let mut logs = String::new();
28 for log in &self.failed_step_logs {
29 logs.push_str(log.contents());
30 }
31 logs
32 }
33}
34
35#[derive(Debug)]
36pub struct StepErrorLog {
37 pub step_name: String,
38 pub contents: String,
39}
40
41impl StepErrorLog {
42 pub fn new(step_name: String, error_log: String) -> Self {
43 StepErrorLog {
44 step_name,
45 contents: error_log,
46 }
47 }
48
49 pub fn contents(&self) -> &str {
50 self.contents.as_str()
51 }
52}
53
54pub fn repo_url_to_job_url(repo_url: &str, run_id: &str, job_id: &str) -> String {
55 let run_url = repo_url_to_run_url(repo_url, run_id);
56 run_url_to_job_url(&run_url, job_id)
57}
58
59pub fn repo_url_to_run_url(repo_url: &str, run_id: &str) -> String {
60 format!("{repo_url}/actions/runs/{run_id}")
61}
62
63pub fn run_url_to_job_url(run_url: &str, job_id: &str) -> String {
64 format!("{run_url}/job/{job_id}")
65}
66
67pub fn distance_to_other_issues(
68 issue_body: &str,
69 other_issues: &[octocrab::models::issues::Issue],
70) -> usize {
71 let other_issue_bodies: Vec<String> = other_issues
72 .iter()
73 .map(|issue| issue.body.as_deref().unwrap_or_default().to_string())
74 .collect();
75
76 crate::issue::similarity::issue_text_similarity(issue_body, &other_issue_bodies)
77}
78
79pub fn log_info_downloaded_job_error_logs(job_error_logs: &[JobErrorLog]) {
81 log::info!("Got {} job error log(s)", job_error_logs.len());
82 for log in job_error_logs {
83 log::info!(
84 "\n\
85 \tName: {name}\n\
86 \tJob ID: {job_id}\
87 {failed_steps}",
88 name = log.job_name,
89 job_id = log.job_id,
90 failed_steps = log
91 .failed_step_logs
92 .iter()
93 .fold(String::new(), |acc, step| {
94 format!(
95 "{acc}\n\t Step: {step_name} | Log length: {log_len}",
96 acc = acc,
97 step_name = step.step_name,
98 log_len = step.contents().len()
99 )
100 })
101 );
102 }
103}
104
105pub fn job_error_logs_from_log_and_failed_jobs_and_steps(
115 logs: &[JobLog],
116 failed_jobs: &[&Job],
117 failed_steps: &[&Step],
118) -> Vec<JobErrorLog> {
119 let mut job_error_logs: Vec<JobErrorLog> = Vec::new();
120 for job in failed_jobs {
121 log::info!("Extracting error logs for job: {}", job.name);
122 let name = job.name.clone();
123 let step_error_logs: Vec<StepErrorLog> =
124 find_error_logs_for_job_steps(logs, &name, failed_steps);
125 job_error_logs.push(JobErrorLog::new(job.id, name, step_error_logs));
126 }
127 job_error_logs
128}
129
130fn find_error_logs_for_job_steps(
132 logs: &[JobLog],
133 job_name: &str,
134 steps: &[&Step],
135) -> Vec<StepErrorLog> {
136 steps
137 .iter()
138 .filter_map(|step| {
139 let step_name = step.name.clone();
140 let job_lob = match find_error_log(logs, job_name, &step_name) {
141 Some(log) => log,
142 None => {
143 log::error!("No log found for failed step: {step_name} in job: {job_name}. Continuing...");
144 return None;
145 }
146 };
147 Some(StepErrorLog::new(step_name, job_lob.content.clone()))
148 })
149 .collect()
150}
151
152fn find_error_log<'j>(logs: &'j [JobLog], job_name: &str, step_name: &str) -> Option<&'j JobLog> {
155 logs.iter()
156 .find(|log| log.name.contains(step_name) && log.name.contains(job_name))
157}