<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/rectangling-onboarding/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://ropensci.org/blog/2018/04/26/rectangling-onboarding/"> rOpenSci - open tools for open science</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Our <a rel="nofollow" href="https://ropensci.org/blog/2017/09/11/software-review-update/" target="_blank">onboarding<br>
reviews</a>,<br>
that ensure that packages contributed by the community undergo a<br>
transparent, constructive, non adversarial and open review process, take<br>
place in the issue tracker of a GitHub repository. Development of the<br>
packages we onboard also takes place in the open, most often in GitHub<br>
repositories.</p>
<p>Therefore, when wanting to get data about our onboarding system for<br>
giving a data-driven overview, my mission was to extract data from<br>
GitHub and git repositories, and to put it into nice rectangles (as<br>
defined <a rel="nofollow" href="https://speakerdeck.com/jennybc/data-rectangling" target="_blank">by Jenny<br>
Bryan</a>) ready for<br>
analysis. You might call that the first step of a “tidy git analysis”<br>
using the term coined by <a rel="nofollow" href="https://drsimonj.svbtle.com/embarking-on-a-tidy-git-analysis" target="_blank">Simon<br>
Jackson</a>.<br>
So, how did I collect data?</p>
<h3>A side-note about GitHub</h3>
<p>In the following, I’ll mention repositories. All of them are git<br>
repositories, which means they’re folders under version control, where<br>
roughly said all changes are saved via commits and their messages (more<br>
or less) describing what’s been changed in the commit. Now, on top of<br>
that these repositories live on GitHub which means they get to enjoy<br>
some infratructure such as issue trackers, milestones, starring by<br>
admirers, etc. If that ecosystem is brand new to you, I recommend<br>
reading <a rel="nofollow" href="http://happygitwithr.com/" target="_blank">this book</a>, especially its <a rel="nofollow" href="http://happygitwithr.com/big-picture.html" target="_blank">big<br>
picture chapter</a>.</p>
<h3>Package review processes: weaving the threads</h3>
<p>Each package submission is an issue thread in our onboarding repository,<br>
see an example<br>
<a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/136" target="_blank">here</a>. The first<br>
comment in that issue is the submission itself, followed by many<br>
comments by the editor, reviewers and authors. On top of all the data<br>
that’s saved there, mostly text data, we have a private<br>
<a rel="nofollow" href="https://airtable.com/" target="_blank">Airtable</a> workspace where we have a table of<br>
reviewers and their reviews, with direct links to the issue comments<br>
that are reviews.</p>
<h4>Getting issue threads</h4>
<p>Unsurprisingly, the first step here was to “get issue threads”. What do<br>
I mean? I wanted a table of all issue threads, one line per comment,<br>
with columns indicating the time at which something was written, and<br>
columns digesting the data from the issue itself, e.g. guessing the role<br>
from the commenter from other information: the first user of the issue<br>
is the “author”.</p>
<p>I used to use GitHub API V3 and then heard about <a rel="nofollow" href="https://developer.github.com/v4/" target="_blank">GitHub API<br>
V4</a> which blew my mind. As if I<br>
weren’t impressed enough by the mere existence of this API and <a rel="nofollow" href="https://developer.github.com/v4/#why-is-github-using-graphql" target="_blank">its<br>
advantages</a>,</p>
<ul>
<li>
<p>I discovered the rOpenSci <a rel="nofollow" href="https://github.com/ropensci/ghql" target="_blank"><code>ghql</code><br>
package</a> allows one to interact<br>
with such an API and that its docs actually use GitHub API V4 as an<br>
example!</p>
</li>
<li>
<p>Carl Boettiger told me about <a rel="nofollow" href="http://www.carlboettiger.info/2017/12/11/data-rectangling-with-jq/" target="_blank">his way to rectangle JSON<br>
data</a>,<br>
using <a rel="nofollow" href="https://stedolan.github.io/jq/" target="_blank">jq</a>, a language for<br>
processing JSON, via a dedicated rOpenSci package,<br>
<a rel="nofollow" href="https://github.com/ropensci/jqr" target="_blank"><code>jqr</code></a>.</p>
</li>
</ul>
<p>I have nothing against GitHub API V3 and<br>
<a rel="nofollow" href="https://github.com/r-lib/gh" target="_blank"><code>gh</code></a> and <code>purrr</code> workflows, but I was<br>
curious and really enjoyed learning these new tools and writing this<br>
code. I had written a <code>gh</code>/<code>purrr</code> code for getting the same information<br>
and it felt clumsier, but it might just be because I wasn’t<br>
perfectionist enough when writing it! I achieved writing the correct<br>
GitHub V4 API query to get <em>just</em> what I needed by <a rel="nofollow" href="https://developer.github.com/v4/explorer/" target="_blank">using its online<br>
explorer</a>. I then succeeded<br>
in transforming the JSON output into a rectangle by reading Carl’s post<br>
but also by taking advantage of another online explorer, <a rel="nofollow" href="https://jqplay.org/" target="_blank">jq<br>
play</a> where I pasted my output via<br>
<code>writeClipboard</code>. That’s nearly always the way I learn about query<br>
tools: using some sort of explorer and then pasting the code into a<br>
script. When I am more experienced, I can skip the explorer part.</p>
<p>The first function I wrote was one for getting the issue number of the<br>
last onboarding issue, because then I looped/mapped over all issues.</p>
<pre><code>library(&quot;ghql&quot;)
library(&quot;httr&quot;)
library(&quot;magrittr&quot;)
# function to get number of last issue
get_last_issue &lt;- function(){
  query = '{
  repository(owner: &quot;ropensci&quot;, name: &quot;onboarding&quot;) {
  issues(last: 1) {
  edges{
  node{
  number
  }
  }
  }
}
}'
token &lt;- Sys.getenv(&quot;GITHUB_GRAPHQL_TOKEN&quot;)
  cli &lt;- GraphqlClient$new(
    url = &quot;https://api.github.com/graphql&quot;,
    headers = add_headers(Authorization = paste0(&quot;Bearer &quot;, token))
  )
  
  ## define query
  ### creat a query class first
  qry &lt;- Query$new()
  qry$query('issues', query)
  last_issue &lt;-cli$exec(qry$queries$issues)
  last_issue %>%
    jqr::jq('.data.repository.issues.edges[].node.number') %>%
   as.numeric()
}
get_last_issue()
</code></pre>
<pre><code>## [1] 201
</code></pre>
<p>Then I wrote a function for getting all the precious info I needed from<br>
an issue thread. At the time it lived on its own in an R script, now<br>
it’s gotten included in my <a rel="nofollow" href="https://github.com/ropenscilabs/ghrecipes" target="_blank"><code>ghrecipes</code><br>
package</a> as<br>
<a rel="nofollow" href="https://github.com/ropenscilabs/ghrecipes/blob/master/R/get_issue_thread.R" target="_blank"><code>get_issue_thread</code></a><br>
so you can check out the code there, along with other useful recipes for<br>
analyzing GitHub data.</p>
<p>Then I launched this code to get all data! It was very satisfying.</p>
<pre><code>#get all threads
issues &lt;- purrr::map_df(1:get_last_issue(), get_issue_thread)

# for the one(s) with 101 comments get the 100 last comments
long_issues &lt;- issues %>%
  dplyr::count(issue) %>%
  dplyr::filter(n == 101) %>%
  dplyr::pull(issue)

issues2 &lt;- purrr::map_df(long_issues, get_issue_thread, first = FALSE)
all_issues &lt;- dplyr::bind_rows(issues, issues2)
all_issues &lt;- unique(all_issues)


readr::write_csv(all_issues, &quot;data/all_threads_v4.csv&quot;)
</code></pre>
<h4>Digesting them and complementing them with Airtable data</h4>
<p>In the previous step we got a rectangle of all threads, with information<br>
from the first issue comment (such as labels) distributed to all the<br>
comments of the threads.</p>
<pre><code>issues &lt;- readr::read_csv(&quot;data/all_threads_v4.csv&quot;)
issues &lt;- janitor::clean_names(issues)
issues &lt;- dplyr::rename(issues, user = author)
issues &lt;- dplyr::select(issues, - dplyr::contains(&quot;topic&quot;))
issues %>%
  head() %>%
  dplyr::select(- body) %>%
  knitr::kable()
</code></pre>
<table>
<thead>
<tr>
<th align="left">title</th>
<th align="left">author_association</th>
<th align="left">assignee</th>
<th align="left">created_at</th>
<th align="left">closed_at</th>
<th align="left">user</th>
<th align="left">comment_url</th>
<th align="left">package</th>
<th align="left">pulled</th>
<th align="right">issue</th>
<th align="left">meta</th>
<th align="left">x6_approved</th>
<th align="left">out_of_scope</th>
<th align="left">x4_review_s_in_awaiting_changes</th>
<th align="left">x0_presubmission</th>
<th align="left">question</th>
<th align="left">x3_reviewer_s_assigned</th>
<th align="left">holding</th>
<th align="left">legacy</th>
<th align="left">x1_editor_checks</th>
<th align="left">x5_awaiting_reviewer_s_response</th>
<th align="left">x2_seeking_reviewer_s</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-10 23:22:45</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">richfitz</td>
<td align="left">NA</td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-10 23:26:11</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">richfitz</td>
<td align="left"><a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/1#issuecomment-78170639" target="_blank">https://github.com/ropensci/onboarding/issues/1#issuecomment-78170639</a></td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-11 19:29:32</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">karthik</td>
<td align="left"><a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/1#issuecomment-78351979" target="_blank">https://github.com/ropensci/onboarding/issues/1#issuecomment-78351979</a></td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-11 21:08:59</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">sckott</td>
<td align="left"><a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/1#issuecomment-78372187" target="_blank">https://github.com/ropensci/onboarding/issues/1#issuecomment-78372187</a></td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-11 21:13:11</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">karthik</td>
<td align="left"><a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/1#issuecomment-78373054" target="_blank">https://github.com/ropensci/onboarding/issues/1#issuecomment-78373054</a></td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">OWNER</td>
<td align="left">sckott</td>
<td align="left">2015-03-11 21:33:45</td>
<td align="left">2015-03-31 00:16:28</td>
<td align="left">richfitz</td>
<td align="left"><a rel="nofollow" href="https://github.com/ropensci/onboarding/issues/1#issuecomment-78377124" target="_blank">https://github.com/ropensci/onboarding/issues/1#issuecomment-78377124</a></td>
<td align="left">TRUE</td>
<td align="left">TRUE</td>
<td align="right">1</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
<td align="left">NA</td>
</tr>
</tbody>
</table>
<p>Now we need a few steps more:</p>
<ul>
<li>
<p>transforming NA into FALSE for variables corresponding to labels,</p>
</li>
<li>
<p>getting the package name from Airtable since the titles of issues<br>
are not uniformly formatted,</p>
</li>
<li>
<p>knowing which comment is a review,</p>
</li>
<li>
<p>deducing the role of the user writing the comment<br>
(author/editor/reviewer/community manager/other).</p>
</li>
</ul>
<p>Below binary variables are transformed and only rows corresponding to<br>
approved packages are kept.</p>
<pre><code># labels
replace_1 &lt;- function(x){
 !is.na(x[1])
}

# binary variables
ncol_issues &lt;- ncol(issues)
issues &lt;- dplyr::group_by(issues, issue) %>%
  dplyr::arrange(created_at) %>%
  dplyr::mutate_at(9:(ncol_issues-1), replace_1) %>%
  dplyr::ungroup()


# keep only issues that are finished
issues &lt;- dplyr::filter(issues, package, !x0_presubmission, 
                        !out_of_scope, !legacy,
                        !x1_editor_checks, x6_approved)
issues &lt;- dplyr::select(issues, - dplyr::starts_with(&quot;x&quot;),
                        - package, - out_of_scope, - legacy,
                        - meta, - holding, - pulled, - question)
</code></pre>
<p>Then, thanks to the <code>airtabler</code> package we can add the name of the<br>
package, and identify review comments.</p>
<pre><code># airtable data
airtable &lt;- airtabler::airtable(&quot;appZIB8hgtvjoV99D&quot;, &quot;Reviews&quot;)
airtable &lt;- airtable$Reviews$select_all()

airtable &lt;- dplyr::mutate(airtable,
                          issue = as.numeric(stringr::str_replace(onboarding_url, 
                                                                  &quot;.*issues\\/&quot;, &quot;&quot;)))

# we get the name of the package
# and we know which comments are reviews
reviews &lt;- dplyr::select(airtable, review_url, issue, package) %>%
  dplyr::mutate(is_review = TRUE) 

issues &lt;- dplyr::left_join(issues, reviews, by = c(&quot;issue&quot;, &quot;comment_url&quot; = &quot;review_url&quot;))
issues &lt;- dplyr::mutate(issues, is_review = !is.na(is_review))
</code></pre>
<p>Finally, the non elegant code below attributes a role to each user<br>
(commenter is its more precise version that differentiates reviewer 1<br>
from reviewer 2). I could have used <code>dplyr</code> <code>case_when</code>.</p>
<pre><code># non elegant code to guess role
issues &lt;- dplyr::group_by(issues, issue)
issues &lt;- dplyr::arrange(issues, created_at)
issues &lt;- dplyr::mutate(issues, author = user[1])
issues &lt;- dplyr::mutate(issues, package = unique(package[!is.na(package)]))
issues &lt;- dplyr::mutate(issues, assignee = assignee[1])
issues &lt;- dplyr::mutate(issues, reviewer1 = ifelse(!is.na(user[is_review][1]), user[is_review][1], &quot;&quot;))
issues &lt;- dplyr::mutate(issues, reviewer2 = ifelse(!is.na(user[is_review][2]), user[is_review][2], &quot;&quot;))
issues &lt;- dplyr::mutate(issues, reviewer3 = ifelse(!is.na(user[is_review][3]), user[is_review][3], &quot;&quot;))
issues &lt;- dplyr::ungroup(issues)
issues &lt;- dplyr::group_by(issues, issue, created_at, user)
# regexp because in at least 1 case assignee = 2 names glued together
issues &lt;- dplyr::mutate(issues, commenter = ifelse(stringr::str_detect(assignee, user), &quot;editor&quot;, &quot;other&quot;))
issues &lt;- dplyr::mutate(issues, commenter = ifelse(user == author, &quot;author&quot;, commenter))
issues &lt;- dplyr::mutate(issues, commenter = ifelse(user == reviewer1, &quot;reviewer1&quot;, commenter))
issues &lt;- dplyr::mutate(issues, commenter = ifelse(user == reviewer2, &quot;reviewer2&quot;, commenter))
issues &lt;- dplyr::mutate(issues, commenter = ifelse(user == reviewer3, &quot;reviewer3&quot;, commenter))
issues &lt;- dplyr::mutate(issues, commenter = ifelse(user == &quot;stefaniebutland&quot;, &quot;community_manager&quot;, commenter))
issues &lt;- dplyr::ungroup(issues)
issues &lt;- dplyr::mutate(issues, role = commenter,
                        role = ifelse(stringr::str_detect(role, &quot;reviewer&quot;),
                                      &quot;reviewer&quot;, role))

issues &lt;- dplyr::select(issues, - author, - reviewer1, - reviewer2, - reviewer3, - assignee,
                        - author_association, - comment_url)
readr::write_csv(issues, &quot;data/clean_data.csv&quot;)
</code></pre>
<p>The role “other” corresponds to anyone chiming in, while the community<br>
manager role is planning blog posts with the package author. We indeed<br>
have a <a rel="nofollow" href="https://ropensci.org/tags/review/" target="_blank">series of guest blog posts from package<br>
authors</a> that illustrate the review<br>
process as well as their onboarded packages.</p>
<p>Here is the final table. I unselect “body” because formatting in the<br>
text could break the output here, but I do have the text corresponding<br>
to each comment.</p>
<pre><code>issues %>%
  dplyr::select(- body) %>%
  head() %>%
  knitr::kable()
</code></pre>
<table>
<thead>
<tr>
<th align="left">title</th>
<th align="left">created_at</th>
<th align="left">closed_at</th>
<th align="left">user</th>
<th align="right">issue</th>
<th align="left">package</th>
<th align="left">is_review</th>
<th align="left">commenter</th>
<th align="left">role</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-03-31 00:25:14</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">richfitz</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">author</td>
<td align="left">author</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-04-01 17:30:51</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">sckott</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">editor</td>
<td align="left">editor</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-04-01 17:36:03</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">karthik</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">other</td>
<td align="left">other</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-04-02 03:36:09</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">jeroen</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">reviewer2</td>
<td align="left">reviewer</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-04-02 03:50:43</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">gaborcsardi</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">other</td>
<td align="left">other</td>
</tr>
<tr>
<td align="left">rrlite</td>
<td align="left">2015-04-02 03:53:57</td>
<td align="left">2015-04-13 23:26:38</td>
<td align="left">richfitz</td>
<td align="right">6</td>
<td align="left">rrlite</td>
<td align="left">FALSE</td>
<td align="left">author</td>
<td align="left">author</td>
</tr>
</tbody>
</table>
<p>There are 2521 comments, corresponding to 70 onboarded packages.</p>
<h3>Submitted repositories: down to a few metrics</h3>
<p>As mentioned earlier, onboarded packages are most often developped on<br>
GitHub. After onboarding they live in the <a rel="nofollow" href="https://github.com/ropensci/" target="_blank">ropensci GitHub<br>
organization</a>, previously some of them<br>
were onboarded into <a rel="nofollow" href="https://github.com/ropenscilabs/" target="_blank">ropenscilabs</a> but<br>
they should all be transferred soon. In any case, their being on GitHub<br>
means it’s possible to get their history to have a glimpse at work<br>
represented by onboarding!</p>
<h4>Getting all onboarded repositories</h4>
<p>Using rOpenSci <a rel="nofollow" href="https://github.com/ropensci/git2r" target="_blank"><code>git2r</code> package</a> I<br>
cloned all onboarded repositories in a “repos” folder. Since I didn’t<br>
know which package was in ropensci or ropenscilabs, I tried both.</p>
<pre><code>airtable &lt;- airtabler::airtable(&quot;appZIB8hgtvjoV99D&quot;, &quot;Reviews&quot;)
airtable &lt;- airtable$Reviews$select_all()

safe_clone &lt;- purrr::safely(git2r::clone)

# github link either ropensci or ropenscilabs
clone_repo &lt;- function(package_name){
  print(package_name)
  url &lt;- paste0(&quot;https://github.com/ropensci/&quot;, package_name, &quot;.git&quot;)
  local_path &lt;- paste0(getwd(), &quot;/repos/&quot;, package_name)
  clone_from_ropensci &lt;- safe_clone(url = url, local_path = local_path,
                                    progress = FALSE)
  if(is.null(clone_from_ropensci$result)){
    url &lt;- paste0(&quot;https://github.com/ropenscilabs/&quot;, package_name, &quot;.git&quot;)
    clone_from_ropenscilabs &lt;- safe_clone(url = url, local_path = local_path,
                                          progress = FALSE)
    if(is.null(clone_from_ropenscilabs$result)){
      message(&quot;OUILLE&quot;)
    }
  }
  
}

pkgs &lt;- unique(airtable$package)
pkgs &lt;- pkgs[!pkgs %in% fs::dir_ls()]
pkgs &lt;- pkgs[pkgs != &quot;rrricanes&quot;]
purrr::walk(pkgs, clone_repo)
</code></pre>
<p>I didn’t clone “rrricanes” because it was too big!</p>
<h4>Getting commits reports</h4>
<p>I then got the commit logs of each repo for various reasons:</p>
<ul>
<li>
<p>commits themselves show how much code and documentation editing was<br>
done during review</p>
</li>
<li>
<p>I wanted to be able to <code>git reset hard</code> the repo at its state at<br>
submission, for which I needed the commit logs.</p>
</li>
</ul>
<p>I used the <a rel="nofollow" href="https://github.com/lorenzwalthert/gitsum/issues" target="_blank"><code>gitsum</code><br>
package</a> to get commit<br>
logs because its dedicated high-level functions made it easier than with<br>
<code>git2r</code>.</p>
<pre><code>library(&quot;magrittr&quot;)

get_report &lt;- function(package_name){
  message(package_name)
  local_path &lt;- paste0(getwd(), &quot;/repos/&quot;, package_name)
  if(length(fs::dir_ls(local_path)) != 0){
    gitsum::init_gitsum(local_path, over_write = TRUE)
    report &lt;- gitsum::parse_log_detailed(local_path)
    report &lt;- dplyr::select(report, - nested)
    report$package &lt;- package_name
    if(!&quot;datetime&quot; %in% names(report)){
      report &lt;-  dplyr::mutate(report,
                               hour = as.numeric(stringr::str_sub(timezone, 1, 3)),
                               minute = as.numeric(stringr::str_sub(timezone, 4, 5)),
                               datetime = date + lubridate::hours(-1 * hour) + lubridate::minutes(-1 * minute))
     report &lt;- dplyr::select(report, - hour, - minute, - timezone)
      
    }
    report &lt;- dplyr::select(report, - date)
    return(report)
  }else{
    return(NULL)
  }
}

packages &lt;- fs::dir_ls(&quot;repos&quot;) 
packages &lt;- stringr::str_replace_all(packages, &quot;repos\\/&quot;, &quot;&quot;)
purrr::map_df(packages, get_report) %>%
   readr::write_csv(&quot;output/gitsum_reports.csv&quot;)
</code></pre>
<h4>Getting repositories as at submission</h4>
<p>Crossing information from the issue threads and from commit logs, I<br>
could find the latest commit before submission and create a copy of each<br>
repo before resetting it at this state. This is the closest to a<br>
<a rel="nofollow" href="http://harrypotter.wikia.com/wiki/Time-Turner" target="_blank">Time-Turner</a> that I<br>
have!</p>
<pre><code>library(&quot;magrittr&quot;)
# get issues opening datetime
issues &lt;- readr::read_csv(&quot;data/clean_data.csv&quot;)
issues &lt;- dplyr::group_by(issues, package)
issues &lt;- dplyr::summarise(issues, opened = min(created_at))

# now for each package keep only commits before that
commits &lt;- readr::read_csv(&quot;output/gitsum_reports.csv&quot;)
commits &lt;- dplyr::left_join(commits, issues, by = &quot;package&quot;)
commits &lt;- dplyr::group_by(commits, package)
commits &lt;- dplyr::filter(commits, datetime &lt;= opened)
# and from them keep the latest one, 
# that's the latest commit before submission!
commits &lt;- dplyr::filter(commits, datetime == max(datetime), !is_merge)
commits &lt;- dplyr::summarize(commits, hash = hash[1])

# small helper function
get_sha &lt;- function(commit){
  commit@sha
}

set_archive &lt;- function(package_name, commit){
  message(package_name)
  # copy the entire repo to another location
  local_path &lt;- paste0(getwd(), &quot;/repos/&quot;, package_name)
  local_path_archive &lt;- paste0(getwd(), &quot;/repos_at_submission/&quot;, package_name)
  fs::dir_copy(local_path, local_path_archive)
  
  # get all commits -- it's fast which is why I don't use gitsum report here
  commits &lt;- git2r::commits(git2r::repository(local_path_archive))
  
  # get their sha
  sha &lt;- purrr::map_chr(commits, get_sha)
  
  # all of this to extract the commit with the sha of the latest commit before submission
  # in other words the latest commit before submission
  commit &lt;- commits[sha == commit][[1]]
  
  # do a hard reset at that commit
  git2r::reset(commit, reset_type = &quot;hard&quot;)
}

purrr::walk2(commits$package, commits$hash, set_archive)
</code></pre>
<h3>Outlook: getting even more data? Or analyzing this dataset</h3>
<p>There’s more data to be collected or prepared! From GitHub issues,<br>
<a rel="nofollow" href="http://www.masalmon.eu/2017/12/21/wherehaveyoubeen/" target="_blank">using GitHub<br>
archive</a> one could<br>
get the labelling history: when did an issue go from “editor-checks” to<br>
“seeking-reviewers” for instance? It’d help characterize the usual speed<br>
of the process. One could also try to investigate the formal and less<br>
formal links between the onboarded repository and the review: did<br>
commits and issues mention the onboarding review (with words), or even<br>
actually put a link to it? Are actors in the process little or very<br>
active on GitHub for other activities, e.g. could we see that some<br>
reviewers create or revive their GitHub account especially for<br>
reviewing?</p>
<p>Rather than enlarging my current dataset, I’ll present its analysis in<br>
two further blog posts answering the questions “How much work is<br>
rOpenSci onboarding?” and “How to characterize the social weather of<br>
rOpenSci onboarding?”. In case you’re too impatient, in the meantime you<br>
can dive into this blog post by Augustina Ragwitz about <a rel="nofollow" href="https://developer.ibm.com/code/2018/03/01/countering-bean-counting-open-source-influence-beyond-commits/" target="_blank">measuring<br>
open-source influence beyond<br>
commits</a><br>
and this one by rOpenSci co-founder Scott Chamberlain about <a rel="nofollow" href="https://recology.info/2018/02/git-commits/" target="_blank">exploring<br>
git commits with <code>git2r</code></a>.</p>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://ropensci.org/blog/2018/04/26/rectangling-onboarding/"> rOpenSci - open tools for open science</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/1t2z3o-AJv8" height="1"></a></div>