Skip to content

Commit 0d49828

Browse files
authored
Merge pull request #25 from ropensci-review-tools/cm-data-gh-contrib
gh contribs for #23
2 parents e57fea1 + 5e92bc1 commit 0d49828

File tree

15 files changed

+1077
-757
lines changed

15 files changed

+1077
-757
lines changed

DESCRIPTION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: repometrics
22
Title: Metrics for Your Code Repository
3-
Version: 0.1.1.026
3+
Version: 0.1.1.036
44
Authors@R:
55
person("Mark", "Padgham", , "mark.padgham@email.com", role = c("aut", "cre"),
66
comment = c(ORCID = "0000-0003-2172-5265"))

R/chaoss-external.R

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ has_gh_ci_tests <- function (path) {
4242
#' @noRd
4343
prop_commits_in_change_req <- function (path, end_date = Sys.Date ()) {
4444

45+
# Suppress no visible binding notes:
46+
number <- action <- NULL
47+
4548
or <- org_repo_from_path (path)
4649

4750
gh_dat <- github_issues_prs_query (org = or [1], repo = or [2])

R/cm-data-gh-contribs.R

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
cm_data_gh_contributors <- function (path) {
2+
3+
log <- cm_data_gitlog (path)
4+
gh_url <- pkg_gh_url_from_path (path)
5+
6+
7+
}
8+
9+
contribs_from_log <- function (log) {
10+
11+
gh_handle <- unique (log$aut_name)
12+
gh_email <- log$aut_email [match (gh_handle, log$aut_name)]
13+
14+
# Remove any duplicates of either, but excluding non-entries:
15+
rm_dup_rows <- function (x) {
16+
x <- gsub ("\\s+", "", x)
17+
index <- seq_along (x)
18+
index_out <- which (duplicated (x) & nzchar (x))
19+
if (length (index_out) > 0) {
20+
index <- index [-(index_out)]
21+
}
22+
return (index)
23+
}
24+
index1 <- rm_dup_rows (gh_handle)
25+
index2 <- rm_dup_rows (gh_email)
26+
27+
# Then extract only instances where neither handles nor emails are
28+
# duplicated:
29+
index_table <- table (c (index1, index2))
30+
index <- as.integer (names (index_table) [which (index_table == 2L)])
31+
32+
data.frame (
33+
handle = gh_handle,
34+
email = gh_email
35+
) [index, ]
36+
}
37+
38+
contribs_from_gh_api <- function (path, n_per_page = 100) {
39+
40+
is_test_env <- Sys.getenv ("REPOMETRICS_TESTS") == "true"
41+
42+
gh_url <- pkg_gh_url_from_path (path)
43+
if (is.null (gh_url)) {
44+
return (NULL)
45+
}
46+
47+
org_repo <- gsub ("https://github.com/", "", gh_url, fixed = TRUE)
48+
if (!grepl ("\\/$", org_repo)) {
49+
org_repo <- paste0 (org_repo, "/")
50+
}
51+
52+
u_base <- "https://api.github.com/repos/"
53+
u_org_repo <- paste0 (u_base, org_repo)
54+
u_endpoint <- paste0 (u_org_repo, "contributors")
55+
56+
req <- httr2::request (u_endpoint) |>
57+
httr2::req_url_query (per_page = n_per_page)
58+
59+
body <- NULL
60+
next_page <- 1
61+
62+
while (!is.null (next_page)) {
63+
64+
req <- add_gh_token_to_req (req)
65+
resp <- httr2::req_perform (req)
66+
httr2::resp_check_status (resp)
67+
68+
body <- c (body, httr2::resp_body_json (resp))
69+
70+
next_page <- get_next_page (resp)
71+
if (is_test_env) {
72+
next_page <- NULL
73+
}
74+
75+
req <- httr2::request (u_endpoint) |>
76+
httr2::req_url_query (per_page = 10) |>
77+
httr2::req_url_query (page = next_page)
78+
}
79+
80+
login <- vapply (body, function (i) i$login, character (1L))
81+
ctb_id <- vapply (body, function (i) i$id, integer (1L))
82+
avatar_url <- vapply (body, function (i) i$avatar_url, character (1L))
83+
api_url <- vapply (body, function (i) i$url, character (1L))
84+
gh_url <- vapply (body, function (i) i$html_url, character (1L))
85+
contributions <- vapply (body, function (i) i$contributions, integer (1L))
86+
87+
ctbs <- data.frame (
88+
login = login,
89+
ctb_id = ctb_id,
90+
avatar_url = avatar_url,
91+
api_url = api_url,
92+
gh_url = gh_url,
93+
contributions = contributions
94+
)
95+
96+
ctbs_user_info <- lapply (ctbs$login, user_from_gh_api)
97+
ctbs_user_info <- do.call (rbind, ctbs_user_info)
98+
99+
ctbs <- dplyr::left_join (ctbs, ctbs_user_info, by = c ("login", "ctb_id"))
100+
101+
return (ctbs)
102+
}
103+
104+
user_from_gh_api <- function (user) {
105+
106+
u_base <- "https://api.github.com/users/"
107+
u_endpoint <- paste0 (u_base, user)
108+
109+
req <- httr2::request (u_endpoint) |>
110+
add_gh_token_to_req ()
111+
resp <- httr2::req_perform (req)
112+
httr2::resp_check_status (resp)
113+
body <- httr2::resp_body_json (resp)
114+
115+
data.frame (
116+
login = body$login,
117+
ctb_id = body$id,
118+
name = null2na_char (body$name),
119+
company = null2na_char (body$company),
120+
email = null2na_char (body$email),
121+
location = null2na_char (body$location),
122+
blog = null2na_char (body$blog),
123+
bio = null2na_char (body$bio),
124+
public_repos = body$public_repos,
125+
followers = body$followers,
126+
following = body$following,
127+
created_at = body$created_at,
128+
updated_at = body$updated_at
129+
)
130+
}

R/gh-queries.R

+16-12
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,8 @@ github_repo_workflow_query <- function (org = NULL, repo = NULL, n = 30L) {
99
u_repo <- paste0 (u_base, org, "/", repo, "/")
1010
u_wf <- paste0 (u_repo, "actions/runs?per_page=", n)
1111

12-
req <- httr2::request (u_wf)
13-
14-
if (!nzchar (Sys.getenv ("GITHUB_WORKFLOW"))) {
15-
tok <- get_gh_token ()
16-
headers <- list (Authorization = paste0 ("Bearer ", tok))
17-
req <- httr2::req_headers (req, "Authorization" = headers)
18-
}
12+
req <- httr2::request (u_wf) |>
13+
add_gh_token_to_req ()
1914

2015
resp <- httr2::req_perform (req)
2116
httr2::resp_check_status (resp)
@@ -26,9 +21,17 @@ github_repo_workflow_query <- function (org = NULL, repo = NULL, n = 30L) {
2621
ids <- vapply (workflows, function (i) i$id, numeric (1L))
2722
names <- vapply (workflows, function (i) i$name, character (1L))
2823
shas <- vapply (workflows, function (i) i$head_sha, character (1L))
29-
titles <- vapply (workflows, function (i) i$display_title, character (1L))
30-
status <- vapply (workflows, function (i) i$status, character (1L))
31-
conclusion <- vapply (workflows, function (i) i$conclusion, character (1L))
24+
titles <- vapply (
25+
workflows,
26+
function (i) null2na_char (i$display_title),
27+
character (1L)
28+
)
29+
status <- vapply (
30+
workflows,
31+
function (i) null2na_char (i$status),
32+
character (1L)
33+
)
34+
conclusion <- vapply (workflows, function (i) null2na_char (i$conclusion), character (1L))
3235
created <- vapply (workflows, function (i) i$created_at, character (1L))
3336
created <- to_posix (created)
3437

@@ -169,8 +172,9 @@ get_next_page <- function (resp) {
169172
if (!is.null (link)) {
170173
next_ptn <- "rel\\=\\\"next"
171174
if (grepl (next_ptn, link)) {
172-
# "next" is always first; where there are multiples, "prev" comes
173-
# after "next"
175+
links <- strsplit (link, ",\\s+") [[1]]
176+
link <- grep (next_ptn, links, value = TRUE)
177+
174178
ptn <- "<([^>]+)>"
175179
next_page <- regmatches (link, regexpr (ptn, link))
176180
next_page <- gsub ("^.*&page\\=|>", "", next_page)

R/utils.R

+21-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ null2na_int <- function (x) {
1717
ifelse (length (x) == 0, NA_integer_, x)
1818
}
1919

20+
#' Convert single values of length 0 or NULL to `NA_character_`.
21+
#' @noRd
22+
null2na_char <- function (x) {
23+
ifelse (length (x) == 0, NA_character_, x)
24+
}
25+
2026
set_num_cores <- function (num_cores) {
2127
if (num_cores <= 0L) {
2228
num_cores <- parallel::detectCores () + num_cores
@@ -57,19 +63,32 @@ pkg_name_from_path <- function (path) {
5763
}
5864

5965
pkg_gh_url_from_path <- function (path) {
66+
6067
desc <- fs::dir_ls (path, type = "file", regexp = "DESCRIPTION$")
6168
checkmate::assert_file_exists (desc)
6269

6370
desc <- read.dcf (desc)
6471
ret <- NULL
6572
if ("URL" %in% colnames (desc)) {
66-
url <- unname (desc [, "URL"])
67-
url <- strsplit (gsub ("\\n", "", url), ",") [[1]]
73+
url <- strsplit (unname (desc [, "URL"]), "\\n|,") [[1]]
74+
url <- gsub ("^[[:space:]]*", "", url)
75+
url <- gsub ("[[:space:]].*$", "", url)
6876
ret <- grep ("github\\.com", url, value = TRUE)
6977
}
7078
return (ret)
7179
}
7280

81+
add_gh_token_to_req <- function (req) {
82+
if (!nzchar (Sys.getenv ("GITHUB_WORKFLOW"))) {
83+
tok <- get_gh_token ()
84+
headers <- list (Authorization = paste0 ("Bearer ", tok))
85+
req <- httr2::req_headers (req, "Authorization" = headers)
86+
}
87+
88+
return (req)
89+
}
90+
91+
7392
org_repo_from_path <- function (path) {
7493

7594
url <- pkg_gh_url_from_path (path)

codemeta.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"codeRepository": "https://github.com/ropensci-review-tools/repometrics",
99
"issueTracker": "https://github.com/ropensci-review-tools/repometrics/issues",
1010
"license": "https://spdx.org/licenses/GPL-3.0",
11-
"version": "0.1.1.026",
11+
"version": "0.1.1.036",
1212
"programmingLanguage": {
1313
"@type": "ComputerLanguage",
1414
"name": "R",

inst/httptest2/redact.R

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ function (resp) {
1010
resp <- httptest2::gsub_response (
1111
resp,
1212
"https://api.github.com/repos/",
13-
"ghapi/",
13+
"ghrepos/",
14+
fixed = TRUE
15+
)
16+
17+
resp <- httptest2::gsub_response (
18+
resp,
19+
"https://api.github.com/users/",
20+
"ghusers/",
1421
fixed = TRUE
1522
)
1623

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[
2+
{
3+
"login": "gaborcsardi",
4+
"id": 660288,
5+
"node_id": "MDQ6VXNlcjY2MDI4OA==",
6+
"avatar_url": "https://avatars.githubusercontent.com/u/660288?v=4",
7+
"gravatar_id": "",
8+
"url": "ghusers/gaborcsardi",
9+
"html_url": "https://github.com/gaborcsardi",
10+
"followers_url": "ghusers/gaborcsardi/followers",
11+
"following_url": "ghusers/gaborcsardi/following{/other_user}",
12+
"gists_url": "ghusers/gaborcsardi/gists{/gist_id}",
13+
"starred_url": "ghusers/gaborcsardi/starred{/owner}{/repo}",
14+
"subscriptions_url": "ghusers/gaborcsardi/subscriptions",
15+
"organizations_url": "ghusers/gaborcsardi/orgs",
16+
"repos_url": "ghusers/gaborcsardi/repos",
17+
"events_url": "ghusers/gaborcsardi/events{/privacy}",
18+
"received_events_url": "ghusers/gaborcsardi/received_events",
19+
"type": "User",
20+
"user_view_type": "public",
21+
"site_admin": false,
22+
"contributions": 88
23+
},
24+
{
25+
"login": "hfrick",
26+
"id": 12950918,
27+
"node_id": "MDQ6VXNlcjEyOTUwOTE4",
28+
"avatar_url": "https://avatars.githubusercontent.com/u/12950918?v=4",
29+
"gravatar_id": "",
30+
"url": "ghusers/hfrick",
31+
"html_url": "https://github.com/hfrick",
32+
"followers_url": "ghusers/hfrick/followers",
33+
"following_url": "ghusers/hfrick/following{/other_user}",
34+
"gists_url": "ghusers/hfrick/gists{/gist_id}",
35+
"starred_url": "ghusers/hfrick/starred{/owner}{/repo}",
36+
"subscriptions_url": "ghusers/hfrick/subscriptions",
37+
"organizations_url": "ghusers/hfrick/orgs",
38+
"repos_url": "ghusers/hfrick/repos",
39+
"events_url": "ghusers/hfrick/events{/privacy}",
40+
"received_events_url": "ghusers/hfrick/received_events",
41+
"type": "User",
42+
"user_view_type": "public",
43+
"site_admin": false,
44+
"contributions": 78
45+
}
46+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"login": "gaborcsardi",
3+
"id": 660288,
4+
"node_id": "MDQ6VXNlcjY2MDI4OA==",
5+
"avatar_url": "https://avatars.githubusercontent.com/u/660288?v=4",
6+
"gravatar_id": "",
7+
"url": "ghusers/gaborcsardi",
8+
"html_url": "https://github.com/gaborcsardi",
9+
"followers_url": "ghusers/gaborcsardi/followers",
10+
"following_url": "ghusers/gaborcsardi/following{/other_user}",
11+
"gists_url": "ghusers/gaborcsardi/gists{/gist_id}",
12+
"starred_url": "ghusers/gaborcsardi/starred{/owner}{/repo}",
13+
"subscriptions_url": "ghusers/gaborcsardi/subscriptions",
14+
"organizations_url": "ghusers/gaborcsardi/orgs",
15+
"repos_url": "ghusers/gaborcsardi/repos",
16+
"events_url": "ghusers/gaborcsardi/events{/privacy}",
17+
"received_events_url": "ghusers/gaborcsardi/received_events",
18+
"type": "User",
19+
"user_view_type": "public",
20+
"site_admin": false,
21+
"name": "Gábor Csárdi",
22+
"company": "@posit-pbc",
23+
"blog": "https://gaborcsardi.org",
24+
"location": "Barcelona",
25+
"email": "csardi.gabor@gmail.com",
26+
"hireable": null,
27+
"bio": null,
28+
"twitter_username": null,
29+
"public_repos": 194,
30+
"public_gists": 19,
31+
"followers": 1432,
32+
"following": 0,
33+
"created_at": "2011-03-09T17:29:25Z",
34+
"updated_at": "2024-11-18T09:12:36Z"
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"login": "hfrick",
3+
"id": 12950918,
4+
"node_id": "MDQ6VXNlcjEyOTUwOTE4",
5+
"avatar_url": "https://avatars.githubusercontent.com/u/12950918?v=4",
6+
"gravatar_id": "",
7+
"url": "ghusers/hfrick",
8+
"html_url": "https://github.com/hfrick",
9+
"followers_url": "ghusers/hfrick/followers",
10+
"following_url": "ghusers/hfrick/following{/other_user}",
11+
"gists_url": "ghusers/hfrick/gists{/gist_id}",
12+
"starred_url": "ghusers/hfrick/starred{/owner}{/repo}",
13+
"subscriptions_url": "ghusers/hfrick/subscriptions",
14+
"organizations_url": "ghusers/hfrick/orgs",
15+
"repos_url": "ghusers/hfrick/repos",
16+
"events_url": "ghusers/hfrick/events{/privacy}",
17+
"received_events_url": "ghusers/hfrick/received_events",
18+
"type": "User",
19+
"user_view_type": "public",
20+
"site_admin": false,
21+
"name": "Hannah Frick",
22+
"company": null,
23+
"blog": "http://www.frick.ws",
24+
"location": "London",
25+
"email": null,
26+
"hireable": null,
27+
"bio": "tidymodels team",
28+
"twitter_username": null,
29+
"public_repos": 59,
30+
"public_gists": 5,
31+
"followers": 174,
32+
"following": 0,
33+
"created_at": "2015-06-18T10:51:22Z",
34+
"updated_at": "2024-11-02T17:27:12Z"
35+
}

0 commit comments

Comments
 (0)