From 906e57e24feedf34157f6f30e10ea7166c219aad Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 12:39:45 +0100 Subject: [PATCH 1/6] start 'github_issues_prs_query' fn for #11 [ci skip] --- DESCRIPTION | 2 +- R/gh-queries.R | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ codemeta.json | 2 +- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 99be5e6..ff87444 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.009 +Version: 0.1.1.010 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/R/gh-queries.R b/R/gh-queries.R index c2742ad..4d69421 100644 --- a/R/gh-queries.R +++ b/R/gh-queries.R @@ -42,3 +42,83 @@ github_repo_workflow_query <- function (org = NULL, repo = NULL, n = 30L) { created = created ) } + +#' Use the GitHub Rest API activity list to extract event types. +#' Activity requests are described at +#' \url{https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-activities} +#' and the list of all event types is at +#' \url{https://docs.github.com/en/rest/using-the-rest-api/github-event-types?apiVersion=2022-11-28}. +#' @noRd +github_issues_prs_query <- function (org = NULL, repo = NULL) { + + period <- get_repometrics_period () + + u_base <- "https://api.github.com/repos/" + u_repo <- paste0 (u_base, org, "/", repo, "/") + u_wf <- paste0 (u_repo, "activity?per_page=100") + + body <- NULL + this_url <- u_wf + while (!is.null (this_url)) { + + req <- httr2::request (this_url) |> + add_token_to_req () + + resp <- httr2::req_perform (req) + httr2::resp_check_status (resp) + + this_body <- httr2::resp_body_json (resp) + body <- c (body, this_body) + this_url <- get_next_link (resp) + } + + ids <- vapply (body, function (i) i$id, numeric (1L)) + activity_type <- vapply (body, function (i) i$activity_type, character (1L)) + login <- vapply (body, function (i) i$actor$login, character (1L)) + timestamp <- vapply (body, function (i) i$timestamp, character (1L)) + timestamp <- as.POSIXct (timestamp, format = "%Y-%m-%dT%H:%M:%S", tz = "UTC") + + data.frame ( + id = ids, + activity_type = activity_type, + login = login, + timestamp = timestamp + ) +} + +add_token_to_req <- function (req) { + + if (!nzchar (Sys.getenv ("GITHUB_WORKFLOW"))) { + tok <- get_gh_token () + headers <- list (Authorization = paste0 ("Bearer ", tok)) + req <- httr2::req_headers (req, "Authorization" = headers) + } + + return (req) +} + +#' Pagination for Rest API. see +#' https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api +#' @noRd +get_next_link <- function (resp) { + + link <- httr2::resp_headers (resp)$link + + next_url <- NULL + + if (!is.null (link)) { + next_ptn <- "rel\\=\\\"next" + if (grepl (next_ptn, link)) { + # "next" is always first; where there are multiples, "prev" comes + # after "next" + ptn <- "<([^>]+)>" + next_url <- regmatches (link, regexpr (ptn, link)) + next_url <- gsub ("<|>", "", next_url) + if (!grepl ("after", next_url)) { + cli::cli_abort ("Pagination link in GitHub Rest API malformed: [{next_url}]") + } + } + } + + return (next_url) +} diff --git a/codemeta.json b/codemeta.json index fd21f5d..bff0dc8 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.009", + "version": "0.1.1.010", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", From bae73d9e0b8160a8d6dc6f34327cdb30c4510680 Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 14:58:58 +0100 Subject: [PATCH 2/6] add 'prop_commits_in_change_req' fn for #11 --- DESCRIPTION | 2 +- R/chaoss-external.R | 25 ++++++++++++++++ R/gh-queries.R | 72 ++++++++++++++++++++++++++++++++++++++------- R/utils.R | 4 +++ codemeta.json | 2 +- 5 files changed, 93 insertions(+), 12 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ff87444..473e7d1 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.010 +Version: 0.1.1.011 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/R/chaoss-external.R b/R/chaoss-external.R index 1f609be..8a77d93 100644 --- a/R/chaoss-external.R +++ b/R/chaoss-external.R @@ -34,3 +34,28 @@ has_gh_ci_tests <- function (path) { h <- gert::git_log (repo = path, max = 1e6) any (ci_data$sha %in% h$commit) } + +#' The "Ratio of Code Commits linked with Change Requests" CHAOSS metric. This +#' is defined as, "Percentage of new code commits linked with change requests +#' in the last 90 days." +#' \url{https://chaoss.community/kb/metrics-model-collaboration-development-index/}. +prop_commits_in_change_req <- function (path, end_date = Sys.Date ()) { + + or <- org_repo_from_path (path) + + gh_dat <- github_issues_prs_query (org = or [1], repo = or [2]) + + # Reduce to PR open-close events: + gh_prs <- dplyr::filter (gh_dat, !is.na (number)) |> + dplyr::group_by (number) |> + dplyr::filter (action == "closed") + + start_date <- as.Date (end_date - get_repometrics_period ()) + index <- which (as.Date (gh_prs$merged_at) >= start_date) + + num_commits_from_prs <- sum (gh_prs$commits [index]) + + log <- git_log_in_period (path, end_date, get_repometrics_period ()) + + ifelse (nrow (log) == 0, 0, num_commits_from_prs / nrow (log)) +} diff --git a/R/gh-queries.R b/R/gh-queries.R index 4d69421..843568c 100644 --- a/R/gh-queries.R +++ b/R/gh-queries.R @@ -30,7 +30,7 @@ github_repo_workflow_query <- function (org = NULL, repo = NULL, n = 30L) { status <- vapply (workflows, function (i) i$status, character (1L)) conclusion <- vapply (workflows, function (i) i$conclusion, character (1L)) created <- vapply (workflows, function (i) i$created_at, character (1L)) - created <- as.POSIXct (created, format = "%Y-%m-%dT%H:%M:%S", tz = "UTC") + created <- to_posix (created) data.frame ( name = names, @@ -51,11 +51,9 @@ github_repo_workflow_query <- function (org = NULL, repo = NULL, n = 30L) { #' @noRd github_issues_prs_query <- function (org = NULL, repo = NULL) { - period <- get_repometrics_period () - u_base <- "https://api.github.com/repos/" u_repo <- paste0 (u_base, org, "/", repo, "/") - u_wf <- paste0 (u_repo, "activity?per_page=100") + u_wf <- paste0 (u_repo, "events?per_page=100") body <- NULL this_url <- u_wf @@ -72,17 +70,71 @@ github_issues_prs_query <- function (org = NULL, repo = NULL) { this_url <- get_next_link (resp) } - ids <- vapply (body, function (i) i$id, numeric (1L)) - activity_type <- vapply (body, function (i) i$activity_type, character (1L)) + # Extraction function for single fields which may not be present + extract_one <- function (body, field = "action", naval = NA_character_) { + ret_type <- do.call (typeof (naval), list (1L)) + vapply (body, function (i) { + ifelse (field %in% names (i$payload), i$payload [[field]], naval) + }, ret_type) + } + + # Extraction function for doubly-nexted fields which may not be present + extract_two <- function (body, + field1 = "pull_request", + field2 = "comments", + naval = NA_character_) { + + ret_type <- do.call (typeof (naval), list (1L)) + vapply (body, function (i) { + ret <- naval + if (field1 %in% names (i$payload)) { + if (field2 %in% names (i$payload [[field1]])) { + ret <- i$payload [[field1]] [[field2]] + } + } + ifelse (is.null (ret), naval, ret) + }, ret_type) + } + + # Items which are always present: + ids <- vapply (body, function (i) i$id, character (1L)) + type <- vapply (body, function (i) i$type, character (1L)) login <- vapply (body, function (i) i$actor$login, character (1L)) - timestamp <- vapply (body, function (i) i$timestamp, character (1L)) - timestamp <- as.POSIXct (timestamp, format = "%Y-%m-%dT%H:%M:%S", tz = "UTC") + + # Single-nested items: + action <- extract_one (body, "action", NA_character_) + number <- extract_one (body, "number", NA_integer_) + + # Doubly-nested items: + num_comments <- extract_two (body, "pull_request", "comments", NA_integer_) + num_review_comments <- + extract_two (body, "pull_request", "review_comments", NA_integer_) + commits <- extract_two (body, "pull_request", "commits", NA_integer_) + additions <- extract_two (body, "pull_request", "additions", NA_integer_) + deletions <- extract_two (body, "pull_request", "deletions", NA_integer_) + changed_files <- + extract_two (body, "pull_request", "changed_files", NA_integer_) + created_at <- + extract_two (body, "pull_request", "created_at", NA_character_) + created_at <- to_posix (created_at) + merged_at <- + extract_two (body, "pull_request", "created_at", NA_character_) + merged_at <- to_posix (merged_at) data.frame ( id = ids, - activity_type = activity_type, + type = type, login = login, - timestamp = timestamp + action = action, + number = number, + commits = commits, + num_comments = num_comments, + num_review_comments = num_review_comments, + additions = additions, + deletions = deletions, + changed_files = changed_files, + created_at = created_at, + merged_at = merged_at ) } diff --git a/R/utils.R b/R/utils.R index d12723b..2cc64c5 100644 --- a/R/utils.R +++ b/R/utils.R @@ -28,6 +28,10 @@ set_num_cores <- function (num_cores) { return (num_cores) } +to_posix <- function (x) { + as.POSIXct (x, format = "%Y-%m-%dT%H:%M:%S", tz = "UTC") +} + # nocov start get_gh_token <- function () { e <- Sys.getenv () diff --git a/codemeta.json b/codemeta.json index bff0dc8..d7c4c55 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.010", + "version": "0.1.1.011", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", From c237bd84df42d5aa0c3286aca4dfd0cf525d6424 Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 16:19:37 +0100 Subject: [PATCH 3/6] test 'github_issues_pr_qry' fn for #11 --- DESCRIPTION | 2 +- R/gh-queries.R | 11 +- codemeta.json | 2 +- inst/httptest2/redact.R | 7 + .../gh_pr_qry/ghapi/repo/events-3fe229.json | 235 ++++++++++++++++++ tests/testthat/test-chaoss-metrics-external.R | 27 ++ 6 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 tests/testthat/gh_pr_qry/ghapi/repo/events-3fe229.json diff --git a/DESCRIPTION b/DESCRIPTION index 473e7d1..f3ce13b 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.011 +Version: 0.1.1.013 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/R/gh-queries.R b/R/gh-queries.R index 843568c..8604e4d 100644 --- a/R/gh-queries.R +++ b/R/gh-queries.R @@ -53,7 +53,9 @@ github_issues_prs_query <- function (org = NULL, repo = NULL) { u_base <- "https://api.github.com/repos/" u_repo <- paste0 (u_base, org, "/", repo, "/") - u_wf <- paste0 (u_repo, "events?per_page=100") + + is_test_env <- Sys.getenv ("REPOMETRICS_TESTS") == "true" + u_wf <- paste0 (u_repo, "events?per_page=", ifelse (is_test_env, 2, 100)) body <- NULL this_url <- u_wf @@ -67,7 +69,12 @@ github_issues_prs_query <- function (org = NULL, repo = NULL) { this_body <- httr2::resp_body_json (resp) body <- c (body, this_body) - this_url <- get_next_link (resp) + + if (!is_test_env) { + this_url <- get_next_link (resp) + } else { + this_url <- NULL + } } # Extraction function for single fields which may not be present diff --git a/codemeta.json b/codemeta.json index d7c4c55..654a5a5 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.011", + "version": "0.1.1.013", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", diff --git a/inst/httptest2/redact.R b/inst/httptest2/redact.R index d63997d..50e7d3b 100644 --- a/inst/httptest2/redact.R +++ b/inst/httptest2/redact.R @@ -14,6 +14,13 @@ function (resp) { fixed = TRUE ) + resp <- httptest2::gsub_response ( + resp, + "ropensci-review-tools/goodpractice", + "repo/", + fixed = TRUE + ) + test_repo <- "ropensci-review-tools/repometrics" resp <- httptest2::gsub_response ( resp, diff --git a/tests/testthat/gh_pr_qry/ghapi/repo/events-3fe229.json b/tests/testthat/gh_pr_qry/ghapi/repo/events-3fe229.json new file mode 100644 index 0000000..53c978d --- /dev/null +++ b/tests/testthat/gh_pr_qry/ghapi/repo/events-3fe229.json @@ -0,0 +1,235 @@ +[ + { + "id": "43737496856", + "type": "IssueCommentEvent", + "actor": { + "id": 6697851, + "login": "mpadge", + "display_login": "mpadge", + "gravatar_id": "", + "url": "https://api.github.com/users/mpadge", + "avatar_url": "https://avatars.githubusercontent.com/u/6697851?" + }, + "repo": { + "id": 50784274, + "name": "repo/", + "url": "ghapi/repo/" + }, + "payload": { + "action": "created", + "issue": { + "url": "ghapi/repo//issues/182", + "repository_url": "ghapi/repo/", + "labels_url": "ghapi/repo//issues/182/labels{/name}", + "comments_url": "ghapi/repo//issues/182/comments", + "events_url": "ghapi/repo//issues/182/events", + "html_url": "https://github.com/repo//issues/182", + "id": 2634833618, + "node_id": "I_kwDOAwboEs6dDF7S", + "number": 182, + "title": "rcmdcheck_vignettes_run fails on wanted errors", + "user": { + "login": "valentingar", + "id": 74970146, + "node_id": "MDQ6VXNlcjc0OTcwMTQ2", + "avatar_url": "https://avatars.githubusercontent.com/u/74970146?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/valentingar", + "html_url": "https://github.com/valentingar", + "followers_url": "https://api.github.com/users/valentingar/followers", + "following_url": "https://api.github.com/users/valentingar/following{/other_user}", + "gists_url": "https://api.github.com/users/valentingar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/valentingar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/valentingar/subscriptions", + "organizations_url": "https://api.github.com/users/valentingar/orgs", + "repos_url": "https://api.github.com/users/valentingar/repos", + "events_url": "https://api.github.com/users/valentingar/events{/privacy}", + "received_events_url": "https://api.github.com/users/valentingar/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 1, + "created_at": "2024-11-05T08:57:19Z", + "updated_at": "2024-11-12T11:33:01Z", + "closed_at": "2024-11-12T11:33:00Z", + "author_association": "NONE", + "active_lock_reason": null, + "body": "**Problem:** \r\nIf you want to demonstrate an error message in the package vignette, by setting the chunk option `error=TRUE`, `rcmdcheck_vignettes_run` still returns a note that the code cannot be run. \r\n\r\n**Expected behaviour:**\r\nIt should recognise the `error = TRUE` comment and not test these chunks.\r\n\r\n**Steps to reproduce:**\r\n- create new package\r\n- add vignette `usethis::use_vignette()`\r\n- add a chunk with option `error=TRUE` and code that causes an error message (e.g. `mod <- lm()`)\r\n- run `devtools::build_vignettes`\r\n- run `goodpractice::gp(checks = \"rcmdcheck_vignettes_run\")`", + "reactions": { + "url": "ghapi/repo//issues/182/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "ghapi/repo//issues/182/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned" + }, + "comment": { + "url": "ghapi/repo//issues/comments/2470293065", + "html_url": "https://github.com/repo//issues/182#issuecomment-2470293065", + "issue_url": "ghapi/repo//issues/182", + "id": 2470293065, + "node_id": "IC_kwDOAwboEs6TPa5J", + "user": { + "login": "mpadge", + "id": 6697851, + "node_id": "MDQ6VXNlcjY2OTc4NTE=", + "avatar_url": "https://avatars.githubusercontent.com/u/6697851?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mpadge", + "html_url": "https://github.com/mpadge", + "followers_url": "https://api.github.com/users/mpadge/followers", + "following_url": "https://api.github.com/users/mpadge/following{/other_user}", + "gists_url": "https://api.github.com/users/mpadge/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mpadge/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mpadge/subscriptions", + "organizations_url": "https://api.github.com/users/mpadge/orgs", + "repos_url": "https://api.github.com/users/mpadge/repos", + "events_url": "https://api.github.com/users/mpadge/events{/privacy}", + "received_events_url": "https://api.github.com/users/mpadge/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "created_at": "2024-11-12T11:33:00Z", + "updated_at": "2024-11-12T11:33:00Z", + "author_association": "MEMBER", + "body": "Thanks @valentingar. So i followed all of those steps, but I see this, starting with vignette code to confirm `error = TRUE` chunk:\r\n\r\n``` r\r\nsetwd (\"////\")\r\nreadLines (\"vignettes/vignette.Rmd\")\r\n```\r\n\r\n #> [1] \"---\" \r\n #> [2] \"title: \\\"vignette\\\"\" \r\n #> [3] \"output: rmarkdown::html_vignette\" \r\n #> [4] \"vignette: >\" \r\n #> [5] \" %\\\\VignetteIndexEntry{vignette}\" \r\n #> [6] \" %\\\\VignetteEngine{knitr::rmarkdown}\"\r\n #> [7] \" %\\\\VignetteEncoding{UTF-8}\" \r\n #> [8] \"---\" \r\n #> [9] \"\" \r\n #> [10] \"```{r, include = FALSE}\" \r\n #> [11] \"knitr::opts_chunk$set(\" \r\n #> [12] \" collapse = TRUE,\" \r\n #> [13] \" comment = \\\"#>\\\"\" \r\n #> [14] \")\" \r\n #> [15] \"```\" \r\n #> [16] \"\" \r\n #> [17] \"```{r setup}\" \r\n #> [18] \"library(demo)\" \r\n #> [19] \"```\" \r\n #> [20] \"\" \r\n #> [21] \"```{r error, error = TRUE}\" \r\n #> [22] \"stop (\\\"This is an error\\\")\" \r\n #> [23] \"```\"\r\n\r\nThen:\r\n\r\n``` r\r\nlibrary (goodpractice)\r\npackageVersion (\"goodpractice\")\r\n#> [1] '1.0.5.9001'\r\ng <- gp (checks = \"rcmdcheck_vignettes_run\")\r\n#> ℹ Preparing: rcmdcheck\r\ng\r\n#> \r\n#> ♥ Aha! Super package! Keep up the slick work!\r\ng$checks\r\n#> $rcmdcheck_vignettes_run\r\n#> [1] TRUE\r\n```\r\n\r\nCreated on 2024-11-12 with [reprex v2.1.1](https://reprex.tidyverse.org)<\/sup>\r\n\r\nLooks all good to me, but happy to look further if you still see the issue.", + "reactions": { + "url": "ghapi/repo//issues/comments/2470293065/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "performed_via_github_app": null + } + }, + "public": true, + "created_at": "2024-11-12T11:33:03Z", + "org": { + "id": 85607140, + "login": "ropensci-review-tools", + "gravatar_id": "", + "url": "https://api.github.com/orgs/ropensci-review-tools", + "avatar_url": "https://avatars.githubusercontent.com/u/85607140?" + } + }, + { + "id": "43737496184", + "type": "IssuesEvent", + "actor": { + "id": 6697851, + "login": "mpadge", + "display_login": "mpadge", + "gravatar_id": "", + "url": "https://api.github.com/users/mpadge", + "avatar_url": "https://avatars.githubusercontent.com/u/6697851?" + }, + "repo": { + "id": 50784274, + "name": "repo/", + "url": "ghapi/repo/" + }, + "payload": { + "action": "closed", + "issue": { + "url": "ghapi/repo//issues/182", + "repository_url": "ghapi/repo/", + "labels_url": "ghapi/repo//issues/182/labels{/name}", + "comments_url": "ghapi/repo//issues/182/comments", + "events_url": "ghapi/repo//issues/182/events", + "html_url": "https://github.com/repo//issues/182", + "id": 2634833618, + "node_id": "I_kwDOAwboEs6dDF7S", + "number": 182, + "title": "rcmdcheck_vignettes_run fails on wanted errors", + "user": { + "login": "valentingar", + "id": 74970146, + "node_id": "MDQ6VXNlcjc0OTcwMTQ2", + "avatar_url": "https://avatars.githubusercontent.com/u/74970146?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/valentingar", + "html_url": "https://github.com/valentingar", + "followers_url": "https://api.github.com/users/valentingar/followers", + "following_url": "https://api.github.com/users/valentingar/following{/other_user}", + "gists_url": "https://api.github.com/users/valentingar/gists{/gist_id}", + "starred_url": "https://api.github.com/users/valentingar/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/valentingar/subscriptions", + "organizations_url": "https://api.github.com/users/valentingar/orgs", + "repos_url": "https://api.github.com/users/valentingar/repos", + "events_url": "https://api.github.com/users/valentingar/events{/privacy}", + "received_events_url": "https://api.github.com/users/valentingar/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [ + + ], + "milestone": null, + "comments": 1, + "created_at": "2024-11-05T08:57:19Z", + "updated_at": "2024-11-12T11:33:01Z", + "closed_at": "2024-11-12T11:33:00Z", + "author_association": "NONE", + "active_lock_reason": null, + "body": "**Problem:** \r\nIf you want to demonstrate an error message in the package vignette, by setting the chunk option `error=TRUE`, `rcmdcheck_vignettes_run` still returns a note that the code cannot be run. \r\n\r\n**Expected behaviour:**\r\nIt should recognise the `error = TRUE` comment and not test these chunks.\r\n\r\n**Steps to reproduce:**\r\n- create new package\r\n- add vignette `usethis::use_vignette()`\r\n- add a chunk with option `error=TRUE` and code that causes an error message (e.g. `mod <- lm()`)\r\n- run `devtools::build_vignettes`\r\n- run `goodpractice::gp(checks = \"rcmdcheck_vignettes_run\")`", + "reactions": { + "url": "ghapi/repo//issues/182/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "ghapi/repo//issues/182/timeline", + "performed_via_github_app": null, + "state_reason": "not_planned" + } + }, + "public": true, + "created_at": "2024-11-12T11:33:01Z", + "org": { + "id": 85607140, + "login": "ropensci-review-tools", + "gravatar_id": "", + "url": "https://api.github.com/orgs/ropensci-review-tools", + "avatar_url": "https://avatars.githubusercontent.com/u/85607140?" + } + } +] diff --git a/tests/testthat/test-chaoss-metrics-external.R b/tests/testthat/test-chaoss-metrics-external.R index c225066..7dc0f10 100644 --- a/tests/testthat/test-chaoss-metrics-external.R +++ b/tests/testthat/test-chaoss-metrics-external.R @@ -37,3 +37,30 @@ test_that ("chaoss has CI external", { c ("name", "id", "sha", "title", "status", "conclusion", "created") ) }) + +test_that ("chaoss external commits in prs", { + + # This tests this internal function called by "prop_commits_in_prs". The + # latter function ultimately returns nothing, so this test is needed to + # confirm the intermediate data structure. + Sys.setenv ("REPOMETRICS_TESTS" = "true") + + org <- "ropensci-review-tools" + repo <- "goodpractice" + + dat <- with_mock_dir ( + "gh_pr_qry", + github_issues_prs_query (org, repo) + ) + + expect_s3_class (dat, "data.frame") + expect_equal (nrow (dat), 2L) # 2 hard-coded in 'gh-queries.R' for test env + expect_equal (ncol (dat), 13L) + nms <- c ( + "id", "type", "login", "action", "number", "commits", + "num_comments", "num_review_comments", "additions", "deletions", + "changed_files", "created_at", "merged_at" + ) + expect_equal (names (dat), nms) + expect_true (all (c ("created", "closed") %in% dat$action)) +}) From f42faf038703bd7e4bed55ac53b40753bb5e4124 Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 16:30:15 +0100 Subject: [PATCH 4/6] test full 'externa_prop_commits' fn for #11 --- DESCRIPTION | 2 +- codemeta.json | 2 +- tests/testthat/test-chaoss-metrics-external.R | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f3ce13b..b2d7751 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.013 +Version: 0.1.1.014 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/codemeta.json b/codemeta.json index 654a5a5..367de25 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.013", + "version": "0.1.1.014", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", diff --git a/tests/testthat/test-chaoss-metrics-external.R b/tests/testthat/test-chaoss-metrics-external.R index 7dc0f10..39c48d5 100644 --- a/tests/testthat/test-chaoss-metrics-external.R +++ b/tests/testthat/test-chaoss-metrics-external.R @@ -64,3 +64,27 @@ test_that ("chaoss external commits in prs", { expect_equal (names (dat), nms) expect_true (all (c ("created", "closed") %in% dat$action)) }) + +test_that ("chaoss external prop commits in change req", { + + Sys.setenv ("REPOMETRICS_TESTS" = "true") + pkg <- system.file ("extdata", "testpkg.zip", package = "repometrics") + flist <- unzip (pkg, exdir = fs::path_temp ()) + path <- fs::path_dir (flist [1]) + + desc_path <- fs::dir_ls (path, type = "file", regexp = "DESCRIPTION$") + url <- "https://github.com/ropensci-review-tools/goodpractice" + desc <- c ( + readLines (desc_path), + paste0 ("URL: ", url) + ) + writeLines (desc, desc_path) + + end_date <- as.Date ("2024-01-01") + prop_commits <- with_mock_dir ("gh_pr_qry", { + prop_commits_in_change_req (path = path, end_date = end_date) + }) + expect_identical (prop_commits, 0.) + + fs::dir_delete (path) +}) From 712a60dc82e33549235d4ffc6749ac142ad68f8f Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 16:44:48 +0100 Subject: [PATCH 5/6] fix pagination in gh event query for #11 events have different pagination than activities; the latter use a cursor, events just have a page number --- DESCRIPTION | 2 +- R/gh-queries.R | 28 +++++++++++++--------------- codemeta.json | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b2d7751..159389f 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.014 +Version: 0.1.1.015 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/R/gh-queries.R b/R/gh-queries.R index 8604e4d..fca28ba 100644 --- a/R/gh-queries.R +++ b/R/gh-queries.R @@ -55,11 +55,12 @@ github_issues_prs_query <- function (org = NULL, repo = NULL) { u_repo <- paste0 (u_base, org, "/", repo, "/") is_test_env <- Sys.getenv ("REPOMETRICS_TESTS") == "true" - u_wf <- paste0 (u_repo, "events?per_page=", ifelse (is_test_env, 2, 100)) + url0 <- paste0 (u_repo, "events?per_page=", ifelse (is_test_env, 2, 100)) body <- NULL - this_url <- u_wf - while (!is.null (this_url)) { + next_page <- 1 + this_url <- url0 + while (!is.null (next_page)) { req <- httr2::request (this_url) |> add_token_to_req () @@ -70,11 +71,11 @@ github_issues_prs_query <- function (org = NULL, repo = NULL) { this_body <- httr2::resp_body_json (resp) body <- c (body, this_body) - if (!is_test_env) { - this_url <- get_next_link (resp) - } else { - this_url <- NULL + next_page <- get_next_page (resp) + if (is_test_env) { + next_page <- NULL } + this_url <- paste0 (url0, "&page=", next_page) } # Extraction function for single fields which may not be present @@ -159,11 +160,11 @@ add_token_to_req <- function (req) { #' Pagination for Rest API. see #' https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api #' @noRd -get_next_link <- function (resp) { +get_next_page <- function (resp) { link <- httr2::resp_headers (resp)$link - next_url <- NULL + next_page <- NULL if (!is.null (link)) { next_ptn <- "rel\\=\\\"next" @@ -171,13 +172,10 @@ get_next_link <- function (resp) { # "next" is always first; where there are multiples, "prev" comes # after "next" ptn <- "<([^>]+)>" - next_url <- regmatches (link, regexpr (ptn, link)) - next_url <- gsub ("<|>", "", next_url) - if (!grepl ("after", next_url)) { - cli::cli_abort ("Pagination link in GitHub Rest API malformed: [{next_url}]") - } + next_page <- regmatches (link, regexpr (ptn, link)) + next_page <- gsub ("^.*&page\\=|>", "", next_page) } } - return (next_url) + return (next_page) } diff --git a/codemeta.json b/codemeta.json index 367de25..1bb33fd 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.014", + "version": "0.1.1.015", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", From 0bd82b2898ffd63398e65442befd5c53f378b533 Mon Sep 17 00:00:00 2001 From: mpadge Date: Thu, 14 Nov 2024 17:13:21 +0100 Subject: [PATCH 6/6] test 'metric_has_ci' fn for #11 --- DESCRIPTION | 2 +- R/chaoss-hybrid.R | 5 +++- codemeta.json | 2 +- .../testthat/_snaps/chaoss-metrics-hybrid.md | 7 ++++++ tests/testthat/test-chaoss-metrics-hybrid.R | 25 +++++++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 tests/testthat/_snaps/chaoss-metrics-hybrid.md create mode 100644 tests/testthat/test-chaoss-metrics-hybrid.R diff --git a/DESCRIPTION b/DESCRIPTION index 159389f..aecaca2 100755 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: repometrics Title: Metrics for Your Code Repository -Version: 0.1.1.015 +Version: 0.1.1.016 Authors@R: person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-2172-5265")) diff --git a/R/chaoss-hybrid.R b/R/chaoss-hybrid.R index f1364bc..d7a507b 100644 --- a/R/chaoss-hybrid.R +++ b/R/chaoss-hybrid.R @@ -1,7 +1,10 @@ # Hybird metrics from both internal structure and external data chaoss_metric_has_ci <- function (path) { - has_ci <- has_gh_ci_tests (path) + + is_test_env <- Sys.getenv ("REPOMETRICS_TESTS") == "true" + has_ci <- ifelse (is_test_env, FALSE, has_gh_ci_tests (path)) + if (!has_ci) { ci_files <- repo_has_ci_files (path) has_ci <- length (ci_files) > 0L diff --git a/codemeta.json b/codemeta.json index 1bb33fd..02a59a0 100644 --- a/codemeta.json +++ b/codemeta.json @@ -8,7 +8,7 @@ "codeRepository": "https://github.com/ropensci-review-tools/repometrics", "issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues", "license": "https://spdx.org/licenses/GPL-3.0", - "version": "0.1.1.015", + "version": "0.1.1.016", "programmingLanguage": { "@type": "ComputerLanguage", "name": "R", diff --git a/tests/testthat/_snaps/chaoss-metrics-hybrid.md b/tests/testthat/_snaps/chaoss-metrics-hybrid.md new file mode 100644 index 0000000..c946833 --- /dev/null +++ b/tests/testthat/_snaps/chaoss-metrics-hybrid.md @@ -0,0 +1,7 @@ +# chaoss metric has_ci + + Code + chk <- chaoss_metric_has_ci(path) + Message + i Unable to determine whether runs are recent for CI service [github]. + diff --git a/tests/testthat/test-chaoss-metrics-hybrid.R b/tests/testthat/test-chaoss-metrics-hybrid.R new file mode 100644 index 0000000..7461952 --- /dev/null +++ b/tests/testthat/test-chaoss-metrics-hybrid.R @@ -0,0 +1,25 @@ +test_that ("chaoss metric has_ci", { + + Sys.setenv ("REPOMETRICS_TESTS" = "true") + + pkg <- system.file ("extdata", "testpkg.zip", package = "repometrics") + flist <- unzip (pkg, exdir = fs::path_temp ()) + path <- fs::path_dir (flist [1]) + + chk <- repo_has_ci_files (path) + expect_length (chk, 0L) + + d <- fs::dir_create (fs::path (path, ".github", "workflows")) + f <- fs::path (d, "workflow.yaml") + writeLines ("a", f) + + chk <- repo_has_ci_files (path) + expect_length (chk, 1L) + expect_equal (chk, "github") + + # Test cli::cli_alert_warning output: + expect_snapshot (chk <- chaoss_metric_has_ci (path)) + expect_true (chk) + + fs::dir_delete (path) +})