1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
use octocrab::models::issues::*; use reqwest::Url; type DateTime = chrono::DateTime<chrono::Utc>; use serde::Serialize; use crate::*; #[derive(Serialize, Debug)] pub struct IssueRec { pub id: i64, pub node_id: String, pub url: Url, pub repository_url: Url, pub labels_url: Url, pub comments_url: Url, pub events_url: Url, pub html_url: Url, pub number: i64, pub state: String, pub title: String, pub body: Option<String>, pub body_text: Option<String>, pub body_html: Option<String>, pub user_id: i64, pub labels: String, pub assignee_id: Option<i64>, pub assignees: String, pub author_association: String, pub milestone: Option<String>, pub locked: bool, pub active_lock_reason: Option<String>, pub comments: u32, pub pull_request: Option<Url>, pub closed_at: Option<DateTime>, pub created_at: DateTime, pub updated_at: DateTime, pub sdc_repository: String, } impl RepositryAware for IssueRec { fn set_repository(&mut self, name: String) { self.sdc_repository = name; } } impl From<Issue> for IssueRec { fn from(from: Issue) -> IssueRec { let labels = from.labels; let assignees = from.assignees; IssueRec { id: from.id, node_id: from.node_id, url: from.url, repository_url: from.repository_url, labels_url: from.labels_url, comments_url: from.comments_url, events_url: from.events_url, html_url: from.html_url, number: from.number, state: from.state, title: from.title, body: from.body, body_text: from.body_text, body_html: from.body_html, user_id: from.user.id, labels: labels .iter() .map(|v| v.name.clone()) .collect::<Vec<String>>() .join(" "), assignee_id: match from.assignee { Some(user) => Some(user.id), None => None, }, assignees: assignees .iter() .map(|v| v.login.clone()) .collect::<Vec<String>>() .join(","), author_association: from.author_association, milestone: match from.milestone { Some(ms) => Some(ms.title), None => None, }, locked: from.locked, active_lock_reason: from.active_lock_reason, comments: from.comments, pull_request: match from.pull_request { Some(pr) => Some(pr.url), None => None, }, closed_at: from.closed_at, created_at: from.created_at, updated_at: from.updated_at, sdc_repository: String::default(), } } } pub struct IssueFetcher { owner: String, name: String, since: Option<DateTime>, octocrab: octocrab::Octocrab, } impl IssueFetcher { pub fn new( owner: String, name: String, since: Option<DateTime>, octocrab: octocrab::Octocrab, ) -> IssueFetcher { IssueFetcher { owner, name, since, octocrab, } } } impl UrlConstructor for IssueFetcher { fn reponame(&self) -> String { format!("{}/{}", self.owner, self.name) } fn entrypoint(&self) -> Option<Url> { let param = Params { state: octocrab::params::State::All.into(), since: self.since, ..Default::default() }; let route = format!( "repos/{owner}/{repo}/issues?{query}", owner = &self.owner, repo = &self.name, query = param.to_query(), ); self.octocrab.absolute_url(route).ok() } } impl LoopWriter for IssueFetcher { type Model = Issue; type Record = IssueRec; } impl IssueFetcher { pub async fn fetch<T: std::io::Write>(&self, mut wtr: csv::Writer<T>) -> octocrab::Result<()> { let mut next: Option<Url> = self.entrypoint(); while let Some(page) = self.octocrab.get_page(&next).await? { next = self.write_and_continue(page, &mut wtr); } Ok(()) } }