R/gh_issues.R

Defines functions gh_label_events gh_issue_labels gh_issue_fmt gh_issue_fetch gh_issue_post

Documented in gh_issue_fetch gh_issue_fmt gh_issue_labels

gh_issue_post <- function(title, body, labels, owner, repo, avoid_dups = TRUE,
                          dry_run = FALSE, open_browser = TRUE) {

  if(missing(title)) stop("Require a title for this issue", call. = FALSE)
  if(missing(body)) stop("Require a body for this issue", call. = FALSE)

  if(avoid_dups) {
    current <- gh_issue_fetch(state = "all") |>
      gh_issue_fmt(which = c("title", "body", "labels")) |>
      tidyr::unnest("labels")

    if(title %in% current$title) {

      if(any(labels[!labels %in% c("draft", "needs-review")] %in% current$labels[current$title == title])) {
        message("Skipping duplicate - ", title,
                " (labels: ", paste0(labels, collapse = ", "), ")",
                dplyr::if_else(dry_run, " [DRY-RUN]", ""))
        return()
      }
    }
  }

  message("Posting issue - ", title, " (labels: ", paste0(labels, collapse = ", "), ")",
          dplyr::if_else(dry_run, " [DRY-RUN]", ""))

  if(!dry_run) {
    r <- gh_cache("POST /repos/{owner}/{repo}/issues",
                  title = title, body = body, labels = as.list(labels),
                  owner = owner, repo = repo)

    if(open_browser) utils::browseURL(r$html_url)
  } else {
    message(paste0("title: ", title, "\n",
                   "labels: ", paste0(labels, collapse = ", "), "\n",
                   "body: \n\n", body))
  }
}

#' Fetch issues from a GH repository
#'
#' @param state Character. Which issues to fetch: "open", "closed", "all"
#' @param labels Character vector. Fetch only issues with these labels. (Optional)
#' @param since Character/Date/datetime. Fetch only issues since this date/time. (Optional)
#' @param owner Character. Owner of the repository
#' @param repo Character. Name of the repository (name of the package)
#' @param issue Numeric. Specific Issue number to fetch.
#' @param verbose Logical. Show progress messages.
#'
#' @return List of issues
#' @export
#'
#' @examples
#' i <- gh_issue_fetch()

gh_issue_fetch <- function(state = "open", labels = NULL, since = NULL,
                           owner = "rosadmin", repo = "scheduled_socials",
                           issue = NULL, verbose = FALSE) {
  if(verbose) message("owner: ", owner, "; repo: ", repo)

  if(!is.null(since)) since <- format(lubridate::as_datetime(since), "%Y-%m-%dT%H:%M:%SZ")

  r <- "/repos/{owner}/{repo}/issues"
  if(!is.null(issue) && is.numeric(issue)) r <- "repos/{owner}/{repo}/issues/{issue}"

  gh_cache(r,
           state = state, labels = labels,
           since = since, issue = issue,
           sort = "created", direction = "desc",
           owner = owner, repo = repo, .limit = Inf)
}


#' Format issues list from GH
#'
#' @param i List of issues from `gh_issue_fetch()`
#' @param which Which fields to includ
#'
#' @return Issues formated as a data frame
#' @export
#'
#' @examples
#' i <- gh_issue_fetch()
#' i <- gh_issue_fmt(i, which = "title")
gh_issue_fmt <- function(i, which = c("title", "number", "body", "labels", "url",
                                  "created", "updated", "gh_user_issue")) {

  df <- dplyr::tibble(url = purrr::map_chr(i, "url"),
                      number = purrr::map_dbl(i, "number"),
                      labels = purrr::map(i, "labels"),
                      title = purrr::map_chr(i, "title"),
                      created = purrr::map_chr(i, "created_at"),
                      updated = purrr::map_chr(i, "updated_at"),
                      body = purrr::map_chr(i, "body", .default = ""),
                      gh_user_issue = purrr::map_chr(i, \(x) x[["user"]]$login)) |>
    dplyr::select(dplyr::any_of(which))


  if("labels" %in% which) {
    df <- dplyr::mutate(df, labels = purrr::map_depth(.data$labels, 2, "name"),
                        n_labels = purrr::map_int(.data$labels, length))
  }

  df
}



#' Extract information on help-wanted labels for issues
#'
#' @param i Data frame of issues from `gh_issue_fmt()`
#' @param labels_help Character. Regular expression to match help-wanted
#'   labels.
#' @param labels_first Character. Regular expression to meatch good-first-issue
#'   labels.
#'
#' @return data frame with added label details
#' @export
#'
#' @examples
#' i <- gh_issue_fetch(owner = "ropensci", repo = "weathercan")
#' i <- gh_issue_fmt(i)
#' i <- gh_issue_labels(i)

gh_issue_labels <- function(
    i,
    labels_help = "(help)|(help wanted)|(help-wanted)|(help_wanted)",
    labels_first = "(good first issue)|(beginner)|(good-first-issue)") {

  i |>
    dplyr::mutate(
      info = stringr::str_remove_all(.data$url, "(https://api.github.com/repos/)|(/issues/\\d+)"),
      owner = stringr::str_extract(.data$info, "^[^/]*"),
      repo = stringr::str_extract(.data$info, "[^/]*$"),
      labels_help = purrr::map_lgl(
        .data$labels,
        \(x) any(stringr::str_detect(tolower(x), .env$labels_help))),
      labels_first = purrr::map_lgl(
        .data$labels,
        \(x) any(stringr::str_detect(tolower(x), .env$labels_first)))) |>
    dplyr::filter(.data$labels_help) |>
    dplyr::mutate(
      events = purrr::pmap(
        list(.data$owner, .data$repo, .data$number),
        \(x, y, z) gh_label_events(x, y, z, labels = .env$labels_help))) |>
    tidyr::unnest("events", keep_empty = TRUE)

}



gh_label_events <- function(owner, repo, issue, labels) {
  events <- gh_cache(endpoint = "/repos/{owner}/{repo}/issues/{issue}/events",
                     owner = owner, repo = repo, issue = issue)

  e <- dplyr::tibble(event = purrr::map_dbl(events, "id"),
                     type = purrr::map_chr(events, "event"),
                     label = purrr::map(events, "label"),
                     gh_user = purrr::map_chr(events, ~.[["actor"]]$login),
                     label_created = purrr::map_chr(events, "created_at")) |>
    dplyr::filter(.data$type == "labeled") |>
    tidyr::unnest("label")

  # Get only the events reflecting a specific 'label' pattern
  if(nrow(e) > 0) {
   e <- e |>
      tidyr::unnest(.data$label) |>
      dplyr::filter(stringr::str_detect(.data$label, .env$labels)) |>
      dplyr::mutate(label_created = lubridate::ymd_hms(.data$label_created)) |>
      dplyr::arrange(dplyr::desc(.data$label_created)) |>
      dplyr::slice(1) |>
      dplyr::select("gh_user_labelled" = "gh_user", "label_created")
  } else e <- data.frame()

  e
}
ropensci-org/promoutils documentation built on Nov. 29, 2024, 7:17 p.m.