diff --git a/DESCRIPTION b/DESCRIPTION
index ba40188f..b45753a7 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -32,6 +32,7 @@ Imports:
fs,
glue,
httr,
+ mime,
jsonlite,
lifecycle,
magrittr,
diff --git a/NAMESPACE b/NAMESPACE
index 954fa503..ac608d63 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -58,6 +58,7 @@ export(dashboard_url_chr)
export(delete_bundle)
export(delete_image)
export(delete_tag)
+export(delete_thumbnail)
export(delete_vanity_url)
export(deploy)
export(deploy_current)
@@ -84,6 +85,7 @@ export(get_oauth_credentials)
export(get_procs)
export(get_tag_data)
export(get_tags)
+export(get_thumbnail)
export(get_timezones)
export(get_usage_shiny)
export(get_usage_static)
@@ -97,6 +99,7 @@ export(get_variant_schedule)
export(get_variants)
export(groups_create_remote)
export(has_image)
+export(has_thumbnail)
export(page_cursor)
export(page_offset)
export(poll_task)
@@ -127,6 +130,7 @@ export(set_schedule_semimonth)
export(set_schedule_week)
export(set_schedule_weekday)
export(set_schedule_year)
+export(set_thumbnail)
export(set_vanity_url)
export(swap_vanity_url)
export(tbl_connect)
diff --git a/NEWS.md b/NEWS.md
index bafcac2d..9ff89bba 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,21 @@
+# Unreleased
+
+## New features
+
+- New functions `set_thumbnail()`, `get_thumbnail()`, `delete_thumbnail()` and
+ `has_thumbnail()` let you interact with content thumbnails, replacing older
+ `*_image()` functions.
+
+## Lifecycle changes
+
+### Newly deprecated
+
+- `set_image_path()`, `set_image_url()`, and `set_image_webshot()` have been
+ deprecated and will be removed in a future update. They have been replaced by
+ `set_thumbnail()`, which works both with local file paths and remote URLs to
+ images. Likewise, `has_image()` and `delete_image()` have been deprecated in
+ favor of `has_thumbnail()` and `delete_thumbnail()`.
+
# connectapi 0.3.0
## Breaking changes
diff --git a/R/connect.R b/R/connect.R
index 2321960c..937c94db 100644
--- a/R/connect.R
+++ b/R/connect.R
@@ -107,7 +107,13 @@ Connect <- R6::R6Class(
#' @description Build a URL relative to the API root
#' @param ... path segments
api_url = function(...) {
- paste(self$server, "__api__", ..., sep = "/")
+ self$server_url("__api__", ...)
+ },
+
+ #' @description Build a URL relative to the server root
+ #' @param ... path segments
+ server_url = function(...) {
+ paste(self$server, ..., sep = "/")
},
#' @description General wrapper around `httr` verbs
diff --git a/R/deploy.R b/R/deploy.R
index 219dd905..b8f460e2 100644
--- a/R/deploy.R
+++ b/R/deploy.R
@@ -399,172 +399,6 @@ deploy_current <- function(content) {
return(ContentTask$new(connect = content$get_connect(), content = content, task = res))
}
-
-#' Get the Content Image
-#'
-#' \lifecycle{experimental}
-#' `get_image` saves the content image to the given path (default: temp file).
-#' `delete_image` removes the image (optionally saving to the given path)
-#' `has_image` returns whether the content has an image
-#'
-#' @param content A content object
-#' @param path optional. The path to the image on disk
-#'
-#' @rdname get_image
-#' @family content functions
-#' @export
-get_image <- function(content, path = NULL) {
- warn_experimental("get_image")
- validate_R6_class(content, "Content")
- guid <- content$get_content()$guid
-
- con <- content$get_connect()
-
- res <- con$GET(
- unversioned_url("applications", guid, "image"),
- parser = NULL
- )
-
- if (httr::status_code(res) == 204) {
- return(NA)
- }
-
- # guess file extension
- if (is.null(path)) {
- ct <- httr::headers(res)$`content-type`
- if (grepl("^image/", ct)) {
- # just strip off 'image/'
- ext <- substr(ct, 7, nchar(ct))
- path <- fs::file_temp(pattern = "content_image_", ext = ext)
- } else {
- # try png
- warning(glue::glue("Could not infer file extension from content type: {ct}. Using '.png'"))
- path <- fs::file_temp(pattern = "content_image_", ext = ".png")
- }
- }
-
- writeBin(httr::content(res, as = "raw"), path)
-
- return(fs::as_fs_path(path))
-}
-
-#' @rdname get_image
-#' @export
-delete_image <- function(content, path = NULL) {
- warn_experimental("delete_image")
- validate_R6_class(content, "Content")
- guid <- content$get_content()$guid
-
- con <- content$get_connect()
-
- if (!is.null(path)) {
- scoped_experimental_silence()
- get_image(content, path)
- }
-
- res <- con$DELETE(unversioned_url("applications", guid, "image"))
-
- return(content)
-}
-
-#' @rdname get_image
-#' @export
-has_image <- function(content) {
- warn_experimental("has_image")
- validate_R6_class(content, "Content")
- guid <- content$get_content()$guid
-
- con <- content$get_connect()
-
- res <- con$GET(unversioned_url("applications", guid, "image"), parser = NULL)
-
- httr::status_code(res) != 204
-}
-
-#' Set the Content Image
-#'
-#' \lifecycle{experimental}
-#'
-#' Set the Content Image using a variety of methods.
-#'
-#' NOTE: `set_image_webshot()` requires [webshot2::webshot()], but currently
-#' skips and warns for any content that requires authentication until the
-#' `webshot2` package supports authentication.
-#'
-#' @param content A content object
-#' @param path The path to an image on disk
-#' @param url The url for an image
-#' @param ... Additional arguments passed on to [webshot2::webshot()]
-#'
-#' @rdname set_image
-#' @family content functions
-#' @export
-set_image_path <- function(content, path) {
- warn_experimental("set_image_path")
- validate_R6_class(content, "Content")
- guid <- content$get_content()$guid
-
- con <- content$get_connect()
-
- res <- con$POST(
- path = unversioned_url("applications", guid, "image"),
- body = httr::upload_file(path)
- )
-
- # return the input (in case it inherits more than just Content)
- content
-}
-
-#' @rdname set_image
-#' @export
-set_image_url <- function(content, url) {
- warn_experimental("set_image_url")
- validate_R6_class(content, "Content")
- parsed_url <- httr::parse_url(url)
- imgfile <- fs::file_temp(pattern = "image", ext = fs::path_ext(parsed_url[["path"]]))
- httr::GET(url, httr::write_disk(imgfile))
-
- set_image_path(content = content, path = imgfile)
-}
-
-#' @rdname set_image
-#' @export
-set_image_webshot <- function(content, ...) {
- warn_experimental("set_image_webshot")
- validate_R6_class(content, "Content")
- imgfile <- fs::file_temp(pattern = "webshot", ext = ".png")
-
- rlang::check_installed("webshot2", "to take screenshots of applications")
- content_details <- content$get_content_remote()
-
- # check if it is possible to take the webshot
- if (content_details$access_type != "all") {
- warning(glue::glue(
- "WARNING: unable to take webshot for content ",
- "'{content_details$guid}' because authentication is not possible yet. ",
- "Set access_type='all' to proceed."
- ))
- return(content)
- }
-
- # default args
- args <- rlang::list2(...)
-
- if (!"cliprect" %in% names(args)) {
- args["cliprect"] <- "viewport"
- }
-
-
- rlang::inject(webshot2::webshot(
- url = content_details$content_url,
- file = imgfile,
- !!!args
- ))
-
- set_image_path(content = content, path = imgfile)
-}
-
-
#' Set the Vanity URL
#'
#' Sets the Vanity URL for a piece of content.
diff --git a/R/thumbnail.R b/R/thumbnail.R
new file mode 100644
index 00000000..2888ae47
--- /dev/null
+++ b/R/thumbnail.R
@@ -0,0 +1,340 @@
+#' Get content item thumbnail
+#'
+#' Download the thumbnail for a content item on Connect to a file on your
+#' computer.
+#'
+#' @param content A content item.
+#' @param path Optional. A path to a file used to write the thumbnail image. If
+#' no path is provided, a temporary file with the correct file extension is
+#' created.
+#'
+#' @returns The path to the downloaded image file, if `content` has a thumbnail; otherwise `NA`.
+#'
+#' @examples
+#' \dontrun{
+#' client <- connect()
+#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+#' thumbnail <- get_thumbnail(item)
+#' }
+#'
+#' @family thumbnail functions
+#' @family content functions
+#' @export
+get_thumbnail <- function(content, path = NULL) {
+ validate_R6_class(content, "Content")
+ guid <- content$content$guid
+ con <- content$connect
+
+ # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
+ # prefer those, falling back to the unversioned endpoints if unavailable.
+ # The "content/{guid}/__thumbnail__" URL is not under "__api__".
+ res <- con$GET(
+ url = con$server_url("content", guid, "__thumbnail__"),
+ parser = NULL
+ )
+ if (httr::status_code(res) == 404) {
+ res <- con$GET(
+ unversioned_url("applications", guid, "image"),
+ parser = NULL
+ )
+ }
+ con$raise_error(res)
+
+ if (httr::status_code(res) == 204) {
+ return(NA_character_)
+ }
+
+ # Guess file extension
+ if (is.null(path)) {
+ path <- tempfile(pattern = glue::glue("content_image_{content$content$guid}_"))
+ }
+ ct <- httr::headers(res)$`content-type`
+ if (ct %in% mime::mimemap) {
+ exts <- names(mime::mimemap[mime::mimemap == ct])
+ # Append the correct extension to the path if it does not match any of the
+ # MIME type's valid file extensions.
+ if (!any(sapply(exts, function(x) endsWith(path, x)))) {
+ path <- paste(path, exts[1], sep = ".")
+ }
+ } else {
+ warning(glue::glue("Could not infer file extension from content type: {ct}."))
+ }
+
+ writeBin(httr::content(res, as = "raw"), path)
+
+ return(path)
+}
+
+
+#' Delete content item thumbnail
+#'
+#' Delete the thumbnail from a content item on Connect.
+#'
+#' @param content A content item.
+#'
+#' @returns The content item (invisibly).
+#'
+#' @examples
+#' \dontrun{
+#' client <- connect()
+#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+#' thumbnail <- get_thumbnail(item)
+#' }
+#'
+#' @family thumbnail functions
+#' @family content functions
+#' @export
+delete_thumbnail <- function(content) {
+ validate_R6_class(content, "Content")
+ guid <- content$content$guid
+ con <- content$connect
+
+ # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
+ # prefer those, falling back to the unversioned endpoints if unavailable.
+ res <- con$DELETE(
+ v1_url("content", guid, "thumbnail"),
+ parser = NULL
+ )
+ # API error code 17 indicates that the request was successful but the thumbnail does not exist.
+ # In this case, we don't need to make another request.
+ # https://docs.posit.co/connect/api/#overview--api-error-codes
+ if (httr::status_code(res) == 404 && !("code" %in% names(httr::content(res)) && isTRUE(httr::content(res)$code == 17))) {
+ res <- con$DELETE(
+ unversioned_url("applications", guid, "image"),
+ parser = NULL
+ )
+ }
+
+ # API error code 17 indicates that the request was successful but the thumbnail does not exist.
+ # We do not want to throw an error in this case.
+ # https://docs.posit.co/connect/api/#overview--api-error-codes
+ if (httr::status_code(res) == 404 && !("code" %in% names(httr::content(res)) && isTRUE(httr::content(res)$code == 17))) {
+ con$raise_error(res)
+ }
+
+ invisible(content)
+}
+
+#' Check content item thumbnail
+#'
+#' Check whether a content item has a thumbnail.
+#'
+#' @param content A content item.
+#'
+#' @returns `TRUE` if the content item has a thumbnail, otherwise `FALSE`.
+#' Throws an error if you do not have permission to view the thumbnail.
+#' @examples
+#' \dontrun{
+#' client <- connect()
+#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+#' has_thumbnail(item)
+#' }
+#'
+#' @family thumbnail functions
+#' @family content functions
+#' @export
+has_thumbnail <- function(content) {
+ validate_R6_class(content, "Content")
+ guid <- content$content$guid
+ con <- content$connect
+
+ # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
+ # prefer those, falling back to the unversioned endpoints if unavailable.
+ # The "content/{guid}/__thumbnail__" URL is not under "__api__".
+ res <- con$GET(
+ url = con$server_url("content", guid, "__thumbnail__"),
+ parser = NULL
+ )
+ if (httr::status_code(res) == 404) {
+ res <- con$GET(
+ unversioned_url("applications", guid, "image"),
+ parser = NULL
+ )
+ }
+ con$raise_error(res)
+ httr::status_code(res) != 204
+}
+
+#' Set content item thumbnail
+#'
+#' Set the thumbnail for a content item.
+#'
+#' @param content A content item.
+#' @param path Either a path to a local file or a URL to an image available over
+#' HTTP/HTTPS. If `path` is an HTTP or HTTPS URL, the image will first
+#' be downloaded.
+#'
+#' @returns The content item (invisibly).
+#'
+#' @examples
+#' \dontrun{
+#' client <- connect()
+#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+#' set_thumbnail(item, "resources/image.png")
+#' }
+#'
+#' @family thumbnail functions
+#' @family content functions
+#' @export
+set_thumbnail <- function(content, path) {
+ validate_R6_class(content, "Content")
+
+ valid_path <- NULL
+ if (file.exists(path)) {
+ valid_path <- path
+ } else {
+ parsed <- httr::parse_url(path)
+ if (!is.null(parsed$scheme) && parsed$scheme %in% c("http", "https")) {
+ valid_path <- tempfile(pattern = "image", fileext = paste0(".", tools::file_ext(parsed[["path"]])))
+ res <- httr::GET(path, httr::write_disk(valid_path))
+ on.exit(unlink(valid_path))
+ if (httr::http_error(res)) {
+ stop(glue::glue("Could not download image from {path}: {httr::http_status(res)$message}"))
+ }
+ }
+ }
+ if (is.null(valid_path)) {
+ stop(glue::glue("Could not locate image at path: {path}"))
+ }
+
+ guid <- content$content$guid
+ con <- content$connect
+
+ # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
+ # prefer those, falling back to the unversioned endpoints if unavailable.
+ res <- con$PUT(
+ path = v1_url("content", guid, "thumbnail"),
+ body = httr::upload_file(valid_path),
+ parser = NULL
+ )
+ if (httr::status_code(res) == 404) {
+ res <- con$POST(
+ path = unversioned_url("applications", guid, "image"),
+ body = httr::upload_file(valid_path),
+ parser = NULL
+ )
+ }
+ con$raise_error(res)
+
+ # return the input (in case it inherits more than just Content)
+ invisible(content)
+}
+
+
+#' Get the Content Image
+#'
+#' @description
+#'
+#' \lifecycle{deprecated}
+#'
+#' Please use [`get_thumbnail`],
+#' [`delete_thumbnail`], and [`has_thumbnail`] instead.
+#'
+#' `get_image` saves the content image to the given path (default: temp file).
+#' `delete_image` removes the image (optionally saving to the given path)
+#' `has_image` returns whether the content has an image
+#'
+#' @param content A content object
+#' @param path optional. The path to the image on disk
+#'
+#' @rdname get_image
+#' @family content functions
+#' @export
+get_image <- function(content, path = NULL) {
+ lifecycle::deprecate_warn("0.3.1", "get_image()", "get_thumbnail()")
+
+ get_thumbnail(content, path)
+}
+
+#' @rdname get_image
+#' @export
+delete_image <- function(content, path = NULL) {
+ lifecycle::deprecate_warn("0.3.1", "delete_image()", "delete_thumbnail()")
+
+ if (!is.null(path)) {
+ get_thumbnail(content, path)
+ }
+
+ delete_thumbnail(content)
+}
+
+#' @rdname get_image
+#' @export
+has_image <- function(content) {
+ lifecycle::deprecate_warn("0.3.1", "has_image()", "has_thumbnail()")
+ has_thumbnail(content)
+}
+
+
+#' Set the Content Image
+#'
+#' @description
+#'
+#' \lifecycle{deprecated}
+#'
+#' Please use [`set_thumbnail`] instead.
+#'
+#' Set the Content Image using a variety of methods.
+#'
+#' @details NOTE: `set_image_webshot()` requires [webshot2::webshot()], but currently
+#' skips and warns for any content that requires authentication until the
+#' `webshot2` package supports authentication.
+#'
+#' @param content A content object
+#' @param path The path to an image on disk
+#' @param url The url for an image
+#' @param ... Additional arguments passed on to [webshot2::webshot()]
+#'
+#' @rdname set_image
+#' @family content functions
+#' @export
+set_image_path <- function(content, path) {
+ lifecycle::deprecate_warn("0.3.1", "set_image_path()", "set_thumbnail()")
+ set_thumbnail(content, path)
+}
+
+
+#' @rdname set_image
+#' @export
+set_image_url <- function(content, url) {
+ lifecycle::deprecate_warn("0.3.1", "set_image_url()", "set_thumbnail()")
+ set_thumbnail(content, url)
+}
+
+
+#' @rdname set_image
+#' @export
+set_image_webshot <- function(content, ...) {
+ lifecycle::deprecate_warn("0.3.1", "set_image_webshot()", "set_thumbnail()")
+ validate_R6_class(content, "Content")
+ imgfile <- fs::file_temp(pattern = "webshot", ext = ".png")
+
+ rlang::check_installed("webshot2", "to take screenshots of applications")
+ content_details <- content$get_content_remote()
+
+ # check if it is possible to take the webshot
+ if (content_details$access_type != "all") {
+ warning(glue::glue(
+ "WARNING: unable to take webshot for content ",
+ "'{content_details$guid}' because authentication is not possible yet. ",
+ "Set access_type='all' to proceed."
+ ))
+ return(content)
+ }
+
+ # default args
+ args <- rlang::list2(...)
+
+ if (!"cliprect" %in% names(args)) {
+ args["cliprect"] <- "viewport"
+ }
+
+
+ rlang::inject(webshot2::webshot(
+ url = content_details$content_url,
+ file = imgfile,
+ !!!args
+ ))
+
+ set_thumbnail(content = content, path = imgfile)
+}
diff --git a/README.Rmd b/README.Rmd
index bf1eaf7c..9bff44ce 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -111,15 +111,15 @@ content <- client %>%
# set an image for content
content %>%
- set_image_path("./my/local/image.png")
+ set_thumbnail("./my/local/image.png")
content %>%
- set_image_url("http://url.example.com/image.png")
+ set_thumbnail("http://url.example.com/image.png")
# set image and a vanity URL
content %>%
- set_image_path("./my/local/image.png") %>%
+ set_thumbnail("./my/local/image.png") %>%
set_vanity_url("/my-awesome-app")
# change access_type to "anyone"
diff --git a/README.md b/README.md
index ac74dd0f..528c703e 100644
--- a/README.md
+++ b/README.md
@@ -107,15 +107,15 @@ content <- client %>%
# set an image for content
content %>%
- set_image_path("./my/local/image.png")
+ set_thumbnail("./my/local/image.png")
content %>%
- set_image_url("http://url.example.com/image.png")
+ set_thumbnail("http://url.example.com/image.png")
# set image and a vanity URL
content %>%
- set_image_path("./my/local/image.png") %>%
+ set_thumbnail("./my/local/image.png") %>%
set_vanity_url("/my-awesome-app")
# change access_type to "anyone"
diff --git a/_pkgdown.yml b/_pkgdown.yml
index cb452c7e..987ddb10 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -33,6 +33,7 @@ reference:
contents:
- matches("content")
- matches("variant")
+ - matches("thumbnail")
- matches("image")
- matches("vanity")
- matches("schedule")
diff --git a/connectapi.Rproj b/connectapi.Rproj
index 270314b8..00d640a9 100644
--- a/connectapi.Rproj
+++ b/connectapi.Rproj
@@ -1,4 +1,5 @@
Version: 1.0
+ProjectId: ddae6158-feaf-4be6-8303-465640e6e5f8
RestoreWorkspace: Default
SaveWorkspace: Default
diff --git a/man/PositConnect.Rd b/man/PositConnect.Rd
index bb7d5d63..1a45f15e 100644
--- a/man/PositConnect.Rd
+++ b/man/PositConnect.Rd
@@ -66,6 +66,7 @@ Other R6 classes:
\item \href{#method-Connect-raise_error}{\code{Connect$raise_error()}}
\item \href{#method-Connect-add_auth}{\code{Connect$add_auth()}}
\item \href{#method-Connect-api_url}{\code{Connect$api_url()}}
+\item \href{#method-Connect-server_url}{\code{Connect$server_url()}}
\item \href{#method-Connect-request}{\code{Connect$request()}}
\item \href{#method-Connect-GET}{\code{Connect$GET()}}
\item \href{#method-Connect-PUT}{\code{Connect$PUT()}}
@@ -219,6 +220,23 @@ Build a URL relative to the API root
\if{html}{\out{
}}\preformatted{Connect$api_url(...)}\if{html}{\out{
}}
}
+\subsection{Arguments}{
+\if{html}{\out{}}
+\describe{
+\item{\code{...}}{path segments}
+}
+\if{html}{\out{
}}
+}
+}
+\if{html}{\out{
}}
+\if{html}{\out{}}
+\if{latex}{\out{\hypertarget{method-Connect-server_url}{}}}
+\subsection{Method \code{server_url()}}{
+Build a URL relative to the server root
+\subsection{Usage}{
+\if{html}{\out{}}\preformatted{Connect$server_url(...)}\if{html}{\out{
}}
+}
+
\subsection{Arguments}{
\if{html}{\out{}}
\describe{
diff --git a/man/content_delete.Rd b/man/content_delete.Rd
index 8431be54..6bf767e5 100644
--- a/man/content_delete.Rd
+++ b/man/content_delete.Rd
@@ -26,17 +26,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/content_item.Rd b/man/content_item.Rd
index 41dcd50f..a0d90fcc 100644
--- a/man/content_item.Rd
+++ b/man/content_item.Rd
@@ -32,17 +32,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/content_title.Rd b/man/content_title.Rd
index 3b4d2cbd..7b6b83f4 100644
--- a/man/content_title.Rd
+++ b/man/content_title.Rd
@@ -28,17 +28,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/content_update.Rd b/man/content_update.Rd
index 8611a525..a0e27136 100644
--- a/man/content_update.Rd
+++ b/man/content_update.Rd
@@ -48,17 +48,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/create_random_name.Rd b/man/create_random_name.Rd
index 8572693b..e00dc799 100644
--- a/man/create_random_name.Rd
+++ b/man/create_random_name.Rd
@@ -25,17 +25,21 @@ Other content functions:
\code{\link{content_update}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/dashboard_url.Rd b/man/dashboard_url.Rd
index e7e4125d..e3dc08a8 100644
--- a/man/dashboard_url.Rd
+++ b/man/dashboard_url.Rd
@@ -25,17 +25,21 @@ Other content functions:
\code{\link{content_update}()},
\code{\link{create_random_name}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/dashboard_url_chr.Rd b/man/dashboard_url_chr.Rd
index 350f125d..166a160c 100644
--- a/man/dashboard_url_chr.Rd
+++ b/man/dashboard_url_chr.Rd
@@ -28,17 +28,21 @@ Other content functions:
\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_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/delete_thumbnail.Rd b/man/delete_thumbnail.Rd
new file mode 100644
index 00000000..4cfbd83f
--- /dev/null
+++ b/man/delete_thumbnail.Rd
@@ -0,0 +1,59 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/thumbnail.R
+\name{delete_thumbnail}
+\alias{delete_thumbnail}
+\title{Delete content item thumbnail}
+\usage{
+delete_thumbnail(content)
+}
+\arguments{
+\item{content}{A content item.}
+}
+\value{
+The content item (invisibly).
+}
+\description{
+Delete the thumbnail from a content item on Connect.
+}
+\examples{
+\dontrun{
+client <- connect()
+item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+thumbnail <- get_thumbnail(item)
+}
+
+}
+\seealso{
+Other thumbnail functions:
+\code{\link{get_thumbnail}()},
+\code{\link{has_thumbnail}()},
+\code{\link{set_thumbnail}()}
+
+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{dashboard_url_chr}()},
+\code{\link{delete_vanity_url}()},
+\code{\link{deploy_repo}()},
+\code{\link{get_bundles}()},
+\code{\link{get_environment}()},
+\code{\link{get_image}()},
+\code{\link{get_jobs}()},
+\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{verify_content_name}()}
+}
+\concept{content functions}
+\concept{thumbnail functions}
diff --git a/man/delete_vanity_url.Rd b/man/delete_vanity_url.Rd
index b0347f1e..64dc15b3 100644
--- a/man/delete_vanity_url.Rd
+++ b/man/delete_vanity_url.Rd
@@ -21,16 +21,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{deploy_repo}()},
\code{\link{get_bundles}()},
\code{\link{get_environment}()},
\code{\link{get_image}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/deploy_repo.Rd b/man/deploy_repo.Rd
index e6718de5..be84c855 100644
--- a/man/deploy_repo.Rd
+++ b/man/deploy_repo.Rd
@@ -63,16 +63,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{delete_vanity_url}()},
\code{\link{get_bundles}()},
\code{\link{get_environment}()},
\code{\link{get_image}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/environment.Rd b/man/environment.Rd
index 0b74d1d8..944b2455 100644
--- a/man/environment.Rd
+++ b/man/environment.Rd
@@ -46,16 +46,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{delete_vanity_url}()},
\code{\link{deploy_repo}()},
\code{\link{get_bundles}()},
\code{\link{get_image}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/get_bundles.Rd b/man/get_bundles.Rd
index 8a1ea282..e59af114 100644
--- a/man/get_bundles.Rd
+++ b/man/get_bundles.Rd
@@ -26,16 +26,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{delete_vanity_url}()},
\code{\link{deploy_repo}()},
\code{\link{get_environment}()},
\code{\link{get_image}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
@@ -48,16 +52,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{delete_vanity_url}()},
\code{\link{deploy_repo}()},
\code{\link{get_environment}()},
\code{\link{get_image}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/get_image.Rd b/man/get_image.Rd
index 75001ebd..d3039e45 100644
--- a/man/get_image.Rd
+++ b/man/get_image.Rd
@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/deploy.R
+% Please edit documentation in R/thumbnail.R
\name{get_image}
\alias{get_image}
\alias{delete_image}
@@ -18,7 +18,11 @@ has_image(content)
\item{path}{optional. The path to the image on disk}
}
\description{
-\lifecycle{experimental}
+\lifecycle{deprecated}
+
+Please use \code{\link{get_thumbnail}},
+\code{\link{delete_thumbnail}}, and \code{\link{has_thumbnail}} instead.
+
\code{get_image} saves the content image to the given path (default: temp file).
\code{delete_image} removes the image (optionally saving to the given path)
\code{has_image} returns whether the content has an image
@@ -32,16 +36,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\code{\link{delete_thumbnail}()},
\code{\link{delete_vanity_url}()},
\code{\link{deploy_repo}()},
\code{\link{get_bundles}()},
\code{\link{get_environment}()},
\code{\link{get_jobs}()},
+\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{verify_content_name}()}
diff --git a/man/get_thumbnail.Rd b/man/get_thumbnail.Rd
new file mode 100644
index 00000000..80ec48e3
--- /dev/null
+++ b/man/get_thumbnail.Rd
@@ -0,0 +1,64 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/thumbnail.R
+\name{get_thumbnail}
+\alias{get_thumbnail}
+\title{Get content item thumbnail}
+\usage{
+get_thumbnail(content, path = NULL)
+}
+\arguments{
+\item{content}{A content item.}
+
+\item{path}{Optional. A path to a file used to write the thumbnail image. If
+no path is provided, a temporary file with the correct file extension is
+created.}
+}
+\value{
+The path to the downloaded image file, if \code{content} has a thumbnail; otherwise \code{NA}.
+}
+\description{
+Download the thumbnail for a content item on Connect to a file on your
+computer.
+}
+\examples{
+\dontrun{
+client <- connect()
+item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+thumbnail <- get_thumbnail(item)
+}
+
+}
+\seealso{
+Other thumbnail functions:
+\code{\link{delete_thumbnail}()},
+\code{\link{has_thumbnail}()},
+\code{\link{set_thumbnail}()}
+
+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{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
+}
+\concept{content functions}
+\concept{thumbnail functions}
diff --git a/man/get_vanity_url.Rd b/man/get_vanity_url.Rd
index 3c601e04..b90dcbf9 100644
--- a/man/get_vanity_url.Rd
+++ b/man/get_vanity_url.Rd
@@ -24,16 +24,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\code{\link{get_thumbnail}()},
\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{verify_content_name}()}
diff --git a/man/git.Rd b/man/git.Rd
index 08ab0c44..fd4f3ef9 100644
--- a/man/git.Rd
+++ b/man/git.Rd
@@ -47,16 +47,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\code{\link{get_thumbnail}()},
\code{\link{get_vanity_url}()},
+\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{verify_content_name}()}
diff --git a/man/has_thumbnail.Rd b/man/has_thumbnail.Rd
new file mode 100644
index 00000000..69656905
--- /dev/null
+++ b/man/has_thumbnail.Rd
@@ -0,0 +1,60 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/thumbnail.R
+\name{has_thumbnail}
+\alias{has_thumbnail}
+\title{Check content item thumbnail}
+\usage{
+has_thumbnail(content)
+}
+\arguments{
+\item{content}{A content item.}
+}
+\value{
+\code{TRUE} if the content item has a thumbnail, otherwise \code{FALSE}.
+Throws an error if you do not have permission to view the thumbnail.
+}
+\description{
+Check whether a content item has a thumbnail.
+}
+\examples{
+\dontrun{
+client <- connect()
+item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+has_thumbnail(item)
+}
+
+}
+\seealso{
+Other thumbnail functions:
+\code{\link{delete_thumbnail}()},
+\code{\link{get_thumbnail}()},
+\code{\link{set_thumbnail}()}
+
+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{dashboard_url_chr}()},
+\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_jobs}()},
+\code{\link{get_thumbnail}()},
+\code{\link{get_vanity_url}()},
+\code{\link{git}},
+\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{verify_content_name}()}
+}
+\concept{content functions}
+\concept{thumbnail functions}
diff --git a/man/jobs.Rd b/man/jobs.Rd
index 4de6bfd5..4ebb580b 100644
--- a/man/jobs.Rd
+++ b/man/jobs.Rd
@@ -27,16 +27,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_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{verify_content_name}()}
diff --git a/man/permissions.Rd b/man/permissions.Rd
index 7c40964a..05835eda 100644
--- a/man/permissions.Rd
+++ b/man/permissions.Rd
@@ -71,16 +71,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\code{\link{get_thumbnail}()},
\code{\link{get_vanity_url}()},
\code{\link{git}},
+\code{\link{has_thumbnail}()},
\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{verify_content_name}()}
diff --git a/man/set_image.Rd b/man/set_image.Rd
index fa72993f..e0150f21 100644
--- a/man/set_image.Rd
+++ b/man/set_image.Rd
@@ -1,5 +1,5 @@
% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/deploy.R
+% Please edit documentation in R/thumbnail.R
\name{set_image_path}
\alias{set_image_path}
\alias{set_image_url}
@@ -22,11 +22,13 @@ set_image_webshot(content, ...)
\item{...}{Additional arguments passed on to \code{\link[webshot2:webshot]{webshot2::webshot()}}}
}
\description{
-\lifecycle{experimental}
+\lifecycle{deprecated}
+
+Please use \code{\link{set_thumbnail}} instead.
+
+Set the Content Image using a variety of methods.
}
\details{
-Set the Content Image using a variety of methods.
-
NOTE: \code{set_image_webshot()} requires \code{\link[webshot2:webshot]{webshot2::webshot()}}, but currently
skips and warns for any content that requires authentication until the
\code{webshot2} package supports authentication.
@@ -40,16 +42,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\code{\link{get_thumbnail}()},
\code{\link{get_vanity_url}()},
\code{\link{git}},
+\code{\link{has_thumbnail}()},
\code{\link{permissions}},
\code{\link{set_run_as}()},
+\code{\link{set_thumbnail}()},
\code{\link{set_vanity_url}()},
\code{\link{swap_vanity_url}()},
\code{\link{verify_content_name}()}
diff --git a/man/set_run_as.Rd b/man/set_run_as.Rd
index 060c584e..772594a1 100644
--- a/man/set_run_as.Rd
+++ b/man/set_run_as.Rd
@@ -44,16 +44,20 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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_thumbnail}()},
\code{\link{set_vanity_url}()},
\code{\link{swap_vanity_url}()},
\code{\link{verify_content_name}()}
diff --git a/man/set_thumbnail.Rd b/man/set_thumbnail.Rd
new file mode 100644
index 00000000..0d3caf60
--- /dev/null
+++ b/man/set_thumbnail.Rd
@@ -0,0 +1,63 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/thumbnail.R
+\name{set_thumbnail}
+\alias{set_thumbnail}
+\title{Set content item thumbnail}
+\usage{
+set_thumbnail(content, path)
+}
+\arguments{
+\item{content}{A content item.}
+
+\item{path}{Either a path to a local file or a URL to an image available over
+HTTP/HTTPS. If \code{path} is an HTTP or HTTPS URL, the image will first
+be downloaded.}
+}
+\value{
+The content item (invisibly).
+}
+\description{
+Set the thumbnail for a content item.
+}
+\examples{
+\dontrun{
+client <- connect()
+item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
+set_thumbnail(item, "resources/image.png")
+}
+
+}
+\seealso{
+Other thumbnail functions:
+\code{\link{delete_thumbnail}()},
+\code{\link{get_thumbnail}()},
+\code{\link{has_thumbnail}()}
+
+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{dashboard_url_chr}()},
+\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_jobs}()},
+\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_vanity_url}()},
+\code{\link{swap_vanity_url}()},
+\code{\link{verify_content_name}()}
+}
+\concept{content functions}
+\concept{thumbnail functions}
diff --git a/man/set_vanity_url.Rd b/man/set_vanity_url.Rd
index 41d95427..85428805 100644
--- a/man/set_vanity_url.Rd
+++ b/man/set_vanity_url.Rd
@@ -37,17 +37,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{swap_vanity_url}()},
\code{\link{verify_content_name}()}
}
diff --git a/man/swap_vanity_url.Rd b/man/swap_vanity_url.Rd
index 69126ae0..36de9bf0 100644
--- a/man/swap_vanity_url.Rd
+++ b/man/swap_vanity_url.Rd
@@ -23,17 +23,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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{verify_content_name}()}
}
diff --git a/man/verify_content_name.Rd b/man/verify_content_name.Rd
index 7219bee7..03abe180 100644
--- a/man/verify_content_name.Rd
+++ b/man/verify_content_name.Rd
@@ -29,17 +29,21 @@ Other content functions:
\code{\link{create_random_name}()},
\code{\link{dashboard_url}()},
\code{\link{dashboard_url_chr}()},
+\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_jobs}()},
+\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}()}
}
diff --git a/tests/integrated/test-deploy.R b/tests/integrated/test-deploy.R
index 029c6ea8..459fe3b7 100644
--- a/tests/integrated/test-deploy.R
+++ b/tests/integrated/test-deploy.R
@@ -247,6 +247,10 @@ test_that("get_image returns NA if no image", {
test_that("set_image_url works", {
scoped_experimental_silence()
+ # This test fails with the 1.8.8.2 image. The failure is to do with
+ # downloading the favicon, used in the test as the remote image.
+ skip_if_connect_older_than(cont1_content$connect, "2021.09.0")
+
res <- set_image_url(cont1_content, glue::glue("{cont1_content$get_connect()$server}/connect/__favicon__"))
expect_true(validate_R6_class(res, "Content"))
@@ -444,3 +448,86 @@ test_that("deployment timestamps respect timezone", {
# (really protecting against being off by hours b/c of timezone differences)
expect_lt(Sys.time() - all_usage$time, lubridate::make_difftime(60, "seconds"))
})
+
+# thumbnail ---------------------------------------------------
+
+test_that("set_thumbnail works with local images", {
+ scoped_experimental_silence()
+ img_path <- rprojroot::find_package_root_file("tests/testthat/examples/logo.png")
+
+ res <- set_thumbnail(cont1_content, img_path)
+
+ expect_true(validate_R6_class(res, "Content"))
+})
+
+test_that("get_thumbnail works", {
+ scoped_experimental_silence()
+ img_path <- rprojroot::find_package_root_file("tests/testthat/examples/logo.png")
+
+ tmp_img <- fs::file_temp(pattern = "img", ext = ".png")
+ get_thumbnail(cont1_content, tmp_img)
+
+ expect_identical(
+ readBin(img_path, "raw"),
+ readBin(tmp_img, "raw")
+ )
+
+ # works again (i.e. does not append data)
+ get_thumbnail(cont1_content, tmp_img)
+ expect_identical(
+ readBin(img_path, "raw"),
+ readBin(tmp_img, "raw")
+ )
+
+ # works with no path
+ auto_path <- get_thumbnail(cont1_content)
+ expect_identical(
+ readBin(img_path, "raw"),
+ readBin(auto_path, "raw")
+ )
+ expect_identical(fs::path_ext(auto_path), "png")
+})
+
+test_that("has_thumbnail works with an image", {
+ scoped_experimental_silence()
+
+ expect_true(has_thumbnail(cont1_content))
+})
+
+test_that("delete_thumbnail works", {
+ scoped_experimental_silence()
+
+ expect_true(validate_R6_class(delete_thumbnail(cont1_content), "Content"))
+ expect_false(has_thumbnail(cont1_content))
+
+ # works again - i.e. if no image available
+ expect_true(validate_R6_class(delete_thumbnail(cont1_content), "Content"))
+})
+
+test_that("has_thumbnail works with no image", {
+ scoped_experimental_silence()
+
+ expect_false(has_thumbnail(cont1_content))
+})
+
+test_that("get_thumbnail returns NA if no image", {
+ scoped_experimental_silence()
+
+ tmp_img <- fs::file_temp(pattern = "img", ext = ".png")
+ response <- get_thumbnail(cont1_content, tmp_img)
+
+ expect_false(identical(tmp_img, response))
+ expect_true(is.na(response))
+})
+
+test_that("set_thumbnail works with remote paths", {
+ scoped_experimental_silence()
+
+ # This test fails with the 1.8.8.2 image. The failure is to do with
+ # downloading the favicon, used in the test as the remote image.
+ skip_if_connect_older_than(cont1_content$connect, "2021.09.0")
+
+ res <- set_thumbnail(cont1_content, glue::glue("{cont1_content$get_connect()$server}/connect/__favicon__"))
+
+ expect_true(validate_R6_class(res, "Content"))
+})
diff --git a/tests/testthat/2024.08.0/__api__/applications/01234567/image-DELETE.204 b/tests/testthat/2024.08.0/__api__/applications/01234567/image-DELETE.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.08.0/__api__/applications/01234567/image-POST.204 b/tests/testthat/2024.08.0/__api__/applications/01234567/image-POST.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.08.0/__api__/applications/01234567/image.R b/tests/testthat/2024.08.0/__api__/applications/01234567/image.R
new file mode 100644
index 00000000..c6ddab3c
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/applications/01234567/image.R
@@ -0,0 +1,12 @@
+structure(
+ list(
+ url = "__api__/applications/01234567/image", status_code = 200L,
+ headers = structure(list(
+ `content-type` = "image/jpeg"
+ )), content = as.raw(c(
+ 0x4e,
+ 0x41
+ ))
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/applications/12345678/image.204 b/tests/testthat/2024.08.0/__api__/applications/12345678/image.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.08.0/__api__/applications/23456789/image-DELETE.R b/tests/testthat/2024.08.0/__api__/applications/23456789/image-DELETE.R
new file mode 100644
index 00000000..ce1c8150
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/applications/23456789/image-DELETE.R
@@ -0,0 +1,8 @@
+structure(
+ list(
+ url = "__api__/applications/23456789/image",
+ status_code = 404L,
+ content = list(code = 4, error = "The requested object does not exist.", payload = NULL)
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/applications/23456789/image-POST.R b/tests/testthat/2024.08.0/__api__/applications/23456789/image-POST.R
new file mode 100644
index 00000000..ce28cff8
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/applications/23456789/image-POST.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/applications/23456789/image", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/applications/23456789/image.R b/tests/testthat/2024.08.0/__api__/applications/23456789/image.R
new file mode 100644
index 00000000..ce28cff8
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/applications/23456789/image.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/applications/23456789/image", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/applications/34567890/image-DELETE.R b/tests/testthat/2024.08.0/__api__/applications/34567890/image-DELETE.R
new file mode 100644
index 00000000..704664a8
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/applications/34567890/image-DELETE.R
@@ -0,0 +1,8 @@
+structure(
+ list(
+ url = "__api__/v1/applications/34567890/thumbnail",
+ status_code = 404L,
+ content = list(code = 17, error = "The requested item could not be removed because it does not exist.", payload = NULL)
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/__api__/applications/951bf3ad/variants.json b/tests/testthat/2024.08.0/__api__/applications/951bf3ad/variants.json
similarity index 100%
rename from tests/testthat/__api__/applications/951bf3ad/variants.json
rename to tests/testthat/2024.08.0/__api__/applications/951bf3ad/variants.json
diff --git a/tests/testthat/__api__/applications/f2f37341/variants.json b/tests/testthat/2024.08.0/__api__/applications/f2f37341/variants.json
similarity index 100%
rename from tests/testthat/__api__/applications/f2f37341/variants.json
rename to tests/testthat/2024.08.0/__api__/applications/f2f37341/variants.json
diff --git a/tests/testthat/__api__/server_settings.json b/tests/testthat/2024.08.0/__api__/server_settings.json
similarity index 100%
rename from tests/testthat/__api__/server_settings.json
rename to tests/testthat/2024.08.0/__api__/server_settings.json
diff --git a/tests/testthat/__api__/v1/content-064d19.json b/tests/testthat/2024.08.0/__api__/v1/content-064d19.json
similarity index 100%
rename from tests/testthat/__api__/v1/content-064d19.json
rename to tests/testthat/2024.08.0/__api__/v1/content-064d19.json
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/01234567.json b/tests/testthat/2024.08.0/__api__/v1/content/01234567.json
new file mode 100644
index 00000000..607b9fb3
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/01234567.json
@@ -0,0 +1,47 @@
+{
+ "guid": "01234567",
+ "name": "shinyapp-2024-08-22",
+ "title": "shinyapp-2024-08-22",
+ "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/01234567/",
+ "dashboard_url": "https://connect.example/connect/#/apps/01234567",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-DELETE.R b/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-DELETE.R
new file mode 100644
index 00000000..30df65ed
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-DELETE.R
@@ -0,0 +1,7 @@
+structure(
+ list(
+ url = "__api__/v1/applications/01234567/thumbnail",
+ status_code = 404L, content = charToRaw("404 page not found")
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-PUT.R b/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-PUT.R
new file mode 100644
index 00000000..a19fb69b
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/01234567/thumbnail-PUT.R
@@ -0,0 +1,7 @@
+structure(
+ list(
+ url = "__api__/v1/content/01234567/thumbnail",
+ status_code = 404L, content = charToRaw("404 page not found")
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/12345678.json b/tests/testthat/2024.08.0/__api__/v1/content/12345678.json
new file mode 100644
index 00000000..1cc306fd
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/12345678.json
@@ -0,0 +1,47 @@
+{
+ "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"
+}
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/23456789.json b/tests/testthat/2024.08.0/__api__/v1/content/23456789.json
new file mode 100644
index 00000000..7a1fcb8d
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/23456789.json
@@ -0,0 +1,47 @@
+{
+ "guid": "23456789",
+ "name": "fake-app-3000",
+ "title": "fake-app-3000",
+ "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/23456789/",
+ "dashboard_url": "https://connect.example/connect/#/apps/23456789",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-DELETE.R b/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-DELETE.R
new file mode 100644
index 00000000..740b5c58
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-DELETE.R
@@ -0,0 +1,7 @@
+structure(
+ list(
+ url = "__api__/v1/applications/23456789/thumbnail",
+ status_code = 404L
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-PUT.R b/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-PUT.R
new file mode 100644
index 00000000..a19fb69b
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/23456789/thumbnail-PUT.R
@@ -0,0 +1,7 @@
+structure(
+ list(
+ url = "__api__/v1/content/01234567/thumbnail",
+ status_code = 404L, content = charToRaw("404 page not found")
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/34567890.json b/tests/testthat/2024.08.0/__api__/v1/content/34567890.json
new file mode 100644
index 00000000..0c3ff7d8
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/34567890.json
@@ -0,0 +1,47 @@
+{
+ "guid": "34567890",
+ "name": "fake-app-3000",
+ "title": "fake-app-3000",
+ "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/23456789/",
+ "dashboard_url": "https://connect.example/connect/#/apps/23456789",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.08.0/__api__/v1/content/34567890/thumbnail-DELETE.R b/tests/testthat/2024.08.0/__api__/v1/content/34567890/thumbnail-DELETE.R
new file mode 100644
index 00000000..704664a8
--- /dev/null
+++ b/tests/testthat/2024.08.0/__api__/v1/content/34567890/thumbnail-DELETE.R
@@ -0,0 +1,8 @@
+structure(
+ list(
+ url = "__api__/v1/applications/34567890/thumbnail",
+ status_code = 404L,
+ content = list(code = 17, error = "The requested item could not be removed because it does not exist.", payload = NULL)
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/__api__/v1/content/8f37d6e0.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/8f37d6e0.json
rename to tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0.json
diff --git a/tests/testthat/__api__/v1/content/8f37d6e0/environment-58ff34-PATCH.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/environment-58ff34-PATCH.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/8f37d6e0/environment-58ff34-PATCH.json
rename to tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/environment-58ff34-PATCH.json
diff --git a/tests/testthat/__api__/v1/content/8f37d6e0/environment-af33b5-PATCH.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/environment-af33b5-PATCH.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/8f37d6e0/environment-af33b5-PATCH.json
rename to tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/environment-af33b5-PATCH.json
diff --git a/tests/testthat/__api__/v1/content/951bf3ad.json b/tests/testthat/2024.08.0/__api__/v1/content/951bf3ad.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/951bf3ad.json
rename to tests/testthat/2024.08.0/__api__/v1/content/951bf3ad.json
diff --git a/tests/testthat/__api__/v1/content/f2f37341.json b/tests/testthat/2024.08.0/__api__/v1/content/f2f37341.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/f2f37341.json
rename to tests/testthat/2024.08.0/__api__/v1/content/f2f37341.json
diff --git a/tests/testthat/__api__/v1/content/f2f37341/permissions.json b/tests/testthat/2024.08.0/__api__/v1/content/f2f37341/permissions.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/f2f37341/permissions.json
rename to tests/testthat/2024.08.0/__api__/v1/content/f2f37341/permissions.json
diff --git a/tests/testthat/__api__/v1/content/f2f37341/permissions/94.json b/tests/testthat/2024.08.0/__api__/v1/content/f2f37341/permissions/94.json
similarity index 100%
rename from tests/testthat/__api__/v1/content/f2f37341/permissions/94.json
rename to tests/testthat/2024.08.0/__api__/v1/content/f2f37341/permissions/94.json
diff --git a/tests/testthat/__api__/v1/oauth/integrations/credentials-fe6213-POST.json b/tests/testthat/2024.08.0/__api__/v1/oauth/integrations/credentials-fe6213-POST.json
similarity index 100%
rename from tests/testthat/__api__/v1/oauth/integrations/credentials-fe6213-POST.json
rename to tests/testthat/2024.08.0/__api__/v1/oauth/integrations/credentials-fe6213-POST.json
diff --git a/tests/testthat/__api__/v1/users-01eb50.json b/tests/testthat/2024.08.0/__api__/v1/users-01eb50.json
similarity index 100%
rename from tests/testthat/__api__/v1/users-01eb50.json
rename to tests/testthat/2024.08.0/__api__/v1/users-01eb50.json
diff --git a/tests/testthat/__api__/v1/users-a7d21f.json b/tests/testthat/2024.08.0/__api__/v1/users-a7d21f.json
similarity index 100%
rename from tests/testthat/__api__/v1/users-a7d21f.json
rename to tests/testthat/2024.08.0/__api__/v1/users-a7d21f.json
diff --git a/tests/testthat/__api__/v1/users-c6c26e.json b/tests/testthat/2024.08.0/__api__/v1/users-c6c26e.json
similarity index 100%
rename from tests/testthat/__api__/v1/users-c6c26e.json
rename to tests/testthat/2024.08.0/__api__/v1/users-c6c26e.json
diff --git a/tests/testthat/__api__/v1/users/20a79ce3.json b/tests/testthat/2024.08.0/__api__/v1/users/20a79ce3.json
similarity index 100%
rename from tests/testthat/__api__/v1/users/20a79ce3.json
rename to tests/testthat/2024.08.0/__api__/v1/users/20a79ce3.json
diff --git a/tests/testthat/__api__/v1/vanities.json b/tests/testthat/2024.08.0/__api__/v1/vanities.json
similarity index 100%
rename from tests/testthat/__api__/v1/vanities.json
rename to tests/testthat/2024.08.0/__api__/v1/vanities.json
diff --git a/tests/testthat/__api__/variants/6644/render-38c41e-POST.json b/tests/testthat/2024.08.0/__api__/variants/6644/render-38c41e-POST.json
similarity index 100%
rename from tests/testthat/__api__/variants/6644/render-38c41e-POST.json
rename to tests/testthat/2024.08.0/__api__/variants/6644/render-38c41e-POST.json
diff --git a/tests/testthat/__api__/variants/6666/render-38c41e-POST.json b/tests/testthat/2024.08.0/__api__/variants/6666/render-38c41e-POST.json
similarity index 100%
rename from tests/testthat/__api__/variants/6666/render-38c41e-POST.json
rename to tests/testthat/2024.08.0/__api__/variants/6666/render-38c41e-POST.json
diff --git a/tests/testthat/2024.08.0/__ping__.json b/tests/testthat/2024.08.0/__ping__.json
new file mode 100644
index 00000000..0db3279e
--- /dev/null
+++ b/tests/testthat/2024.08.0/__ping__.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/tests/testthat/2024.08.0/content/01234567/__thumbnail__.R b/tests/testthat/2024.08.0/content/01234567/__thumbnail__.R
new file mode 100644
index 00000000..3b30d530
--- /dev/null
+++ b/tests/testthat/2024.08.0/content/01234567/__thumbnail__.R
@@ -0,0 +1,7 @@
+structure(
+ list(
+ url = "content/01234567/__thumbnail__",
+ status_code = 404L, content = charToRaw("404 page not found")
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/content/12345678/__thumbnail__.R b/tests/testthat/2024.08.0/content/12345678/__thumbnail__.R
new file mode 100644
index 00000000..0d1504db
--- /dev/null
+++ b/tests/testthat/2024.08.0/content/12345678/__thumbnail__.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "content/12345678/__thumbnail__", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/content/23456789/__thumbnail__.R b/tests/testthat/2024.08.0/content/23456789/__thumbnail__.R
new file mode 100644
index 00000000..ca982d55
--- /dev/null
+++ b/tests/testthat/2024.08.0/content/23456789/__thumbnail__.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "content/23456789/__thumbnail__", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/content/34567890/__thumbnail__.R b/tests/testthat/2024.08.0/content/34567890/__thumbnail__.R
new file mode 100644
index 00000000..ca982d55
--- /dev/null
+++ b/tests/testthat/2024.08.0/content/34567890/__thumbnail__.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "content/23456789/__thumbnail__", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.08.0/non-connect/missing-image/image.png.R b/tests/testthat/2024.08.0/non-connect/missing-image/image.png.R
new file mode 100644
index 00000000..9700514d
--- /dev/null
+++ b/tests/testthat/2024.08.0/non-connect/missing-image/image.png.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "missing-image/image.png", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/applications/23456789/image-DELETE.R b/tests/testthat/2024.09.0/__api__/applications/23456789/image-DELETE.R
new file mode 100644
index 00000000..ce28cff8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/applications/23456789/image-DELETE.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/applications/23456789/image", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/applications/23456789/image-POST.R b/tests/testthat/2024.09.0/__api__/applications/23456789/image-POST.R
new file mode 100644
index 00000000..ce28cff8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/applications/23456789/image-POST.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/applications/23456789/image", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/applications/23456789/image.R b/tests/testthat/2024.09.0/__api__/applications/23456789/image.R
new file mode 100644
index 00000000..ce28cff8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/applications/23456789/image.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/applications/23456789/image", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/server_settings.json b/tests/testthat/2024.09.0/__api__/server_settings.json
new file mode 100644
index 00000000..3c2342c8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/server_settings.json
@@ -0,0 +1,123 @@
+{
+ "hostname": "dogfood01",
+ "version": "2024.09.0-dev+77-g9296f96ffb",
+ "build": "v2024.08.0-77-g9296f96ffb",
+ "about": "Posit Connect v2024.09.0-dev+77-g9296f96ffb",
+ "authentication": {
+ "handles_credentials": false,
+ "handles_login": true,
+ "challenge_response_enabled": false,
+ "external_user_data": true,
+ "external_user_search": true,
+ "external_user_id": true,
+ "groups_enabled": true,
+ "external_group_search": false,
+ "external_group_members": false,
+ "external_group_id": false,
+ "external_group_owner": false,
+ "unique_usernames": true,
+ "name_editable_by": "provider",
+ "email_editable_by": "provider",
+ "username_editable_by": "adminandself",
+ "role_editable_by": "adminandself",
+ "name": "OAuth2",
+ "notice": "NOTICE: Other users will be able to see your full name and email address in Posit Connect.",
+ "warning_delay": 10,
+ "API_key_auth": true
+ },
+ "license": {
+ "ts": 1726138921155,
+ "status": "activated",
+ "expiration": 1759276800000,
+ "days-left": 384,
+ "has-key": true,
+ "has-trial": true,
+ "tier": "standard",
+ "sku-year": "2023",
+ "edition": "",
+ "cores": 0,
+ "connections": 0,
+ "type": "local",
+ "users": 0,
+ "user-activity-days": 365,
+ "users-grace": "true",
+ "shiny-users": 0,
+ "allow-apis": true,
+ "custom-branding": true,
+ "current-user-execution": true,
+ "anonymous-servers": true,
+ "unrestricted-servers": true,
+ "anonymous-branding": false,
+ "oauth-integrations": true,
+ "enable-launcher": false
+ },
+ "license_expiration_ui_warning": true,
+ "end_of_support_ui_warning": true,
+ "deprecated_settings": false,
+ "deprecated_settings_ui_warning": true,
+ "viewers_can_request_privileges": true,
+ "mail_all": false,
+ "mail_configured": true,
+ "public_warning": "",
+ "logged_in_warning": "",
+ "logout_url": "__logout__",
+ "metrics_rrd_enabled": true,
+ "metrics_instrumentation": true,
+ "customized_landing": false,
+ "self_registration": false,
+ "prohibited_usernames": [
+ "connect",
+ "apps",
+ "users",
+ "groups",
+ "setpassword",
+ "user-completion",
+ "confirm",
+ "recent",
+ "reports",
+ "plots",
+ "unpublished",
+ "settings",
+ "metrics",
+ "tokens",
+ "help",
+ "login",
+ "welcome",
+ "register",
+ "resetpassword",
+ "content"
+ ],
+ "username_validator": "default",
+ "viewers_can_only_see_themselves": false,
+ "http_warning": false,
+ "runtimes": [
+ "R",
+ "Python",
+ "Quarto",
+ "TensorFlow"
+ ],
+ "default_content_list_view": "compact",
+ "maximum_app_image_size": 10000000,
+ "server_settings_toggler": true,
+ "git_enabled": true,
+ "git_available": true,
+ "dashboard_path": "/connect",
+ "system_display_name": "Posit Connect",
+ "hide_viewer_documentation": false,
+ "jump_start_enabled": true,
+ "permission_request": true,
+ "tableau_integration_enabled": true,
+ "self_test_enabled": true,
+ "execution_type": "native",
+ "enable_runtime_cache_management": true,
+ "default_image_selection_enabled": true,
+ "default_environment_management_selection": true,
+ "default_r_environment_management": true,
+ "default_py_environment_management": true,
+ "new_parameterization_enabled": false,
+ "use_window_location": false,
+ "locked_content_enabled": true,
+ "oauth_integrations_enabled": true,
+ "labs_enabled": false,
+ "whats_new_enabled": true
+}
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/01234567.json b/tests/testthat/2024.09.0/__api__/v1/content/01234567.json
new file mode 100644
index 00000000..607b9fb3
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/01234567.json
@@ -0,0 +1,47 @@
+{
+ "guid": "01234567",
+ "name": "shinyapp-2024-08-22",
+ "title": "shinyapp-2024-08-22",
+ "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/01234567/",
+ "dashboard_url": "https://connect.example/connect/#/apps/01234567",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/01234567/thumbnail-DELETE.204 b/tests/testthat/2024.09.0/__api__/v1/content/01234567/thumbnail-DELETE.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/01234567/thumbnail-PUT.204 b/tests/testthat/2024.09.0/__api__/v1/content/01234567/thumbnail-PUT.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/12345678.json b/tests/testthat/2024.09.0/__api__/v1/content/12345678.json
new file mode 100644
index 00000000..1cc306fd
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/12345678.json
@@ -0,0 +1,47 @@
+{
+ "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"
+}
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/23456789.json b/tests/testthat/2024.09.0/__api__/v1/content/23456789.json
new file mode 100644
index 00000000..7a1fcb8d
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/23456789.json
@@ -0,0 +1,47 @@
+{
+ "guid": "23456789",
+ "name": "fake-app-3000",
+ "title": "fake-app-3000",
+ "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/23456789/",
+ "dashboard_url": "https://connect.example/connect/#/apps/23456789",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-DELETE.R b/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-DELETE.R
new file mode 100644
index 00000000..ce1c8150
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-DELETE.R
@@ -0,0 +1,8 @@
+structure(
+ list(
+ url = "__api__/applications/23456789/image",
+ status_code = 404L,
+ content = list(code = 4, error = "The requested object does not exist.", payload = NULL)
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-PUT.R b/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-PUT.R
new file mode 100644
index 00000000..085dfd37
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/23456789/thumbnail-PUT.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "__api__/v1/content/23456789/thumbnail", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/34567890.json b/tests/testthat/2024.09.0/__api__/v1/content/34567890.json
new file mode 100644
index 00000000..0c3ff7d8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/34567890.json
@@ -0,0 +1,47 @@
+{
+ "guid": "34567890",
+ "name": "fake-app-3000",
+ "title": "fake-app-3000",
+ "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/23456789/",
+ "dashboard_url": "https://connect.example/connect/#/apps/23456789",
+ "app_role": "owner",
+ "id": "56746"
+}
diff --git a/tests/testthat/2024.09.0/__api__/v1/content/34567890/thumbnail-DELETE.R b/tests/testthat/2024.09.0/__api__/v1/content/34567890/thumbnail-DELETE.R
new file mode 100644
index 00000000..704664a8
--- /dev/null
+++ b/tests/testthat/2024.09.0/__api__/v1/content/34567890/thumbnail-DELETE.R
@@ -0,0 +1,8 @@
+structure(
+ list(
+ url = "__api__/v1/applications/34567890/thumbnail",
+ status_code = 404L,
+ content = list(code = 17, error = "The requested item could not be removed because it does not exist.", payload = NULL)
+ ),
+ class = "response"
+)
diff --git a/tests/testthat/2024.09.0/__ping__.json b/tests/testthat/2024.09.0/__ping__.json
new file mode 100644
index 00000000..0db3279e
--- /dev/null
+++ b/tests/testthat/2024.09.0/__ping__.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/tests/testthat/2024.09.0/content/01234567/__thumbnail__.R b/tests/testthat/2024.09.0/content/01234567/__thumbnail__.R
new file mode 100644
index 00000000..9ffdfce8
--- /dev/null
+++ b/tests/testthat/2024.09.0/content/01234567/__thumbnail__.R
@@ -0,0 +1,9 @@
+structure(list(
+ url = "content/01234567/__thumbnail__", status_code = 200L,
+ headers = structure(list(
+ `content-type` = "image/jpeg"
+ )), content = as.raw(c(
+ 0x4e,
+ 0x41
+ ))
+), class = "response")
diff --git a/tests/testthat/2024.09.0/content/12345678/__thumbnail__.204 b/tests/testthat/2024.09.0/content/12345678/__thumbnail__.204
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/testthat/2024.09.0/content/23456789/__thumbnail__.R b/tests/testthat/2024.09.0/content/23456789/__thumbnail__.R
new file mode 100644
index 00000000..e4a31c50
--- /dev/null
+++ b/tests/testthat/2024.09.0/content/23456789/__thumbnail__.R
@@ -0,0 +1,6 @@
+structure(
+ list(url = "content/23456789/__thumbnail__", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+ )
+
\ No newline at end of file
diff --git a/tests/testthat/2024.09.0/non-connect/missing-image/image.png.R b/tests/testthat/2024.09.0/non-connect/missing-image/image.png.R
new file mode 100644
index 00000000..9700514d
--- /dev/null
+++ b/tests/testthat/2024.09.0/non-connect/missing-image/image.png.R
@@ -0,0 +1,5 @@
+structure(
+ list(url = "missing-image/image.png", status_code = 404L),
+ content = charToRaw("404 page not found"),
+ class = "response"
+)
diff --git a/tests/testthat/Rplots.pdf b/tests/testthat/Rplots.pdf
new file mode 100644
index 00000000..fbadf3e5
Binary files /dev/null and b/tests/testthat/Rplots.pdf differ
diff --git a/tests/testthat/resources/smol.jpg b/tests/testthat/resources/smol.jpg
new file mode 100644
index 00000000..e905241b
Binary files /dev/null and b/tests/testthat/resources/smol.jpg differ
diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R
index 62f21da2..aae96e0f 100644
--- a/tests/testthat/setup.R
+++ b/tests/testthat/setup.R
@@ -31,3 +31,7 @@ set_redactor(
)
}
)
+
+# Mocks are in directories by Connect version. 2024.08.0 contains all mocks
+# created before 2024.09.0, and is the default mock path.
+.mockPaths("2024.08.0")
diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R
index 91a534ca..31f3c8bf 100644
--- a/tests/testthat/test-content.R
+++ b/tests/testthat/test-content.R
@@ -231,4 +231,3 @@ with_mock_api({
expect_identical(v$key, "WrEKKa77")
})
})
-
diff --git a/tests/testthat/test-thumbnail.R b/tests/testthat/test-thumbnail.R
new file mode 100644
index 00000000..c026ea00
--- /dev/null
+++ b/tests/testthat/test-thumbnail.R
@@ -0,0 +1,125 @@
+mock_dirs <- c(
+ "unversioned" = "2024.08.0",
+ "v1" = "2024.09.0"
+)
+
+# The mocked content items are as follows:
+# - 01234567:
+# - Returns 200 and a tiny jpeg when getting the thumbnail
+# - Returns 204 (success) when setting and deleting
+# - 12345678: Returns 204 when getting the thumbnail (no thumbnail)
+# - 23456789: Returns 404 for all endpoints
+# - non-connect/missing-image/image.R: a 404 response
+
+for (api_ver in names(mock_dirs)) {
+ mock_dir <- mock_dirs[[api_ver]]
+ with_mock_dir(mock_dir, {
+ with_mock_api({
+ client <- connect(server = "https://connect.example", api_key = "fake")
+ test_that(glue::glue("get_thumbnail() gets the thumbnail ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+
+ # User-specified path
+ user_path <- tempfile("thumbnail_", fileext = ".jpg")
+ received_path <- get_thumbnail(item, user_path)
+ received <- readBin(received_path, "raw", n = 8)
+ expected <- as.raw(c(0x4e, 0x41))
+ expect_equal(user_path, received_path)
+ expect_equal(received, expected)
+
+ # User-specified path (wrong extension)
+ user_path <- tempfile("thumbnail_", fileext = ".png")
+ received_path <- get_thumbnail(item, user_path)
+ received <- readBin(received_path, "raw", n = 8)
+ expected <- as.raw(c(0x4e, 0x41))
+ expect_equal(paste0(user_path, ".jpeg"), received_path)
+ expect_equal(received, expected)
+
+ # Automatic path
+ received_path <- get_thumbnail(item)
+ received <- readBin(received_path, "raw", n = 8)
+ expected <- as.raw(c(0x4e, 0x41))
+ expect_equal(substring(received_path, nchar(received_path) - 4), ".jpeg")
+ expect_equal(received, expected)
+ })
+
+ test_that(glue::glue("get_thumbnail() returns NA_character_ for 204 status codes ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "12345678")
+
+ # User-specified path
+ user_path <- tempfile("thumbnail_", fileext = ".jpg")
+ received_path <- get_thumbnail(item, user_path)
+ expect_equal(received_path, NA_character_)
+
+ # Automatic path
+ received_path <- get_thumbnail(item)
+ expect_equal(received_path, NA_character_)
+ })
+
+ test_that(glue::glue("get_thumbnail() errors with 404 status codes ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "23456789")
+ expect_error(get_thumbnail(item), "request failed with Client error: \\(404\\) Not Found")
+ })
+
+ test_that(glue::glue("has_thumbnail() returns TRUE when the item has a thumbnail ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+ expect_true(has_thumbnail(item))
+ })
+
+ test_that(glue::glue("has_thumbnail() returns FALSE when the status code is 204 ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "12345678")
+ expect_false(has_thumbnail(item))
+ })
+
+ test_that(glue::glue("has_thumbnail() errors with 404 status codes ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "23456789")
+ expect_error(has_thumbnail(item), "request failed with Client error: \\(404\\) Not Found")
+ })
+
+ test_that(glue::glue("set_thumbnail() returns returns the content item when successful ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+ received <- set_thumbnail(item, "resources/smol.jpg")
+ expect_true(validate_R6_class(received, "Content"))
+ expect_identical(item, received)
+ })
+
+ test_that(glue::glue("set_thumbnail() raises an error when the endpoint returns a 404 ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "23456789")
+ expect_error(set_thumbnail(item, "resources/smol.jpg"), "request failed with Client error: \\(404\\) Not Found")
+ })
+
+ test_that(glue::glue("set_thumbnail() works with remote images ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+ expect_GET(set_thumbnail(item, "https://other.server/non-connect/working-image/not-an-image"),
+ "https://other.server/non-connect/working-image/not-an-image")
+ # We're only asserting that the remote image hits GET. Because we
+ # httr::write_disk() and then using what is written doesn't work
+ # (https://github.com/nealrichardson/httptest/issues/86) the rest of
+ # this function is tested elsewhere, so we have confidence that so long
+ # as getting and writing to disk works, we are good.
+ })
+
+ test_that(glue::glue("set_thumbnail() returns an error when the remote image cannot be found ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+ expect_error(set_thumbnail(item, "https://other.server/non-connect/missing-image/image.png"),
+ "Could not download image from https")
+ })
+
+ test_that(glue::glue("delete_thumbnail() returns the content item when delete works ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "01234567")
+ expect_identical(delete_thumbnail(item), item)
+ })
+
+ test_that(glue::glue("delete_thumbnail() throws an error for other 404 errors ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "23456789")
+ expect_error(delete_thumbnail(item), "request failed with Client error: \\(404\\) Not Found")
+ })
+
+ test_that(glue::glue("delete_thumbnail() returns the content for 404s indicating no thumbnail ({api_ver} - {mock_dir})"), {
+ item <- content_item(client, "34567890")
+ expect_identical(delete_thumbnail(item), item)
+ })
+ })
+ })
+}
+
diff --git a/vignettes/getting-started.Rmd b/vignettes/getting-started.Rmd
index 8455400e..3810329a 100644
--- a/vignettes/getting-started.Rmd
+++ b/vignettes/getting-started.Rmd
@@ -82,7 +82,7 @@ while you wait for deployment to complete:
```r
content_1 %>%
- set_image_url("https://gph.is/29vyb0s") %>%
+ set_thumbnail("https://gph.is/29vyb0s") %>%
set_vanity_url("/my_clever_content")
# ensure the vanity URL is set as expected