Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#56] | Implement pagination #57

Draft
wants to merge 67 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
23b2d0f
#4 download data in asset and kobo class
pr130 Jul 22, 2021
b25b72f
#4 add another test and code styling
pr130 Jul 22, 2021
b04a9a2
Update R/asset.R
pr130 Jul 27, 2021
a423370
Extract function "select_prep_client"
MKyhos Aug 6, 2021
92defc1
Minimal refactoring
MKyhos Aug 6, 2021
f889e85
Init skeleton for new Kobo function "get_paginated"
MKyhos Aug 6, 2021
6e8e9af
Init KoboPaginator class (skeleton only)
MKyhos Aug 6, 2021
f4eca94
Add infix coalesce operator
MKyhos Aug 8, 2021
d0155ea
Proceed on kobo paginator class
MKyhos Aug 8, 2021
732893d
Refactor Kobo$select_prep_client method
MKyhos Aug 8, 2021
058fe36
Getter for private base_url field (KoboClient)
MKyhos Aug 8, 2021
ea0387a
Untested skeleton functionality for resolve_next
MKyhos Aug 8, 2021
b72e00f
Add details to page method
MKyhos Aug 8, 2021
c64b343
Add documentation to set_first_response method
MKyhos Aug 8, 2021
b6f350c
Merge branch 'dev' into issue56_pagination
MKyhos Aug 9, 2021
157b476
mac stuff
pr130 Sep 5, 2021
9cbf081
wrangling submissions vignette
pr130 Sep 5, 2021
22a791d
Merge branch 'dev' into 4-survey-responses-df
pr130 Sep 11, 2021
ff84433
first draft
pr130 Sep 14, 2021
8119905
move readme to top level
pr130 Sep 14, 2021
3e5dab6
readme and contributing md
pr130 Oct 3, 2021
e542c87
progress on get started
pr130 Oct 3, 2021
746d08b
Running automated reformatting
MKyhos Nov 13, 2021
86414bb
Fix namespace import with checkmate
MKyhos Nov 13, 2021
f70f34d
Update docs
MKyhos Nov 13, 2021
f13dff1
Merge branch 'dev' into 50-readme
pr130 Jan 21, 2022
8bc0a77
get started and restructure vignettes/articles
pr130 Jan 24, 2022
c3402af
do not print warning for not specifying v1 url (no consequences as of…
pr130 Jan 24, 2022
b8fe2bf
readme update and code of conduct/contributing.md
pr130 Jan 24, 2022
2d14c62
different landing pages for github and pkgdown
pr130 Jan 24, 2022
02cd7b8
code of conduct and contributing md
pr130 Jan 24, 2022
2037a1c
fix rcmdcheck note re non used imports
pr130 Jan 24, 2022
6220f75
Merge branch '50-readme' into refactor
MKyhos Jan 28, 2022
697c4cd
Remove dependency: glue
MKyhos Jan 28, 2022
dcd1793
Import usethis::ui_ funs once for all
MKyhos Jan 28, 2022
08d44d5
Only one version of list_as_json_char needed
MKyhos Jan 28, 2022
1b490fe
Rename function: check_repair_path -> append_slash
MKyhos Jan 28, 2022
bc10016
Update namespace/docs (missed it)
MKyhos Jan 28, 2022
f0c7041
KoboClient: inline function call
MKyhos Jan 28, 2022
059680c
Kobo/KoboClient: add and update assertions
MKyhos Jan 28, 2022
8dfdea9
hard wrap markdown - delete duplicate file
pr130 Jan 31, 2022
15e678f
Kobo is a class not an object
pr130 Jan 31, 2022
32a4b6a
fix spelling mistake
pr130 Jan 31, 2022
bc8d258
do not use :: if we i import tibble
pr130 Jan 31, 2022
2215df2
Merge branch '50-readme' of https://github.com/CorrelAid/kbtbr into 5…
pr130 Jan 31, 2022
b239564
use tidyverse (@MKyhos said no baseR)
pr130 Jan 31, 2022
f65bd34
no html preview
pr130 Jan 31, 2022
fc88a1c
code of conduct title
pr130 Jan 31, 2022
513ebe9
fix check notes
pr130 Jan 31, 2022
9b7eb72
Merge branch '50-readme' into refactor
MKyhos Feb 1, 2022
b875b2f
Apply new indent
MKyhos Feb 5, 2022
fd2bfe3
Merge branch 'dev' into refactor
MKyhos Feb 5, 2022
2cee6fb
Asset: restructure
MKyhos Feb 5, 2022
e8284cd
KoboClient: bring R6 elements in order
MKyhos Feb 5, 2022
181ad9a
Update docs
MKyhos Feb 5, 2022
fbea853
Asset: fix the active binding access util
MKyhos Feb 5, 2022
fe35f7c
Add Section headers to R6 objects
MKyhos Feb 5, 2022
bc30a28
Apply new indent
MKyhos Feb 5, 2022
10b6a3a
Merge branch 'dev' into issue56_pagination
MKyhos Feb 5, 2022
17a7f6c
Merge branch 'refactor' into issue56_pagination
MKyhos Feb 5, 2022
9ff416c
Kobo: reorder private/public
MKyhos Feb 5, 2022
30318ff
Remove remaining merge marks
MKyhos Feb 5, 2022
abafdc9
Asset: fix description of init
MKyhos Feb 5, 2022
227efd8
Format & add some docs
MKyhos Feb 5, 2022
408dd95
Merge branch 'dev' into issue56_pagination
MKyhos Dec 2, 2022
ad6b5da
First houskeeping after long time...
MKyhos Dec 2, 2022
9832425
Increment version number
MKyhos Dec 2, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: kbtbr
Type: Package
Title: A Wrapper for the KoBoToolbox API
Version: 0.1.0.9000
Version: 0.1.0.9002
Authors@R: c(person("Dimitri", "Marinelli", role = c("aut")),
person("Lada", "Rudnitckaia", role = "aut"),
person("Malte", "Kyhos", role = "aut"),
Expand Down
83 changes: 83 additions & 0 deletions R/kobo-paginator.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#' @title KoboPaginator
#' @description
#' A class that implements link-header style pagination, as is used in
#' the Kobotoolbox API.
#' @export
KoboPaginator <- R6::R6Class(
public = list(
client = NULL,
#' @description
#' @param client KoboClient. An instance of a KoboClient that can
#' be used for the paginated requestes.
initialize = function(client) {
private$client <- assert_class(client, "KoboClient")
},
#' @description
#' @param path
#' @param query
#' @param ... Additional parameters passed to internally
#' called `KoboClient$get()`.
get = function(path, query, ...) {
assert_string(path)
assert_string(query)
private$page(path, query, ...)
},
#' @description
#' Set the initial response
#' @details
#' Usually, the page method would revoke a first response in the
#' normal way, using its `next` element to walk over all subsequent
#' pages. In some settings, the user might provide this initial response
#' object already.
set_first_response = function(response, force_reset = FALSE) {
assert_list(response)
if (!(force_reset || is.null(private$resps))) {
stop("There are already existing responses. Use 'force_reset = TRUE' to reset/delete them.")
}
private$resps <- list(response)
},
get_responses = function() {
return(private$resps)
}
),
private = list(
resps = NULL,
page = function(path, query, sleep = 0.5, ...) {
tmp <- list()

# Retrieve initial response

# Initialize Pagination
tmp[[1]] <- private$resps[[1]] %||% self$client$get(path, query, ...)
next_link <- tmp[[1]][["next"]]
cnt <- 1

repeat {
if (is.null(next_link)) {
message(sprintf("Iterated over %s pages.", cnt))
break
}

# New iteration
Sys.sleep(sleep)
cnt <- cnt + 1
tmp_path <- private$resolve_next(next_link)

tmp[[cnt]] <- self$client$get(tmp_path, query, ...)
tmp[[cnt]]$raise_for_status()
next_link <- tmp[[cnt]][["next"]]
}

private$resps <- tmp
},
resolve_next = function(link) {
# Subtract base path etc. from
resolved <- gsub(
pattern = paste0("^", self$client$get_base_url()),
replacement = "",
x = link
)
return(resolved)
}
)
)
54 changes: 46 additions & 8 deletions R/kobo.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
#' interactions with the various endpoints.
#' @export
Kobo <- R6::R6Class("Kobo",
# private = list(
# ),
public = list(

# Public Fields ============================================================

#' @field session_v2 [kbtbr::KoboClient] session for v2 of the API
session_v2 = NULL,
#' @field session_v1 `KoboClient` session for v1 of the API
#' @field session_v1 [KoboClient] session for v1 of the API
session_v1 = NULL,

# Public Methods ===========================================================
Expand All @@ -28,9 +26,9 @@ Kobo <- R6::R6Class("Kobo",
#' For example: https://kc.correlaid.org.
#' @param kobo_token character. The API token. Defaults to requesting
#' the systen environment `KBTBR_TOKEN`.
#' @param session_v2 [KoboClient] To pass directly
#' @param session_v2 [KoboClient]. Alternatively, pass directly
#' a [KoboClient] instance for the API version v2.
#' @param session_v1 [KoboClient] In addition to session_v2 one can pass
#' @param session_v1 KoboClient. In addition to session_v2 one can pass
#' also a [KoboClient] instance for the API version v1.
initialize = function(base_url_v2 = NULL, base_url_v1 = NULL,
kobo_token = Sys.getenv("KBTBR_TOKEN"),
Expand Down Expand Up @@ -68,7 +66,7 @@ Kobo <- R6::R6Class("Kobo",
#' @param format character. the format to request from the server. either 'json' or 'csv'. defaults to 'json'
#' @param parse whether or not to parse the HTTP response. defaults to TRUE.
#' @return a list encoding of the json server reply if parse=TRUE.
#' Otherwise, it returns the server response as a crul::HttpResponse
#' Otherwise, it returns the server response as a [crul::HttpResponse]
#' object.
get = function(path, query = list(), version = "v2", format = "json",
parse = TRUE) {
Expand Down Expand Up @@ -107,9 +105,12 @@ Kobo <- R6::R6Class("Kobo",
)
}

# Select client
obj <- private$select_prep_client(path, version)
res <- obj$client$get(obj$path, query)
res$raise_for_status()

if (format == "json" & parse) {
if (parse && format == "json") {
res$raise_for_ct_json()
return(res$parse("UTF-8") %>% jsonlite::fromJSON())
} else if (format == "csv" & parse) {
Expand All @@ -123,6 +124,13 @@ Kobo <- R6::R6Class("Kobo",
}
return(res)
},
get_paginated = function(path, query, version = "v2",
format = "json",
parse = TRUE) {
obj <- private$select_prep_client(path, version)
paginator <- KoboPaginator$new(client = obj$client)
res <- paginator$get(path, query)
},

#' @description
#' Wrapper for the POST method of internal session objects.
Expand Down Expand Up @@ -302,5 +310,35 @@ Kobo <- R6::R6Class("Kobo",
)
self$post("assets/", body = body)
}
) # <end public>
), # <end public>
private = list(

# Private Methods ==========================================================

#' @description
#' Logic to select and prepare a client
select_prep_client = function(path, version) {
checkmate::assert_choice(version, c("v1", "v2"))

if (version == "v2") {
obj <- list(
client = self$session_v2,
path = paste0("api/v2/", path)
)
} else if (version == "v1") {
if (checkmate::test_null(self$session_v1)) {
usethis::ui_stop(paste(
"Session for API v1 is not initalized.",
"Please re-initalize the Kobo client with the",
"base_url_v1 argument."
))
}
obj <- list(
client = self$session_v1,
path = paste0("api/v1/", path)
)
}
return(obj)
}
)
)
18 changes: 14 additions & 4 deletions man/Kobo.Rd

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

89 changes: 89 additions & 0 deletions man/KoboPaginator.Rd

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

2 changes: 2 additions & 0 deletions tests/testthat/test-kobo.R
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ test_that("Kobo can get submissions for a survey", {
expect_true(tibble::is_tibble(response_df))
expect_equal(nrow(response_df), 4)
})

# ERRORS -----------

vcr::use_cassette("kobo-get-404", {
test_that("non existing route throws 404 error", {
kobo <- Kobo$new(base_url_v2 = BASE_URL, kobo_token = Sys.getenv("KBTBR_TOKEN"))
Expand Down