diff --git a/NEWS.md b/NEWS.md index ed56491ae..99a3ff221 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # connectapi (development version) +## Breaking changes + +- `get_jobs()` now uses the public endpoint. The data returned by this function + has changed. In particular, the `finalized` column is no longer present, + replaced by `status`. `status == 0` corresponds to `isFALSE(finalized)`. See + `?get_jobs()` for more details about the new return format. (#340) + ## New features - `get_users()` now supports filtering users with the `account_status` and diff --git a/R/connectapi.R b/R/connectapi.R index 7c1e6d063..642cb3907 100644 --- a/R/connectapi.R +++ b/R/connectapi.R @@ -27,5 +27,6 @@ current_connect_version <- "2024.03.0" .onLoad <- function(...) { vctrs::s3_register("dplyr::collect", "tbl_connect") + vctrs::s3_register("vctrs::vec_cast", "character.integer") invisible() } diff --git a/R/content.R b/R/content.R index 890f25f70..d4e3eefd5 100644 --- a/R/content.R +++ b/R/content.R @@ -95,9 +95,23 @@ Content <- R6::R6Class( }, #' @description Return the jobs for this content. jobs = function() { - warn_experimental("jobs") - url <- unversioned_url("applications", self$get_content()$guid, "jobs") - res <- self$get_connect()$GET(url) + res <- self$connect$GET(v1_url("content", self$content$guid, "jobs"), parser = NULL) + use_unversioned <- endpoint_does_not_exist(res) + if (use_unversioned) { + res <- self$connect$GET(unversioned_url("applications", self$content$guid, "jobs"), parser = NULL) + } + self$connect$raise_error(res) + parsed <- httr::content(res, as = "parsed") + if (use_unversioned) { + # The unversioned endpoint does not contain a `status` field. Its field + # `finalized` is `FALSE` corresponds to active jobs. The `finalized` + # field is dropped during parsing. + parsed <- purrr::modify_if(parsed, ~ isFALSE(.x$finalized), function(x) { + x$status <- 0 + x + }) + } + parsed }, #' @description Return a single job for this content. #' @param key The job key. @@ -588,26 +602,73 @@ content_ensure <- function( #' Get Jobs #' -#' `r lifecycle::badge('experimental')` Retrieve details about jobs associated with a `content_item`. -#' "Jobs" in Posit Connect are content executions +#' Retrieve details about server processes associated with a `content_item`, +#' such as a FastAPI app or a Quarto render. +#' +#' Note that Connect versions below 2022.10.0 use a legacy endpoint, and will +#' not return the complete set of information provided by newer versions. #' #' @param content A Content object, as returned by `content_item()` -#' @param key The key for a job #' -#' @rdname jobs +#' @return A data frame with a row for each job, with the following columns: +#' +#' - `id`: The job identifier. +#' - `ppid`: The job's parent process identifier (see Note 1). +#' - `pid`: The job's process identifier. +#' - `key`: The job's unique key identifier. +#' - `remote_id`: The job's identifier for off-host execution configurations +#' (see Note 1). +#' - `app_id`: The job's parent content identifier +#' - `variant_id`: The identifier of the variant owning this job. +#' - `bundle_id`: The identifier of a content bundle linked to this job. +#' - `start_time`: The timestamp (RFC3339) indicating when this job started. +#' - `end_time`: The timestamp (RFC3339) indicating when this job finished. +#' - `last_heartbeat_time`: The timestamp (RFC3339) indicating the last time +#' this job was observed to be running (see Note 1). +#' - `queued_time`: The timestamp (RFC3339) indicating when this job was added +#' to the queue to be processed. Only scheduled reports will present a value +#' for this field (see Note 1). +#' - `queue_name`: The name of the queue which processes the job. Only +#' scheduled reports will present a value for this field (see Note 1). +#' - `tag`: A tag to identify the nature of the job. +#' - `exit_code`: The job's exit code. Present only when job is finished. +#' - `status`: The current status of the job. On Connect 2022.10.0 and newer, +#' one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: +#' 0, otherwise `NA`. +#' - `hostname`: The name of the node which processes the job. +#' - `cluster`: The location where this content runs. Content running on the +#' same server as Connect will have either a null value or the string Local. +#' Gives the name of the cluster when run external to the Connect host +#' (see Note 1). +#' - `image`: The location where this content runs. Content running on +#' the same server as Connect will have either a null value or the string +#' Local. References the name of the target image when content runs in +#' a clustered environment such as Kubernetes (see Note 1). +#' - `run_as`: The UNIX user that executed this job. +#' +#' @note +#' 1. On Connect instances earlier than 2022.10.0, these columns will contain `NA` values. +#' +#' @family job functions #' @family content functions #' @export get_jobs <- function(content) { - warn_experimental("get_jobs") - scoped_experimental_silence() validate_R6_class(content, "Content") jobs <- content$jobs() - parse_connectapi_typed(jobs, connectapi_ptypes$jobs) + parse_connectapi_typed(jobs, connectapi_ptypes$jobs, order_columns = TRUE) } # TODO: Need to test `logged_error` on a real error -#' @rdname jobs +#' +#' Retrieve details about a server process +#' associated with a `content_item`, such as a FastAPI app or a Quarto render. +#' +#' @param content A Content object, as returned by `content_item()` +#' @param key The key for a job +#' +#' @family job functions +#' @family content functions #' @export get_job <- function(content, key) { warn_experimental("get_job") diff --git a/R/parse.R b/R/parse.R index 9b7af67ba..7152125db 100644 --- a/R/parse.R +++ b/R/parse.R @@ -27,7 +27,7 @@ make_timestamp <- function(input) { safe_format(input, "%Y-%m-%dT%H:%M:%SZ", tz = "UTC", usetz = FALSE) } -ensure_columns <- function(.data, ptype) { +ensure_columns <- function(.data, ptype, order_columns = FALSE) { # Given a prototype, ensure that all columns are present and cast to the correct type. # If a column is missing in .data, it will be created with all missing values of the correct type. # If a column is present in both, it will be cast to the correct type. @@ -35,6 +35,11 @@ ensure_columns <- function(.data, ptype) { for (i in names(ptype)) { .data <- ensure_column(.data, ptype[[i]], i) } + + if (order_columns) { + .data <- .data[, unique(c(names(ptype), names(.data))), drop = FALSE] + } + .data } @@ -59,14 +64,14 @@ ensure_column <- function(data, default, name) { if (inherits(default, "list") && !inherits(col, "list")) { col <- list(col) } - col <- vctrs::vec_cast(col, default) + col <- vctrs::vec_cast(col, default, x_arg = name) } data[[name]] <- col data } -parse_connectapi_typed <- function(data, ptype) { - ensure_columns(parse_connectapi(data), ptype) +parse_connectapi_typed <- function(data, ptype, order_columns = FALSE) { + ensure_columns(parse_connectapi(data), ptype, order_columns) } parse_connectapi <- function(data) { @@ -185,6 +190,10 @@ tzone <- function(x) { attr(x, "tzone")[[1]] %||% "" } +vec_cast.character.integer <- function(x, to, ...) { # nolint: object_name_linter + as.character(x) +} + new_datetime <- function(x = double(), tzone = "") { tzone <- tzone %||% "" if (is.integer(x)) { diff --git a/R/ptype.R b/R/ptype.R index 836cac1be..f8a255498 100644 --- a/R/ptype.R +++ b/R/ptype.R @@ -161,20 +161,26 @@ connectapi_ptypes <- list( variant_key = NA_character_, ), jobs = tibble::tibble( - id = NA_integer_, - pid = NA_integer_, + id = NA_character_, + ppid = NA_character_, + pid = NA_character_, key = NA_character_, - app_id = NA_integer_, - app_guid = NA_character_, - variant_id = NA_integer_, - bundle_id = NA_integer_, + remote_id = NA_character_, + app_id = NA_character_, + variant_id = NA_character_, + bundle_id = NA_character_, start_time = NA_datetime_, end_time = NA_datetime_, + last_heartbeat_time = NA_datetime_, + queued_time = NA_datetime_, + queue_name = NA_character_, tag = NA_character_, exit_code = NA_integer_, - finalized = NA, + status = NA_integer_, hostname = NA_character_, - variant_key = NA_character_ + cluster = NA_character_, + image = NA_character_, + run_as = NA_character_, ), job = tibble::tibble( pid = NA_integer_, diff --git a/R/utils.R b/R/utils.R index 444b6243c..3a98fab28 100644 --- a/R/utils.R +++ b/R/utils.R @@ -186,3 +186,13 @@ token_hex <- function(n) { raw <- as.raw(sample(0:255, n, replace = TRUE)) paste(as.character(raw), collapse = "") } + +# Checks to see if an http status contains a 404 error, and that that 404 +# response does not contain an error code indicating that an endpoint was called +# but encountered a 404 for some other reason. +endpoint_does_not_exist <- function(res) { + return( + httr::status_code(res) == "404" && + !("code" %in% names(httr::content(res, as = "parsed"))) + ) +} diff --git a/man/Content.Rd b/man/Content.Rd index 6535367eb..4b2a04696 100644 --- a/man/Content.Rd +++ b/man/Content.Rd @@ -248,7 +248,7 @@ Return the URL for this content in the Posit Connect dashboard. \if{html}{\out{}} \if{latex}{\out{\hypertarget{method-Content-jobs}{}}} \subsection{Method \code{jobs()}}{ -Return the jobs for this content. +Return the jobs for this content \subsection{Usage}{ \if{html}{\out{
}}\preformatted{Content$jobs()}\if{html}{\out{
}} } diff --git a/man/content_delete.Rd b/man/content_delete.Rd index 6bf767e56..e2f412da5 100644 --- a/man/content_delete.Rd +++ b/man/content_delete.Rd @@ -32,6 +32,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_item.Rd b/man/content_item.Rd index a0d90fcc6..2a5594c08 100644 --- a/man/content_item.Rd +++ b/man/content_item.Rd @@ -38,6 +38,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_title.Rd b/man/content_title.Rd index 7b6b83f43..1ae5be076 100644 --- a/man/content_title.Rd +++ b/man/content_title.Rd @@ -34,6 +34,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/content_update.Rd b/man/content_update.Rd index a0e271361..9bb62f685 100644 --- a/man/content_update.Rd +++ b/man/content_update.Rd @@ -54,6 +54,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/create_random_name.Rd b/man/create_random_name.Rd index e00dc7998..0b02f0d6b 100644 --- a/man/create_random_name.Rd +++ b/man/create_random_name.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/dashboard_url.Rd b/man/dashboard_url.Rd index e3dc08a8b..c224c3452 100644 --- a/man/dashboard_url.Rd +++ b/man/dashboard_url.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/dashboard_url_chr.Rd b/man/dashboard_url_chr.Rd index 166a160c6..aa16dcb36 100644 --- a/man/dashboard_url_chr.Rd +++ b/man/dashboard_url_chr.Rd @@ -34,6 +34,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/delete_thumbnail.Rd b/man/delete_thumbnail.Rd index 4cfbd83fe..2cb6fee1a 100644 --- a/man/delete_thumbnail.Rd +++ b/man/delete_thumbnail.Rd @@ -42,6 +42,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/delete_vanity_url.Rd b/man/delete_vanity_url.Rd index 64dc15b35..14ba10a17 100644 --- a/man/delete_vanity_url.Rd +++ b/man/delete_vanity_url.Rd @@ -26,6 +26,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/deploy_repo.Rd b/man/deploy_repo.Rd index 3a986c903..a0b610ecb 100644 --- a/man/deploy_repo.Rd +++ b/man/deploy_repo.Rd @@ -69,6 +69,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/environment.Rd b/man/environment.Rd index 944b24555..5b5dc91c9 100644 --- a/man/environment.Rd +++ b/man/environment.Rd @@ -51,6 +51,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_bundles}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/get_bundles.Rd b/man/get_bundles.Rd index e59af1143..5518b40dd 100644 --- a/man/get_bundles.Rd +++ b/man/get_bundles.Rd @@ -31,6 +31,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, @@ -57,6 +58,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/get_image.Rd b/man/get_image.Rd index 154757802..b426c256a 100644 --- a/man/get_image.Rd +++ b/man/get_image.Rd @@ -41,6 +41,7 @@ Other content functions: \code{\link{deploy_repo}()}, \code{\link{get_bundles}()}, \code{\link{get_environment}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/jobs.Rd b/man/get_job.Rd similarity index 74% rename from man/jobs.Rd rename to man/get_job.Rd index c3dcc17e5..044b5b042 100644 --- a/man/jobs.Rd +++ b/man/get_job.Rd @@ -1,12 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/content.R -\name{get_jobs} -\alias{get_jobs} +\name{get_job} \alias{get_job} -\title{Get Jobs} +\title{Retrieve details about a server process +associated with a \code{content_item}, such as a FastAPI app or a Quarto render.} \usage{ -get_jobs(content) - get_job(content, key) } \arguments{ @@ -15,10 +13,13 @@ get_job(content, key) \item{key}{The key for a job} } \description{ -\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} Retrieve details about jobs associated with a \code{content_item}. -"Jobs" in Posit Connect are content executions +Retrieve details about a server process +associated with a \code{content_item}, such as a FastAPI app or a Quarto render. } \seealso{ +Other job functions: +\code{\link{get_jobs}()} + Other content functions: \code{\link{content_delete}()}, \code{\link{content_item}()}, @@ -33,6 +34,7 @@ Other content functions: \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}}, @@ -46,3 +48,4 @@ Other content functions: \code{\link{verify_content_name}()} } \concept{content functions} +\concept{job functions} diff --git a/man/get_jobs.Rd b/man/get_jobs.Rd new file mode 100644 index 000000000..d29cf1b1f --- /dev/null +++ b/man/get_jobs.Rd @@ -0,0 +1,95 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/content.R +\name{get_jobs} +\alias{get_jobs} +\title{Get Jobs} +\usage{ +get_jobs(content) +} +\arguments{ +\item{content}{A Content object, as returned by \code{content_item()}} +} +\value{ +A data frame with a row for each job, with the following columns: +\itemize{ +\item \code{id}: The job identifier. +\item \code{ppid}: The job's parent process identifier (see Note 1). +\item \code{pid}: The job's process identifier. +\item \code{key}: The job's unique key identifier. +\item \code{remote_id}: The job's identifier for off-host execution configurations +(see Note 1). +\item \code{app_id}: The job's parent content identifier +\item \code{variant_id}: The identifier of the variant owning this job. +\item \code{bundle_id}: The identifier of a content bundle linked to this job. +\item \code{start_time}: The timestamp (RFC3339) indicating when this job started. +\item \code{end_time}: The timestamp (RFC3339) indicating when this job finished. +\item \code{last_heartbeat_time}: The timestamp (RFC3339) indicating the last time +this job was observed to be running (see Note 1). +\item \code{queued_time}: The timestamp (RFC3339) indicating when this job was added +to the queue to be processed. Only scheduled reports will present a value +for this field (see Note 1). +\item \code{queue_name}: The name of the queue which processes the job. Only +scheduled reports will present a value for this field (see Note 1). +\item \code{tag}: A tag to identify the nature of the job. +\item \code{exit_code}: The job's exit code. Present only when job is finished. +\item \code{status}: The current status of the job. On Connect 2022.10.0 and newer, +one of Active: 0, Finished: 1, Finalized: 2; on earlier versions, Active: +0, otherwise \code{NA}. +\item \code{hostname}: The name of the node which processes the job. +\item \code{cluster}: The location where this content runs. Content running on the +same server as Connect will have either a null value or the string Local. +Gives the name of the cluster when run external to the Connect host +(see Note 1). +\item \code{image}: The location where this content runs. Content running on +the same server as Connect will have either a null value or the string +Local. References the name of the target image when content runs in +a clustered environment such as Kubernetes (see Note 1). +\item \code{run_as}: The UNIX user that executed this job. +} +} +\description{ +Retrieve details about server processes associated with a \code{content_item}, +such as a FastAPI app or a Quarto render. +} +\details{ +Note that Connect versions below 2022.10.0 use a legacy endpoint, and will +not return the complete set of information provided by newer versions. +} +\note{ +\enumerate{ +\item On Connect instances earlier than 2022.10.0, these columns will contain \code{NA} values. +} +} +\seealso{ +Other job functions: +\code{\link{get_job}()} + +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_job}()}, +\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{job functions} diff --git a/man/get_thumbnail.Rd b/man/get_thumbnail.Rd index 80ec48e3c..2bcdf8f9d 100644 --- a/man/get_thumbnail.Rd +++ b/man/get_thumbnail.Rd @@ -48,6 +48,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_vanity_url}()}, \code{\link{git}}, diff --git a/man/get_vanity_url.Rd b/man/get_vanity_url.Rd index b90dcbf92..252bc7c09 100644 --- a/man/get_vanity_url.Rd +++ b/man/get_vanity_url.Rd @@ -30,6 +30,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{git}}, diff --git a/man/git.Rd b/man/git.Rd index dbcf4b92c..0c8a03f7f 100644 --- a/man/git.Rd +++ b/man/git.Rd @@ -55,6 +55,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/has_thumbnail.Rd b/man/has_thumbnail.Rd index 696569057..8fb4b96d9 100644 --- a/man/has_thumbnail.Rd +++ b/man/has_thumbnail.Rd @@ -44,6 +44,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/permissions.Rd b/man/permissions.Rd index 05835eda3..519fc6540 100644 --- a/man/permissions.Rd +++ b/man/permissions.Rd @@ -77,6 +77,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_image.Rd b/man/set_image.Rd index b5e644ba3..3007d1793 100644 --- a/man/set_image.Rd +++ b/man/set_image.Rd @@ -48,6 +48,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_run_as.Rd b/man/set_run_as.Rd index 772594a1e..b1a26d707 100644 --- a/man/set_run_as.Rd +++ b/man/set_run_as.Rd @@ -50,6 +50,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_thumbnail.Rd b/man/set_thumbnail.Rd index 0d3caf601..013003c16 100644 --- a/man/set_thumbnail.Rd +++ b/man/set_thumbnail.Rd @@ -47,6 +47,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/set_vanity_url.Rd b/man/set_vanity_url.Rd index 854288056..f0ae9b097 100644 --- a/man/set_vanity_url.Rd +++ b/man/set_vanity_url.Rd @@ -43,6 +43,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/swap_vanity_url.Rd b/man/swap_vanity_url.Rd index 36de9bf0d..98f087181 100644 --- a/man/swap_vanity_url.Rd +++ b/man/swap_vanity_url.Rd @@ -29,6 +29,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/man/verify_content_name.Rd b/man/verify_content_name.Rd index 03abe180e..8610e9813 100644 --- a/man/verify_content_name.Rd +++ b/man/verify_content_name.Rd @@ -35,6 +35,7 @@ Other content functions: \code{\link{get_bundles}()}, \code{\link{get_environment}()}, \code{\link{get_image}()}, +\code{\link{get_job}()}, \code{\link{get_jobs}()}, \code{\link{get_thumbnail}()}, \code{\link{get_vanity_url}()}, diff --git a/tests/testthat/2024.07.0/README.md b/tests/testthat/2024.07.0/README.md new file mode 100644 index 000000000..185124ace --- /dev/null +++ b/tests/testthat/2024.07.0/README.md @@ -0,0 +1 @@ +This dir for mocks that force the use of legacy, unversioned APIs. \ No newline at end of file diff --git a/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json new file mode 100644 index 000000000..cf4fbdad5 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/job/QBZ5jkKfmf9iT9k8.json @@ -0,0 +1,18 @@ +{ + "ppid": 2509183, + "pid": 2509199, + "key": "QBZ5jkKfmf9iT9k8", + "app_id": 52389, + "variant_id": 0, + "bundle_id": 127015, + "tag": "run_app", + "finalized": true, + "hostname": "dogfood02", + "origin": "0001-01-01T00:00:00Z", + "stdout": "2024/11/25 22:17:43.386988748 Running on host: dogfood02\n2024/11/25 22:17:43.387008093 Process ID: 2509199\n2024/11/25 22:17:43.430980380 Linux distribution: Ubuntu 22.04.2 LTS (jammy)\n2024/11/25 22:17:43.441252960 Running as user: uid=1031(rstudio-connect) gid=999(rstudio-connect) groups=999(rstudio-connect)\n2024/11/25 22:17:43.441266159 Connect version: 2024.12.0-dev+41-g32b4bddee2\n2024/11/25 22:17:43.441426656 LANG: C.UTF-8\n2024/11/25 22:17:43.441433843 Working directory: /opt/rstudio-connect/mnt/app\n2024/11/25 22:17:43.442234301 Using R 4.3.1\n2024/11/25 22:17:43.442251215 R.home(): /opt/R/4.3.1/lib/R\n2024/11/25 22:17:43.447902588 Content will use associated Packrat library\n2024/11/25 22:17:43.582826876 Adding Packrat library to R_LIBS and .libPaths: /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1\n2024/11/25 22:17:43.583062744 R_LIBS: /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1\n2024/11/25 22:17:43.583081371 .libPaths(): /opt/rstudio-connect/mnt/app/packrat/lib/x86_64-pc-linux-gnu/4.3.1, /opt/R/4.3.1/lib/R/library\n2024/11/25 22:17:43.614560696 shiny version: 1.7.4\n2024/11/25 22:17:43.614602630 httpuv version: 1.6.11\n2024/11/25 22:17:43.614782708 rmarkdown version: (none)\n2024/11/25 22:17:43.614785084 knitr version: (none)\n2024/11/25 22:17:43.614794794 jsonlite version: 1.8.4\n2024/11/25 22:17:43.614795565 RJSONIO version: (none)\n2024/11/25 22:17:43.614802828 htmltools version: 0.5.5\n2024/11/25 22:17:43.614803648 reticulate version: (none)\n2024/11/25 22:17:43.615021102 Using pandoc: /opt/rstudio-connect/ext/pandoc/2.16\n2024/11/25 22:17:44.634166987 Using Shiny bookmarking base directory /opt/rstudio-connect/mnt/bookmarks\n2024/11/25 22:17:44.635399206 Shiny application starting ...\n", + "stderr": "2024/11/25 22:17:43.030795136 [rsc-session] Content GUID: 8f37d6e0\n2024/11/25 22:17:43.030842219 [rsc-session] Content ID: 52389\n2024/11/25 22:17:43.030849040 [rsc-session] Bundle ID: 127015\n2024/11/25 22:17:43.030854033 [rsc-session] Job Key: QBZ5jkKfmf9iT9k8\n2024/11/25 22:17:45.068540867 \n2024/11/25 22:17:45.068555326 Listening on http://127.0.0.1:33545\n", + "logged_error": null, + "exit_code": 0, + "start_time": 1732573062, + "end_time": null +} diff --git a/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json new file mode 100644 index 000000000..d71beee2b --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/applications/8f37d6e0/jobs.json @@ -0,0 +1,77 @@ +[ + { + "id": 40669829, + "pid": 1153321, + "key": "k3sHkEoWJNwQim7g", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1733268003, + "end_time": null, + "tag": "run_app", + "exit_code": null, + "finalized": false, + "hostname": "dogfood01" + }, + { + "id": 40097386, + "pid": 2516505, + "key": "mxPGVOMVk6f8dso2", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732573574, + "end_time": 1732577139, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 40096649, + "pid": 2509199, + "key": "QBZ5jkKfmf9iT9k8", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732573062, + "end_time": null, + "tag": "run_app", + "exit_code": null, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 40080413, + "pid": 2321354, + "key": "EzxM4sBYJrLSMHg9", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1732553145, + "end_time": 1732556770, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + }, + { + "id": 39368207, + "pid": 2434200, + "key": "HbdzgOJrMmMTq6vu", + "app_id": 52389, + "app_guid": "8f37d6e0", + "variant_id": 0, + "bundle_id": 127015, + "start_time": 1731690180, + "end_time": 1731690383, + "tag": "run_app", + "exit_code": 0, + "finalized": true, + "hostname": "dogfood02" + } +] diff --git a/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R new file mode 100644 index 000000000..abece4989 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs.R @@ -0,0 +1,8 @@ +structure( + list( + url = "__api__/v1/content/8f37d6e0", + status_code = 404L, content = charToRaw("404 page not found"), + headers = structure(list(`content-type` = "text/plain"), class = "insensitive") + ), + class = "response" +) diff --git a/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R new file mode 100644 index 000000000..762c94f22 --- /dev/null +++ b/tests/testthat/2024.07.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.R @@ -0,0 +1,8 @@ +structure( + list( + url = "__api__/v1/content/01234567/thumbnail", + status_code = 404L, content = charToRaw("404 page not found"), + headers = structure(list(`content-type` = "text/plain"), class = "insensitive") + ), + class = "response" +) diff --git a/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json new file mode 100644 index 000000000..eb81c29b8 --- /dev/null +++ b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs.json @@ -0,0 +1,112 @@ +[ + { + "id": "40669829", + "ppid": "1153303", + "pid": "1153321", + "key": "k3sHkEoWJNwQim7g", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-12-03T23:20:03Z", + "end_time": null, + "last_heartbeat_time": "2024-12-03T23:20:13Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 0, + "hostname": "dogfood01", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40097386", + "ppid": "2516489", + "pid": "2516505", + "key": "mxPGVOMVk6f8dso2", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:26:14Z", + "end_time": "2024-11-25T23:25:39Z", + "last_heartbeat_time": "2024-11-25T23:25:36Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40096649", + "ppid": "2509183", + "pid": "2509199", + "key": "QBZ5jkKfmf9iT9k8", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:17:42Z", + "end_time": null, + "last_heartbeat_time": "2024-11-25T22:20:23Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "40080413", + "ppid": "2321337", + "pid": "2321354", + "key": "EzxM4sBYJrLSMHg9", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T16:45:45Z", + "end_time": "2024-11-25T17:46:10Z", + "last_heartbeat_time": "2024-11-25T17:46:08Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + }, + { + "id": "39368207", + "ppid": "2434183", + "pid": "2434200", + "key": "HbdzgOJrMmMTq6vu", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-15T17:03:00Z", + "end_time": "2024-11-15T17:06:23Z", + "last_heartbeat_time": "2024-11-15T17:06:20Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": 0, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" + } +] diff --git a/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json new file mode 100644 index 000000000..b3d6bd65b --- /dev/null +++ b/tests/testthat/2024.08.0/__api__/v1/content/8f37d6e0/jobs/QBZ5jkKfmf9iT9k8.json @@ -0,0 +1,22 @@ +{ + "id": "40096649", + "ppid": "2509183", + "pid": "2509199", + "key": "QBZ5jkKfmf9iT9k8", + "remote_id": null, + "app_id": "52389", + "variant_id": "0", + "bundle_id": "127015", + "start_time": "2024-11-25T22:17:42Z", + "end_time": null, + "last_heartbeat_time": "2024-11-25T22:20:23Z", + "queued_time": null, + "queue_name": null, + "tag": "run_app", + "exit_code": null, + "status": 2, + "hostname": "dogfood02", + "cluster": null, + "image": null, + "run_as": "rstudio-connect" +} diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index e26d202bc..2dc90e074 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -241,3 +241,32 @@ with_mock_api({ expect_identical(v$key, "WrEKKa77") }) }) + +# jobs ----- + +test_that("get_jobs() using the old and new endpoints returns sensible results", { + with_mock_api({ + client <- Connect$new(server = "http://connect.example", api_key = "not-a-key") + item <- content_item(client, "8f37d6e0") + jobs_v1 <- get_jobs(item) + TRUE + }) + + with_mock_dir("2024.07.0", { + jobs_v0 <- get_jobs(item) + }) + + # Columns we expect to be identical + common_cols <- c( + "id", "pid", "key", "app_id", "variant_id", "bundle_id", "start_time", + "end_time", "tag", "exit_code", "hostname" + ) + expect_equal( + jobs_v1[common_cols], + jobs_v1[common_cols] + ) + + # Status columns line up as expected + expect_equal(jobs_v1$status, c(0L, 2L, 2L, 2L, 2L)) + expect_equal(jobs_v0$status, c(0L, NA, NA, NA, NA)) +})