From 057f65fbdbbefeb2f397f64bb6b7d23b10aa4589 Mon Sep 17 00:00:00 2001 From: Florian Mayer Date: Sat, 9 Mar 2024 14:35:36 +0800 Subject: [PATCH] WIP #152: dataset_list * Warn if ODKC version too low * Modernised parsing of httr::content() --- DESCRIPTION | 2 +- NAMESPACE | 1 + R/dataset_list.R | 82 ++++++++++++++++++++++ _pkgdown.yml | 8 +++ man/dataset_list.Rd | 107 +++++++++++++++++++++++++++++ tests/testthat/test-dataset_list.R | 56 +++++++++++++++ 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 R/dataset_list.R create mode 100644 man/dataset_list.Rd create mode 100644 tests/testthat/test-dataset_list.R diff --git a/DESCRIPTION b/DESCRIPTION index aa484401..28abf390 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: ruODK Title: An R Client for the ODK Central API -Version: 1.4.2 +Version: 1.5.0.9000 Authors@R: c(person(given = c("Florian", "W."), family = "Mayer", diff --git a/NAMESPACE b/NAMESPACE index 59b8e4c6..fb76ca1b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,6 +6,7 @@ export(attachment_get) export(attachment_link) export(attachment_list) export(audit_get) +export(dataset_list) export(drop_null_coords) export(encryption_key_list) export(enexpr) diff --git a/R/dataset_list.R b/R/dataset_list.R new file mode 100644 index 00000000..39c0204d --- /dev/null +++ b/R/dataset_list.R @@ -0,0 +1,82 @@ +#' 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 +#' @template param-url +#' @template param-auth +#' @template param-retries +#' @template param-odkcv +#' @template param-orders +#' @template param-tz +#' @return A tibble with exactly one row for each dataset of the given project +#' as per ODK Central API docs. +#' Column names are renamed from ODK's `camelCase` to `snake_case`. +# 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()) +#' +#' ds |> knitr::kable() +#' } +dataset_list <- function(pid = get_default_pid(), + 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 (odkc_version |> semver_lt("2022.3")) { + ru_msg_warn("dataset_list is supported from v2022.3") + } + + httr::RETRY( + "GET", + httr::modify_url(url, path = glue::glue("v1/projects/{pid}/datasets")), + 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") |> + purrr::list_transpose() |> + tibble::as_tibble() |> + janitor::clean_names() |> + dplyr::mutate_at( + dplyr::vars(c("created_at", "last_entity")), + ~ isodt_to_local(., orders = orders, tz = tz) + ) +} + +# usethis::use_test("dataset_list") # nolint diff --git a/_pkgdown.yml b/_pkgdown.yml index 0c8e5a6e..7dcf1c0b 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -23,6 +23,14 @@ reference: desc: "Functions to manage projects." contents: - has_concept("project-management") +- title: Datasets + desc: "Functions to manage datasets of entities." + contents: + - has_concept("dataset-management") +- title: Entities + desc: "Functions to manage entities." + contents: + - has_concept("entity-management") - title: Forms desc: > Functions to manage forms. diff --git a/man/dataset_list.Rd b/man/dataset_list.Rd new file mode 100644 index 00000000..cd4a324f --- /dev/null +++ b/man/dataset_list.Rd @@ -0,0 +1,107 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dataset_list.R +\name{dataset_list} +\alias{dataset_list} +\title{List all datasets of one project.} +\usage{ +dataset_list( + pid = get_default_pid(), + 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{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 tibble with exactly one row for each dataset of the given project +as per ODK Central API docs. +Column names are renamed from ODK's \code{camelCase} to \code{snake_case}. +} +\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()) + +ds |> knitr::kable() +} +} +\seealso{ +\url{ https://docs.getodk.org/central-api-dataset-management/#datasets} +} +\concept{dataset-management} diff --git a/tests/testthat/test-dataset_list.R b/tests/testthat/test-dataset_list.R new file mode 100644 index 00000000..02ff1e39 --- /dev/null +++ b/tests/testthat/test-dataset_list.R @@ -0,0 +1,56 @@ +test_that("dataset_list works", { + skip_if(Sys.getenv("ODKC_TEST_URL") == "", + message = "Test server not configured" + ) + + ds <- dataset_list( + get_test_pid(), + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = get_test_odkc_version() + ) + testthat::expect_true(nrow(ds) > 0) + testthat::expect_true("name" %in% names(ds)) + + # function returns a tibble + testthat::expect_s3_class(ds, "tbl_df") + + # Expected column names + cn <- c( + "name", + "created_at", + "project_id", + "approval_required", + "entities", + "last_entity", + "conflicts" + ) + testthat::expect_equal(names(ds), cn) +}) + + +test_that("dataset_list warns if odkc_version too low", { + skip_if(Sys.getenv("ODKC_TEST_URL") == "", + message = "Test server not configured" + ) + + ds <- dataset_list( + get_test_pid(), + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = "2022.3" + ) + + testthat::expect_warning( + ds <- dataset_list( + get_test_pid(), + url = get_test_url(), + un = get_test_un(), + pw = get_test_pw(), + odkc_version = "1.5.3" + ) + ) + +}) \ No newline at end of file