github_analytics/pull_data/
mod.rs

1use std::collections::HashMap;
2
3use chrono::{NaiveDateTime, Utc};
4use eyre::{eyre, Result};
5use serde_json::Value;
6
7use crate::api::*;
8use crate::models::*;
9
10/// Fetch the data from the github api.
11///
12/// # Arguments
13///
14/// * `start` - The number of days to go back from today.
15///
16/// # Returns
17///
18/// A hashmap with the repo name as key and a vector of interactions as value.
19pub async fn pull_data(start: u64) -> Result<HashMap<String, Vec<Interaction>>> {
20    // End date of the interactions (today).
21    let end = chrono::NaiveDateTime::from_timestamp_millis(chrono::Local::now().timestamp_millis())
22        .ok_or(eyre!("Could not create the end date"))?;
23    // Start date of the interactions (today - specified number of days).
24    let start = end
25        .checked_sub_days(chrono::Days::new(start))
26        .ok_or(eyre!("Could not create the start date"))?;
27    // Repositories to fetch the data from.
28    let repos = vec![
29        ("sayajin-labs", "kakarot"),
30        ("keep-starknet-strange", "beerus"),
31        ("keep-starknet-strange", "garaga"),
32        ("keep-starknet-strange", "quaireaux"),
33        ("keep-starknet-strange", "poseidon-rs"),
34    ];
35    // Github token.
36    let token = std::env::var("GH_TOKEN").expect("Environment variable GH_TOKEN is not set");
37    // Reqwest client.
38    let client = reqwest::Client::builder()
39        .user_agent("keep-starknet-strange")
40        .build()?;
41    // HashMap that will contain the interactions.
42    let mut repos_info: HashMap<String, Vec<Interaction>> = HashMap::new();
43    for (owner, repo) in repos {
44        // Query to fetch the data.
45        let prs_and_issues = gql_query(&client, &QUERY, &token, &owner, &repo).await?;
46        // Vector that will contain the interactions.
47        let mut infos = vec![];
48        // Parse the pull requests.
49        parse_interaction(
50            prs_and_issues["data"]["repository"]["pullRequests"]["nodes"]
51                .as_array()
52                .ok_or(eyre!("Could not fetch the pull requests"))?,
53            &mut infos,
54            &start,
55            &end,
56            "pr".to_owned(),
57            &repo,
58        )?;
59
60        // Parse the issues.
61        parse_interaction(
62            prs_and_issues["data"]["repository"]["issues"]["nodes"]
63                .as_array()
64                .ok_or(eyre!("Could not fetch the issues"))?,
65            &mut infos,
66            &start,
67            &end,
68            "issue".to_owned(),
69            &repo,
70        )?;
71        repos_info.insert(repo.to_owned(), infos);
72    }
73    Ok(repos_info)
74}
75
76/// Parse the interactions.
77///
78/// # Arguments
79///
80/// * `interactions` - The interactions to parse.
81/// * `target` - The vector that will contain the parsed interactions.
82/// * `start` - The start date of the interactions.
83/// * `end` - The end date of the interactions.
84/// * `interaction_type` - The type of the interaction (pr or issue).
85/// * `repo` - The name of the repository.
86fn parse_interaction(
87    interactions: &Vec<Value>,
88    target: &mut Vec<Interaction>,
89    start: &NaiveDateTime,
90    end: &NaiveDateTime,
91    interaction_type: String,
92    repo: &&str,
93) -> Result<()> {
94    for interaction in interactions {
95        // Parse the created_at date.
96        let created_at = chrono::NaiveDateTime::parse_from_str(
97            interaction["createdAt"]
98                .as_str()
99                .ok_or(eyre!("Could not parse created_at"))?,
100            "%Y-%m-%dT%H:%M:%SZ",
101        )?;
102
103        // Parse the closed_at date.
104        let ended = if interaction_type == "pr" {
105            "mergedAt"
106        } else {
107            "closedAt"
108        };
109        // If the interaction is closed, parse the closed_at date.
110        let closed_at: Option<NaiveDateTime> = interaction[ended].as_str().map(|closed_at| {
111            chrono::NaiveDateTime::parse_from_str(closed_at, "%Y-%m-%dT%H:%M:%SZ").unwrap()
112        });
113        // Declare the time variable.
114        let time;
115        // Declare the interaction type variable.
116        let mut inter = interaction_type.clone();
117
118        // If the interaction was created in the desired timeframe add created to the interaction
119        // type.
120        if created_at.ge(start) && created_at.lt(end) {
121            time = created_at.and_local_timezone(Utc).unwrap();
122            inter += " created";
123            // If the interaction was closed in the desired timeframe add ended to the interaction
124            // type.
125        } else if closed_at.is_some()
126            && (closed_at.unwrap().ge(start) && closed_at.unwrap().lt(end))
127        {
128            time = created_at.and_local_timezone(Utc).unwrap();
129            inter += " ended";
130            // If the interaction was not created or closed in the desired timeframe, end the loop.
131        } else {
132            break;
133        }
134        // Parse the author.
135        let author = interaction["author"]["login"]
136            .as_str()
137            .ok_or(eyre!("Could not parse author"))?;
138        // Push the interaction to the vector.
139        target.push(Interaction {
140            time,
141            author: author.to_string(),
142            interaction_type: inter,
143            repo: repo.to_string(),
144        });
145    }
146    Ok(())
147}