diff --git a/NAMESPACE b/NAMESPACE index f7d575a70..45e1c70aa 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -5,18 +5,21 @@ S3method("[",connect_tag_tree) S3method("[[",connect_tag_tree) S3method(api_build,op_base_connect) S3method(api_build,op_head) +S3method(as.data.frame,connect_integration_list) 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_integration_list) S3method(as_tibble,connect_list_hits) -S3method(as_tibble,connect_list_integrations) S3method(connect_vars,op_base) S3method(connect_vars,op_single) S3method(connect_vars,tbl_connect) S3method(dim,tbl_connect) S3method(dimnames,tbl_connect) +S3method(get_integrations,Connect) +S3method(get_integrations,Content) +S3method(get_integrations,default) S3method(head,tbl_connect) S3method(print,connect_integration) S3method(print,connect_tag_tree) @@ -53,7 +56,6 @@ 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) @@ -76,6 +78,7 @@ export(deploy_repo_update) export(download_bundle) export(filter_tag_tree_chr) export(filter_tag_tree_id) +export(get_associations) export(get_audit_logs) export(get_aws_content_credentials) export(get_aws_credentials) @@ -139,6 +142,7 @@ export(set_environment_remove) export(set_image_path) export(set_image_url) export(set_image_webshot) +export(set_integrations) export(set_run_as) export(set_schedule) export(set_schedule_day) diff --git a/NEWS.md b/NEWS.md index 9222c7184..b84b23a76 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,9 +1,11 @@ # connectapi (development version) -- New `content_set_integrations()` function to set the OAuth integration +- New `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) +- `get_integrations()` can now be passed a `Content` class object to retrieve a + list of integrations associated with that piece of content. (#432) # connectapi 0.8.0 diff --git a/R/content.R b/R/content.R index e9278d90c..c2dca3381 100644 --- a/R/content.R +++ b/R/content.R @@ -1434,72 +1434,3 @@ 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 ef8aa74e5..288244ffa 100644 --- a/R/integrations.R +++ b/R/integrations.R @@ -1,70 +1,100 @@ -#' List all OAuth integrations on the Connect server +#' Get OAuth integrations #' #' @description -#' Retrieve information about all OAuth integrations available to Posit Connect. -#' You must have administrator or publisher privileges to perform this action. +#' Retrieve OAuth integrations either from the Connect server or associated with a specific content item. #' -#' @param client A `Connect` R6 client object. +#' If `x` is a `Connect` object, this function lists all OAuth integrations on the server. +#' If `x` is a `Content` object, it returns the integrations associated with that content item. +#' +#' You must have administrator or publisher privileges to use this function. +#' +#' @param x A `Connect` or `Content` R6 object. #' -#' @return A list of OAuth integrations. Each integration is a list with the -#' following elements (all character strings unless indicated otherwise): +#' @return A list of class `connect_integration_list`, where each element is a `connect_integration` object +#' with the following fields (all character strings unless noted otherwise): #' #' * `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. +#' * `created_time`: Timestamp (RFC3339) when the integration was created. +#' * `updated_time`: Timestamp (RFC3339) when the integration was last updated. +#' * `name`: A descriptive name. +#' * `description`: A brief description. +#' * `template`: The template used to configure the integration. +#' * `auth_type`: The OAuth flow used. +#' * `config`: A list with integration-specific config fields. #' -#' Use [as.data.frame()] or [tibble::as_tibble()] to convert to a data frame with -#' parsed types. In the resulting data frame: +#' Use [as.data.frame()] or [tibble::as_tibble()] to convert the result to a data frame with parsed types. #' -#' * `created_time` and `updated_time` are parsed to `POSIXct`. -#' * `config` remains as a list-column. +#' @seealso +#' [get_integration()], [set_integrations()], [get_associations()] #' -#' @seealso [get_oauth_credentials()], [get_oauth_content_credentials()], -#' [get_integration()] +#' @family oauth integration functions #' #' @examples #' \dontrun{ +#' # From a Connect client #' client <- connect() -#' -#' # Fetch all OAuth integrations #' integrations <- get_integrations(client) #' +#' # Filter or update specific ones +#' github_integration <- purrr::keep(integrations, \(x) x$template == "github")[[1]] #' -#' # Update the configuration and metadata for a subset of integrations. -#' json_payload <- toJSON(list( -#' description = "New Description", -#' config = list( -#' client_secret = "new-client-secret" -#' ) +#' json_payload <- jsonlite::toJSON(list( +#' description = "Updated Description", +#' config = list(client_secret = "new-secret") #' ), auto_unbox = TRUE) #' -#' results <- integrations |> -#' purrr::keep(\(x) x$template == "service_to_update") |> -#' purrr::map(\(x) client$PATCH(paste0("v1/oauth/integrations/", x$guid), body = json_payload)) +#' client$PATCH( +#' paste0("v1/oauth/integrations/", github_integration$guid), +#' body = json_payload +#' ) #' +#' # From a Content item +#' content <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") +#' content_integrations <- get_integrations(content) #' -#' # Convert to tibble or data frame -#' integrations_df <- tibble::as_tibble(integrations) +#' # Filter content integrations +#' snowflake_integrations <- purrr::keep(content_integrations, ~ .x$template == "snowflake") #' } #' -#' @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")) +get_integrations <- function(x) { + UseMethod("get_integrations") +} + +#' @export +get_integrations.default <- function(x) { + stop( + "Cannot get integrations for an object of class '", + class(x)[1], + "'" + ) +} + +#' @export +get_integrations.Connect <- function(x) { + error_if_less_than(x$version, "2024.12.0") + integrations <- x$GET(v1_url("oauth", "integrations")) integrations <- lapply(integrations, as_integration) - class(integrations) <- c("connect_list_integrations", class(integrations)) + class(integrations) <- c("connect_integration_list", class(integrations)) + integrations +} + +#' @export +get_integrations.Content <- function(x) { + error_if_less_than(x$connect$version, "2024.12.0") + assoc <- x$connect$GET(v1_url( + "content", + x$content$guid, + "oauth", + "integrations", + "associations" + )) + integrations <- purrr::map( + assoc, + ~ get_integration(x$connect, .x$oauth_integration_guid) + ) + class(integrations) <- c("connect_integration_list", class(integrations)) integrations } @@ -73,14 +103,14 @@ get_integrations <- function(client) { #' @description #' Converts an list returned by [get_integrations()] into a data frame. #' -#' @param x A `connect_list_integrations` object (from [get_integrations()]). +#' @param x A `connect_integration_list` object (from [get_integrations()]). #' @param row.names Passed to [base::as.data.frame()]. #' @param optional Passed to [base::as.data.frame()]. #' @param ... Passed to [base::as.data.frame()]. #' #' @return A `data.frame` with one row per integration. #' @export -as.data.frame.connect_list_integrations <- function( +as.data.frame.connect_integration_list <- function( x, row.names = NULL, # nolint optional = FALSE, @@ -95,17 +125,17 @@ as.data.frame.connect_list_integrations <- function( ) } -#' Convert integrations list to a tibble +#' Convert integration list to a tibble #' #' @description #' Converts a list returned by [get_integrations()] to a tibble. #' -#' @param x A `connect_list_integrations` object. +#' @param x A `connect_integration_list` object. #' @param ... Unused. #' #' @return A tibble with one row per integration. #' @export -as_tibble.connect_list_integrations <- function(x, ...) { +as_tibble.connect_integration_list <- function(x, ...) { parse_connectapi_typed(x, connectapi_ptypes$integrations) } @@ -113,15 +143,16 @@ as_tibble.connect_list_integrations <- function(x, ...) { #' Convert objects to integration class #' -#' @param x An object to convert to an integration +#' @param x An object to convert to an integration. +#' @param ... Unused. #' #' @return An integration object -as_integration <- function(x) { +as_integration <- function(x, ...) { UseMethod("as_integration") } #' @export -as_integration.default <- function(x) { +as_integration.default <- function(x, ...) { stop( "Cannot convert object of class '", class(x)[1], @@ -130,7 +161,7 @@ as_integration.default <- function(x) { } #' @export -as_integration.list <- function(x) { +as_integration.list <- function(x, ...) { structure(x, class = c("connect_integration", "list")) } @@ -169,7 +200,7 @@ print.connect_integration <- function(x, ...) { #' * `config`: A list with the OAuth integration configuration. Fields #' differ between integrations. #' -#' @seealso [get_oauth_credentials()], [get_oauth_content_credentials()], [get_integrations()] +#' @seealso [get_integrations()], [get_associations()], [set_integrations()] #' #' @examples #' \dontrun{ @@ -183,3 +214,130 @@ get_integration <- function(client, guid) { validate_R6_class(client, "Connect") as_integration(client$GET(v1_url("oauth", "integrations", guid))) } + +# Get and set integrations on content + +#' Set all OAuth integrations for a content item +#' +#' @description +#' Removes all existing OAuth integrations associated with 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 The complete set of integrations to be associated with the +#' content. May be a single `connect_integration` object, a list of +#' `connect_integration` objects, or `NULL`. Passing in an empty list or +#' explicitly passing `NULL` will remove all associated integrations from the +#' content. +#' +#' @return Invisibly returns `NULL`. +#' +#' @seealso +#' [get_integrations()], [get_integration()], [get_associations()], [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]] +#' set_integrations(content, github_integration) +#' +#' # Associate multiple integrations at once +#' selected_integrations <- integrations[1:2] +#' set_integrations(content, selected_integrations) +#' +#' # Unset integrations +#' set_integrations(content, NULL) +#' } +#' +#' @family oauth integration functions +#' @family content functions +#' @export +set_integrations <- function(content, integrations) { + validate_R6_class(content, "Content") + # Handle a single integration + if (inherits(integrations, "connect_integration")) { + integrations <- list(integrations) + } else if (!is.null(integrations) && !inherits(integrations, "list")) { + stop( + "'integrations' must be a 'connect_integration' class object, a list, ", + "or NULL." + ) + } + # 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) +} + +#' Get OAuth associations for a piece of content +#' +#' @description +#' Given a `Content` object, retrieves a list of its +#' OAuth associations. An association contains a content GUID and an association +#' GUID, and indicates that the integration can be used by the content when it +#' runs. +#' +#' @param x A `Content` object +#' +#' @return A list of OAuth integration associations. Each association includes details such as: +#' * `app_guid`: The content item's GUID (deprecated, use `content_guid` instead). +#' * `content_guid`: The content item's GUID. +#' * `oauth_integration_guid`: The GUID of the OAuth integration. +#' * `oauth_integration_name`: The name of the OAuth integration. +#' * `oauth_integration_description`: A description of the OAuth integration. +#' * `oauth_integration_template`: The template used for this OAuth integration. +#' * `oauth_integration_auth_type`: The authentication type (e.g., "Viewer" or "Service Account"). +#' * `created_time`: The timestamp when the association was created. +#' +#' @seealso +#' [set_integrations()], [get_integrations()], [get_integration()] +#' +#' @examples +#' \dontrun{ +#' client <- connect() +#' +#' # Get OAuth associations for an app. +#' my_app <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") +#' my_app_associations <- get_associations(my_app) +#' +#' # Given those associations, retrieve the integrations themselves. +#' my_app_integrations <- purrr::map( +#' my_app_associations, +#' ~ get_integration(client, .x$oauth_integration_guid) +#' ) +#' } +#' +#' @family oauth integration functions +#' @family content functions +#' @export +get_associations <- function(x) { + validate_R6_class(x, "Content") + x$connect$GET(v1_url( + "content", + x$content$guid, + "oauth", + "integrations", + "associations" + )) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 38da95153..ea87909f6 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -67,8 +67,9 @@ reference: Functions to work with OAuth integrations contents: - matches("integration") - - content_set_integrations + - set_integrations - get_integrations + - get_associations - get_oauth_credentials - get_oauth_content_credentials @@ -79,8 +80,8 @@ reference: - starts_with("get") - as.data.frame.connect_list_hits - as_tibble.connect_list_hits - - as.data.frame.connect_list_integrations - - as_tibble.connect_list_integrations + - as.data.frame.connect_integration_list + - as_tibble.connect_integration_list - title: "Other" desc: > diff --git a/man/as.data.frame.connect_list_integrations.Rd b/man/as.data.frame.connect_integration_list.Rd similarity index 66% rename from man/as.data.frame.connect_list_integrations.Rd rename to man/as.data.frame.connect_integration_list.Rd index 86258a782..b7cfb1d06 100644 --- a/man/as.data.frame.connect_list_integrations.Rd +++ b/man/as.data.frame.connect_integration_list.Rd @@ -1,13 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/integrations.R -\name{as.data.frame.connect_list_integrations} -\alias{as.data.frame.connect_list_integrations} +\name{as.data.frame.connect_integration_list} +\alias{as.data.frame.connect_integration_list} \title{Convert integrations list to a data frame} \usage{ -\method{as.data.frame}{connect_list_integrations}(x, row.names = NULL, optional = FALSE, ...) +\method{as.data.frame}{connect_integration_list}(x, row.names = NULL, optional = FALSE, ...) } \arguments{ -\item{x}{A \code{connect_list_integrations} object (from \code{\link[=get_integrations]{get_integrations()}}).} +\item{x}{A \code{connect_integration_list} object (from \code{\link[=get_integrations]{get_integrations()}}).} \item{row.names}{Passed to \code{\link[base:as.data.frame]{base::as.data.frame()}}.} diff --git a/man/as_integration.Rd b/man/as_integration.Rd index c042cebe7..43c312aef 100644 --- a/man/as_integration.Rd +++ b/man/as_integration.Rd @@ -4,10 +4,12 @@ \alias{as_integration} \title{Convert objects to integration class} \usage{ -as_integration(x) +as_integration(x, ...) } \arguments{ -\item{x}{An object to convert to an integration} +\item{x}{An object to convert to an integration.} + +\item{...}{Unused.} } \value{ An integration object diff --git a/man/as_tibble.connect_list_integrations.Rd b/man/as_tibble.connect_integration_list.Rd similarity index 55% rename from man/as_tibble.connect_list_integrations.Rd rename to man/as_tibble.connect_integration_list.Rd index 3d3397bd1..4a358509b 100644 --- a/man/as_tibble.connect_list_integrations.Rd +++ b/man/as_tibble.connect_integration_list.Rd @@ -1,13 +1,13 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/integrations.R -\name{as_tibble.connect_list_integrations} -\alias{as_tibble.connect_list_integrations} -\title{Convert integrations list to a tibble} +\name{as_tibble.connect_integration_list} +\alias{as_tibble.connect_integration_list} +\title{Convert integration list to a tibble} \usage{ -\method{as_tibble}{connect_list_integrations}(x, ...) +\method{as_tibble}{connect_integration_list}(x, ...) } \arguments{ -\item{x}{A \code{connect_list_integrations} object.} +\item{x}{A \code{connect_integration_list} object.} \item{...}{Unused.} } diff --git a/man/content_delete.Rd b/man/content_delete.Rd index 4c9da7a53..49c258902 100644 --- a/man/content_delete.Rd +++ b/man/content_delete.Rd @@ -21,7 +21,6 @@ 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}()}, @@ -29,6 +28,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -41,6 +41,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/content_item.Rd b/man/content_item.Rd index 43f73cf8b..e66b941aa 100644 --- a/man/content_item.Rd +++ b/man/content_item.Rd @@ -27,7 +27,6 @@ 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}()}, @@ -35,6 +34,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -47,6 +47,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/content_title.Rd b/man/content_title.Rd index dc9e96eb6..b709ef128 100644 --- a/man/content_title.Rd +++ b/man/content_title.Rd @@ -24,13 +24,13 @@ 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}()}, \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -43,6 +43,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/content_update.Rd b/man/content_update.Rd index 40c5a8636..2763e3083 100644 --- a/man/content_update.Rd +++ b/man/content_update.Rd @@ -44,13 +44,13 @@ 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}()}, \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -63,6 +63,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/create_random_name.Rd b/man/create_random_name.Rd index 6eaec2362..dfea5099d 100644 --- a/man/create_random_name.Rd +++ b/man/create_random_name.Rd @@ -21,13 +21,13 @@ 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}()}, \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -40,6 +40,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/dashboard_url.Rd b/man/dashboard_url.Rd index 89fff4c4d..cd14e177d 100644 --- a/man/dashboard_url.Rd +++ b/man/dashboard_url.Rd @@ -21,13 +21,13 @@ 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}()}, \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -40,6 +40,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/delete_thumbnail.Rd b/man/delete_thumbnail.Rd index 88b01a76d..5a7ecbef6 100644 --- a/man/delete_thumbnail.Rd +++ b/man/delete_thumbnail.Rd @@ -32,13 +32,13 @@ 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}()}, \code{\link{dashboard_url}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -51,6 +51,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/delete_vanity_url.Rd b/man/delete_vanity_url.Rd index f156d517e..0d18b3d45 100644 --- a/man/delete_vanity_url.Rd +++ b/man/delete_vanity_url.Rd @@ -16,13 +16,13 @@ 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}()}, \code{\link{dashboard_url}()}, \code{\link{delete_thumbnail}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -35,6 +35,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/deploy_repo.Rd b/man/deploy_repo.Rd index 7558379c1..3e6772e05 100644 --- a/man/deploy_repo.Rd +++ b/man/deploy_repo.Rd @@ -59,13 +59,13 @@ 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}()}, \code{\link{dashboard_url}()}, \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -78,6 +78,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/environment.Rd b/man/environment.Rd index f86437fb9..0aac5576a 100644 --- a/man/environment.Rd +++ b/man/environment.Rd @@ -41,7 +41,6 @@ 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}()}, @@ -49,6 +48,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_image}()}, \code{\link{get_job}()}, @@ -60,6 +60,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_associations.Rd b/man/get_associations.Rd new file mode 100644 index 000000000..92bcde3fa --- /dev/null +++ b/man/get_associations.Rd @@ -0,0 +1,87 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/integrations.R +\name{get_associations} +\alias{get_associations} +\title{Get OAuth associations for a piece of content} +\usage{ +get_associations(x) +} +\arguments{ +\item{x}{A \code{Content} object} +} +\value{ +A list of OAuth integration associations. Each association includes details such as: +\itemize{ +\item \code{app_guid}: The content item's GUID (deprecated, use \code{content_guid} instead). +\item \code{content_guid}: The content item's GUID. +\item \code{oauth_integration_guid}: The GUID of the OAuth integration. +\item \code{oauth_integration_name}: The name of the OAuth integration. +\item \code{oauth_integration_description}: A description of the OAuth integration. +\item \code{oauth_integration_template}: The template used for this OAuth integration. +\item \code{oauth_integration_auth_type}: The authentication type (e.g., "Viewer" or "Service Account"). +\item \code{created_time}: The timestamp when the association was created. +} +} +\description{ +Given a \code{Content} object, retrieves a list of its +OAuth associations. An association contains a content GUID and an association +GUID, and indicates that the integration can be used by the content when it +runs. +} +\examples{ +\dontrun{ +client <- connect() + +# Get OAuth associations for an app. +my_app <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") +my_app_associations <- get_associations(my_app) + +# Given those associations, retrieve the integrations themselves. +my_app_integrations <- purrr::map( + my_app_associations, + ~ get_integration(client, .x$oauth_integration_guid) +) +} + +} +\seealso{ +\code{\link[=set_integrations]{set_integrations()}}, \code{\link[=get_integrations]{get_integrations()}}, \code{\link[=get_integration]{get_integration()}} + +Other oauth integration functions: +\code{\link{get_integration}()}, +\code{\link{get_integrations}()}, +\code{\link{set_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_integrations}()}, +\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/get_bundles.Rd b/man/get_bundles.Rd index 523055e07..fd885f88a 100644 --- a/man/get_bundles.Rd +++ b/man/get_bundles.Rd @@ -21,7 +21,6 @@ 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}()}, @@ -29,6 +28,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, \code{\link{get_job}()}, @@ -40,6 +40,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, @@ -51,7 +52,6 @@ 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}()}, @@ -59,6 +59,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, \code{\link{get_job}()}, @@ -70,6 +71,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_image.Rd b/man/get_image.Rd index 0de4ef2ba..98db6ee7a 100644 --- a/man/get_image.Rd +++ b/man/get_image.Rd @@ -31,7 +31,6 @@ 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}()}, @@ -39,6 +38,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_job}()}, @@ -50,6 +50,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_integration.Rd b/man/get_integration.Rd index 7d31d4164..e92a63467 100644 --- a/man/get_integration.Rd +++ b/man/get_integration.Rd @@ -43,10 +43,11 @@ 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()}} +\code{\link[=get_integrations]{get_integrations()}}, \code{\link[=get_associations]{get_associations()}}, \code{\link[=set_integrations]{set_integrations()}} Other oauth integration functions: -\code{\link{content_set_integrations}()}, -\code{\link{get_integrations}()} +\code{\link{get_associations}()}, +\code{\link{get_integrations}()}, +\code{\link{set_integrations}()} } \concept{oauth integration functions} diff --git a/man/get_integrations.Rd b/man/get_integrations.Rd index 75eca0101..18e6c475d 100644 --- a/man/get_integrations.Rd +++ b/man/get_integrations.Rd @@ -2,75 +2,72 @@ % Please edit documentation in R/integrations.R \name{get_integrations} \alias{get_integrations} -\title{List all OAuth integrations on the Connect server} +\title{Get OAuth integrations} \usage{ -get_integrations(client) +get_integrations(x) } \arguments{ -\item{client}{A \code{Connect} R6 client object.} +\item{x}{A \code{Connect} or \code{Content} R6 object.} } \value{ -A list of OAuth integrations. Each integration is a list with the -following elements (all character strings unless indicated otherwise): +A list of class \code{connect_integration_list}, where each element is a \code{connect_integration} object +with the following fields (all character strings unless noted otherwise): \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. +\item \code{created_time}: Timestamp (RFC3339) when the integration was created. +\item \code{updated_time}: Timestamp (RFC3339) when the integration was last updated. +\item \code{name}: A descriptive name. +\item \code{description}: A brief description. +\item \code{template}: The template used to configure the integration. +\item \code{auth_type}: The OAuth flow used. +\item \code{config}: A list with integration-specific config fields. } -Use \code{\link[=as.data.frame]{as.data.frame()}} or \code{\link[tibble:as_tibble]{tibble::as_tibble()}} to convert to a data frame with -parsed types. In the resulting data frame: -\itemize{ -\item \code{created_time} and \code{updated_time} are parsed to \code{POSIXct}. -\item \code{config} remains as a list-column. -} +Use \code{\link[=as.data.frame]{as.data.frame()}} or \code{\link[tibble:as_tibble]{tibble::as_tibble()}} to convert the result to a data frame with parsed types. } \description{ -Retrieve information about all OAuth integrations available to Posit Connect. -You must have administrator or publisher privileges to perform this action. +Retrieve OAuth integrations either from the Connect server or associated with a specific content item. + +If \code{x} is a \code{Connect} object, this function lists all OAuth integrations on the server. +If \code{x} is a \code{Content} object, it returns the integrations associated with that content item. + +You must have administrator or publisher privileges to use this function. } \examples{ \dontrun{ +# From a Connect client client <- connect() - -# Fetch all OAuth integrations integrations <- get_integrations(client) +# Filter or update specific ones +github_integration <- purrr::keep(integrations, \(x) x$template == "github")[[1]] -# Update the configuration and metadata for a subset of integrations. -json_payload <- toJSON(list( - description = "New Description", - config = list( - client_secret = "new-client-secret" - ) +json_payload <- jsonlite::toJSON(list( + description = "Updated Description", + config = list(client_secret = "new-secret") ), auto_unbox = TRUE) -results <- integrations |> - purrr::keep(\(x) x$template == "service_to_update") |> - purrr::map(\(x) client$PATCH(paste0("v1/oauth/integrations/", x$guid), body = json_payload)) +client$PATCH( + paste0("v1/oauth/integrations/", github_integration$guid), + body = json_payload +) +# From a Content item +content <- content_item(client, "12345678-90ab-cdef-1234-567890abcdef") +content_integrations <- get_integrations(content) -# Convert to tibble or data frame -integrations_df <- tibble::as_tibble(integrations) +# Filter content integrations +snowflake_integrations <- purrr::keep(content_integrations, ~ .x$template == "snowflake") } } \seealso{ -\code{\link[=get_oauth_credentials]{get_oauth_credentials()}}, \code{\link[=get_oauth_content_credentials]{get_oauth_content_credentials()}}, -\code{\link[=get_integration]{get_integration()}} +\code{\link[=get_integration]{get_integration()}}, \code{\link[=set_integrations]{set_integrations()}}, \code{\link[=get_associations]{get_associations()}} Other oauth integration functions: -\code{\link{content_set_integrations}()}, -\code{\link{get_integration}()} +\code{\link{get_associations}()}, +\code{\link{get_integration}()}, +\code{\link{set_integrations}()} } \concept{oauth integration functions} diff --git a/man/get_job.Rd b/man/get_job.Rd index 6d5cea7ce..6c138033e 100644 --- a/man/get_job.Rd +++ b/man/get_job.Rd @@ -25,7 +25,6 @@ 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}()}, @@ -33,6 +32,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -44,6 +44,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_jobs.Rd b/man/get_jobs.Rd index f685a2f31..75b43e382 100644 --- a/man/get_jobs.Rd +++ b/man/get_jobs.Rd @@ -100,7 +100,6 @@ 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}()}, @@ -108,6 +107,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -119,6 +119,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_log.Rd b/man/get_log.Rd index 3c399fd5a..72cd8cb3d 100644 --- a/man/get_log.Rd +++ b/man/get_log.Rd @@ -47,7 +47,6 @@ 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}()}, @@ -55,6 +54,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -66,6 +66,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_thumbnail.Rd b/man/get_thumbnail.Rd index 194c230d3..afad4c30c 100644 --- a/man/get_thumbnail.Rd +++ b/man/get_thumbnail.Rd @@ -37,7 +37,6 @@ 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}()}, @@ -45,6 +44,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -56,6 +56,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/get_vanity_url.Rd b/man/get_vanity_url.Rd index 84e3d1489..08718d50b 100644 --- a/man/get_vanity_url.Rd +++ b/man/get_vanity_url.Rd @@ -19,7 +19,6 @@ 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}()}, @@ -27,6 +26,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -38,6 +38,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/git.Rd b/man/git.Rd index e69533fe7..b11defae8 100644 --- a/man/git.Rd +++ b/man/git.Rd @@ -44,7 +44,6 @@ 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}()}, @@ -52,6 +51,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -63,6 +63,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/has_thumbnail.Rd b/man/has_thumbnail.Rd index ed83fe456..99b79b38c 100644 --- a/man/has_thumbnail.Rd +++ b/man/has_thumbnail.Rd @@ -33,7 +33,6 @@ 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}()}, @@ -41,6 +40,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -52,6 +52,7 @@ Other content functions: \code{\link{git}}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/permissions.Rd b/man/permissions.Rd index 48d581a59..2e9e2b204 100644 --- a/man/permissions.Rd +++ b/man/permissions.Rd @@ -66,7 +66,6 @@ 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}()}, @@ -74,6 +73,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -85,6 +85,7 @@ Other content functions: \code{\link{git}}, \code{\link{has_thumbnail}()}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/set_image.Rd b/man/set_image.Rd index ec5036fa1..394efea6b 100644 --- a/man/set_image.Rd +++ b/man/set_image.Rd @@ -37,7 +37,6 @@ 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}()}, @@ -45,6 +44,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -56,6 +56,7 @@ Other content functions: \code{\link{git}}, \code{\link{has_thumbnail}()}, \code{\link{permissions}}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/content_set_integrations.Rd b/man/set_integrations.Rd similarity index 65% rename from man/content_set_integrations.Rd rename to man/set_integrations.Rd index 0ba6e622e..e8c6b6d2c 100644 --- a/man/content_set_integrations.Rd +++ b/man/set_integrations.Rd @@ -1,22 +1,25 @@ % 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} +% Please edit documentation in R/integrations.R +\name{set_integrations} +\alias{set_integrations} +\title{Set all OAuth integrations for a content item} \usage{ -content_set_integrations(content, integrations) +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.} +\item{integrations}{The complete set of integrations to be associated with the +content. May be a single \code{connect_integration} object, a list of +\code{connect_integration} objects, or \code{NULL}. Passing in an empty list or +explicitly passing \code{NULL} will remove all associated integrations from the +content.} } \value{ -Invisibly returns \code{NULL}. A message is printed on success. +Invisibly returns \code{NULL}. } \description{ -Removes any existing OAuth integration associations for a content item, and +Removes all existing OAuth integrations associated with a content item, and creates associations with the integrations provided. You must have administrator or publisher privileges to perform this action. } @@ -30,18 +33,22 @@ integrations <- get_integrations(client) # Associate a single integration github_integration <- purrr::keep(integrations, \(x) x$template == "github")[[1]] -content_set_integrations(content, github_integration) +set_integrations(content, github_integration) # Associate multiple integrations at once selected_integrations <- integrations[1:2] -content_set_integrations(content, selected_integrations) +set_integrations(content, selected_integrations) + +# Unset integrations +set_integrations(content, NULL) } } \seealso{ -\code{\link[=get_integrations]{get_integrations()}}, \code{\link[=get_integration]{get_integration()}}, \code{\link[=content_item]{content_item()}} +\code{\link[=get_integrations]{get_integrations()}}, \code{\link[=get_integration]{get_integration()}}, \code{\link[=get_associations]{get_associations()}}, \code{\link[=content_item]{content_item()}} Other oauth integration functions: +\code{\link{get_associations}()}, \code{\link{get_integration}()}, \code{\link{get_integrations}()} @@ -55,6 +62,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, diff --git a/man/set_run_as.Rd b/man/set_run_as.Rd index cc26419b1..3e6e72b53 100644 --- a/man/set_run_as.Rd +++ b/man/set_run_as.Rd @@ -39,7 +39,6 @@ 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}()}, @@ -47,6 +46,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -59,6 +59,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, \code{\link{swap_vanity_url}()}, diff --git a/man/set_thumbnail.Rd b/man/set_thumbnail.Rd index df0f54bcd..458623e1d 100644 --- a/man/set_thumbnail.Rd +++ b/man/set_thumbnail.Rd @@ -36,7 +36,6 @@ 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}()}, @@ -44,6 +43,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -56,6 +56,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_vanity_url}()}, \code{\link{swap_vanity_url}()}, diff --git a/man/set_vanity_url.Rd b/man/set_vanity_url.Rd index 0f5caea5e..92bb3ae16 100644 --- a/man/set_vanity_url.Rd +++ b/man/set_vanity_url.Rd @@ -32,7 +32,6 @@ 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}()}, @@ -40,6 +39,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -52,6 +52,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{swap_vanity_url}()}, diff --git a/man/swap_vanity_url.Rd b/man/swap_vanity_url.Rd index 30a2cae57..f8b89996a 100644 --- a/man/swap_vanity_url.Rd +++ b/man/swap_vanity_url.Rd @@ -22,7 +22,6 @@ 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}()}, @@ -30,6 +29,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -42,6 +42,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/swap_vanity_urls.Rd b/man/swap_vanity_urls.Rd index 0ab2d9236..acd488a94 100644 --- a/man/swap_vanity_urls.Rd +++ b/man/swap_vanity_urls.Rd @@ -21,7 +21,6 @@ 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}()}, @@ -29,6 +28,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -41,6 +41,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/terminate_jobs.Rd b/man/terminate_jobs.Rd index 6c7c349b7..3764fbe75 100644 --- a/man/terminate_jobs.Rd +++ b/man/terminate_jobs.Rd @@ -49,7 +49,6 @@ 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}()}, @@ -57,6 +56,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -69,6 +69,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/man/verify_content_name.Rd b/man/verify_content_name.Rd index 53414d4b6..bc1774f84 100644 --- a/man/verify_content_name.Rd +++ b/man/verify_content_name.Rd @@ -24,7 +24,6 @@ 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}()}, @@ -32,6 +31,7 @@ Other content functions: \code{\link{delete_thumbnail}()}, \code{\link{delete_vanity_url}()}, \code{\link{deploy_repo}()}, +\code{\link{get_associations}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, @@ -44,6 +44,7 @@ Other content functions: \code{\link{has_thumbnail}()}, \code{\link{permissions}}, \code{\link{set_image_path}()}, +\code{\link{set_integrations}()}, \code{\link{set_run_as}()}, \code{\link{set_thumbnail}()}, \code{\link{set_vanity_url}()}, diff --git a/tests/testthat/2025.07.0/__api__/v1/content/12345678-064d19.json b/tests/testthat/2025.07.0/__api__/v1/content/12345678-064d19.json new file mode 100644 index 000000000..6101474e5 --- /dev/null +++ b/tests/testthat/2025.07.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/2025.07.0/__api__/v1/content/12345678/oauth/integrations/associations.json b/tests/testthat/2025.07.0/__api__/v1/content/12345678/oauth/integrations/associations.json new file mode 100644 index 000000000..2ab85ba9c --- /dev/null +++ b/tests/testthat/2025.07.0/__api__/v1/content/12345678/oauth/integrations/associations.json @@ -0,0 +1,22 @@ +[ + { + "app_guid": "12345678", + "content_guid": "12345678", + "oauth_integration_guid": "0000001", + "oauth_integration_name": "Integration 1", + "oauth_integration_description": "This is the first description", + "oauth_integration_template": "template1", + "oauth_integration_auth_type": "Viewer", + "created_time": "2025-08-05T20:02:39Z" + }, + { + "app_guid": "12345678", + "content_guid": "12345678", + "oauth_integration_guid": "0000002", + "oauth_integration_name": "Integration 2", + "oauth_integration_description": "This is the second description", + "oauth_integration_template": "template2", + "oauth_integration_auth_type": "Different", + "created_time": "2025-08-05T20:02:39Z" + } +] diff --git a/tests/testthat/2025.07.0/__api__/v1/oauth/integrations.json b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations.json new file mode 100644 index 000000000..ed52cdad0 --- /dev/null +++ b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations.json @@ -0,0 +1,39 @@ +[ + { + "id": "1", + "guid": "0000001", + "created_time": "2025-08-01T15:00:00Z", + "updated_time": "2025-08-05T20:02:39Z", + "name": "Integration 1", + "description": "This is the first description", + "template": "template1", + "auth_type": "Viewer", + "config": { + "auth_mode": "Confidential", + "auth_type": "Viewer", + "authorization_uri": "https://api.example.com/oauth/authorize", + "client_id": "client_id_template1", + "scopes": "read write", + "token_endpoint_auth_method": "client_secret_post", + "token_uri": "https://api.example.com/oauth/token", + "use_pkce": true + } + }, + { + "id": "2", + "guid": "0000002", + "created_time": "2025-07-15T10:30:00Z", + "updated_time": "2025-08-05T20:02:39Z", + "name": "Integration 2", + "description": "This is the second description", + "template": "template2", + "auth_type": "Different", + "config": { + "account_url": "https://template2.example.com", + "auth_mode": "Public", + "client_id": "client_id_template2", + "scopes": "api:read api:write", + "credential_type": "oauth2" + } + } +] \ No newline at end of file diff --git a/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000001.json b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000001.json new file mode 100644 index 000000000..3bf0244c3 --- /dev/null +++ b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000001.json @@ -0,0 +1,20 @@ +{ + "id": "1", + "guid": "0000001", + "created_time": "2025-08-01T15:00:00Z", + "updated_time": "2025-08-05T20:02:39Z", + "name": "Integration 1", + "description": "This is the first description", + "template": "template1", + "auth_type": "Viewer", + "config": { + "auth_mode": "Confidential", + "auth_type": "Viewer", + "authorization_uri": "https://api.example.com/oauth/authorize", + "client_id": "client_id_template1", + "scopes": "read write", + "token_endpoint_auth_method": "client_secret_post", + "token_uri": "https://api.example.com/oauth/token", + "use_pkce": true + } +} \ No newline at end of file diff --git a/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000002.json b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000002.json new file mode 100644 index 000000000..d3c7918e6 --- /dev/null +++ b/tests/testthat/2025.07.0/__api__/v1/oauth/integrations/0000002.json @@ -0,0 +1,17 @@ +{ + "id": "2", + "guid": "0000002", + "created_time": "2025-07-15T10:30:00Z", + "updated_time": "2025-08-05T20:02:39Z", + "name": "Integration 2", + "description": "This is the second description", + "template": "template2", + "auth_type": "Different", + "config": { + "account_url": "https://template2.example.com", + "auth_mode": "Public", + "client_id": "client_id_template2", + "scopes": "api:read api:write", + "credential_type": "oauth2" + } +} \ No newline at end of file diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index 16c8ba712..eb29015e0 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -466,33 +466,3 @@ 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 71c039e49..c34e626c5 100644 --- a/tests/testthat/test-integrations.R +++ b/tests/testthat/test-integrations.R @@ -2,7 +2,7 @@ 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_s3_class(integrations, "connect_list_integrations") + expect_s3_class(integrations, "connect_integration_list") expect_equal(integrations[[1]]$name, "GitHub Integration") expect_equal(integrations[[2]]$updated_time, "2025-03-25T19:07:01Z") @@ -100,3 +100,85 @@ with_mock_dir("2024.12.0", { expect_equal(x$guid, "f8688548") }) }) + +test_that("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( + set_integrations(x, y), + url = "https://connect.example/__api__/v1/content/12345678/oauth/integrations/associations", + '[{"oauth_integration_guid":"f8688548"}]' + ) + ) +}) + +test_that("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( + set_integrations(x, "string"), + "'integrations' must be a 'connect_integration' class object, a list, or NULL" + ) + expect_error( + set_integrations(x, list("string")), + "All items must be 'connect_integration' objects" + ) +}) + +with_mock_dir("2025.07.0", { + test_that("get_integrations() works with Content objects", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + content <- content_item(client, "12345678") + integrations <- get_integrations(content) + + expect_s3_class(integrations, "connect_integration_list") + expect_equal(length(integrations), 2) + expect_equal(integrations[[1]]$name, "Integration 1") + expect_equal(integrations[[2]]$template, "template2") + expect_s3_class(integrations[[1]], "connect_integration") + }) + + test_that("get_associations() returns association metadata", { + client <- Connect$new(server = "https://connect.example", api_key = "fake") + content <- content_item(client, "12345678") + associations <- get_associations(content) + + expect_type(associations, "list") + expect_equal(length(associations), 2) + expect_equal(associations[[1]]$oauth_integration_guid, "0000001") + expect_equal(associations[[1]]$oauth_integration_name, "Integration 1") + expect_equal(associations[[1]]$oauth_integration_template, "template1") + expect_equal(associations[[2]]$oauth_integration_template, "template2") + expect_true(!is.null(associations[[1]]$created_time)) + }) +}) + + +test_that("get_integrations() with Content errs on older Connect versions", { + client <- MockConnect$new("2024.11.1") + content <- Content$new( + connect = client, + content = list(guid = "12345678") + ) + expect_error( + get_integrations(content), + "This feature requires Posit Connect version 2024.12.0 but you are using 2024.11.1" + ) +}) + +test_that("get_integrations() fails when provided the wrong class", { + expect_error( + get_integrations("string"), + "Cannot get integrations for an object of class 'character'" + ) + expect_error( + get_integrations(list()), + "Cannot get integrations for an object of class 'list'" + ) +})