Skip to content

Commit

Permalink
Add odata_entitylist_data_get
Browse files Browse the repository at this point in the history
* TODO add filters
  • Loading branch information
florianm committed Nov 5, 2024
1 parent 21a1c0a commit 0268f65
Show file tree
Hide file tree
Showing 20 changed files with 464 additions and 3 deletions.
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

S3method(print,odata_entitylist_data_get)
S3method(print,odata_entitylist_metadata_get)
S3method(print,odata_entitylist_service_get)
S3method(print,ru_settings)
Expand Down Expand Up @@ -64,6 +65,7 @@ export(handle_ru_datetimes)
export(handle_ru_geopoints)
export(handle_ru_geoshapes)
export(handle_ru_geotraces)
export(odata_entitylist_data_get)
export(odata_entitylist_metadata_get)
export(odata_entitylist_service_get)
export(odata_metadata_get)
Expand Down
170 changes: 170 additions & 0 deletions R/odata_entitylist_data_get.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#' Get the Data Document from the OData Dataset Service.
#'
#' `r lifecycle::badge("experimental")`
#'
#' A data document is the straightforward JSON representation of all the
#' Entities in a Dataset.
#'
#' The `$top` and `$skip` querystring parameters, specified by OData, apply limit
#' and offset operations to the data, respectively.
#'
#' The `$count` parameter, also an OData standard, will annotate the response
#' data with the total row count, regardless of the scoping requested by `$top`
#' and `$skip`.
#' If `$top` parameter is provided in the request then the response will include
#' `@odata.nextLink` that you can use as is to fetch the next set of data.
#' As of ODK Central v2023.4, `@odata.nextLink` contains a `$skiptoken`
#' (an opaque cursor) to better paginate around deleted Entities.
#'
#' The $filter querystring parameter can be used to filter certain data fields
#' in the system-level schema, but not the Dataset properties.
#' The operators lt, le, eq, ne, ge, gt, not, and, and or are supported.
#' The built-in functions now, year, month, day, hour, minute, second are
#' supported.
#'
#' The fields you can query against are as follows:
#'
#' Entity Metadata OData Field Name
#' Entity Creator Actor ID __system/creatorId
#' Entity Timestamp __system/createdAt
#' Entity Update Timestamp __system/updatedAt
#' Entity Conflict __system/conflict
#'
#' Note that createdAt and updatedAt are time components.
#' This means that any comparisons you make need to account for the full time
#' of the entity. It might seem like $filter=__system/createdAt le 2020-01-31
#' would return all results on or before 31 Jan 2020, but in fact only entities
#' made before midnight of that day would be accepted.
#' To include all of the month of January, you need to filter by either
#' $filter=__system/createdAt le 2020-01-31T23:59:59.999Z or
#' $filter=__system/createdAt lt 2020-02-01.
#' Remember also that you can query by a specific timezone.
#'
#' Please see the OData documentation on $filter operations and functions for
#' more information.
#'
#' The $select query parameter will return just the fields you specify and is
#' supported on __id, __system, __system/creatorId, __system/createdAt and
#' __system/updatedAt, as well as on user defined properties.
#'
#' The $orderby query parameter will return Entities sorted by different fields,
#' which come from the same list used by $filter, as noted above.
#' The order can be specified as ASC (ascending) or DESC (descending),
#' which are case-insensitive. Multiple sort expressions can be used together,
#' separated by commas, e.g. $orderby=__system/creatorId ASC,
#' __system/conflict DESC.
#'
#' As the vast majority of clients only support the JSON OData format,
#' that is the only format ODK Central offers.
#'
#' @template tpl-structure-nested
#' @template tpl-names-cleaned-top-level
#' @template tpl-def-entitylist
#' @template tpl-entitylist-dataset
#' @template tpl-auth-missing
#' @template tpl-compat-2022-3
#' @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 An S3 class `odata_entitylist_data_get` with two list items:
#' * `context` The URL for the OData metadata document
#' * `value` A tibble of EntitySets available in this EntityList
# nolint start
#' @seealso \url{https://docs.getodk.org/central-api-odata-endpoints/#id3}
#' @seealso \url{http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#_Toc31358948}
# 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 = "...")
#'
#' ds <- entitylist_list(pid = get_default_pid())
#'
#' ds1 <- odata_entitylist_data_get(pid = get_default_pid(), did = ds$name[1])
#'
#' ds1
#' ds1$context
#' ds1$value
#' }
odata_entitylist_data_get <- function(pid = get_default_pid(),
did = "",
url = get_default_url(),
un = get_default_un(),
pw = get_default_pw(),
retries = get_retries(),
odkc_version = get_default_odkc_version(),
orders = get_default_orders(),
tz = get_default_tz()) {
yell_if_missing(url, un, pw, pid = pid, did = did)

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

ds <- httr::RETRY(
"GET",
httr::modify_url(url,
path = glue::glue(
"v1/projects/{pid}/datasets/",
"{URLencode(did, reserved = TRUE)}.svc/Entities"
)
),
httr::add_headers(
"Accept" = "application/json"
),
httr::authenticate(un, pw),
times = retries
) |>
yell_if_error(url, un, pw) |>
httr::content(encoding = "utf-8") |>
janitor::clean_names()

entities <- ds$value |>
# Replace NULLs with NA in each list
purrr::map(~ purrr::modify_if(.x, is.null, ~NA)) |>
purrr::map_dfr(
~ {
# Extract main fields excluding __system
main_fields <- .x[!names(.x) %in% "__system"]

# Extract system fields or empty list if NULL
system_fields <- .x[["__system"]] %||% list()

# Ensure any NULL fields within system are NA
system_fields <- purrr::modify_if(system_fields, is.null, ~NA)

# Combine main and system fields into a single tibble row
tibble::as_tibble(c(main_fields, system_fields))
}
) |>
janitor::clean_names() |>
# Remove duplicate rows by `id`
dplyr::distinct(id, .keep_all = TRUE) |>
dplyr::mutate(
dplyr::across(
dplyr::matches("created_at|updated_at"),
~ isodt_to_local(., orders = orders, tz = tz)
)
)

structure(
list(context = ds$odata_context, value = entities),
class = c("odata_entitylist_data_get", "list")
)
}

#' @export
print.odata_entitylist_data_get <- function(x, ...) {
cat("<ruODK OData EntityList Data>", sep = "\n")
cat(" OData Context: ", x$context, "\n")
cat(" OData Entities:", nrow(x$value), "\n")
}

# usethis::use_test("odata_entitylist_data_get") # nolint
1 change: 1 addition & 0 deletions R/ruODK.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ utils::globalVariables(c(
".",
"archived",
"children",
"id",
"name",
"path",
"type",
Expand Down
1 change: 1 addition & 0 deletions man/entity_audits.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_changes.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_create.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_delete.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_detail.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_list.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_update.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entity_versions.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entitylist_detail.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entitylist_download.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entitylist_list.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/entitylist_update.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 0268f65

Please sign in to comment.