Skip to content

Commit

Permalink
WIP #152: Add entity_list and entity_detail
Browse files Browse the repository at this point in the history
  • Loading branch information
florianm committed Mar 31, 2024
1 parent b4214bf commit 4eba32c
Show file tree
Hide file tree
Showing 17 changed files with 714 additions and 20 deletions.
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Type: Package
Package: ruODK
Title: An R Client for the ODK Central API
Version: 1.4.9.9003
Version: 1.4.9.9004
Authors@R:
c(person(given = c("Florian", "W."),
family = "Mayer",
Expand Down
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export(encryption_key_list)
export(enexpr)
export(enquo)
export(ensym)
export(entity_detail)
export(entity_list)
export(entitylist_detail)
export(entitylist_download)
export(entitylist_list)
Expand Down
144 changes: 144 additions & 0 deletions R/entity_detail.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#' Show details of one Entity.
#'
#' `r lifecycle::badge("maturing")`
#'
#'
#' This function returns the metadata and current data of an Entity.
#'
#' The data and label of an Entity are found in the `current_version` columns
#' under data and label respectively.
#' The `current_version` column contains version-specific metadata fields
#' such as version, baseVersion, details about conflicts that version
#' introduced, etc. More details are available from `entity_versions()`.
#' The Entity also contains top-level system metadata such as `uuid`,
#' `created_at` and `updated_at` timestamps, and `creator_id` (or full creator
#' if extended metadata is requested).
#'
#' ## Conflicts
#' An Entity's metadata also has a conflict field, which indicates the current
#' conflict status of the Entity. The value of a conflict can either be:
#'
#' - null. The Entity does not have conflicting versions.
#' - soft. The Entity has a version that was based on a version other than the
#' version immediately prior to it. (The specified `base_version` was not the
#' latest version on the server.)
#' - hard. The Entity has a version that was based on a version other than the
#' version immediately prior to it. Further, that version updated the same
#' property as another update.
#'
#' If an Entity has a conflict, it can be marked as resolved.
#' After that, the conflict field will be null until a new conflicting version
#' is received.
#'
#' ## Structure
#' The response from ODK Central from this endpoint is irregular and dynamic.
#' `ruODK` preserves the original structure as not to introduce additional
#' complexity. If a use case exists to decompose the original structure further
#' please create a GitHub issue.
#'
#' ## Names
#' Names are cleaned at the top level only. List columns contain original
#' `camelCase` names.
#'
#' ## Authentication
#' `entity_detail()` will fail with incorrect or missing authentication.
#'
#' ## Compatibility
#' 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-eid
#' @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 Entity 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-entity-management/#entities-metadata}
# nolint end
#' @family entity-management
#' @export
#' @examples
#' \dontrun{
#' # See vignette("setup") for setup and authentication options
#' # ruODK::ru_setup(svc = "....svc", un = "[email protected]", pw = "...")
#'
#' el <- entitylist_list()
#'
#' # Entity List name (dataset ID, did)
#' did <- el$name[1]
#'
#' # All Entities of Entity List
#' en <- entity_list(did = did)
#'
#' ed <- entity_detail(did = did, eid = en$uuid[1])
#'
#' # The current version of the first Entity
#' ev <- en$current_version_version[1]
#' }
entity_detail <- function(pid = get_default_pid(),
did = NULL,
eid = 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("entity_detail requires the Entity List name as 'did=\"name\"'.")

Check warning on line 105 in R/entity_detail.R

View check run for this annotation

Codecov / codecov/patch

R/entity_detail.R#L105

Added line #L105 was not covered by tests
}

if (is.null(eid)) {
ru_msg_abort("entity_detail requires the Entity UUID as 'eid=\"uuid\"'.")

Check warning on line 109 in R/entity_detail.R

View check run for this annotation

Codecov / codecov/patch

R/entity_detail.R#L109

Added line #L109 was not covered by tests
}

if (odkc_version |> semver_lt("2022.3")) {
ru_msg_warn("entity_detail is supported from v2022.3")

Check warning on line 113 in R/entity_detail.R

View check run for this annotation

Codecov / codecov/patch

R/entity_detail.R#L113

Added line #L113 was not covered by tests
}

pth <- glue::glue(
"v1/projects/{pid}/datasets/{URLencode(did, reserved = TRUE)}/entities/{eid}"
)

httr::RETRY(
"GET",
httr::modify_url(url, path = pth),
httr::add_headers("Accept" = "application/json"),
httr::authenticate(un, pw),
times = retries
) |>
yell_if_error(url, un, pw) |>
httr::content(encoding = "utf-8") |>
# purrr::list_transpose() |>
#
# tibble::enframe() |>
# tibble::as_tibble(.name_repair = "universal") |>
# tidyr::pivot_wider(names_from = "name", values_from = "value") |>
# tidyr::unnest_wider("conflict", names_sep = "_") |>
# janitor::clean_names() |>
# tidyr::unnest_wider("current_version", names_sep = "_") |>
janitor::clean_names()
# dplyr::mutate_at(
# dplyr::vars(dplyr::contains("_at")),
# ~ isodt_to_local(., orders = orders, tz = tz)
# )
}

# usethis::use_test("entity_detail") # nolint
104 changes: 104 additions & 0 deletions R/entity_list.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#' List all Entities of a kind.
#'
#' `r lifecycle::badge("maturing")`
#'
#' This function returns a list of the Entities of a kind (belonging to an
#' Entity List or Dataset).
#' Please note that this endpoint only returns metadata of the entities, not the
#' data. If you want to get the data of all entities then please refer to OData
#' Dataset Service
#'
#' You can provide ?deleted=true to get only deleted entities.
#'
#' `entity_list()` will fail with incorrect or missing authentication.
#'
#' 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 tibble with exactly one row for each Entity 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-entity-management/#entities-metadata}
# nolint end
#' @family entity-management
#' @export
#' @examples
#' \dontrun{
#' # See vignette("setup") for setup and authentication options
#' # ruODK::ru_setup(svc = "....svc", un = "[email protected]", pw = "...")
#'
#' el <- entitylist_list()
#'
#' # Entity List name (dataset ID)
#' did <- el$name[1]
#'
#' # All Entities of Entity List
#' en <- entity_list(did = el$name[1])
#'
#' # The UUID of the first Entity
#' eid <- en$uuid[1]
#'
#' # The current version of the first Entity
#' ev <- en$current_version_version[1]
#' }
entity_list <- 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("entity_list requires the Entity List name as 'did=\"name\"'.")

Check warning on line 72 in R/entity_list.R

View check run for this annotation

Codecov / codecov/patch

R/entity_list.R#L72

Added line #L72 was not covered by tests
}

if (odkc_version |> semver_lt("2022.3")) {
ru_msg_warn("entity_list is supported from v2022.3")

Check warning on line 76 in R/entity_list.R

View check run for this annotation

Codecov / codecov/patch

R/entity_list.R#L76

Added line #L76 was not covered by tests
}

pth <- glue::glue(
"v1/projects/{pid}/datasets/{URLencode(did, reserved = TRUE)}/entities"
)

httr::RETRY(
"GET",
httr::modify_url(url, path = pth),
httr::add_headers("Accept" = "application/json"),
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() |>
tidyr::unnest_wider("current_version", names_sep = "_") |>
tidyr::unnest_wider("conflict", names_sep = "_") |>
janitor::clean_names() |>
dplyr::mutate_at(
dplyr::vars(dplyr::contains("_at")),
~ isodt_to_local(., orders = orders, tz = tz)
)
}

# usethis::use_test("entity_list") # nolint
3 changes: 1 addition & 2 deletions R/entitylist_detail.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ entitylist_detail <- function(pid = get_default_pid(),
)
),
httr::add_headers(
"Accept" = "application/json",
"X-Extended-Metadata" = "true"
"Accept" = "application/json"
),
httr::authenticate(un, pw),
times = retries
Expand Down
8 changes: 4 additions & 4 deletions R/entitylist_list.R
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
#' @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`.
#' @return A tibble with exactly one row for each Entity List of the given
#' Project as per ODK Central API docs.
#' Column names are renamed from ODK Central's `camelCase` to `snake_case`.
# nolint start
#' @seealso \url{ https://docs.getodk.org/central-api-dataset-management/#datasets}
# nolint end
Expand All @@ -35,7 +35,7 @@
#' @examples
#' \dontrun{
#' # See vignette("setup") for setup and authentication options
#' # ruODK::ru_setup(svc = "....svc", un = "[email protected]", pw = "...")
#' # ruODK::ru_setup(svc = "... .svc", un = "[email protected]", pw = "...")
#'
#' ds <- entitylist_list(pid = get_default_pid())
#'
Expand Down
17 changes: 12 additions & 5 deletions R/isodt_to_local.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,29 @@
#'
#' `r lifecycle::badge("stable")`
#'
#' This function is used internally by \code{ruODK} to parse ISO timestamps
#' This function is used internally by `ruODK` to parse ISO timestamps
#' to timezone-aware local times.
#'
#' Warnings are suppressed through `lubridate::parse_date_time(quiet=TRUE)`.
#'
#' @param datetime_string (character) An ISO8601 datetime string as produced by
#' XForms exported from ODK Central.
#' @param orders (vector of character) Orders of datetime elements for
#' lubridate.
#' `lubridate`.
#' Default: \code{c("YmdHMS", "YmdHMSz", "Ymd HMS", "Ymd HMSz")}.
#' @template param-tz
#' @return A lubridate PosixCT datetime in the given timezone.
#' @param quiet (bool) Used in `lubridate::parse_date_time(quiet=quiet)` to
#' suppress warnings from attempting to parse all empty values or columns.
#' Run with `quiet=FALSE` to show any `lubridate` warnings.
#' @return A `lubridate` PosixCT datetime in the given timezone.
#' @family utilities
#' @keywords internal
isodt_to_local <- function(datetime_string,
orders = c("YmdHMS", "YmdHMSz"),
tz = get_default_tz()) {
tz = get_default_tz(),
quiet=TRUE
) {
datetime_string %>%
lubridate::parse_date_time(orders = orders) %>%
lubridate::parse_date_time(orders = orders, quiet=quiet) %>%
lubridate::with_tz(., tzone = tz)
}
2 changes: 2 additions & 0 deletions man-roxygen/param-eid.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#' @param eid (chr) The UUID of an Entity, which can be retrieved by
#' `entity_list()`.
Loading

0 comments on commit 4eba32c

Please sign in to comment.