diff --git a/NAMESPACE b/NAMESPACE index 340e5a15..f7d575a7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,8 @@ S3method(api_build,op_head) S3method(as.data.frame,connect_list_hits) S3method(as.data.frame,connect_list_integrations) S3method(as.data.frame,tbl_connect) +S3method(as_integration,default) +S3method(as_integration,list) S3method(as_tibble,connect_list_hits) S3method(as_tibble,connect_list_integrations) S3method(connect_vars,op_base) @@ -16,6 +18,7 @@ S3method(connect_vars,tbl_connect) S3method(dim,tbl_connect) S3method(dimnames,tbl_connect) S3method(head,tbl_connect) +S3method(print,connect_integration) S3method(print,connect_tag_tree) S3method(print,tbl_connect) export("%>%") @@ -50,6 +53,7 @@ export(content_list_guid_has_access) export(content_list_with_permissions) export(content_render) export(content_restart) +export(content_set_integrations) export(content_title) export(content_update) export(content_update_access_type) @@ -86,6 +90,7 @@ export(get_group_members) export(get_group_permission) export(get_groups) export(get_image) +export(get_integration) export(get_integrations) export(get_job) export(get_job_list) diff --git a/NEWS.md b/NEWS.md index 3857b0de..9222c718 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # connectapi (development version) +- New `content_set_integrations()` function to set the OAuth integration + associations for a content item. (#414) +- New `get_integration()` function to retrieve details of a specific OAuth integration + from a Connect server. (#431) + # connectapi 0.8.0 ## Breaking changes diff --git a/R/content.R b/R/content.R index c2dca338..e9278d90 100644 --- a/R/content.R +++ b/R/content.R @@ -1434,3 +1434,72 @@ get_content_packages <- function(content) { res <- content$packages() parse_connectapi_typed(res, connectapi_ptypes$content_packages) } + +# Integrations ---- + +#' Set all OAuth integration associations for a content item +#' +#' @description +#' Removes any existing OAuth integration associations for a content item, and +#' creates associations with the integrations provided. You must have +#' administrator or publisher privileges to perform this action. +#' +#' @param content A `Content` R6 object representing the content item to modify. +#' @param integrations A single `connect_integration` object or a list of +#' `connect_integration` objects to associate with this content. +#' +#' @return Invisibly returns `NULL`. A message is printed on success. +#' +#' @seealso +#' [get_integrations()], [get_integration()], [content_item()] +#' +#' @examples +#' \dontrun{ +#' client <- connect() +#' +#' content <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") +#' +#' integrations <- get_integrations(client) +#' +#' # Associate a single integration +#' github_integration <- purrr::keep(integrations, \(x) x$template == "github")[[1]] +#' content_set_integrations(content, github_integration) +#' +#' # Associate multiple integrations at once +#' selected_integrations <- integrations[1:2] +#' content_set_integrations(content, selected_integrations) +#' } +#' +#' @family oauth integration functions +#' @family content functions +#' @export +content_set_integrations <- function(content, integrations) { + validate_R6_class(content, "Content") + # Handle a single integration + if (inherits(integrations, "connect_integration")) { + integrations <- list(integrations) + } else if (!inherits(integrations, "list")) { + stop( + "`integrations` must be a `connect_integration` class object or a list ", + "of `connect_integration` objects." + ) + } + # Ensure that all the items we've been passed are integrations + if (!purrr::every(integrations, ~ inherits(.x, "connect_integration"))) { + stop("All items must be `connect_integration` objects") + } + + payload <- purrr::map(integrations, ~ list(oauth_integration_guid = .x$guid)) + + content$connect$PUT( + v1_url( + "content", + content$content$guid, + "oauth", + "integrations", + "associations" + ), + body = payload + ) + invisible(NULL) +} diff --git a/R/integrations.R b/R/integrations.R index b39122a0..ef8aa74e 100644 --- a/R/integrations.R +++ b/R/integrations.R @@ -20,7 +20,7 @@ #' * `template`: The template used to configure this OAuth integration. #' * `auth_type`: The authentication type indicates which OAuth flow is used by #' this integration. -#' * `config`: A sub-list list with the OAuth integration configuration. Fields +#' * `config`: A list with the OAuth integration configuration. Fields #' differ between integrations. #' #' Use [as.data.frame()] or [tibble::as_tibble()] to convert to a data frame with @@ -29,7 +29,8 @@ #' * `created_time` and `updated_time` are parsed to `POSIXct`. #' * `config` remains as a list-column. #' -#' @seealso [get_oauth_credentials()], [get_oauth_content_credentials()] +#' @seealso [get_oauth_credentials()], [get_oauth_content_credentials()], +#' [get_integration()] #' #' @examples #' \dontrun{ @@ -56,15 +57,18 @@ #' integrations_df <- tibble::as_tibble(integrations) #' } #' +#' @family oauth integration functions #' @export get_integrations <- function(client) { + validate_R6_class(client, "Connect") error_if_less_than(client$version, "2024.12.0") integrations <- client$GET(v1_url("oauth", "integrations")) + integrations <- lapply(integrations, as_integration) class(integrations) <- c("connect_list_integrations", class(integrations)) integrations } -#' Convert integrations data to a data frame +#' Convert integrations list to a data frame #' #' @description #' Converts an list returned by [get_integrations()] into a data frame. @@ -91,7 +95,7 @@ as.data.frame.connect_list_integrations <- function( ) } -#' Convert integrations data to a tibble +#' Convert integrations list to a tibble #' #' @description #' Converts a list returned by [get_integrations()] to a tibble. @@ -104,3 +108,78 @@ as.data.frame.connect_list_integrations <- function( as_tibble.connect_list_integrations <- function(x, ...) { parse_connectapi_typed(x, connectapi_ptypes$integrations) } + +# Integration class ---- + +#' Convert objects to integration class +#' +#' @param x An object to convert to an integration +#' +#' @return An integration object +as_integration <- function(x) { + UseMethod("as_integration") +} + +#' @export +as_integration.default <- function(x) { + stop( + "Cannot convert object of class '", + class(x)[1], + "' to an integration" + ) +} + +#' @export +as_integration.list <- function(x) { + structure(x, class = c("connect_integration", "list")) +} + +#' @export +print.connect_integration <- function(x, ...) { + cat("Integration:", x$name, "\n") + cat("GUID:", x$guid, "\n") + cat("Template:", x$template, "\n") + invisible(x) +} + +#' Get the details of an OAuth integration +#' +#' @description +#' Given the GUID of an OAuth integration available on a Connect server, retrieve +#' its details. You must have administrator or publisher privileges to perform +#' this action. +#' +#' @param client A `Connect` R6 client object. +#' @param guid The GUID of an integration available on the Connect server. +#' +#' @return A `connect_integration` object representing an OAuth integration, +#' which has the following fields: +#' +#' * `id`: The internal identifier of this OAuth integration. +#' * `guid`: The GUID of this OAuth integration. +#' * `created_time`: The timestamp (RFC3339) indicating when this integration +#' was created. +#' * `updated_time`: The timestamp (RFC3339) indicating when this integration +#' was last updated +#' * `name`: A descriptive name to identify the OAuth integration. +#' * `description`: A brief text to describe the OAuth integration. +#' * `template`: The template used to configure this OAuth integration. +#' * `auth_type`: The authentication type indicates which OAuth flow is used by +#' this integration. +#' * `config`: A list with the OAuth integration configuration. Fields +#' differ between integrations. +#' +#' @seealso [get_oauth_credentials()], [get_oauth_content_credentials()], [get_integrations()] +#' +#' @examples +#' \dontrun{ +#' client <- connect() +#' x <- get_integration(client, guid) +#' } +#' +#' @family oauth integration functions +#' @export +get_integration <- function(client, guid) { + validate_R6_class(client, "Connect") + as_integration(client$GET(v1_url("oauth", "integrations", guid))) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index ae9f6d7d..38da9515 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -62,6 +62,16 @@ reference: contents: - matches("repo") + - title: "OAuth Integrations" + desc: > + Functions to work with OAuth integrations + contents: + - matches("integration") + - content_set_integrations + - get_integrations + - get_oauth_credentials + - get_oauth_content_credentials + - title: "Reporting" desc: > Helpers to "get" data out of Connect diff --git a/man/as.data.frame.connect_list_integrations.Rd b/man/as.data.frame.connect_list_integrations.Rd index 8f54ff5b..86258a78 100644 --- a/man/as.data.frame.connect_list_integrations.Rd +++ b/man/as.data.frame.connect_list_integrations.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/integrations.R \name{as.data.frame.connect_list_integrations} \alias{as.data.frame.connect_list_integrations} -\title{Convert integrations data to a data frame} +\title{Convert integrations list to a data frame} \usage{ \method{as.data.frame}{connect_list_integrations}(x, row.names = NULL, optional = FALSE, ...) } diff --git a/man/as_integration.Rd b/man/as_integration.Rd new file mode 100644 index 00000000..c042cebe --- /dev/null +++ b/man/as_integration.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/integrations.R +\name{as_integration} +\alias{as_integration} +\title{Convert objects to integration class} +\usage{ +as_integration(x) +} +\arguments{ +\item{x}{An object to convert to an integration} +} +\value{ +An integration object +} +\description{ +Convert objects to integration class +} diff --git a/man/as_tibble.connect_list_integrations.Rd b/man/as_tibble.connect_list_integrations.Rd index a060b497..3d3397bd 100644 --- a/man/as_tibble.connect_list_integrations.Rd +++ b/man/as_tibble.connect_list_integrations.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/integrations.R \name{as_tibble.connect_list_integrations} \alias{as_tibble.connect_list_integrations} -\title{Convert integrations data to a tibble} +\title{Convert integrations list to a tibble} \usage{ \method{as_tibble}{connect_list_integrations}(x, ...) } diff --git a/man/content_delete.Rd b/man/content_delete.Rd index 58a21cf4..4c9da7a5 100644 --- a/man/content_delete.Rd +++ b/man/content_delete.Rd @@ -21,6 +21,7 @@ logs, and resources about a content item. It \emph{cannot} be undone. \seealso{ Other content functions: \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/content_item.Rd b/man/content_item.Rd index aabc8aac..43f73cf8 100644 --- a/man/content_item.Rd +++ b/man/content_item.Rd @@ -27,6 +27,7 @@ connect() \%>\% \seealso{ Other content functions: \code{\link{content_delete}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/content_set_integrations.Rd b/man/content_set_integrations.Rd new file mode 100644 index 00000000..0ba6e622 --- /dev/null +++ b/man/content_set_integrations.Rd @@ -0,0 +1,79 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/content.R +\name{content_set_integrations} +\alias{content_set_integrations} +\title{Set all OAuth integration associations for a content item} +\usage{ +content_set_integrations(content, integrations) +} +\arguments{ +\item{content}{A \code{Content} R6 object representing the content item to modify.} + +\item{integrations}{A single \code{connect_integration} object or a list of +\code{connect_integration} objects to associate with this content.} +} +\value{ +Invisibly returns \code{NULL}. A message is printed on success. +} +\description{ +Removes any existing OAuth integration associations for a content item, and +creates associations with the integrations provided. You must have +administrator or publisher privileges to perform this action. +} +\examples{ +\dontrun{ +client <- connect() + +content <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") + +integrations <- get_integrations(client) + +# Associate a single integration +github_integration <- purrr::keep(integrations, \(x) x$template == "github")[[1]] +content_set_integrations(content, github_integration) + +# Associate multiple integrations at once +selected_integrations <- integrations[1:2] +content_set_integrations(content, selected_integrations) +} + +} +\seealso{ +\code{\link[=get_integrations]{get_integrations()}}, \code{\link[=get_integration]{get_integration()}}, \code{\link[=content_item]{content_item()}} + +Other oauth integration functions: +\code{\link{get_integration}()}, +\code{\link{get_integrations}()} + +Other content functions: +\code{\link{content_delete}()}, +\code{\link{content_item}()}, +\code{\link{content_title}()}, +\code{\link{content_update}()}, +\code{\link{create_random_name}()}, +\code{\link{dashboard_url}()}, +\code{\link{delete_thumbnail}()}, +\code{\link{delete_vanity_url}()}, +\code{\link{deploy_repo}()}, +\code{\link{get_bundles}()}, +\code{\link{get_environment}()}, +\code{\link{get_image}()}, +\code{\link{get_job}()}, +\code{\link{get_jobs}()}, +\code{\link{get_log}()}, +\code{\link{get_thumbnail}()}, +\code{\link{get_vanity_url}()}, +\code{\link{git}}, +\code{\link{has_thumbnail}()}, +\code{\link{permissions}}, +\code{\link{set_image_path}()}, +\code{\link{set_run_as}()}, +\code{\link{set_thumbnail}()}, +\code{\link{set_vanity_url}()}, +\code{\link{swap_vanity_url}()}, +\code{\link{swap_vanity_urls}()}, +\code{\link{terminate_jobs}()}, +\code{\link{verify_content_name}()} +} +\concept{content functions} +\concept{oauth integration functions} diff --git a/man/content_title.Rd b/man/content_title.Rd index 9d74b6b1..dc9e96eb 100644 --- a/man/content_title.Rd +++ b/man/content_title.Rd @@ -24,6 +24,7 @@ is missing (deleted) or not visible, then returns the \code{default} Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, \code{\link{dashboard_url}()}, diff --git a/man/content_update.Rd b/man/content_update.Rd index 29cef5e3..40c5a863 100644 --- a/man/content_update.Rd +++ b/man/content_update.Rd @@ -44,6 +44,7 @@ etc. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{create_random_name}()}, \code{\link{dashboard_url}()}, diff --git a/man/create_random_name.Rd b/man/create_random_name.Rd index e4103ed8..6eaec236 100644 --- a/man/create_random_name.Rd +++ b/man/create_random_name.Rd @@ -21,6 +21,7 @@ connectapi::verify_content_name Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{dashboard_url}()}, diff --git a/man/dashboard_url.Rd b/man/dashboard_url.Rd index b86e53a2..89fff4c4 100644 --- a/man/dashboard_url.Rd +++ b/man/dashboard_url.Rd @@ -21,6 +21,7 @@ Returns the URL for the content dashboard (opened to the selected pane). Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/delete_thumbnail.Rd b/man/delete_thumbnail.Rd index 87a30706..88b01a76 100644 --- a/man/delete_thumbnail.Rd +++ b/man/delete_thumbnail.Rd @@ -32,6 +32,7 @@ Other thumbnail functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/delete_vanity_url.Rd b/man/delete_vanity_url.Rd index c30dbcbc..f156d517 100644 --- a/man/delete_vanity_url.Rd +++ b/man/delete_vanity_url.Rd @@ -16,6 +16,7 @@ Delete the vanity URL for a piece of content. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/deploy_repo.Rd b/man/deploy_repo.Rd index 5cc312c0..7558379c 100644 --- a/man/deploy_repo.Rd +++ b/man/deploy_repo.Rd @@ -59,6 +59,7 @@ connectapi::poll_task, connectapi::repo_check_branches, connectapi::repo_check_m Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/environment.Rd b/man/environment.Rd index 704906f2..f86437fb 100644 --- a/man/environment.Rd +++ b/man/environment.Rd @@ -41,6 +41,7 @@ variables not specified) Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_bundles.Rd b/man/get_bundles.Rd index 16146f87..523055e0 100644 --- a/man/get_bundles.Rd +++ b/man/get_bundles.Rd @@ -21,6 +21,7 @@ Lists bundles for a content item Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, @@ -50,6 +51,7 @@ Other content functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_image.Rd b/man/get_image.Rd index 2bced5af..0de4ef2b 100644 --- a/man/get_image.Rd +++ b/man/get_image.Rd @@ -31,6 +31,7 @@ Please use \code{\link{get_thumbnail}}, Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_integration.Rd b/man/get_integration.Rd new file mode 100644 index 00000000..7d31d416 --- /dev/null +++ b/man/get_integration.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/integrations.R +\name{get_integration} +\alias{get_integration} +\title{Get the details of an OAuth integration} +\usage{ +get_integration(client, guid) +} +\arguments{ +\item{client}{A \code{Connect} R6 client object.} + +\item{guid}{The GUID of an integration available on the Connect server.} +} +\value{ +A \code{connect_integration} object representing an OAuth integration, +which has the following fields: +\itemize{ +\item \code{id}: The internal identifier of this OAuth integration. +\item \code{guid}: The GUID of this OAuth integration. +\item \code{created_time}: The timestamp (RFC3339) indicating when this integration +was created. +\item \code{updated_time}: The timestamp (RFC3339) indicating when this integration +was last updated +\item \code{name}: A descriptive name to identify the OAuth integration. +\item \code{description}: A brief text to describe the OAuth integration. +\item \code{template}: The template used to configure this OAuth integration. +\item \code{auth_type}: The authentication type indicates which OAuth flow is used by +this integration. +\item \code{config}: A list with the OAuth integration configuration. Fields +differ between integrations. +} +} +\description{ +Given the GUID of an OAuth integration available on a Connect server, retrieve +its details. You must have administrator or publisher privileges to perform +this action. +} +\examples{ +\dontrun{ +client <- connect() +x <- get_integration(client, guid) +} + +} +\seealso{ +\code{\link[=get_oauth_credentials]{get_oauth_credentials()}}, \code{\link[=get_oauth_content_credentials]{get_oauth_content_credentials()}}, \code{\link[=get_integrations]{get_integrations()}} + +Other oauth integration functions: +\code{\link{content_set_integrations}()}, +\code{\link{get_integrations}()} +} +\concept{oauth integration functions} diff --git a/man/get_integrations.Rd b/man/get_integrations.Rd index 26dad24c..75eca010 100644 --- a/man/get_integrations.Rd +++ b/man/get_integrations.Rd @@ -24,7 +24,7 @@ was last updated \item \code{template}: The template used to configure this OAuth integration. \item \code{auth_type}: The authentication type indicates which OAuth flow is used by this integration. -\item \code{config}: A sub-list list with the OAuth integration configuration. Fields +\item \code{config}: A list with the OAuth integration configuration. Fields differ between integrations. } @@ -66,5 +66,11 @@ integrations_df <- tibble::as_tibble(integrations) } \seealso{ -\code{\link[=get_oauth_credentials]{get_oauth_credentials()}}, \code{\link[=get_oauth_content_credentials]{get_oauth_content_credentials()}} +\code{\link[=get_oauth_credentials]{get_oauth_credentials()}}, \code{\link[=get_oauth_content_credentials]{get_oauth_content_credentials()}}, +\code{\link[=get_integration]{get_integration()}} + +Other oauth integration functions: +\code{\link{content_set_integrations}()}, +\code{\link{get_integration}()} } +\concept{oauth integration functions} diff --git a/man/get_job.Rd b/man/get_job.Rd index 48588c3e..6d5cea7c 100644 --- a/man/get_job.Rd +++ b/man/get_job.Rd @@ -25,6 +25,7 @@ Other job functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_jobs.Rd b/man/get_jobs.Rd index ba9a2959..f685a2f3 100644 --- a/man/get_jobs.Rd +++ b/man/get_jobs.Rd @@ -100,6 +100,7 @@ Other job functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_log.Rd b/man/get_log.Rd index 28a1f272..3c399fd5 100644 --- a/man/get_log.Rd +++ b/man/get_log.Rd @@ -47,6 +47,7 @@ Other job functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_thumbnail.Rd b/man/get_thumbnail.Rd index bc4c1d42..194c230d 100644 --- a/man/get_thumbnail.Rd +++ b/man/get_thumbnail.Rd @@ -37,6 +37,7 @@ Other thumbnail functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/get_vanity_url.Rd b/man/get_vanity_url.Rd index fe582453..84e3d148 100644 --- a/man/get_vanity_url.Rd +++ b/man/get_vanity_url.Rd @@ -19,6 +19,7 @@ Get the vanity URL for a piece of content. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/git.Rd b/man/git.Rd index 6d379c29..e69533fe 100644 --- a/man/git.Rd +++ b/man/git.Rd @@ -44,6 +44,7 @@ connectapi::deploy_repo Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/has_thumbnail.Rd b/man/has_thumbnail.Rd index fa1e78fa..ed83fe45 100644 --- a/man/has_thumbnail.Rd +++ b/man/has_thumbnail.Rd @@ -33,6 +33,7 @@ Other thumbnail functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/permissions.Rd b/man/permissions.Rd index 8eb3e05d..48d581a5 100644 --- a/man/permissions.Rd +++ b/man/permissions.Rd @@ -66,6 +66,7 @@ This makes it easier to find / isolate this record. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/set_image.Rd b/man/set_image.Rd index a35e284b..ec5036fa 100644 --- a/man/set_image.Rd +++ b/man/set_image.Rd @@ -37,6 +37,7 @@ skips and warns for any content that requires authentication until the Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/set_run_as.Rd b/man/set_run_as.Rd index 43593146..cc26419b 100644 --- a/man/set_run_as.Rd +++ b/man/set_run_as.Rd @@ -39,6 +39,7 @@ connectapi::content_update Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/set_thumbnail.Rd b/man/set_thumbnail.Rd index 63e2360e..df0f54bc 100644 --- a/man/set_thumbnail.Rd +++ b/man/set_thumbnail.Rd @@ -36,6 +36,7 @@ Other thumbnail functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/set_vanity_url.Rd b/man/set_vanity_url.Rd index 7bd9c776..0f5caea5 100644 --- a/man/set_vanity_url.Rd +++ b/man/set_vanity_url.Rd @@ -32,6 +32,7 @@ connect() \%>\% Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/swap_vanity_url.Rd b/man/swap_vanity_url.Rd index c9854ef0..30a2cae5 100644 --- a/man/swap_vanity_url.Rd +++ b/man/swap_vanity_url.Rd @@ -22,6 +22,7 @@ This function is deprecated; please use \code{\link{swap_vanity_urls}}. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/swap_vanity_urls.Rd b/man/swap_vanity_urls.Rd index c95957b0..0ab2d923 100644 --- a/man/swap_vanity_urls.Rd +++ b/man/swap_vanity_urls.Rd @@ -21,6 +21,7 @@ Swap the vanity URLs of two pieces of content. Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/terminate_jobs.Rd b/man/terminate_jobs.Rd index b3245828..6c7c349b 100644 --- a/man/terminate_jobs.Rd +++ b/man/terminate_jobs.Rd @@ -49,6 +49,7 @@ Other job functions: Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/man/verify_content_name.Rd b/man/verify_content_name.Rd index f26d3b5d..53414d4b 100644 --- a/man/verify_content_name.Rd +++ b/man/verify_content_name.Rd @@ -24,6 +24,7 @@ connectapi::create_random_name Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, +\code{\link{content_set_integrations}()}, \code{\link{content_title}()}, \code{\link{content_update}()}, \code{\link{create_random_name}()}, diff --git a/tests/testthat/2024.12.0/__api__/v1/content/12345678-064d19.json b/tests/testthat/2024.12.0/__api__/v1/content/12345678-064d19.json new file mode 100644 index 00000000..6101474e --- /dev/null +++ b/tests/testthat/2024.12.0/__api__/v1/content/12345678-064d19.json @@ -0,0 +1,69 @@ +{ + "guid": "12345678", + "name": "fake-app-2000", + "title": "fake-app-2000", + "description": "", + "access_type": "acl", + "locked": false, + "locked_message": "", + "connection_timeout": null, + "read_timeout": null, + "init_timeout": null, + "idle_timeout": null, + "max_processes": null, + "min_processes": null, + "max_conns_per_process": null, + "load_factor": null, + "memory_request": null, + "memory_limit": null, + "cpu_request": null, + "cpu_limit": null, + "amd_gpu_limit": null, + "nvidia_gpu_limit": null, + "service_account_name": null, + "default_image_name": null, + "created_time": "2024-08-22T15:44:48Z", + "last_deployed_time": "2024-08-22T15:44:49Z", + "bundle_id": "142621", + "app_mode": "shiny", + "content_category": "", + "parameterized": false, + "cluster_name": "Local", + "image_name": null, + "r_version": "4.4.0", + "py_version": null, + "quarto_version": null, + "r_environment_management": true, + "default_r_environment_management": null, + "py_environment_management": null, + "default_py_environment_management": null, + "run_as": null, + "run_as_current_user": false, + "owner_guid": "fe07bf64", + "content_url": "https://connect.example/content/12345678/", + "dashboard_url": "https://connect.example/connect/#/apps/12345678", + "app_role": "owner", + "id": "56746", + "tags": [ + { + "id": "2", + "name": "tag1", + "parent_id": "1", + "created_time": "2017-04-18T19:54:24Z", + "updated_time": "2017-05-19T18:15:39Z" + }, + { + "id": "3", + "name": "tag2", + "parent_id": "2", + "created_time": "2017-04-18T19:54:32Z", + "updated_time": "2017-05-03T20:15:27Z" + } + ], + "owner": { + "guid": "2bdffba4-2efd-436c-a30d-6dc5b5e2bb99", + "username": "a_user", + "first_name": "A", + "last_name": "User" + } +} diff --git a/tests/testthat/2024.12.0/__api__/v1/oauth/integrations/f8688548.json b/tests/testthat/2024.12.0/__api__/v1/oauth/integrations/f8688548.json new file mode 100644 index 00000000..a61babb1 --- /dev/null +++ b/tests/testthat/2024.12.0/__api__/v1/oauth/integrations/f8688548.json @@ -0,0 +1,20 @@ +{ + "id": "4", + "guid": "f8688548", + "created_time": "2024-08-01T20:14:31Z", + "updated_time": "2025-03-25T19:08:26Z", + "name": "GitHub Integration", + "description": "with refresh support ", + "template": "custom", + "auth_type": "Viewer", + "config": { + "auth_mode": "Confidential", + "auth_type": "Viewer", + "authorization_uri": "https://github.com/login/oauth/authorize", + "client_id": "client_id_123", + "scopes": "offline_access openid profile email repo read:user", + "token_endpoint_auth_method": "client_secret_post", + "token_uri": "https://github.com/login/oauth/access_token", + "use_pkce": true + } +} diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index eb29015e..16c8ba71 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -466,3 +466,33 @@ test_that("get_content_packages() gets packages", { ) }) }) + +test_that("content_set_integrations() sends expected request", { + with_mock_dir("2024.12.0", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + x <- content_item(client, "12345678") + y <- get_integration(client, "f8688548") + }) + without_internet( + expect_PUT( + content_set_integrations(x, y), + url = "https://connect.example/__api__/v1/content/12345678/oauth/integrations/associations", + '[{"oauth_integration_guid":"f8688548"}]' + ) + ) +}) + +test_that("content_set_integrations() fails when provided the wrong class", { + with_mock_dir("2024.12.0", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + x <- content_item(client, "12345678") + }) + expect_error( + content_set_integrations(x, "string"), + "`integrations` must be a `connect_integration` class object or a list" + ) + expect_error( + content_set_integrations(x, list("string")), + "All items must be `connect_integration` objects" + ) +}) diff --git a/tests/testthat/test-integrations.R b/tests/testthat/test-integrations.R index 30127a2d..71c039e4 100644 --- a/tests/testthat/test-integrations.R +++ b/tests/testthat/test-integrations.R @@ -2,12 +2,12 @@ with_mock_dir("2024.12.0", { test_that("get_integrations() gets integrations", { client <- Connect$new(server = "https://connect.example", api_key = "fake") integrations <- get_integrations(client) - expect_true(inherits(integrations, "connect_list_integrations")) + expect_s3_class(integrations, "connect_list_integrations") - # Check a few fields expect_equal(integrations[[1]]$name, "GitHub Integration") expect_equal(integrations[[2]]$updated_time, "2025-03-25T19:07:01Z") expect_equal(integrations[[1]]$config$client_id, "client_id_123") + expect_s3_class(integrations[[1]], "connect_integration") }) test_that("get_integrations() can be converted to a data frame correctly", { @@ -46,3 +46,57 @@ test_that("get_integrations() errs on older Connect versions", { "This feature requires Posit Connect version 2024.12.0 but you are using 2024.11.1" ) }) + +test_that("as_integration correctly converts lists to integration objects", { + valid_integration <- list( + id = "123", + guid = "abc-123", + created_time = Sys.time(), + updated_time = Sys.time(), + name = "Test Integration", + description = "A test integration", + template = "github", + auth_type = "oauth2", + config = list(client_id = "client_id") + ) + + result <- as_integration(valid_integration) + expect_s3_class(result, "connect_integration") + expect_identical(result$guid, valid_integration$guid) +}) + +test_that("as_integration.default errors on non-list input", { + expect_error(as_integration(42), "Cannot convert object of class") + expect_error( + as_integration("string"), + "Cannot convert object of class" + ) +}) + +test_that("print.integration produces expected output", { + test_int <- structure( + list( + name = "Test Integration", + guid = "abc-123", + template = "github" + ), + class = c("connect_integration", "list") + ) + + output <- capture.output(result <- print(test_int)) + + expect_match(output[1], "Integration: Test Integration") + expect_match(output[2], "GUID: abc-123") + expect_match(output[3], "Template: github") + expect_identical(result, test_int) +}) + +with_mock_dir("2024.12.0", { + test_that("integration creates a single integration", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + x <- get_integration(client, "f8688548") + expect_s3_class(x, "connect_integration") + expect_equal(x$template, "custom") + expect_equal(x$guid, "f8688548") + }) +})