From c18190122e90c406d300c640823b3c1e6475016a Mon Sep 17 00:00:00 2001 From: Florian Mayer Date: Sun, 10 Mar 2024 13:21:52 +0800 Subject: [PATCH] WIP #152: dataset_detail --- NAMESPACE | 1 + R/dataset_detail.R | 88 ++++++++++++++++++++ man/dataset_detail.Rd | 117 +++++++++++++++++++++++++++ man/dataset_list.Rd | 3 + tests/testthat/test-dataset_detail.R | 64 +++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 R/dataset_detail.R create mode 100644 man/dataset_detail.Rd create mode 100644 tests/testthat/test-dataset_detail.R diff --git a/NAMESPACE b/NAMESPACE index fb76ca1..94ebb94 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,7 @@ export(attachment_get) export(attachment_link) export(attachment_list) export(audit_get) +export(dataset_detail) export(dataset_list) export(drop_null_coords) export(encryption_key_list) diff --git a/R/dataset_detail.R b/R/dataset_detail.R new file mode 100644 index 0000000..ad242cc --- /dev/null +++ b/R/dataset_detail.R @@ -0,0 +1,88 @@ +#' List all datasets of one project. +#' +#' While the API endpoint will return all datasets for one project, +#' \code{\link{dataset_list}} will fail with incorrect or missing +#' authentication. +#' +#' A Dataset is a named collection of Entities that have the same properties. +#' A Dataset can be linked to Forms as Attachments. This will make it available +#' to clients as an automatically-updating CSV. +#' +#' This function is supported from ODK Central v2022.3 and will warn if the +#' given odkc_version is lower. +#' +#' `r lifecycle::badge("maturing")` +#' +#' @template param-pid +#' @param did (chr) The dataset name. +#' @template param-url +#' @template param-auth +#' @template param-retries +#' @template param-odkcv +#' @template param-orders +#' @template param-tz +#' @return A list of lists following the exact format and naming of the API +#' response. Since this nested list is so deeply nested and irregularly shaped +#' it is not trivial to rectangle the result into a tibble. +# nolint start +#' @seealso \url{ https://docs.getodk.org/central-api-dataset-management/#datasets} +# nolint end +#' @family dataset-management +#' @export +#' @examples +#' \dontrun{ +#' # See vignette("setup") for setup and authentication options +#' # ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...") +#' +#' ds <- dataset_list(pid = get_default_pid()) +#' ds1 <- dataset_detail(pid = get_default_pid(), did = ds$name[1]) +#' +#' ds1 |> listviewer::jsonedit() +#' ds1$linkedForms |> purrr::list_transpose() |> tibble::as_tibble() +#' ds1$sourceForms |> purrr::list_transpose() |> tibble::as_tibble() +#' ds1$properties |> purrr::list_transpose() |> tibble::as_tibble() +#' } +dataset_detail <- function(pid = get_default_pid(), + did = NULL, + url = get_default_url(), + un = get_default_un(), + pw = get_default_pw(), + retries = get_retries(), + odkc_version = get_default_odkc_version(), + orders = c( + "YmdHMS", + "YmdHMSz", + "Ymd HMS", + "Ymd HMSz", + "Ymd", + "ymd" + ), + tz = get_default_tz()) { + yell_if_missing(url, un, pw, pid = pid) + + if (is.null(did)) + ru_msg_abort("dataset_detail requires the dataset names as 'did=\"name\"'.") + + if (odkc_version |> semver_lt("2022.3")) { + ru_msg_warn("dataset_detail is supported from v2022.3") + } + + ds <- httr::RETRY( + "GET", + httr::modify_url(url, + path = glue::glue( + "v1/projects/{pid}/datasets/", + "{URLencode(did, reserved = TRUE)}" + )), + httr::add_headers( + "Accept" = "application/json", + "X-Extended-Metadata" = "true" + ), + httr::authenticate(un, pw), + times = retries + ) |> + yell_if_error(url, un, pw) |> + httr::content(encoding="utf-8") +} + +# usethis::use_test("dataset_detail") # nolint diff --git a/man/dataset_detail.Rd b/man/dataset_detail.Rd new file mode 100644 index 0000000..26c202a --- /dev/null +++ b/man/dataset_detail.Rd @@ -0,0 +1,117 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dataset_detail.R +\name{dataset_detail} +\alias{dataset_detail} +\title{List all datasets of one project.} +\usage{ +dataset_detail( + pid = get_default_pid(), + did = NULL, + url = get_default_url(), + un = get_default_un(), + pw = get_default_pw(), + retries = get_retries(), + odkc_version = get_default_odkc_version(), + orders = c("YmdHMS", "YmdHMSz", "Ymd HMS", "Ymd HMSz", "Ymd", "ymd"), + tz = get_default_tz() +) +} +\arguments{ +\item{pid}{The numeric ID of the project, e.g.: 2. + +Default: \code{\link{get_default_pid}}. + +Set default \code{pid} through \code{ru_setup(pid="...")}. + +See \code{vignette("Setup", package = "ruODK")}.} + +\item{did}{(chr) The dataset name.} + +\item{url}{The ODK Central base URL without trailing slash. + +Default: \code{\link{get_default_url}}. + +Set default \code{url} through \code{ru_setup(url="...")}. + +See \code{vignette("Setup", package = "ruODK")}.} + +\item{un}{The ODK Central username (an email address). +Default: \code{\link{get_default_un}}. +Set default \code{un} through \code{ru_setup(un="...")}. +See \code{vignette("Setup", package = "ruODK")}.} + +\item{pw}{The ODK Central password. +Default: \code{\link{get_default_pw}}. +Set default \code{pw} through \code{ru_setup(pw="...")}. +See \code{vignette("Setup", package = "ruODK")}.} + +\item{retries}{The number of attempts to retrieve a web resource. + +This parameter is given to \code{\link[httr]{RETRY}(times = retries)}. + +Default: 3.} + +\item{odkc_version}{The ODK Central version as a semantic version string +(year.minor.patch), e.g. "2023.5.1". The version is shown on ODK Central's +version page \verb{/version.txt}. Discard the "v". +\code{ruODK} uses this parameter to adjust for breaking changes in ODK Central. + +Default: \code{\link{get_default_odkc_version}} or "2023.5.1" if unset. + +Set default \code{get_default_odkc_version} through +\code{ru_setup(odkc_version="2023.5.1")}. + +See \code{vignette("Setup", package = "ruODK")}.} + +\item{orders}{(vector of character) Orders of datetime elements for +lubridate. + +Default: +\code{c("YmdHMS", "YmdHMSz", "Ymd HMS", "Ymd HMSz", "Ymd", "ymd")}.} + +\item{tz}{A timezone to convert dates and times to. + +Read \code{vignette("setup", package = "ruODK")} to learn how \code{ruODK}'s +timezone can be set globally or per function.} +} +\value{ +A list of lists following the exact format and naming of the API +response. Since this nested list is so deeply nested and irregularly shaped +it is not trivial to rectangle the result into a tibble. +} +\description{ +While the API endpoint will return all datasets for one project, +\code{\link{dataset_list}} will fail with incorrect or missing +authentication. +} +\details{ +A Dataset is a named collection of Entities that have the same properties. +A Dataset can be linked to Forms as Attachments. This will make it available +to clients as an automatically-updating CSV. + +This function is supported from ODK Central v2022.3 and will warn if the +given odkc_version is lower. + +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#maturing}{\figure{lifecycle-maturing.svg}{options: alt='[Maturing]'}}}{\strong{[Maturing]}} +} +\examples{ +\dontrun{ +# See vignette("setup") for setup and authentication options +# ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...") + +ds <- dataset_list(pid = get_default_pid()) +ds1 <- dataset_detail(pid = get_default_pid(), did = ds$name[1]) + +ds1 |> listviewer::jsonedit() +ds1$linkedForms |> purrr::list_transpose() |> tibble::as_tibble() +ds1$sourceForms |> purrr::list_transpose() |> tibble::as_tibble() +ds1$properties |> purrr::list_transpose() |> tibble::as_tibble() +} +} +\seealso{ +\url{ https://docs.getodk.org/central-api-dataset-management/#datasets} + +Other dataset-management: +\code{\link{dataset_list}()} +} +\concept{dataset-management} diff --git a/man/dataset_list.Rd b/man/dataset_list.Rd index cd4a324..b88ad8e 100644 --- a/man/dataset_list.Rd +++ b/man/dataset_list.Rd @@ -103,5 +103,8 @@ ds |> knitr::kable() } \seealso{ \url{ https://docs.getodk.org/central-api-dataset-management/#datasets} + +Other dataset-management: +\code{\link{dataset_detail}()} } \concept{dataset-management} diff --git a/tests/testthat/test-dataset_detail.R b/tests/testthat/test-dataset_detail.R new file mode 100644 index 0000000..fcee8c3 --- /dev/null +++ b/tests/testthat/test-dataset_detail.R @@ -0,0 +1,64 @@ +test_that("dataset_detail works", { + + ds <- dataset_list(pid = get_default_pid(), + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = get_test_odkc_version()) + + ds1 <- dataset_detail(pid = get_default_pid(), + did = ds$name[1], + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = get_test_odkc_version()) + + # dataset_detail returns a list + testthat::expect_is(ds1, "list") + + # linkedForms contain form xmlFormId and name + lf <- ds1$linkedForms |> purrr::list_transpose() |> tibble::as_tibble() + testthat::expect_equal(names(lf), c("xmlFormId", "name" )) + + # sourceForms contain form xmlFormId and name + sf <- ds1$sourceForms |> purrr::list_transpose() |> tibble::as_tibble() + testthat::expect_equal(names(sf), c("xmlFormId", "name" )) + + # properties lists attributes of entities + pr <- ds1$properties |> purrr::list_transpose() |> tibble::as_tibble() + testthat::expect_equal(names(pr), c("name", "publishedAt", "odataName", "forms")) +}) + + +test_that("dataset_detail warns if odkc_version too low", { + skip_if(Sys.getenv("ODKC_TEST_URL") == "", + message = "Test server not configured" + ) + + ds <- dataset_list(pid = get_default_pid(), + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = "2022.3") + + ds1 <- dataset_detail( + pid = get_test_pid(), + did = ds$name[1], + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = "2022.3" + ) + + testthat::expect_warning( + ds1 <- dataset_detail( + pid = get_test_pid(), + did = ds$name[1], + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = "1.5.3" + ) + ) + +}) \ No newline at end of file