-
Notifications
You must be signed in to change notification settings - Fork 26
feat: create, update, and delete integrations (stacked version) #440
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4188aa9
cc4cb98
940ad5a
57e4bcc
9a2e43f
69534b0
95bd94e
97f11a2
ead8571
562c7e7
9eee7cb
5c3d170
1582d27
d7a4095
5543147
cccc83a
c4c570e
bb6824f
ba81b63
a3a94e5
1487a78
5fb342c
f92670e
fe021c2
20f0c35
86617bb
5e68c7d
b6052a6
a2c6741
18c8e64
354204e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ | |
#' @param x A `Connect` or `Content` R6 object. | ||
#' | ||
#' @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): | ||
#' with the following fields. (Raw API fields are character strings unless noted otherwise): | ||
#' | ||
#' * `id`: The internal identifier of this OAuth integration. | ||
#' * `guid`: The GUID of this OAuth integration. | ||
|
@@ -67,15 +67,15 @@ get_integrations.default <- function(x) { | |
stop( | ||
"Cannot get integrations for an object of class '", | ||
class(x)[1], | ||
"'" | ||
"'. 'x' must be a 'Connect' or 'Content' object." | ||
) | ||
} | ||
|
||
#' @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) | ||
integrations <- purrr::map(integrations, ~ as_integration(.x, client = x)) | ||
class(integrations) <- c("connect_integration_list", class(integrations)) | ||
integrations | ||
} | ||
|
@@ -101,7 +101,7 @@ get_integrations.Content <- function(x) { | |
#' Convert integrations list to a data frame | ||
#' | ||
#' @description | ||
#' Converts an list returned by [get_integrations()] into a data frame. | ||
#' Converts a list returned by [get_integrations()] into a data frame. | ||
#' | ||
#' @param x A `connect_integration_list` object (from [get_integrations()]). | ||
#' @param row.names Passed to [base::as.data.frame()]. | ||
|
@@ -144,25 +144,20 @@ as_tibble.connect_integration_list <- function(x, ...) { | |
#' Convert objects to integration class | ||
#' | ||
#' @param x An object to convert to an integration. | ||
#' @param ... Unused. | ||
#' @param client The Connect client object where the integration comes from. | ||
#' | ||
#' @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")) | ||
#' @return An integration object. The object has all the fields from the | ||
#' integrations endpoint (see [get_integrations()]) and a Connect client as a | ||
#' `client` attribute (`attr(x, "client")`) | ||
as_integration <- function(x, client) { | ||
if (!inherits(x, "list")) { | ||
stop( | ||
"Cannot convert object of class '", | ||
class(x)[1], | ||
"' to an integration" | ||
) | ||
} | ||
structure(x, class = c("connect_integration", "list"), client = client) | ||
} | ||
|
||
#' @export | ||
|
@@ -212,10 +207,11 @@ print.connect_integration <- function(x, ...) { | |
#' @export | ||
get_integration <- function(client, guid) { | ||
validate_R6_class(client, "Connect") | ||
as_integration(client$GET(v1_url("oauth", "integrations", guid))) | ||
error_if_less_than(client$version, "2024.12.0") | ||
as_integration(client$GET(v1_url("oauth", "integrations", guid)), client) | ||
} | ||
|
||
# Get and set integrations on content | ||
# Get and set integrations on content ---- | ||
|
||
#' Set all OAuth integrations for a content item | ||
#' | ||
|
@@ -341,3 +337,209 @@ get_associations <- function(x) { | |
"associations" | ||
)) | ||
} | ||
|
||
|
||
# Manage integrations ---- | ||
|
||
#' Create an OAuth integration | ||
#' | ||
#' @description | ||
#' Creates a new OAuth integration on the Posit Connect server. OAuth integrations | ||
#' allow content to access external resources using OAuth credentials. | ||
#' | ||
#' You must have administrator privileges to perform this action. | ||
#' | ||
#' See the Posit Connect documentation on | ||
#' [OAuth integrations](https://docs.posit.co/connect/admin/integrations/oauth-integrations/) for | ||
#' more information. | ||
#' | ||
#' @param client A `Connect` R6 client object. | ||
#' @param name A descriptive name to identify the integration. | ||
#' @param description Optional, default `NULL.` A brief description of the integration. | ||
#' @param template The template to use to configure this integration (e.g., | ||
#' "custom", "github", "google", "connect"). | ||
#' @param config A list containing the configuration for the integration. The | ||
#' required fields vary depending on the template selected. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Link to API docs? |
||
#' | ||
#' @return A `connect_integration` object representing the newly created | ||
#' integration. See [get_integration()] for details on the returned object. | ||
#' | ||
#' @seealso [get_integrations()], [get_integration()], [update_integration()], | ||
#' [delete_integration()] | ||
#' | ||
#' @examples | ||
#' \dontrun{ | ||
#' client <- connect() | ||
#' | ||
#' # Create a GitHub OAuth integration | ||
#' github_integration <- create_integration( | ||
#' client, | ||
#' name = "GitHub Integration", | ||
#' description = "Integration with GitHub for OAuth access", | ||
#' template = "github", | ||
#' config = list( | ||
#' client_id = "your-client-id", | ||
#' client_secret = "your-client-secret" | ||
#' ) | ||
#' ) | ||
#' | ||
#' # Create a custom OAuth integration | ||
#' custom_integration <- create_integration( | ||
#' client, | ||
#' name = "Custom API Integration", | ||
#' description = "Integration with our custom API", | ||
#' template = "custom", | ||
#' config = list( | ||
#' auth_mode = "Confidential", | ||
#' auth_type = "Viewer", | ||
#' authorization_uri = "https://api.example.com/oauth/authorize", | ||
#' client_id = "your-client-id", | ||
#' client_secret = "your-client-secret", | ||
#' token_uri = "https://api.example.com/oauth/token" | ||
#' ) | ||
#' ) | ||
#' } | ||
#' | ||
#' @family oauth integration functions | ||
#' @export | ||
create_integration <- function( | ||
client, | ||
name, | ||
description = NULL, | ||
template, | ||
config | ||
) { | ||
validate_R6_class(client, "Connect") | ||
error_if_less_than(client$version, "2024.12.0") | ||
result <- client$POST( | ||
v1_url("oauth", "integrations"), | ||
body = list( | ||
name = name, | ||
description = description, | ||
template = template, | ||
config = config | ||
) | ||
) | ||
as_integration(result, client) | ||
} | ||
|
||
#' Update an OAuth integration | ||
#' | ||
#' @description | ||
#' Updates an existing OAuth integration. All fields except `integration` are optional, | ||
#' and are unchanged if not provided. | ||
#' | ||
#' You must have administrator privileges to perform this action. | ||
#' | ||
#' See the Posit Connect documentation on | ||
#' [OAuth integrations](https://docs.posit.co/connect/admin/integrations/oauth-integrations/) for | ||
#' more information. | ||
#' | ||
#' @param integration A `connect_integration` object (as returned by [get_integrations()], | ||
#' [get_integration()], or [create_integration()]). | ||
#' @param name A new name for the integration. | ||
#' @param description A new description for the integration. | ||
#' @param template The template to use (generally not changed after creation). | ||
#' @param config A list with updated OAuth integration configuration. If `NULL` | ||
#' (default), the configuration remains unchanged. You can update individual | ||
#' configuration fields without affecting others. | ||
#' | ||
#' @return A `connect_integration` object representing the updated OAuth | ||
#' integration. See [get_integration()] for details on the returned object. | ||
#' | ||
#' @seealso [get_integrations()], [get_integration()], [create_integration()], | ||
#' [delete_integration()] | ||
#' | ||
#' @examples | ||
#' \dontrun{ | ||
#' client <- connect() | ||
#' | ||
#' # Get an existing integration | ||
#' integration <- get_integration(client, "your-integration-guid") | ||
#' | ||
#' # Update the integration's name and description | ||
#' updated_integration <- update_integration( | ||
#' integration, | ||
#' name = "Updated GitHub Integration", | ||
#' description = "A more descriptive description." | ||
#' ) | ||
#' | ||
#' # Update only the client secret in the configuration | ||
#' updated_integration <- update_integration( | ||
#' integration, | ||
#' config = list( | ||
#' client_secret = "your-new-client-secret" | ||
#' ) | ||
#' ) | ||
#' } | ||
#' | ||
#' @family oauth integration functions | ||
#' @export | ||
update_integration <- function( | ||
integration, | ||
name = NULL, | ||
description = NULL, | ||
template = NULL, | ||
config = NULL | ||
) { | ||
if (!inherits(integration, "connect_integration")) { | ||
stop("'integration' must be a 'connect_integration' object") | ||
} | ||
client <- attr(integration, "client") | ||
validate_R6_class(client, "Connect") | ||
error_if_less_than(client$version, "2024.12.0") | ||
result <- client$PATCH( | ||
v1_url("oauth", "integrations", integration$guid), | ||
body = list( | ||
name = name, | ||
description = description, | ||
template = template, | ||
config = config | ||
) | ||
) | ||
as_integration(result, client) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason we're returning an actual object on a PATCH method? I wouldn't expect a function updating an integration to return the new integration object but maybe that's a reasonable side effect in the context of this package. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the reasons are two-fold. One is that it matches the way that if you're working with local R objects, the way you update an object is to call a function on it and assign that result to something -- like: animals <- c("cat", "dog")
# update animals
animals <- c(animals, "bat") In the context of integrations and Connect, it matches the way the So if you imagine that in the context of a script, if I'm say, updating an integration, like giving a GitHub integration a new key, it makes sense for my local representation of that object to be updated to the new version. client <- connect()
github_integration <- get_integrations(client) |>
purrr::keep(~ .x$template == "github")[[1]]
github_integration <- update_integration(github_integration, config = list(key = NEW_KEY))
# Now this object will reflect the updated info.
github_integration$config$key There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, makes perfect sense. thanks!! |
||
} | ||
|
||
#' Delete an OAuth integration | ||
#' | ||
#' @description | ||
#' Deletes an OAuth integration from the Posit Connect server. This permanently | ||
#' removes the integration and any associated content associations. | ||
#' | ||
#' You must have administrator privileges to perform this action. | ||
#' | ||
#' See the Posit Connect documentation on | ||
#' [OAuth integrations](https://docs.posit.co/connect/admin/integrations/oauth-integrations/) for | ||
#' more information. | ||
#' | ||
#' @param integration A `connect_integration` object (as returned by [get_integrations()], | ||
#' [get_integration()], or [create_integration()]). | ||
#' | ||
#' @return Returns `NULL` invisibly if successful. | ||
#' | ||
#' @seealso [get_integrations()], [get_integration()], [create_integration()], | ||
#' [update_integration()] | ||
#' | ||
#' @examples | ||
#' \dontrun{ | ||
#' client <- connect() | ||
#' | ||
#' # Get an integration to delete | ||
#' integration <- get_integration(client, "your-integration-guid") | ||
#' | ||
#' # Delete the integration | ||
#' delete_integration(integration) | ||
#' } | ||
#' | ||
#' @family oauth integration functions | ||
#' @export | ||
delete_integration <- function(integration) { | ||
if (!inherits(integration, "connect_integration")) { | ||
stop("'integration' must be a 'connect_integration' object") | ||
} | ||
client <- attr(integration, "client") | ||
validate_R6_class(client, "Connect") | ||
error_if_less_than(client$version, "2024.12.0") | ||
client$DELETE(v1_url("oauth", "integrations", integration$guid)) | ||
invisible(NULL) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Link to API docs for where you can get a complete list of supported values?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API docs say "See List OAuth templates for more information" — I think our idea is that people call that endpoint. I think linking to the OAuth integration docs overview here might be better right now? 🤔
Adding a
get_templates()
function is tracked here: #435There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, whatever is best. Just flagging that we should provide users with a way of knowing what the valid values here are.