Commit 97982da5 authored by Wohlgemuth, Jason's avatar Wohlgemuth, Jason
Browse files

feat: Add language use gitlab api feature

parent 5bdd61ea
Loading
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ pub async fn run(
    // let langs = gitlab::languages().await?;
    // dbg!(langs);
    // gitlab_merge_request_note_example().await;
    // gitlab_example().await;
    gitlab_example().await;
    // orcid_example().await;
    // ror_example().await;
    Ok(())
@@ -54,6 +54,7 @@ Here is a list
#[allow(dead_code)]
async fn gitlab_example() {
    let group = "24758";
    let project = "16689";
    let options = gitlab::Options::from_env();
    let runners = gitlab::runners(options.clone()).await;
    println!("Runners: {:#?}", runners);
@@ -67,7 +68,9 @@ async fn gitlab_example() {
        | Err(ref why) => panic!("Runner: Err({why:#?})"),
    }
    let options = options.with_identifier(group);
    println!("Groups: {:#?}", gitlab::groups(options).await);
    println!("Groups: {:#?}", gitlab::groups(options.clone()).await);
    let options = options.with_identifier(project);
    println!("Languages: {:#?}", gitlab::language_use(options).await.unwrap().entries());
}
#[allow(dead_code)]
async fn orcid_example() {
+5 −0
Original line number Diff line number Diff line
@@ -70,6 +70,11 @@
                    "method": "get",
                    "template": "{{ base }}/runners/{{ identifier }}/jobs{{ query }}"
                },
                {
                    "name": "languages",
                    "method": "get",
                    "template": "{{ base }}/projects/{{ identifier }}/languages{{ query }}"
                },
                {
                    "name": "merge-request-note",
                    "method": "post",
+66 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ pub type RunnersResponse = Vec<RunnerDetails>;
pub type TreeResponse = Vec<TreeEntry>;
/// Type for GitLab programming language entries
pub type ProgrammingLanguageEntries = Vec<ProgrammingLanguageMetadata>;
/// Type for GitLab project programming language usage entries
pub type ProgrammingLanguageUseEntries = Vec<ProgrammingLanguageUseMetadata>;
/// Access level of the runner
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -71,6 +73,20 @@ pub struct ProgrammingLanguagesResponse {
    /// Flattened language metadata entries
    pub languages: ProgrammingLanguageEntries,
}
/// Programming language usage entry for a project
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ProgrammingLanguageUseMetadata {
    /// Display name of the language
    pub name: String,
    /// Relative share of repository content for this language
    pub percentage: f64,
}
/// Parsed response for GitLab project language usage
#[derive(Clone, Debug, Default, Serialize)]
pub struct ProgrammingLanguageUseResponse {
    /// Flattened language usage entries
    pub languages: ProgrammingLanguageUseEntries,
}
/// Group visibility level
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
@@ -652,11 +668,41 @@ impl ProgrammingLanguagesResponse {
        Self { languages }
    }
}
impl ProgrammingLanguageUseResponse {
    /// Parse a raw language-to-percentage map into normalized entries
    pub fn parse(data: HashMap<String, f64>) -> Self {
        let mut languages = data
            .into_iter()
            .map(|(name, percentage)| ProgrammingLanguageUseMetadata { name, percentage })
            .collect::<ProgrammingLanguageUseEntries>();
        languages.sort_by(|a, b| a.name.cmp(&b.name));
        Self { languages }
    }
    /// Get language data entry tuples, (name, percentage), sorted by percentage in descending order
    pub fn entries(&self) -> Vec<(String, f64)> {
        let mut entries = self
            .languages
            .iter()
            .map(|ProgrammingLanguageUseMetadata { name, percentage }| (name.clone(), *percentage))
            .collect::<Vec<_>>();
        entries.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(core::cmp::Ordering::Equal));
        entries
    }
    /// Get language names, sorted by percentage in descending order
    pub fn names(&self) -> Vec<String> {
        self.entries().into_iter().map(|(name, _)| name).collect()
    }
}
impl<'de> serde::Deserialize<'de> for ProgrammingLanguagesResponse {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        HashMap::<String, ProgrammingLanguageDetails>::deserialize(deserializer).map(Self::parse)
    }
}
impl<'de> serde::Deserialize<'de> for ProgrammingLanguageUseResponse {
    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        HashMap::<String, f64>::deserialize(deserializer).map(Self::parse)
    }
}
#[async_trait]
impl DatabasePersistence for ProgrammingLanguagesResponse {
    /// Persist GitLab programming language metadata to local database
@@ -781,6 +827,26 @@ pub async fn groups(options: Options) -> ApiResult<GroupsResponse> {
        | Err(why) => Err(why),
    }
}
/// Get programming languages used by a GitLab project
///
/// See <https://docs.gitlab.com/api/projects/?utm_source=perplexity#retrieve-programming-language-usage-information> for more information on GitLab API.
pub async fn language_use(options: Options) -> ApiResult<ProgrammingLanguageUseResponse> {
    let action = "languages";
    let Options {
        token, identifier, domain, ..
    } = options;
    match Endpoint::from_template("gitlab::api").map(|e| e.with_domain(&domain)) {
        | Ok(endpoint) => {
            let params = vec![
                param!(Header, "PRIVATE-TOKEN", &token),
                param!(TemplateValue, "identifier", &identifier.unwrap_or_default()),
            ];
            let response = endpoint.invoke_with::<PaginationField, EmptyField>(action, Some(params)).await;
            endpoint.handle::<ProgrammingLanguageUseResponse>(response)
        }
        | Err(why) => Err(why),
    }
}
/// Download programming language metadata from GitLab linguist source file
pub async fn languages() -> ApiResult<ProgrammingLanguagesResponse> {
    let names = ["gitlab::org", "github::org"];
+13 −3
Original line number Diff line number Diff line
@@ -213,7 +213,7 @@ mod github_api {

    #[test]
    fn test_template_endpoint_for() {
        let endpoint = Endpoint::from_template("github").map(|e| e.with_domain("api.github.com")).unwrap();
        let endpoint = Endpoint::from_template("github::api").map(|e| e.with_domain("api.github.com")).unwrap();
        assert_eq!(endpoint.base(), "https://api.github.com");
        assert!(endpoint.resources.iter().any(|r| r.name == "tree"));
    }
@@ -264,7 +264,7 @@ mod github_api {
#[cfg(test)]
mod gitlab_api {
    use super::*;
    use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguagesResponse};
    use crate::io::api::gitlab::{PaginationField, ProgrammingLanguageDetails, ProgrammingLanguageUseResponse, ProgrammingLanguagesResponse};

    #[test]
    fn test_query_string() {
@@ -320,6 +320,16 @@ mod gitlab_api {
        assert_eq!(response.languages[0].name, "Python");
        assert_eq!(response.languages[0].language_id, Some(303));
    }
    #[test]
    fn test_programming_language_use_response_deserializes_map() {
        let json = r#"{"Rust":98.12,"Makefile":0.5,"Python":0.49}"#;
        let response: ProgrammingLanguageUseResponse = serde_json::from_str(json).expect("should deserialize language usage map");
        assert_eq!(response.languages.len(), 3);
        assert_eq!(response.languages[0].name, "Makefile");
        assert_eq!(response.languages[0].percentage, 0.5);
        assert_eq!(response.languages[1].name, "Python");
        assert_eq!(response.languages[2].name, "Rust");
    }
}
#[cfg(test)]
mod orcid_api {
@@ -666,7 +676,7 @@ mod orcid_api {
    }
    #[test]
    fn test_from_template_with_domain() {
        let result = Endpoint::from_template("gitlab").map(|e| e.with_domain("custom-gitlab.example.com"));
        let result = Endpoint::from_template("gitlab::api").map(|e| e.with_domain("custom-gitlab.example.com"));
        assert!(result.is_ok());
        let endpoint = result.unwrap();
        assert_eq!(endpoint.domain, "custom-gitlab.example.com");
+559 −511

File changed.

Preview size limit exceeded, changes collapsed.