R/issues.R

Defines functions browse_issue view_issue view_issues update_issue create_issue

Documented in browse_issue create_issue update_issue view_issue view_issues

#  FUNCTION: create_issue ------------------------------------------------------
#
#' Create an issue in a repository
#'
#' This function creates a new issue for the specified repository in GitHub. It
#' can also be used to assign the issue to a user and add labels or a milestone.
#'
#' For more details see the GitHub API documentation:
#'
#' - <https://docs.github.com/en/rest/reference/issues#create-an-issue>
#'
#' @param title (string) The title of the issue.
#' @param repo (string) The repository specified in the format: `owner/repo`.
#' @param body (string, optional) The contents of the issue.
#' @param assignees (character, optional) Logins for Users to assign to this
#'   issue. NOTE: Only users with push access can set assignees for new issues.
#' @param labels (character, optional) Labels to associate with this issue.
#'   NOTE: Only users with push access can set labels for new issues.
#' @param milestone (character or integer, optional) The title or number of the
#'   milestone to associate this issue with. NOTE: Only users with push access
#'   can set the milestone for new issues.
#' @param ... Parameters passed to [gh_request()].
#'
#' @return `create_issue()` returns a list of the issue's properties.
#'
#' **Issue Properties:**
#'
#' - **number**: The number assigned to the issue.
#' - **title**: The title of the issue.
#' - **body**: The body contents of the issue.
#' - **assignees**: The users assigned to the issue.
#' - **labels**: The labels attached to the issue.
#' - **milestone**: The milestone assigned to the issue.
#' - **state**: The state of the issue - either `"open"` or `"closed"`.
#' - **repository**: The repository the issue is in.
#' - **pull_request**: Whether the issue is a pull request.
#' - **html_url**: The URL of the issue's web page in GitHub.
#' - **creator**: The creator's login.
#' - **created_at**: When the issue was created.
#' - **updated_at**: When the issue was last updated.
#' - **closed_at**: When the issue was closed.
#'
#' @examples
#' \dontrun{
#'
#'   create_issue(
#'     title     = "user issue",
#'     repo      = "ChadGoymer/test-issues",
#'     body      = "This is an issue to test create_issue()",
#'     assignees = "ChadGoymer",
#'     labels    = "feature",
#'     milestone = "release-1.0"
#'   )
#'
#'   create_issue(
#'     title     = "organization issue",
#'     repo      = "HairyCoos/test-issues",
#'     body      = "This is an issue to test create_issue()",
#'     assignees = "ChadGoymer"
#'   )
#'
#' }
#'
#' @export
#'
create_issue <- function(
  title,
  repo,
  body,
  assignees,
  labels,
  milestone,
  ...
) {
  assert_character(title, n = 1)
  assert_repo(repo)

  payload <- list(title = title)

  if (!is_missing_or_null(body)) {
    assert_character(body, n = 1)
    payload$body <- body
  }

  if (!is_missing_or_null(assignees)) {
    assert_character(assignees)
    payload$assignees <- as.list(assignees)
  }

  if (!is_missing_or_null(labels)) {
    assert_character(labels)
    payload$labels <- as.list(labels)
  }

  if (!is_missing_or_null(milestone)) {
    if (is_character(milestone, n = 1)) {
      milestone <- view_milestone(milestone, repo = repo, ...)$number
    }

    assert_natural(milestone, n = 1)
    payload$milestone <- milestone
  }

  info("Creating issue '", title, "' for repository '", repo, "'")
  issue_lst <- gh_url("repos", repo, "issues") %>%
    gh_request("POST", payload = payload, ...)

  info("Transforming results", level = 4)
  issue_gh <- select_properties(issue_lst, properties$issue) %>%
    modify_list(
      assignees = map_chr(issue_lst$assignees, "login"),
      labels    = map_chr(issue_lst$labels, "name"),
      .before   = "milestone"
    ) %>%
    modify_list(
      pull_request = !is_null(issue_lst$pull_request),
      .before      = "creator"
    ) %>%
    modify_list(repository = repo)

  info("Done", level = 7)
  issue_gh
}


#  FUNCTION: update_issue ------------------------------------------------------
#
#' Update an issue in a repository
#'
#' This function updates an issue for the specified repository in GitHub. It can
#' be used to change the title, body, or assignees, it can also be used to
#' replace labels and milestones, or to close the issue.
#'
#' For more details see the GitHub API documentation:
#'
#' - <https://docs.github.com/en/rest/reference/issues#update-an-issue>
#'
#' @param issue (string or character) The number or title of the issue.
#' @param repo (string) The repository specified in the format: `owner/repo`.
#' @param title (string, optional) The title of the issue.
#' @param body (string, optional) The contents of the issue.
#' @param assignees (character, optional) Logins for Users to assign to this
#'   issue. NOTE: Only users with push access can set assignees for new issues.
#'   Setting to `NULL` clears all assignees.
#' @param labels (character, optional) Labels to associate with this issue.
#'   NOTE: Only users with push access can set labels for new issues. Setting to
#'   `NULL` clears all labels.
#' @param milestone (character or integer, optional) The title or number of the
#'   milestone to associate this issue with. NOTE: Only users with push access
#'   can set the milestone for new issues. Setting to `NULL` clears the current
#'   milestone.
#' @param state (string, optional) The state of the issue. Either `"open"` or
#'   `"closed"`.
#' @param ... Parameters passed to [gh_request()].
#'
#' @return `update_issue()` returns a list of the issue's properties.
#'
#' **Issue Properties:**
#'
#' - **number**: The number assigned to the issue.
#' - **title**: The title of the issue.
#' - **body**: The body contents of the issue.
#' - **assignees**: The users assigned to the issue.
#' - **labels**: The labels attached to the issue.
#' - **milestone**: The milestone assigned to the issue.
#' - **state**: The state of the issue - either `"open"` or `"closed"`.
#' - **repository**: The repository the issue is in.
#' - **pull_request**: Whether the issue is a pull request.
#' - **html_url**: The URL of the issue's web page in GitHub.
#' - **creator**: The creator's login.
#' - **created_at**: When the issue was created.
#' - **updated_at**: When the issue was last updated.
#' - **closed_at**: When the issue was closed.
#'
#' @examples
#' \dontrun{
#'
#'   # Update the properties of a issue
#'   update_issue(
#'     issue   = "test issue",
#'     repo        = "ChadGoymer/githapi",
#'     title       = "updated test issue",
#'     description = "This is an updated test issue",
#'     due_on      = "2020-12-01"
#'   )
#'
#'   # Close a issue
#'   update_issue(
#'     issue = "updated test issue",
#'     repo      = "ChadGoymer/githapi",
#'     state     = "closed"
#'   )
#'
#' }
#'
#' @export
#'
update_issue <- function(
  issue,
  repo,
  title,
  body,
  assignees,
  labels,
  milestone,
  state,
  ...
) {
  assert_repo(repo)

  payload <- list()

  if (!is_missing_or_null(title)) {
    assert_character(title, n = 1)
    payload$title <- title
  }

  if (!is_missing_or_null(body)) {
    assert_character(body, n = 1)
    payload$body <- body
  }

  if (!missing(assignees)) {
    is_null(assignees) || assert_character(assignees)
    payload$assignees <- as.list(assignees)
  }

  if (!missing(labels)) {
    is_null(labels) || assert_character(labels)
    payload$labels <- as.list(labels)
  }

  if (!missing(milestone)) {
    if (is_character(milestone, n = 1)) {
      milestone <- view_milestone(milestone, repo = repo, ...)$number
    }
    is_null(milestone) || is_natural(milestone, n = 1)
    payload <- c(payload, list(milestone = milestone))
  }

  if (!is_missing_or_null(state)) {
    assert_character(state, n = 1)
    assert_in(state, values$issue$state)
    payload$state <- state
  }

  issue <- view_issue(issue, repo = repo, ...)

  info("Updating issue '", issue$title, "' in repository '", repo, "'")
  issue_lst <- gh_url("repos", repo, "issues", issue$number) %>%
    gh_request("PATCH", payload = payload, ...)

  info("Transforming results", level = 4)
  issue_gh <- select_properties(issue_lst, properties$issue) %>%
    modify_list(
      assignees = map_chr(issue_lst$assignees, "login"),
      labels    = map_chr(issue_lst$labels, "name"),
      .before   = "milestone"
    ) %>%
    modify_list(
      pull_request = !is_null(issue_lst$pull_request),
      .before      = "creator"
    ) %>%
    modify_list(repository = repo)

  info("Done", level = 7)
  issue_gh
}


#  FUNCTION: view_issues -------------------------------------------------------
#
#' View issues within a repository or organization
#'
#' `view_issues()` summarises issues in a table with the properties as columns
#' and a row for each issue in the repository or organization. `view_issue()`
#' returns a list of all properties for a single issue. `browse_issue()` opens
#' the web page for the issue in the default browser.
#'
#' You can summarise all the issues in a repository or organization by
#' specifying the arguments. If neither are specified then all the issues
#' assigned to the authenticated user are returned. You can filter the issues
#' based on the labels, milestone, whether they have been updated since a
#' specified date or whether they are `"open"` or `"closed"`. Finally, the order
#' the results are returned can be controlled with `sort` and `direction`.
#'
#' For more details see the GitHub API documentation:
#'
#' - <https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user>
#' - <https://docs.github.com/en/rest/reference/issues#list-organization-issues-assigned-to-the-authenticated-user>
#' - <https://docs.github.com/en/rest/reference/issues#list-repository-issues>
#' - <https://docs.github.com/en/rest/reference/issues#get-an-issue>
#'
#' @param issue (string or character) The number or title of the issue.
#' @param repo (string, optional) The repository specified in the format:
#'   `owner/repo`.
#' @param org (string, optional) The name of the organization.
#' @param labels (character, optional) Label names to filter by.
#' @param milestone (string or integer) Milestone number or title to filter by.
#' @param since (string, optional) A date & time to filter by. Must be in the
#'   format: `YYYY-MM-DD HH:MM:SS`.
#' @param state (string, optional) The state of the issues to return. Can be
#'   either `"open"`, `"closed"`, or `"all"`. Default: `"open"`.
#' @param sort (string, optional) The property to order the returned issues by.
#'   Can be either `"created"`, `"updated"`, or `"comments"`. Default:
#'   `"created"`.
#' @param direction (string, optional) The direction of the sort. Can be either
#'   `"asc"` or `"desc"`. Default: `"desc"`.
#' @param n_max (integer, optional) Maximum number to return. Default: `1000`.
#' @param ... Parameters passed to [gh_page()] or [gh_request()].
#'
#' @return `view_issues()` returns a tibble of issue properties. `view_issue()`
#'   returns a list of properties for a single issue. `browse_issue()` opens the
#'   default browser on the issue's page and returns the URL.
#'
#' **Issue Properties:**
#'
#' - **number**: The number assigned to the issue.
#' - **title**: The title of the issue.
#' - **body**: The body contents of the issue.
#' - **assignees**: The users assigned to the issue.
#' - **labels**: The labels attached to the issue.
#' - **milestone**: The milestone assigned to the issue.
#' - **state**: The state of the issue - either `"open"` or `"closed"`.
#' - **repository**: The repository the issue is in.
#' - **pull_request**: Whether the issue is a pull request.
#' - **html_url**: The URL of the issue's web page in GitHub.
#' - **creator**: The creator's login.
#' - **created_at**: When the issue was created.
#' - **updated_at**: When the issue was last updated.
#' - **closed_at**: When the issue was closed.
#'
#' @examples
#' \dontrun{
#'
#'   # View open issues in a repository
#'   view_issues("ChadGoymer/githapi")
#'
#'   # View closed issues in a repository
#'   view_issues("ChadGoymer/githapi", state = "closed")
#'
#'   # View a single issue
#'   view_issue("test issue", "ChadGoymer/githapi")
#'
#'   # Open a issue's page in a browser
#'   browse_issue("test issue", "ChadGoymer/githapi")
#'
#' }
#'
#' @export
#'
view_issues <- function(
  repo,
  org,
  labels,
  milestone,
  since,
  state     = "open",
  sort      = "created",
  direction = "desc",
  n_max     = 1000,
  ...
) {
  assert_character(state, n = 1)
  assert_in(state, values$issue$state)
  assert_character(sort, n = 1)
  assert_in(sort, values$issue$sort)
  assert_character(direction, n = 1)
  assert_in(direction, values$issue$direction)

  if (!is_missing_or_null(since)) {
    assert_character(since, n = 1)
    since <- as.POSIXct(since, format = "%Y-%m-%d %H:%M:%S") %>%
      format("%Y-%m-%dT%H:%M:%SZ", tz = "UTC")
    assert(
      !is.na(since),
      "'since' must be specified in the format 'YYYY-MM-DD hh:mm:ss'"
    )
  } else {
    since <- NULL
  }

  if (!is_missing_or_null(labels)) {
    assert_character(labels)
    labels <- str_c(labels, collapse = ",")
  } else {
    labels <- NULL
  }

  if (!is_missing_or_null(repo)) {
    assert_repo(repo)

    if (!is_missing_or_null(milestone)) {
      if (is_character(milestone, n = 1)) {
        milestone <- view_milestone(
          milestone = milestone,
          repo      = repo,
          ...
        ) %>%
          pluck("number")
      }
      assert_natural(milestone, n = 1)
    } else {
      milestone <- NULL
    }

    info("Viewing issues for repository '", repo, "'")
    url <- gh_url(
      "repos", repo, "issues",
      labels    = labels,
      milestone = milestone,
      state     = state,
      sort      = sort,
      direction = direction,
      since     = since
    )
  } else if (!is_missing_or_null(org)) {
    assert_character(org, n = 1)

    info("Viewing issues for organization '", org, "'")
    url <- gh_url(
      "orgs", org, "issues",
      labels    = labels,
      state     = state,
      sort      = sort,
      direction = direction,
      since     = since
    )
  } else {
    info("Viewing issues assigned to the authenticated user")
    url <- gh_url(
      "issues",
      labels    = labels,
      state     = state,
      sort      = sort,
      direction = direction,
      since     = since
    )
  }

  issues_lst <- gh_page(url = url, n_max = n_max, ...)

  info("Transforming results", level = 4)
  issues_gh <- bind_properties(issues_lst, properties$issue) %>%
    add_column(
      labels  = map(issues_lst, ~ map_chr(.$labels, "name")),
      .before = "milestone"
    ) %>%
    add_column(
      assignees = map(issues_lst, ~ map_chr(.$assignees, "login")),
      .before   = "labels"
    ) %>%
    add_column(
      pull_request = map_lgl(issues_lst, ~ !is_null(.$pull_request)),
      .before      = "creator"
    )

  info("Done", level = 7)
  issues_gh
}


#  FUNCTION: view_issue --------------------------------------------------------
#
#' @rdname view_issues
#' @export
#'
view_issue <- function(
  issue,
  repo,
  ...
) {
  assert_repo(repo)

  if (is_natural(issue, n = 1)) {
    info("Viewing issue '", issue, "' for repository '", repo, "'")
    issue_lst <- gh_url("repos", repo, "issues", issue) %>%
      gh_request("GET", ...)
  } else if (is_character(issue, n = 1)) {
    info("Viewing issue '", issue, "' for repository '", repo, "'")
    issue_lst <- gh_url("repos", repo, "issues", state = "all") %>%
      gh_find(property  = "title", value = issue, ...)
  } else {
    error("'issue' must be an integer or character vector of length 1")
  }

  info("Transforming results", level = 4)
  issue_gh <- select_properties(issue_lst, properties$issue) %>%
    modify_list(
      assignees = map_chr(issue_lst$assignees, "login"),
      labels    = map_chr(issue_lst$labels, "name"),
      .before   = "milestone"
    ) %>%
    modify_list(
      pull_request = !is_null(issue_lst$pull_request),
      .before      = "creator"
    ) %>%
    modify_list(repository = repo)

  info("Done", level = 7)
  issue_gh
}


#  FUNCTION: browse_issue ------------------------------------------------------
#
#' @rdname view_issues
#' @export
#'
browse_issue <- function(
  issue,
  repo,
  ...
) {
  issue <- view_issue(issue = issue, repo = repo, ...)

  info("Browsing issue '", issue$title, "' in repository '", repo, "'")
  httr::BROWSE(issue$html_url)

  info("Done", level = 7)
  structure(
    issue$html_url,
    class   = c("github", "character"),
    url     = attr(issue, "url"),
    request = attr(issue, "request"),
    status  = attr(issue, "status"),
    header  = attr(issue, "header")
  )
}
ChadGoymer/githapi documentation built on Oct. 22, 2021, 10:56 a.m.