diff --git a/NEWS.md b/NEWS.md index 0624c691..e06fb0dc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,8 @@ server: `create_integration()`, `update_integration()` and `delete_integration()`. (#434) - Support content search API with the `search_content()` function. (#272) +- New `search_content()` function which lets you search and filter content items + on the Connect server. (#272, #447) # connectapi 0.8.0 diff --git a/R/content.R b/R/content.R index b7c04cab..4ba1ae6d 100644 --- a/R/content.R +++ b/R/content.R @@ -1441,16 +1441,13 @@ get_content_packages <- function(content) { #' @param q The search query, using the syntax described in the Connect #' documentation on [content search #' terms](https://docs.posit.co/connect/user/viewing-content/#searching-content) -#' @param page_number Integer. The page to return relative to the given `page_size`. -#' Must be greater than 0. -#' @param page_size Integer. The number of items to include in each page. This -#' parameter is "best effort" since there may not be enough results to honor the -#' request. If `page_size` is less than 1 or greater than 500, an error will be -#' returned. #' @param include Comma-separated character string of values indicating additional #' details to include in the response. Values can be `owner` and `vanity_url`; #' both are included by default. -#' @param ... Extra arguments. Currently not used. +#' @param ... Extra arguments. Passing in `page_number` and `page_size` will +#' affect the internal pagination for Connect's content search API. Setting +#' `page_number` will change the page at which pagination *starts*, and +#' `page_size` will control the size of pages (max 500). #' #' @return #' A list containing sub-fields: @@ -1587,13 +1584,32 @@ get_content_packages <- function(content) { search_content <- function( client, q = NULL, - page_number = 1, - page_size = 500, include = "owner,vanity_url", ... ) { error_if_less_than(client$version, "2024.04.0") + page_offset( + client, + req = .search_content( + client, + q = q, + include = include, + # page_size and page_number can be passed in via `...`. Since this call is + # still passed to page_offset, page_number affects the *starting* page, + # but pagination still continues. + ... + ) + ) +} + +.search_content <- function( + client, + q, + page_number = 1, + page_size = 500, + include +) { path <- v1_url("search", "content") query <- list( diff --git a/man/search_content.Rd b/man/search_content.Rd index 5fa2b507..6f3881b2 100644 --- a/man/search_content.Rd +++ b/man/search_content.Rd @@ -4,14 +4,7 @@ \alias{search_content} \title{Search for content on the Connect server} \usage{ -search_content( - client, - q = NULL, - page_number = 1, - page_size = 500, - include = "owner,vanity_url", - ... -) +search_content(client, q = NULL, include = "owner,vanity_url", ...) } \arguments{ \item{client}{A Connect object} @@ -19,19 +12,14 @@ search_content( \item{q}{The search query, using the syntax described in the Connect documentation on \href{https://docs.posit.co/connect/user/viewing-content/#searching-content}{content search terms}} -\item{page_number}{Integer. The page to return relative to the given \code{page_size}. -Must be greater than 0.} - -\item{page_size}{Integer. The number of items to include in each page. This -parameter is "best effort" since there may not be enough results to honor the -request. If \code{page_size} is less than 1 or greater than 500, an error will be -returned.} - \item{include}{Comma-separated character string of values indicating additional details to include in the response. Values can be \code{owner} and \code{vanity_url}; both are included by default.} -\item{...}{Extra arguments. Currently not used.} +\item{...}{Extra arguments. Passing in \code{page_number} and \code{page_size} will +affect the internal pagination for Connect's content search API. Setting +\code{page_number} will change the page at which pagination \emph{starts}, and +\code{page_size} will control the size of pages (max 500).} } \value{ A list containing sub-fields: diff --git a/tests/testthat/2025.09.0/__api__/v1/search/content-0f26be.json b/tests/testthat/2025.09.0/__api__/v1/search/content-0f26be.json index 5bdf3d30..7dfbeda0 100644 --- a/tests/testthat/2025.09.0/__api__/v1/search/content-0f26be.json +++ b/tests/testthat/2025.09.0/__api__/v1/search/content-0f26be.json @@ -1,117 +1,17 @@ +// Used in test-content.R { "total": 2, + "current_page": 1, "results": [ { "guid": "c9f68287", "name": "sea-bream-report", - "title": "sea bream report", - "description": "", - "access_type": "all", - "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, - "default_environment_guid": null, - "created_time": "2024-08-29T17:52:30Z", - "last_deployed_time": "2025-09-25T17:26:05Z", - "bundle_id": "306864", - "app_mode": "python-shiny", - "content_category": "", - "parameterized": false, - "environment_guid": null, - "cluster_name": "Local", - "image_name": null, - "r_version": null, - "py_version": "3.10.17", - "quarto_version": null, - "r_environment_management": null, - "default_r_environment_management": null, - "py_environment_management": true, - "default_py_environment_management": null, - "run_as": null, - "run_as_current_user": false, - "owner_guid": "c2250bb4", - "content_url": "https://connect.example/content/c9f68287/", - "dashboard_url": "https://connect.example/connect/#/apps/c9f68287", - "app_role": "none", - "id": "57138", - "owner": { - "guid": "c2250bb4", - "username": "toph_allen", - "first_name": "Toph", - "last_name": "Allen" - }, - "public_content_status": null + "title": "sea bream report" }, { "guid": "53032a0e", "name": "sea-bream-dashboard", - "title": "sea bream dashboard", - "description": "", - "access_type": "all", - "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, - "default_environment_guid": null, - "created_time": "2024-08-29T17:49:34Z", - "last_deployed_time": "2025-09-25T17:23:21Z", - "bundle_id": "306844", - "app_mode": "python-shiny", - "content_category": "", - "parameterized": false, - "environment_guid": null, - "cluster_name": "Local", - "image_name": null, - "r_version": null, - "py_version": "3.10.17", - "quarto_version": null, - "r_environment_management": null, - "default_r_environment_management": null, - "py_environment_management": true, - "default_py_environment_management": null, - "run_as": null, - "run_as_current_user": false, - "owner_guid": "c2250bb4", - "content_url": "https://connect.example/content/53032a0e/", - "dashboard_url": "https://connect.example/connect/#/apps/53032a0e", - "app_role": "none", - "id": "57137", - "owner": { - "guid": "c2250bb4", - "username": "toph_allen", - "first_name": "Toph", - "last_name": "Allen" - }, - "public_content_status": null + "title": "sea bream dashboard" } ] } diff --git a/tests/testthat/2025.09.0/__api__/v1/search/content-d37a6d.json b/tests/testthat/2025.09.0/__api__/v1/search/content-d37a6d.json new file mode 100644 index 00000000..f51e9959 --- /dev/null +++ b/tests/testthat/2025.09.0/__api__/v1/search/content-d37a6d.json @@ -0,0 +1,17 @@ +// Used in test-content.R +{ + "total": 3, + "current_page": 1, + "results": [ + { + "guid": "c9f68287", + "name": "blobfish-dashboard", + "title": "blobfish dashboard" + }, + { + "guid": "53032a0e", + "name": "blobfish-api", + "title": "blobfish api" + } + ] +} diff --git a/tests/testthat/2025.09.0/__api__/v1/search/content-eaf9f2.json b/tests/testthat/2025.09.0/__api__/v1/search/content-eaf9f2.json new file mode 100644 index 00000000..aa91191c --- /dev/null +++ b/tests/testthat/2025.09.0/__api__/v1/search/content-eaf9f2.json @@ -0,0 +1,12 @@ +// Used in test-content.R +{ + "total": 3, + "current_page": 2, + "results": [ + { + "guid": "abc12345", + "name": "blobfish-report", + "title": "blobfish report" + } + ] +} diff --git a/tests/testthat/test-content.R b/tests/testthat/test-content.R index 4047953e..1197f9b3 100644 --- a/tests/testthat/test-content.R +++ b/tests/testthat/test-content.R @@ -478,26 +478,50 @@ with_mock_dir("2025.09.0", { test_that("content search returns the expected list of content", { res <- search_content(client, q = "sea bream") expect_equal( - purrr::map_chr(res$results, "owner_guid"), - c("c2250bb4", "c2250bb4") + purrr::map_chr(res, "guid"), + c("c9f68287", "53032a0e") ) expect_equal( - purrr::map_chr(res$results, "title"), + purrr::map_chr(res, "title"), c("sea bream report", "sea bream dashboard") ) }) + test_that("content search fetches multiple pages correctly", { + res <- search_content(client, q = "blobfish") + expect_equal(length(res), 3) + expect_equal( + purrr::map_chr(res, "title"), + c("blobfish dashboard", "blobfish api", "blobfish report") + ) + }) + test_that("content search passes all parameters through correctly", { without_internet( expect_GET( search_content( client, q = "bream", - include = "owner", page_number = 2, - page_size = 5 + page_size = 20, + include = "owner" + ), + "https://connect.example/__api__/v1/search/content?q=bream&page_number=2&page_size=20&include=owner" + ) + ) + }) + + test_that("the inner .search_content() func calls the endpoint correctly", { + without_internet( + expect_GET( + .search_content( + client, + q = "bream", + page_number = 2, + page_size = 20, + include = "owner" ), - "https://connect.example/__api__/v1/search/content?q=bream&page_number=2&page_size=5&include=owner" + "https://connect.example/__api__/v1/search/content?q=bream&page_number=2&page_size=20&include=owner" ) ) })