diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e9f4dd9..5a28b93 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -36,26 +36,41 @@ Git and GitHub.
For more general info about contributing to `ruODK`, see the
[Resources](#resources) at the end of this document.
+### Naming conventions
+ruODK names functions after ODK Central endpoints. If there are aliases, such as
+"Dataset" and "Entity List", choose the alias that is shown to Central users
+(here, choose "Entity List") over internally used terms.
+
+Function names combine the object name (`project`, `form`, `submission`,
+`attachment`, `entitylist`, `entity`, etc.) with the action (`list`, `detail`,
+`patch`) as snake case, e.g. `project_list()`.
+In case of any uncertainty, discussion is welcome.
+
+In contrast, `pyODK` uses a class based approach with the pluralised object name
+separated from the action `client.entity_lists.list()`.
+
+Documentation should capitalise ODK Central object names: Project, Form,
+Submission, Entity.
+
### Prerequisites
-To test the package, you will need valid credentials for the ODK Central instance
-used as a test server.
-Create an [account request issue](https://github.com/ropensci/ruODK/issues/new/choose).
+To test the package, you will need valid credentials for an existing ODK Central
+instance to be used as a test server.
Before you do a pull request, you should always file an issue and make sure
the maintainers agree that it is a problem, and is happy with your basic proposal
for fixing it.
If you have found a bug, follow the issue template to create a minimal
-[reprex](https://www.tidyverse.org/help/#reprex).
+[reprex](https://www.tidyverse.org/help/#reprex) if you can do so without
+revealing sensitive information. Never include credentials in your reprex.
### Checklists
Some changes have intricate internal and external dependencies, which are easy
to miss and break. These checklists aim to avoid these pitfalls.
-Test and update reverse dependencies (wastdr, etlTurtleNesting, etc.).
-
#### Adding a dependency
* Update DESCRIPTION
-* Update GH Actions install workflows - do R package deps have system deps? Can GHA install them in all environments?
+* Update GH Actions install workflows - do R package deps have system deps?
+ Can GHA install them in all environments?
* Update Dockerfile
* Update binder install.R
* Update installation instructions
diff --git a/DESCRIPTION b/DESCRIPTION
index aa48440..cbc80ff 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.4.9.9002
Authors@R:
c(person(given = c("Florian", "W."),
family = "Mayer",
diff --git a/NAMESPACE b/NAMESPACE
index 59b8e4c..4d3257e 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -11,6 +11,10 @@ export(encryption_key_list)
export(enexpr)
export(enquo)
export(ensym)
+export(entitylist_detail)
+export(entitylist_download)
+export(entitylist_list)
+export(entitylist_update)
export(expr)
export(exprs)
export(form_detail)
diff --git a/NEWS.md b/NEWS.md
index ec465a1..adcfaf6 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,7 @@
+# ruODK 1.5.0
+## Major changes
+* Support Entities and Entity Lists (Datasets) (#152)
+
# ruODK 1.4.2
This release migrates the `ruODK` test suite to a new test server
`ruodk.getodk.cloud` which was generously sponsored by GetODK.
@@ -16,9 +20,9 @@ This release fixes a few compatibility issues and bumps dependencies to R (4.1)
and imported/suggested packages.
Upgrade carefully and revert to 1.3.12 if things go awry.
-* Update to new tidyselect syntax: Using vectors of names to select makes
- tidyselect complain (WARN, soon ERROR). We wrap all programmatic selections of
- variable names in `dplyr::all_of()` where we expect a single variable to be
+* Update to new `tidyselect` syntax: Using vectors of names to select makes
+ `tidyselect` complain (WARN, soon ERROR). We wrap all programmatic selections
+ of variable names in `dplyr::all_of()` where we expect a single variable to be
selected, and `dplyr::any_of()` where we select using fuzzy matching
(e.g. `dplyr::starts_with()`). (#146)
* Make `ruODK::form_list()` robust against `reviewState` missing from outdated
diff --git a/R/entitylist_detail.R b/R/entitylist_detail.R
new file mode 100644
index 0000000..78a770e
--- /dev/null
+++ b/R/entitylist_detail.R
@@ -0,0 +1,93 @@
+#' Show Entity List details.
+#'
+#' `r lifecycle::badge("maturing")`
+#'
+#' An Entity List is a named collection of Entities that have the same
+#' properties.
+#' Entity List 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.
+#'
+#' @template param-pid
+#' @template param-did
+#' @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 entity-management
+#' @export
+#' @examples
+#' \dontrun{
+#' # See vignette("setup") for setup and authentication options
+#' # ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+#'
+#' ds <- entitylist_list(pid = get_default_pid())
+#' ds1 <- entitylist_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()
+#' }
+entitylist_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("entitylist_detail requires the Entity List name as 'did=\"name\"'.")
+ }
+
+ if (odkc_version |> semver_lt("2022.3")) {
+ ru_msg_warn("entitylist_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("entitylist_detail") # nolint
diff --git a/R/entitylist_download.R b/R/entitylist_download.R
new file mode 100644
index 0000000..f1b4da6
--- /dev/null
+++ b/R/entitylist_download.R
@@ -0,0 +1,213 @@
+#' Download an Entity List as CSV.
+#'
+#' `r lifecycle::badge("maturing")`
+#'
+#' The downloaded CSV file is named after the entity list name.
+#' The download location defaults to the current workdir, but can be modified
+#' to a different folder path which will be created if it doesn't exist.
+#'
+#' An Entity List is a named collection of Entities that have the same
+#' properties.
+#' Entity List can be linked to Forms as Attachments.
+#' This will make it available to clients as an automatically-updating CSV.
+#'
+#' Entity Lists can be used as Attachments in other Forms, but they can also be
+#' downloaded directly as a CSV file.
+#' The CSV format closely matches the OData Dataset (Entity List) Service
+#' format, with columns for system properties such as `__id` (the Entity UUID),
+#' `__createdAt`, `__creatorName`, etc., the Entity Label label, and the
+#' Dataset (Entity List )/Entity Properties themselves.
+#' If any Property for an given Entity is blank (e.g. it was not captured by
+#' that Form or was left blank), that field of the CSV is blank.
+#'
+#' The ODK Central `$filter` querystring parameter can be used to filter on
+#' system-level properties, similar to how filtering in the OData Dataset
+#' (Entity List) Service works.
+#' Of the [OData filter specs](https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31358948)
+#' ODK Central implements a [growing set of features
+#' ](https://docs.getodk.org/central-api-odata-endpoints/#data-document).
+#' `ruODK` provides the parameter `filter` (str) which, if set, will be passed
+#' on to the ODK Central endpoint as is.
+#'
+#' The ODK Central endpoint supports the [`ETag` header
+#' ](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag), which can
+#' be used to avoid downloading the same content more than once.
+#' When an API consumer calls this endpoint, the endpoint returns a value in
+#' the `ETag` header.
+#' If you pass that value in the [`If-None-Match` header
+#' ](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match)
+#' of a subsequent request,
+#' then if the Entity List has not been changed since the previous request,
+#' you will receive 304 Not Modified response; otherwise you'll get the new
+#' data.
+#' `ruODK` provides the parameter `etag` which can be set from the output of
+#' a previous call to `entitylist_download()`. `ruODK` strips the `W/\"` and
+#' `\"` from the returned etag and expects the stripped etag as parameter.
+#'
+#' @template param-pid
+#' @template param-did
+#' @template param-url
+#' @template param-auth
+#' @param local_dir The local folder to save the downloaded files to,
+#' default: \code{here::here}.
+#' If the folder does not exist it will be created.
+#' @param etag (str) The etag value from a previous call to
+#' `entitylist_download()`. The value must be stripped of the `W/\"` and `\"`,
+#' which is the format of the etag returned by `entitylist_download()`.
+#' If provided, only new entities will be returned.
+#' If the same `local_dir` is chosen and `overwrite` is set to `TRUE`,
+#' the downloaded CSV will also be overwritte, losing the Entities downloaded
+#' earlier.
+#' Default: NULL (no filtering, all entities returned).
+#' @param filter (str) A valid filter string.
+#' Default: NULL (no filtering, all entities returned).
+#' @param overwrite Whether to overwrite previously downloaded file,
+#' default: FALSE
+#' @template param-retries
+#' @template param-odkcv
+#' @template param-orders
+#' @template param-tz
+#' @template param-verbose
+#' @return A list of four items:
+#' - entities (tbl_df) The Entity List as tibble
+#' - http_status (int) The HTTP status code of the response.
+#' 200 if OK, 304 if a given etag finds no new entities created.
+#' - etag (str) The ETag to use in subsequent calls to `entitylist_download()`
+#' - downloaded_to (fs_path) The path to the downloaded CSV file
+#' - downloaded_on (POSIXct) The time of download in the local timezome
+# nolint start
+#' @seealso \url{https://docs.getodk.org/central-api-dataset-management/#datasets}
+# nolint end
+#' @family entity-management
+#' @export
+#' @examples
+#' \dontrun{
+#' # See vignette("setup") for setup and authentication options
+#' # ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+#'
+#' ds <- entitylist_list(pid = get_default_pid())
+#' ds1 <- entitylist_download(pid = get_default_pid(), did = ds$name[1])
+#' # ds1$entities
+#' # ds1$etag
+#' # ds1$downloaded_to
+#' # ds1$downloaded_on
+#'
+#' ds2 <- entitylist_download(
+#' pid = get_default_pid(),
+#' did = ds$name[1],
+#' etag = ds1$etag
+#' )
+#' # ds2$http_status == 304
+#'
+#' newest_entity_date <- as.Date(max(ds1$entities$`__createdAt`))
+#' ds3 <- entitylist_download(
+#' pid = get_default_pid(),
+#' did = ds$name[1],
+#' filter = glue::glue("__createdAt le {newest_entity_date}")
+#' )
+#' }
+entitylist_download <- function(pid = get_default_pid(),
+ did = NULL,
+ url = get_default_url(),
+ un = get_default_un(),
+ pw = get_default_pw(),
+ local_dir = here::here(),
+ filter = NULL,
+ etag = NULL,
+ overwrite = TRUE,
+ retries = get_retries(),
+ odkc_version = get_default_odkc_version(),
+ orders = c(
+ "YmdHMS",
+ "YmdHMSz",
+ "Ymd HMS",
+ "Ymd HMSz",
+ "Ymd",
+ "ymd"
+ ),
+ tz = get_default_tz(),
+ verbose = get_ru_verbose()) {
+ # Gatecheck params
+ yell_if_missing(url, un, pw, pid = pid)
+
+ if (is.null(did)) {
+ ru_msg_abort(
+ "entitylist_download requires the Entity List name as 'did=\"name\"'."
+ )
+ }
+
+ # Gatecheck ODKC version
+ if (odkc_version |> semver_lt("2022.3")) {
+ ru_msg_warn("entitylist_download is supported from v2022.3")
+ }
+
+ # Download file destination directory
+ if (!fs::dir_exists(local_dir)) {
+ fs::dir_create(local_dir)
+ }
+
+ # Downloaded file path
+ pth <- fs::path(local_dir, glue::glue("{did}.csv"))
+
+ # Emit message
+ if (fs::file_exists(pth)) {
+ if (overwrite == TRUE) {
+ "Overwriting previous entity list: \"{pth}\"" %>%
+ glue::glue() %>%
+ ru_msg_success(verbose = verbose)
+ } else {
+ "Keeping previous entity list: \"{pth}\"" %>%
+ glue::glue() %>%
+ ru_msg_success(verbose = verbose)
+ }
+ } else {
+ "Downloading entity list \"{did}\" to {pth}" %>%
+ glue::glue() %>%
+ ru_msg_success(verbose = verbose)
+ }
+
+ # Headers: accept CSV, set ETag if given
+ headers <- c(Accept = "text/csv; charset=utf-8")
+ if (!is.null(etag)) {
+ if (odkc_version |> semver_lt("2023.3")) {
+ ru_msg_warn("entitylist_download ETag is supported from v2023.3")
+ }
+ headers <- c(headers, c("If-None-Match" = etag))
+ }
+
+ # Query: filter
+ query <- NULL
+ if (!is.null(filter)) {
+ query <- list("$filter" = utils::URLencode(filter, reserved = TRUE))
+ }
+
+ res <- httr::RETRY(
+ "GET",
+ httr::modify_url(
+ url,
+ path = glue::glue(
+ "v1/projects/{pid}/datasets/",
+ "{utils::URLencode(did, reserved = TRUE)}/entities.csv"
+ ),
+ query = query
+ ),
+ httr::add_headers(.headers = headers),
+ httr::authenticate(un, pw),
+ httr::write_disk(pth, overwrite = overwrite),
+ times = retries
+ )
+ # yell_if_error(url, un, pw) # allow HTTP 304 for no new submissions
+
+ list(
+ entities = httr::content(res, encoding = "utf-8"),
+ etag = res$headers$etag |>
+ stringr::str_remove_all(stringr::fixed("W/\"")) |>
+ stringr::str_remove_all(stringr::fixed("\"")),
+ http_status = res$status_code,
+ downloaded_to = pth,
+ downloaded_on = isodt_to_local(res$date, orders = orders, tz = tz)
+ )
+}
+
+
+# usethis::use_test("entitylist_download") # nolint
diff --git a/R/entitylist_list.R b/R/entitylist_list.R
new file mode 100644
index 0000000..66dc833
--- /dev/null
+++ b/R/entitylist_list.R
@@ -0,0 +1,86 @@
+#' List all Entity Lists of one Project.
+#'
+#' `r lifecycle::badge("maturing")`
+#'
+#' While the API endpoint will return all Entity Lists for one Project,
+#' \code{\link{entitylist_list}} will fail with incorrect or missing
+#' authentication.
+#'
+#' An Entity List is a named collection of Entities that have the same properties.
+#' An Entity List can be linked to Forms as Attachments.
+#' This will make it available to clients as an automatically-updating CSV.
+#'
+#' ODK Central calls Entity Lists internally Datasets. `ruODK` chooses the term
+#' Entity Lists as it is used in the ODK Central user interface and conveys
+#' its relation to Entities better than the term Dataset.
+#'
+#' This function is supported from ODK Central v2022.3 and will warn if the
+#' given odkc_version is lower.
+#'
+#' @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 entity-management
+#' @export
+#' @examples
+#' \dontrun{
+#' # See vignette("setup") for setup and authentication options
+#' # ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+#'
+#' ds <- entitylist_list(pid = get_default_pid())
+#'
+#' ds |> knitr::kable()
+#' }
+entitylist_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("entitylist_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("entitylist_list") # nolint
diff --git a/R/entitylist_update.R b/R/entitylist_update.R
new file mode 100644
index 0000000..128d48c
--- /dev/null
+++ b/R/entitylist_update.R
@@ -0,0 +1,113 @@
+#' Update Entity List details.
+#'
+#' `r lifecycle::badge("maturing")`
+#'
+#' You can only update `approvalRequired` using this endpoint.
+#' The approvalRequired flag controls the Entity creation flow;
+#' if it is true then the Submission must be approved before an Entity can be
+#' created from it and if it is false then an Entity is created as soon as the
+#' Submission is received by the ODK Central.
+#' By default `approvalRequired` is false for the Entity Lists created after
+#' v2023.3. Entity Lists created prior to that will have approvalRequired set to
+#' true.
+#'
+#' An Entity List is a named collection of Entities that have the same
+#' properties.
+#' An Entity List 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-did
+#' @param approval_required (lgl) The value to set approvalRequired to.
+#' If TRUE, a submission must be approved before an entity is created,
+#' if FALSE, an entity is created as soon as the submission is received by
+#' ODK Central.
+#' Default: FALSE.
+#' @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 for `entitylist_detail`.
+#' 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 = "...")
+#'
+#' pid <- get_default_pid()
+#'
+#' ds <- entitylist_list(pid = pid)
+#'
+#' did <- ds$name[1]
+#'
+#' ds1 <- entitylist_detail(pid = pid, did = did)
+#' ds1$approvalRequired # FALSE
+#'
+#' ds2 <- entitylist_update(pid = pid, did = did, approval_required = TRUE)
+#' ds2$approvalRequired # TRUE
+#'
+#' ds3 <- entitylist_update(pid = pid, did = did, approval_required = FALSE)
+#' ds3$approvalRequired # FALSE
+#' }
+entitylist_update <- function(pid = get_default_pid(),
+ did = NULL,
+ approval_required = FALSE,
+ 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("entitylist_update requires the Entity List name as 'did=\"name\"'.")
+ }
+
+ if (odkc_version |> semver_lt("2022.3")) {
+ ru_msg_warn("entitylist_update is supported from v2022.3")
+ }
+
+ ds <- httr::RETRY(
+ "PATCH",
+ httr::modify_url(url,
+ path = glue::glue(
+ "v1/projects/{pid}/datasets/",
+ "{URLencode(did, reserved = TRUE)}"
+ )
+ ),
+ httr::add_headers(
+ "Accept" = "application/json"
+ ),
+ encode = "json",
+ body = list(approvalRequired = approval_required),
+ httr::authenticate(un, pw),
+ times = retries
+ ) |>
+ yell_if_error(url, un, pw) |>
+ httr::content(encoding = "utf-8")
+}
+
+# usethis::use_test("entitylist_update") # nolint
diff --git a/R/ru_setup.R b/R/ru_setup.R
index 5260a38..e2469b9 100644
--- a/R/ru_setup.R
+++ b/R/ru_setup.R
@@ -637,15 +637,9 @@ get_default_odkc_version <- function() {
#' @export
#' @examples
#' get_default_odkc_version() |> semver_gt("0.8.0")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_gt("2024.1.0")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_gt("2024.1.1")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_gt("2024.1.2")
+#' "2024.1.1" |> semver_gt("2024.1.0")
+#' "2024.1.1" |> semver_gt("2024.1.1")
+#' "2024.1.1" |> semver_gt("2024.1.2")
semver_gt <- function(sv = get_default_odkc_version(), to = "1.5.0") {
base_version <- parse_odkc_version(sv, env_var = "")
to_version <- parse_odkc_version(to, env_var = "")
@@ -668,15 +662,9 @@ semver_gt <- function(sv = get_default_odkc_version(), to = "1.5.0") {
#' @export
#' @examples
#' get_default_odkc_version() |> semver_lt("0.8.0")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_lt("2024.1.0")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_lt("2024.1.1")
-#' "2024.1.1" |>
-#' parse_odkc_version() |>
-#' semver_lt("2024.1.2")
+#' "2024.1.1" |> semver_lt("2024.1.0")
+#' "2024.1.1" |> semver_lt("2024.1.1")
+#' "2024.1.1" |> semver_lt("2024.1.2")
semver_lt <- function(sv = get_default_odkc_version(), to = "1.5.0") {
base_version <- parse_odkc_version(sv, env_var = "")
to_version <- parse_odkc_version(to, env_var = "")
diff --git a/R/submission_export.R b/R/submission_export.R
index 8c3ca36..038a2d2 100644
--- a/R/submission_export.R
+++ b/R/submission_export.R
@@ -127,6 +127,10 @@ submission_export <- function(local_dir = here::here(),
"{URLencode(fid, reserved = TRUE)}/submissions{url_ext}"
)
+ if (!fs::dir_exists(local_dir)) {
+ fs::dir_create(local_dir)
+ }
+
pth <- fs::path(
local_dir,
glue::glue("{URLencode(fid, reserved = TRUE)}{file_ext}")
diff --git a/README.Rmd b/README.Rmd
index f65a0d4..80e851d 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -223,11 +223,13 @@ See the [contributing guide](https://docs.ropensci.org/ruODK/CONTRIBUTING.html)
on best practices and further readings for code contributions.
## Attribution
-`ruODK` was developed, and is maintained, by Florian Mayer for the Western Australian
+`ruODK` was developed by Florian Mayer for the Western Australian
[Department of Biodiversity, Conservation and Attractions (DBCA)](https://www.dbca.wa.gov.au/).
The development was funded both by DBCA core funding and external funds from
the [North West Shelf Flatback Turtle Conservation Program](https://flatbacks.dbca.wa.gov.au/).
+ruODK is maintained and extended by Florian Mayer.
+
To cite package `ruODK` in publications use:
```{r citation}
diff --git a/_pkgdown.yml b/_pkgdown.yml
index 0c8e5a6..de1a1b5 100644
--- a/_pkgdown.yml
+++ b/_pkgdown.yml
@@ -23,6 +23,17 @@ reference:
desc: "Functions to manage projects."
contents:
- has_concept("project-management")
+- title: Entities
+ desc: >
+ An Entity List is a named collection of Entities that have the same
+ properties.
+ In the ODK Central API and in the related ODK XForms specification,
+ collections of Entities are referred to as Datasets.
+ The term "Entity List" is used for this concept in the Central frontend UI,
+ user documentation, and all other text intended for end users who are not
+ developers. Accordingly, ruODK uses the term "entitylist_" for Entity Lists.
+ contents:
+ - has_concept("entity-management")
- title: Forms
desc: >
Functions to manage forms.
diff --git a/inst/rmarkdown/templates/odata/skeleton/skeleton.Rmd b/inst/rmarkdown/templates/odata/skeleton/skeleton.Rmd
index 3866df4..e8470d3 100644
--- a/inst/rmarkdown/templates/odata/skeleton/skeleton.Rmd
+++ b/inst/rmarkdown/templates/odata/skeleton/skeleton.Rmd
@@ -41,12 +41,12 @@ usethis::edit_r_environ()
```{r paste_env_vars, echo=FALSE, eval=FALSE}
# ODK Central web user with read-permitted role on project
-ODKC_UN="my@email.com"
-ODKC_PW="my-odkc-password"
+ODKC_UN <- "my@email.com"
+ODKC_PW <- "my-odkc-password"
# CKAN user with permissions to create a dataset
-CKAN_URL="https://demo.ckan.org"
-CKAN_KEY="my-ckan-api-key"
+CKAN_URL <- "https://demo.ckan.org"
+CKAN_KEY <- "my-ckan-api-key"
```
@@ -85,7 +85,8 @@ ruODK::ru_setup(
svc = paste0("https://my-odkc-instance.com/v1/projects/1/forms/form_id.svc"),
tz = "Australia/Perth", # your local timezone
odkc_version = "2023.5.1", # your ODKC version, only needed for older versions
- verbose = TRUE)
+ verbose = TRUE
+)
loc <- fs::path("media")
fs::dir_create(loc)
@@ -107,7 +108,7 @@ See vignette "spatial" for more operations on geo fields.
-->
```{r dl_submissions}
data <- ruODK::odata_submission_get(
- table = ft$url[1],
+ table = ft$url[1],
local_dir = loc,
wkt = TRUE
)
@@ -126,9 +127,9 @@ data_sub2 <- ruODK::odata_submission_get(
local_dir = loc
) %>%
dplyr::left_join(
- data,
+ data,
by = c("submissions_id" = "id")
-)
+ )
# repeat for any remaining nested tables
```
@@ -150,15 +151,15 @@ leaflet::leaflet(width = 800, height = 600) %>%
leaflet::clearBounds() %>%
leaflet::addAwesomeMarkers(
data = data,
- #
+ #
# # Adjust to your coordinate field names
- #
- lng = ~location_longitude,
- lat = ~location_latitude,
+ #
+ lng = ~location_longitude,
+ lat = ~location_latitude,
icon = leaflet::makeAwesomeIcon(text = "Q", markerColor = "red"),
- #
+ #
# # With your own field names
- #
+ #
# label = ~ glue::glue("{location_area_name} {encounter_start_datetime}"),
# popup = ~ glue::glue(
# "
{location_area_name}
",
@@ -169,7 +170,7 @@ leaflet::leaflet(width = 800, height = 600) %>%
# ''
# ),
- #
+ #
# # If there are many submissions, cluster markers for performance:
clusterOptions = leaflet::markerClusterOptions()
) %>%
@@ -201,7 +202,7 @@ created initially.
# Prepare report and products as local files
#
rep_fn <- "my_report.html" # The file name you save this template under
-data_fn <- here::here(loc, "data.csv") %>% as.character() # Main data
+data_fn <- here::here(loc, "data.csv") %>% as.character() # Main data
data_sub1_fn <- here::here(loc, "data_sub1.csv") %>% as.character() # Sub tbl 1
data_sub2_fn <- here::here(loc, "data_sub2.csv") %>% as.character() # Sub tbl 2
zip_fn <- "products.zip" # Attachments as one zip file (top level)
@@ -217,7 +218,7 @@ zip(zipfile = zip_fn, files = fs::dir_ls(loc))
#------------------------------------------------------------------------------#
# CKAN
#
-# Upload to a CKAN data catalogue
+# Upload to a CKAN data catalogue
# Needs url and API key of a write permitted user
# See ROpenSci package ckanr
ckanr::ckanr_setup(url = Sys.getenv("CKAN_URL"), key = Sys.getenv("CKAN_KEY"))
@@ -226,20 +227,25 @@ ckan_ds_name <- "my-ckan-dataset-slug"
# Run once to create resources on an existing dataset, then comment out
d <- ckanr::package_show(ckan_ds_name)
res_data_main <- ckanr::resource_create(
- package_id = d$id, name="Main data", upload = data_fn)
+ package_id = d$id, name = "Main data", upload = data_fn
+)
res_data_sub1 <- ckanr::resource_create(
- package_id = d$id, name="Nested data table 1", upload = data_sub1_fn)
+ package_id = d$id, name = "Nested data table 1", upload = data_sub1_fn
+)
res_data_sub2 <- ckanr::resource_create(
- package_id = d$id, name="Nested data table 2", upload = data_sub2_fn)
+ package_id = d$id, name = "Nested data table 2", upload = data_sub2_fn
+)
# add remaining tables
if (fs::file_exists(rep_fn)) {
-res_report <- ckanr::resource_create(
- package_id = d$id, name="Data report", upload = rep_fn)
+ res_report <- ckanr::resource_create(
+ package_id = d$id, name = "Data report", upload = rep_fn
+ )
}
if (fs::file_exists(zip_fn)) {
-res_zip <- ckanr::resource_create(
- package_id = d$id, name="All data and attachments", upload = zip_fn)
+ res_zip <- ckanr::resource_create(
+ package_id = d$id, name = "All data and attachments", upload = zip_fn
+ )
}
# Paste res_data_main$id over RID and keep here, repeat for each resource
r <- ckanr::resource_update(res_data_main$id, path = data_fn)
@@ -258,12 +264,12 @@ googledrive::drive_auth(use_oob = TRUE)
# Upload to Google Drive
gd_fn <- "My Google Drive folder name"
-googledrive::drive_ls(gd_fn) %>% googledrive::drive_rm(.) # Wipe older outputs
+googledrive::drive_ls(gd_fn) %>% googledrive::drive_rm(.) # Wipe older outputs
if (fs::file_exists(rep_fn)) {
- googledrive::drive_upload(rep_fn, path=rep_fn) # Report as HTML
+ googledrive::drive_upload(rep_fn, path = rep_fn) # Report as HTML
}
-googledrive::drive_upload(data_fn, path=data_fn) # Main data as CSV
-googledrive::drive_upload(data_sub1_fn, path=data_sub1_fn) # Sub table 1 as CSV
-googledrive::drive_upload(data_sub2_fn, path=data_sub2_fn) # Sub table 2 as CSV
-googledrive::drive_upload(zip_fn, path=zip_fn) # All outputs as ZIP
+googledrive::drive_upload(data_fn, path = data_fn) # Main data as CSV
+googledrive::drive_upload(data_sub1_fn, path = data_sub1_fn) # Sub table 1 as CSV
+googledrive::drive_upload(data_sub2_fn, path = data_sub2_fn) # Sub table 2 as CSV
+googledrive::drive_upload(zip_fn, path = zip_fn) # All outputs as ZIP
```
diff --git a/man-roxygen/param-did.R b/man-roxygen/param-did.R
new file mode 100644
index 0000000..f26e7e4
--- /dev/null
+++ b/man-roxygen/param-did.R
@@ -0,0 +1 @@
+#' @param did (chr) The name of the Entity List, internally called Dataset.
diff --git a/man/entitylist_detail.Rd b/man/entitylist_detail.Rd
new file mode 100644
index 0000000..b863bb6
--- /dev/null
+++ b/man/entitylist_detail.Rd
@@ -0,0 +1,121 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/entitylist_detail.R
+\name{entitylist_detail}
+\alias{entitylist_detail}
+\title{Show Entity List details.}
+\usage{
+entitylist_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 name of the Entity List, internally called Dataset.}
+
+\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{
+\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#maturing}{\figure{lifecycle-maturing.svg}{options: alt='[Maturing]'}}}{\strong{[Maturing]}}
+}
+\details{
+An Entity List is a named collection of Entities that have the same
+properties.
+Entity List 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.
+}
+\examples{
+\dontrun{
+# See vignette("setup") for setup and authentication options
+# ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+
+ds <- entitylist_list(pid = get_default_pid())
+ds1 <- entitylist_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 entity-management:
+\code{\link{entitylist_download}()},
+\code{\link{entitylist_list}()}
+}
+\concept{entity-management}
diff --git a/man/entitylist_download.Rd b/man/entitylist_download.Rd
new file mode 100644
index 0000000..75b43fa
--- /dev/null
+++ b/man/entitylist_download.Rd
@@ -0,0 +1,194 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/entitylist_download.R
+\name{entitylist_download}
+\alias{entitylist_download}
+\title{Download an Entity List as CSV.}
+\usage{
+entitylist_download(
+ pid = get_default_pid(),
+ did = NULL,
+ url = get_default_url(),
+ un = get_default_un(),
+ pw = get_default_pw(),
+ local_dir = here::here(),
+ filter = NULL,
+ etag = NULL,
+ overwrite = TRUE,
+ retries = get_retries(),
+ odkc_version = get_default_odkc_version(),
+ orders = c("YmdHMS", "YmdHMSz", "Ymd HMS", "Ymd HMSz", "Ymd", "ymd"),
+ tz = get_default_tz(),
+ verbose = get_ru_verbose()
+)
+}
+\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 name of the Entity List, internally called Dataset.}
+
+\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{local_dir}{The local folder to save the downloaded files to,
+default: \code{here::here}.
+If the folder does not exist it will be created.}
+
+\item{filter}{(str) A valid filter string.
+Default: NULL (no filtering, all entities returned).}
+
+\item{etag}{(str) The etag value from a previous call to
+\code{entitylist_download()}. The value must be stripped of the \verb{W/\\"} and \verb{\\"},
+which is the format of the etag returned by \code{entitylist_download()}.
+If provided, only new entities will be returned.
+If the same \code{local_dir} is chosen and \code{overwrite} is set to \code{TRUE},
+the downloaded CSV will also be overwritte, losing the Entities downloaded
+earlier.
+Default: NULL (no filtering, all entities returned).}
+
+\item{overwrite}{Whether to overwrite previously downloaded file,
+default: FALSE}
+
+\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.}
+
+\item{verbose}{Whether to display debug messages or not.
+
+Read \code{vignette("setup", package = "ruODK")} to learn how \code{ruODK}'s
+verbosity can be set globally or per function.}
+}
+\value{
+A list of four items:
+\itemize{
+\item entities (tbl_df) The Entity List as tibble
+\item http_status (int) The HTTP status code of the response.
+200 if OK, 304 if a given etag finds no new entities created.
+\item etag (str) The ETag to use in subsequent calls to \code{entitylist_download()}
+\item downloaded_to (fs_path) The path to the downloaded CSV file
+\item downloaded_on (POSIXct) The time of download in the local timezome
+}
+}
+\description{
+\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#maturing}{\figure{lifecycle-maturing.svg}{options: alt='[Maturing]'}}}{\strong{[Maturing]}}
+}
+\details{
+The downloaded CSV file is named after the entity list name.
+The download location defaults to the current workdir, but can be modified
+to a different folder path which will be created if it doesn't exist.
+
+An Entity List is a named collection of Entities that have the same
+properties.
+Entity List can be linked to Forms as Attachments.
+This will make it available to clients as an automatically-updating CSV.
+
+Entity Lists can be used as Attachments in other Forms, but they can also be
+downloaded directly as a CSV file.
+The CSV format closely matches the OData Dataset (Entity List) Service
+format, with columns for system properties such as \verb{__id} (the Entity UUID),
+\verb{__createdAt}, \verb{__creatorName}, etc., the Entity Label label, and the
+Dataset (Entity List )/Entity Properties themselves.
+If any Property for an given Entity is blank (e.g. it was not captured by
+that Form or was left blank), that field of the CSV is blank.
+
+The ODK Central \verb{$filter} querystring parameter can be used to filter on
+system-level properties, similar to how filtering in the OData Dataset
+(Entity List) Service works.
+Of the \href{https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31358948}{OData filter specs}
+ODK Central implements a \href{https://docs.getodk.org/central-api-odata-endpoints/#data-document}{growing set of features }.
+\code{ruODK} provides the parameter \code{filter} (str) which, if set, will be passed
+on to the ODK Central endpoint as is.
+
+The ODK Central endpoint supports the \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag}{\code{ETag} header }, which can
+be used to avoid downloading the same content more than once.
+When an API consumer calls this endpoint, the endpoint returns a value in
+the \code{ETag} header.
+If you pass that value in the \href{https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match}{\code{If-None-Match} header }
+of a subsequent request,
+then if the Entity List has not been changed since the previous request,
+you will receive 304 Not Modified response; otherwise you'll get the new
+data.
+\code{ruODK} provides the parameter \code{etag} which can be set from the output of
+a previous call to \code{entitylist_download()}. \code{ruODK} strips the \verb{W/\\"} and
+\verb{\\"} from the returned etag and expects the stripped etag as parameter.
+}
+\examples{
+\dontrun{
+# See vignette("setup") for setup and authentication options
+# ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+
+ds <- entitylist_list(pid = get_default_pid())
+ds1 <- entitylist_download(pid = get_default_pid(), did = ds$name[1])
+# ds1$entities
+# ds1$etag
+# ds1$downloaded_to
+# ds1$downloaded_on
+
+ds2 <- entitylist_download(
+ pid = get_default_pid(),
+ did = ds$name[1],
+ etag = ds1$etag
+)
+# ds2$http_status == 304
+
+newest_entity_date <- as.Date(max(ds1$entities$`__createdAt`))
+ds3 <- entitylist_download(
+ pid = get_default_pid(),
+ did = ds$name[1],
+ filter = glue::glue("__createdAt le {newest_entity_date}")
+)
+}
+}
+\seealso{
+\url{https://docs.getodk.org/central-api-dataset-management/#datasets}
+
+Other entity-management:
+\code{\link{entitylist_detail}()},
+\code{\link{entitylist_list}()}
+}
+\concept{entity-management}
diff --git a/man/entitylist_list.Rd b/man/entitylist_list.Rd
new file mode 100644
index 0000000..560d5a1
--- /dev/null
+++ b/man/entitylist_list.Rd
@@ -0,0 +1,115 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/entitylist_list.R
+\name{entitylist_list}
+\alias{entitylist_list}
+\title{List all Entity Lists of one Project.}
+\usage{
+entitylist_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{
+\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#maturing}{\figure{lifecycle-maturing.svg}{options: alt='[Maturing]'}}}{\strong{[Maturing]}}
+}
+\details{
+While the API endpoint will return all Entity Lists for one Project,
+\code{\link{entitylist_list}} will fail with incorrect or missing
+authentication.
+
+An Entity List is a named collection of Entities that have the same properties.
+An Entity List can be linked to Forms as Attachments.
+This will make it available to clients as an automatically-updating CSV.
+
+ODK Central calls Entity Lists internally Datasets. \code{ruODK} chooses the term
+Entity Lists as it is used in the ODK Central user interface and conveys
+its relation to Entities better than the term Dataset.
+
+This function is supported from ODK Central v2022.3 and will warn if the
+given odkc_version is lower.
+}
+\examples{
+\dontrun{
+# See vignette("setup") for setup and authentication options
+# ruODK::ru_setup(svc = "....svc", un = "me@email.com", pw = "...")
+
+ds <- entitylist_list(pid = get_default_pid())
+
+ds |> knitr::kable()
+}
+}
+\seealso{
+\url{ https://docs.getodk.org/central-api-dataset-management/#datasets}
+
+Other entity-management:
+\code{\link{entitylist_detail}()},
+\code{\link{entitylist_download}()}
+}
+\concept{entity-management}
diff --git a/man/entitylist_update.Rd b/man/entitylist_update.Rd
new file mode 100644
index 0000000..9ad5662
--- /dev/null
+++ b/man/entitylist_update.Rd
@@ -0,0 +1,137 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/entitylist_update.R
+\name{entitylist_update}
+\alias{entitylist_update}
+\title{Update Entity List details.}
+\usage{
+entitylist_update(
+ pid = get_default_pid(),
+ did = NULL,
+ approval_required = FALSE,
+ 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 name of the Entity List, internally called Dataset.}
+
+\item{approval_required}{(lgl) The value to set approvalRequired to.
+If TRUE, a submission must be approved before an entity is created,
+if FALSE, an entity is created as soon as the submission is received by
+ODK Central.
+Default: FALSE.}
+
+\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 for \code{entitylist_detail}.
+Since this nested list is so deeply nested and irregularly shaped
+it is not trivial to rectangle the result into a tibble.
+}
+\description{
+\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#maturing}{\figure{lifecycle-maturing.svg}{options: alt='[Maturing]'}}}{\strong{[Maturing]}}
+}
+\details{
+You can only update \code{approvalRequired} using this endpoint.
+The approvalRequired flag controls the Entity creation flow;
+if it is true then the Submission must be approved before an Entity can be
+created from it and if it is false then an Entity is created as soon as the
+Submission is received by the ODK Central.
+By default \code{approvalRequired} is false for the Entity Lists created after
+v2023.3. Entity Lists created prior to that will have approvalRequired set to
+true.
+
+An Entity List is a named collection of Entities that have the same
+properties.
+An Entity List 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 = "...")
+
+pid <- get_default_pid()
+
+ds <- entitylist_list(pid = pid)
+
+did <- ds$name[1]
+
+ds1 <- entitylist_detail(pid = pid, did = did)
+ds1$approvalRequired # FALSE
+
+ds2 <- entitylist_update(pid = pid, did = did, approval_required = TRUE)
+ds2$approvalRequired # TRUE
+
+ds3 <- entitylist_update(pid = pid, did = did, approval_required = FALSE)
+ds3$approvalRequired # FALSE
+}
+}
+\seealso{
+\url{ https://docs.getodk.org/central-api-dataset-management/#datasets}
+}
+\concept{dataset-management}
diff --git a/man/semver_gt.Rd b/man/semver_gt.Rd
index c1097a6..5cc244c 100644
--- a/man/semver_gt.Rd
+++ b/man/semver_gt.Rd
@@ -25,15 +25,9 @@ Show whether a given semver is greater than a baseline version.
}
\examples{
get_default_odkc_version() |> semver_gt("0.8.0")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_gt("2024.1.0")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_gt("2024.1.1")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_gt("2024.1.2")
+"2024.1.1" |> semver_gt("2024.1.0")
+"2024.1.1" |> semver_gt("2024.1.1")
+"2024.1.1" |> semver_gt("2024.1.2")
}
\seealso{
Other ru_settings:
diff --git a/man/semver_lt.Rd b/man/semver_lt.Rd
index 5011ebd..dbc23c9 100644
--- a/man/semver_lt.Rd
+++ b/man/semver_lt.Rd
@@ -25,15 +25,9 @@ Show whether a given semver is lesser than a baseline version.
}
\examples{
get_default_odkc_version() |> semver_lt("0.8.0")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_lt("2024.1.0")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_lt("2024.1.1")
-"2024.1.1" |>
- parse_odkc_version() |>
- semver_lt("2024.1.2")
+"2024.1.1" |> semver_lt("2024.1.0")
+"2024.1.1" |> semver_lt("2024.1.1")
+"2024.1.1" |> semver_lt("2024.1.2")
}
\seealso{
Other ru_settings:
diff --git a/tests/testthat/test-entitylist_detail.R b/tests/testthat/test-entitylist_detail.R
new file mode 100644
index 0000000..b2f502e
--- /dev/null
+++ b/tests/testthat/test-entitylist_detail.R
@@ -0,0 +1,70 @@
+test_that("entitylist_detail works", {
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+ did <- ds$name[1]
+
+ ds1 <- entitylist_detail(did = did)
+
+ # entitylist_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("entitylist_detail errors if did is missing", {
+ testthat::expect_error(
+ entitylist_detail()
+ )
+})
+
+test_that("entitylist_detail warns if odkc_version too low", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+ did <- ds$name[1]
+
+ ds1 <- entitylist_detail(did = did)
+
+ testthat::expect_warning(
+ ds1 <- entitylist_detail(did = did, odkc_version = "1.5.3")
+ )
+})
+
+
+# usethis::use_r("entitylist_detail") # nolint
diff --git a/tests/testthat/test-entitylist_download.R b/tests/testthat/test-entitylist_download.R
new file mode 100644
index 0000000..f0281f9
--- /dev/null
+++ b/tests/testthat/test-entitylist_download.R
@@ -0,0 +1,165 @@
+test_that("entitylist_download works", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ # skip_on_ci()
+
+ tempd <- fs::path(tempdir(), "new_dir")
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+
+ ds1 <- entitylist_download(did = ds$name[1], local_dir = tempd)
+
+ # Format:
+ # list(
+ # entities = httr::content(res, encoding = "utf-8"),
+ # etag = res$headers$etag,
+ # downloaded_to = pth,
+ # downloaded_on = isodt_to_local(res$date, orders = orders, tz = tz)
+ # )
+
+ # The Entity List is also returned as a tibble
+ testthat::expect_s3_class(ds1$entities, "tbl_df")
+
+ # An ETag was returned
+ testthat::expect_is(ds1$etag, "character")
+
+ # The CSV file was downloaded
+ testthat::expect_true(fs::file_exists(ds1$downloaded_to))
+
+ # The timestamp is included
+ testthat::expect_s3_class(ds1$downloaded_on, "POSIXct")
+
+ # Download to same location, do not overwrite: error
+ # Error: Path exists and overwrite is FALSE
+ testthat::expect_error(
+ entitylist_download(
+ did = ds$name[1],
+ local_dir = tempd,
+ overwrite = FALSE
+ )
+ )
+
+ ds2 <- entitylist_download(
+ did = ds$name[1],
+ local_dir = tempd,
+ overwrite = TRUE
+ )
+
+ # The returned file path is the same as from the first download
+ testthat::expect_equal(ds1$downloaded_to, ds2$downloaded_to)
+
+ # Clean up
+ fs::dir_ls(tempd) %>% fs::file_delete()
+})
+
+
+test_that("entitylist_download etag works", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ # skip_on_ci()
+
+ tempd <- tempdir()
+ fs::dir_ls(tempd) %>% fs::file_delete()
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+
+ ds1 <- entitylist_download(did = ds$name[1], local_dir = tempd)
+
+ # Download only entities added since last download (ds1) = None
+ ds2 <- entitylist_download(
+ did = ds$name[1],
+ local_dir = tempd,
+ overwrite = TRUE,
+ etag = ds1$etag
+ )
+
+ testthat::expect_equal(ds2$http_status, 304)
+ testthat::expect_equal(ds2$entities, NULL)
+})
+
+test_that("entitylist_download filter works", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ # skip_on_ci()
+
+ tempd <- tempdir()
+ fs::dir_ls(tempd) %>% fs::file_delete()
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+
+ ds1 <- entitylist_download(did = ds$name[1], local_dir = tempd)
+
+ newest_entity_date <- as.Date(max(ds1$entities$`__createdAt`))
+
+ # Should return all entities (created before or on date of latest entity)
+ # Currently returns HTTP 501 not implemented
+ # ds2 <- entitylist_download(
+ # did = ds$name[1],
+ # filter=glue::glue("__createdAt le {newest_entity_date}")
+ # )
+
+ # testthat::expect_equal(ds2$http_status, 200)
+ # testthat::expect_true(nrow(ds2$entities))
+})
+
+test_that("entitylist_download errors if did is missing", {
+ testthat::expect_error(
+ entitylist_download()
+ )
+})
+
+test_that("entitylist_download warns if odkc_version too low", {
+ tempd <- tempdir()
+ fs::dir_ls(tempd) %>% fs::file_delete()
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+
+ testthat::expect_warning(
+ entitylist_download(
+ did = ds$name[1],
+ local_dir = tempd,
+ odkc_version = "1.5.3"
+ )
+ )
+})
+
+
+# usethis::use_r("entitylist_download") # nolint
diff --git a/tests/testthat/test-entitylist_list.R b/tests/testthat/test-entitylist_list.R
new file mode 100644
index 0000000..f12572a
--- /dev/null
+++ b/tests/testthat/test-entitylist_list.R
@@ -0,0 +1,58 @@
+test_that("entitylist_list works", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+ 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("entitylist_list warns if odkc_version too low", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+ did <- ds$name[1]
+
+ ds1 <- entitylist_list()
+
+ testthat::expect_warning(
+ ds1 <- entitylist_list(odkc_version = "1.5.3")
+ )
+})
+
+# usethis::use_r("entitylist_list") # nolint
diff --git a/tests/testthat/test-entitylist_update.R b/tests/testthat/test-entitylist_update.R
new file mode 100644
index 0000000..ac59c2d
--- /dev/null
+++ b/tests/testthat/test-entitylist_update.R
@@ -0,0 +1,55 @@
+test_that("entitylist_update works", {
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+
+ ds1 <- entitylist_detail(did = ds$name[1])
+
+ did <- ds$name[1]
+
+ # Update dataset with opposite approvalRequired
+ ds2 <- entitylist_update(did = did, approval_required = !ds1$approvalRequired)
+ testthat::expect_false(ds1$approvalRequired == ds2$approvalRequired)
+
+ # Update dataset with opposite approvalRequired again
+ ds3 <- entitylist_update(did = did, approval_required = !ds2$approvalRequired)
+ testthat::expect_false(ds2$approvalRequired == ds3$approvalRequired)
+ testthat::expect_true(ds1$approvalRequired == ds3$approvalRequired)
+})
+
+test_that("entitylist_update errors if did is missing", {
+ testthat::expect_error(
+ entitylist_update()
+ )
+})
+
+test_that("entitylist_update warns if odkc_version too low", {
+ skip_if(Sys.getenv("ODKC_TEST_URL") == "",
+ message = "Test server not configured"
+ )
+ ru_setup(
+ pid = get_test_pid(),
+ url = get_test_url(),
+ un = get_test_un(),
+ pw = get_test_pw(),
+ odkc_version = get_test_odkc_version()
+ )
+
+ ds <- entitylist_list()
+ did <- ds$name[1]
+
+ ds1 <- entitylist_update(did = did)
+
+ testthat::expect_warning(
+ ds1 <- entitylist_update(did = did, odkc_version = "1.5.3")
+ )
+})
+
+
+# usethis::use_r("entitylist_update") # nolint
diff --git a/tests/testthat/test-form_schema.R b/tests/testthat/test-form_schema.R
index 4c217a3..4d5454d 100644
--- a/tests/testthat/test-form_schema.R
+++ b/tests/testthat/test-form_schema.R
@@ -21,6 +21,7 @@ test_that("form_schema works with unpublished draft forms", {
form_schema(
pid = get_test_pid(),
fid = "Locations_draft",
+ draft = TRUE,
url = get_test_url(),
un = get_test_un(),
pw = get_test_pw(),
@@ -34,6 +35,7 @@ test_that("form_schema works with unpublished draft forms", {
parse = FALSE,
pid = get_test_pid(),
fid = "Locations_draft",
+ draft = TRUE,
url = get_test_url(),
un = get_test_un(),
pw = get_test_pw(),
diff --git a/tests/testthat/test-submission_export.R b/tests/testthat/test-submission_export.R
index 3ef5a2b..a498657 100644
--- a/tests/testthat/test-submission_export.R
+++ b/tests/testthat/test-submission_export.R
@@ -5,8 +5,7 @@ test_that("submission_export works", {
)
# A fresh litterbox
- t <- tempdir()
- fs::dir_ls(t) %>% fs::file_delete()
+ t <- fs::path(tempdir(), "new_dir")
# High expectations
pth <- fs::path(
@@ -101,6 +100,9 @@ test_that("submission_export works", {
# Find the payload
testthat::expect_true(fid_csv %in% fs::dir_ls(t))
+
+ # Clean up
+ fs::dir_ls(t) %>% fs::file_delete()
})
test_that("submission_export works with encryption", {
diff --git a/tic.R b/tic.R
deleted file mode 100644
index f48abca..0000000
--- a/tic.R
+++ /dev/null
@@ -1,47 +0,0 @@
-# installs dependencies, runs R CMD check, runs covr::codecov()
-do_package_checks(
- error_on="error",
- args = c(
- "--no-manual",
- "--as-cran",
- "--no-vignettes",
- "--no-build-vignettes",
- "--no-multiarch"
- )
-)
-
-# failed: ‘terra’, ‘sf’, ‘raster’, ‘leafpop’, ‘leaflet’, ‘satellite’, ‘leafem’
-if(ci_get_env("matrix.config.os") == "macOS-latest"){
- get_stage("install") %>%
- add_step(step_install_cran("proj4")) %>%
- add_step(step_install_github("r-spatial/sf", dependencies = TRUE, force = TRUE)) %>%
- add_step(step_install_cran("raster")) %>%
- add_step(step_install_cran("leaflet")) %>%
- add_step(step_install_cran("leafpop")) %>%
- add_step(step_install_github("r-spatial/leafem", dependencies = TRUE)) %>%
- add_step(step_install_cran("terra"))
-}
-
-if(ci_get_env("matrix.config.os") == "ubuntu-20.04"){
- get_stage("install") %>%
- # add_step(step_install_github(c("tidyverse/readr"))) %>%
- # https://stackoverflow.com/q/61875754/2813717 - install proj4
- add_step(step_install_cran("proj4")) %>%
- # sf install fixed by cpp11
- add_step(step_install_github("r-lib/cpp11", dependencies = TRUE)) %>%
- add_step(step_install_github("r-spatial/sf", dependencies = TRUE, force = TRUE)) %>%
- add_step(step_install_github("r-spatial/mapview", dependencies = TRUE)) %>%
- add_step(step_install_github("r-spatial/leafem", dependencies = TRUE)) %>%
- # add_step(step_install_cran("listviewer")) %>%
- # libicui8n not found: fixed by stringi forced install
- add_step(step_install_github("gagolews/stringi", dependencies = TRUE, force = TRUE))
-}
-
-# # rOpenSci build their own docs, see build at
-# # https://dev.ropensci.org/job/ruODK/lastBuild/console
-#
-# if (ci_on_ghactions() && ci_has_env("BUILD_PKGDOWN")) {
-# # creates pkgdown site and pushes to gh-pages branch
-# # only for the runner with the "BUILD_PKGDOWN" env var set
-# do_pkgdown()
-# }
diff --git a/vignettes/restful-api.Rmd b/vignettes/restful-api.Rmd
index 4cf8b45..20a20e5 100644
--- a/vignettes/restful-api.Rmd
+++ b/vignettes/restful-api.Rmd
@@ -62,13 +62,13 @@ are used by `ruODK`'s other functions (unless specified otherwise).
```{r ru_setup}
library(ruODK)
-ruODK::ru_setup(
- svc = "https://sandbox.central.getodk.org/v1/projects/14/forms/build_Flora-Quadrat-0-4_1564384341.svc",
- un = Sys.getenv("ODKC_TEST_UN"),
- pw = Sys.getenv("ODKC_TEST_PW"),
- tz = "Australia/Perth",
- verbose = TRUE
-)
+# ruODK::ru_setup(
+# svc = "Form OData Service URL",
+# un = Sys.getenv("ODKC_TEST_UN"),
+# pw = Sys.getenv("ODKC_TEST_PW"),
+# tz = "Australia/Perth",
+# verbose = TRUE
+# )
t <- fs::dir_create("media")
```
@@ -311,14 +311,17 @@ In order to import each submission, we need to retrieve the data by
```{r submission_data, eval=F}
# One submission
-fq_one_submission <- ruODK::get_one_submission(fq_submission_list$instance_id[[1]])
+fq_one_submission <- ruODK::get_one_submission(
+ fq_submission_list$instance_id[[1]]
+)
# Multiple submissions
fq_submissions <- ruODK::submission_get(fq_submission_list$instance_id)
```
## Parse submissions
-The data in `sub` is one row of the bulk downloaded submissions in `data_quadrat`.
+The data in `sub` is one row of the bulk downloaded submissions in
+`data_quadrat`.
The data in `submissions` represents all (or let's pretend, the selected)
submissions in `data_quadrat`.
The field `xml` contains the actual submission data including repeating groups.
@@ -327,8 +330,8 @@ The structure is different to the output of `ruODK::odata_submission_get`,
therefore `ruODK::odata_submission_rectangle` does not work for those, as
here we might have repeating groups included in a submission.
-This structure could be used for upload into data warehouses accepting nested data
-as e.g. JSON.
+This structure could be used for upload into data warehouses accepting nested
+data as e.g. JSON.
```{r view_submission_data, fig.width=7}
if (requireNamespace("listviewer")) {
diff --git a/vignettes/setup.Rmd b/vignettes/setup.Rmd
index 994fb02..c3cbba0 100644
--- a/vignettes/setup.Rmd
+++ b/vignettes/setup.Rmd
@@ -107,36 +107,36 @@ calling `ru_setup()` or `Sys.setenv()` with respective arguments.
usethis::edit_r_environ(scope = "user")
```
-```{r renviron, eval=FALSE}
-ODKC_PID <- 1
-ODKC_FID <- "form_id"
-ODKC_URL <- "https://my-instance.getodk.cloud"
-ODKC_UN <- "me@email.com"
-ODKC_PW <- "..."
-ODKC_PP <- "..."
-ODKC_VERSION <- "2023.5.1"
+```{eval=FALSE}
+ODKC_PID=1
+ODKC_FID="form_id"
+ODKC_URL="https://my-instance.getodk.cloud"
+ODKC_UN="me@email.com"
+ODKC_PW="..."
+ODKC_PP="..."
+ODKC_VERSION="2023.5.1"
# ODK Test server
-ODKC_TEST_SVC <- "https://ruodk.getodk.cloud/v1/projects/1/forms/Flora-Quadrat-04.svc"
-ODKC_TEST_URL <- "https://ruodk.getodk.cloud"
-ODKC_TEST_PID <- 1
-ODKC_TEST_PID_ENC <- 2
-ODKC_TEST_PP <- "ThePassphrase"
-ODKC_TEST_FID <- "Flora-Quadrat-04"
-ODKC_TEST_FID_ZIP <- "Spotlighting-06"
-ODKC_TEST_FID_ATT <- "Flora-Quadrat-04-att"
-ODKC_TEST_FID_GAP <- "Flora-Quadrat-04-gap"
-ODKC_TEST_FID_WKT <- "Locations"
-ODKC_TEST_FID_I8N0 <- "I8n_no_lang"
-ODKC_TEST_FID_I8N1 <- "I8n_label_lng"
-ODKC_TEST_FID_I8N2 <- "I8n_label_choices"
-ODKC_TEST_FID_ENC <- "Locations"
-ODKC_TEST_VERSION <- "2023.5.1"
-RU_VERBOSE <- TRUE
-RU_TIMEZONE <- "Australia/Perth"
-RU_RETRIES <- 3
-ODKC_TEST_UN <- "..."
-ODKC_TEST_PW <- "..."
+ODKC_TEST_SVC="https://ruodk.getodk.cloud/v1/projects/1/forms/Flora-Quadrat-04.svc"
+ODKC_TEST_URL="https://ruodk.getodk.cloud"
+ODKC_TEST_PID=1
+ODKC_TEST_PID_ENC=2
+ODKC_TEST_PP="ThePassphrase"
+ODKC_TEST_FID="Flora-Quadrat-04"
+ODKC_TEST_FID_ZIP="Spotlighting-06"
+ODKC_TEST_FID_ATT="Flora-Quadrat-04-att"
+ODKC_TEST_FID_GAP="Flora-Quadrat-04-gap"
+ODKC_TEST_FID_WKT="Locations"
+ODKC_TEST_FID_I8N0="I8n_no_lang"
+ODKC_TEST_FID_I8N1="I8n_label_lng"
+ODKC_TEST_FID_I8N2="I8n_label_choices"
+ODKC_TEST_FID_ENC="Locations"
+ODKC_TEST_VERSION="2023.5.1"
+RU_VERBOSE=TRUE
+RU_TIMEZONE="Australia/Perth"
+RU_RETRIES=3
+ODKC_TEST_UN="..."
+ODKC_TEST_PW="..."
```
As an alternative to setting environment variables through `~/.Renviron`, you