diff --git a/.Rbuildignore b/.Rbuildignore index f12b066..b6be4de 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -1,3 +1,5 @@ +^renv$ +^renv\.lock$ ^.*\.Rproj$ ^\.Rproj\.user$ ^\.github$ @@ -9,3 +11,5 @@ ^CRAN-RELEASE$ ^\.httr-oauth$ ^codecov\.yml$ +.httr-oauth +^cran-comments\.md$ diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 30d7777..1a27b06 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -29,7 +29,6 @@ jobs: R_REMOTES_NO_ERRORS_FROM_WARNINGS: true RSPM: ${{ matrix.config.rspm }} GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - FITBITR_ENVIRONMENT: ${{ secrets.FITBITR_ENVIRONMENT }} steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/test-coverage.yaml b/.github/workflows/test-coverage.yaml deleted file mode 100644 index 1fdc899..0000000 --- a/.github/workflows/test-coverage.yaml +++ /dev/null @@ -1,49 +0,0 @@ -on: - push: - branches: - - main - - master - pull_request: - branches: - - main - - master - -name: test-coverage - -jobs: - test-coverage: - runs-on: macOS-latest - env: - GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} - FITBITR_ENVIRONMENT: ${{ secrets.FITBITR_ENVIRONMENT }} - steps: - - uses: actions/checkout@v2 - - - uses: r-lib/actions/setup-r@v1 - - - uses: r-lib/actions/setup-pandoc@v1 - - - name: Query dependencies - run: | - install.packages('remotes') - saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) - writeLines(sprintf("R-%i.%i", getRversion()$major, getRversion()$minor), ".github/R-version") - shell: Rscript {0} - - - name: Restore R package cache - uses: actions/cache@v2 - with: - path: ${{ env.R_LIBS_USER }} - key: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1-${{ hashFiles('.github/depends.Rds') }} - restore-keys: ${{ runner.os }}-${{ hashFiles('.github/R-version') }}-1- - - - name: Install dependencies - run: | - install.packages(c("remotes")) - remotes::install_deps(dependencies = TRUE) - remotes::install_cran("covr") - shell: Rscript {0} - - - name: Test coverage - run: covr::codecov() - shell: Rscript {0} diff --git a/.gitignore b/.gitignore index 094800f..c47d9a9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ vignettes/*.pdf .Renviron docs .DS_Store +fitbitr-config.dcf +.Rprofile +.fitbitr-token.rds diff --git a/DESCRIPTION b/DESCRIPTION index 94656c3..cb320d8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Type: Package Package: fitbitr Title: Interface with the 'Fitbit' API -Version: 0.2.0 +Version: 0.3.0 Authors@R: person(given = "Matt", family = "Kaye", @@ -38,10 +38,12 @@ Imports: Suggests: covr, checkmate (>= 2.0.0), + devtools, + rprojroot, spelling, testthat (>= 3.0.0) Config/testthat/edition: 3 Encoding: UTF-8 Language: en-US Roxygen: list(markdown = TRUE) -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.3 diff --git a/NAMESPACE b/NAMESPACE index 9b7b8ca..4b1e02b 100755 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,29 +1,35 @@ # Generated by roxygen2: do not edit by hand export("%>%") -export(activity_calories) -export(activity_summary) -export(calories) -export(calories_bmr) -export(distance) -export(elevation) -export(floors) -export(generate_token) -export(heart_rate_intraday) -export(heart_rate_zones) -export(lifetime_bests) -export(lifetime_totals) -export(load_cached_token) -export(minutes_fairly_active) -export(minutes_lightly_active) -export(minutes_sedentary) -export(minutes_very_active) -export(sleep_stage_granular) -export(sleep_stage_summary) -export(sleep_summary) -export(steps) -export(tracker_bests) -export(tracker_totals) +export(generate_fitbitr_token) +export(get_active_zone_minutes_intraday) +export(get_activity_calories) +export(get_activity_summary) +export(get_calories) +export(get_calories_bmr) +export(get_calories_intraday) +export(get_distance) +export(get_distance_intraday) +export(get_elevation) +export(get_elevation_intraday) +export(get_floors) +export(get_floors_intraday) +export(get_heart_rate_intraday) +export(get_heart_rate_zones) +export(get_lifetime_bests) +export(get_lifetime_totals) +export(get_minutes_fairly_active) +export(get_minutes_lightly_active) +export(get_minutes_sedentary) +export(get_minutes_very_active) +export(get_sleep_stage_granular) +export(get_sleep_stage_summary) +export(get_sleep_summary) +export(get_steps) +export(get_steps_intraday) +export(get_tracker_bests) +export(get_tracker_totals) +export(perform_get) importFrom(dplyr,across) importFrom(dplyr,arrange) importFrom(dplyr,bind_rows) @@ -36,14 +42,14 @@ importFrom(httr,add_headers) importFrom(httr,content) importFrom(httr,oauth2.0_token) importFrom(httr,oauth_app) -importFrom(httr,stop_for_status) importFrom(janitor,clean_names) +importFrom(jsonlite,fromJSON) +importFrom(jsonlite,toJSON) +importFrom(jsonlite,validate) importFrom(lubridate,as_datetime) -importFrom(lubridate,today) importFrom(magrittr,"%>%") importFrom(purrr,flatten) importFrom(purrr,flatten_dfr) -importFrom(purrr,keep) importFrom(purrr,list_modify) importFrom(purrr,map) importFrom(purrr,map_chr) @@ -52,9 +58,6 @@ importFrom(purrr,pluck) importFrom(rlang,":=") importFrom(rlang,.data) importFrom(rlang,abort) -importFrom(rlang,inform) -importFrom(rlang,warn) importFrom(tibble,enframe) importFrom(tidyr,pivot_wider) importFrom(tidyr,unnest_wider) -importFrom(utils,askYesNo) diff --git a/NEWS.md b/NEWS.md index 49306f3..3e7523f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,19 @@ +# fitbitr 0.3.0 + +`v0.3.0` is a significant overhaul of `fitbitr` + +### Breaking changes: + +* Functions are now verb-prefixed, so `steps` has become `get_steps()` + +### Other changes + +* Significantly more validation exists for function arguments before sending requests to the API +* More information is returned to the user on API errors +* Adds new intraday time series +* Many more updates to internals +* Significant additional test coverage and overhauls how tests are run. + # fitbitr 0.2.0 * Added a `NEWS.md` file to track changes to the package. diff --git a/R/aaa.R b/R/aaa.R index 2905685..3d00c91 100755 --- a/R/aaa.R +++ b/R/aaa.R @@ -5,6 +5,7 @@ invisible() } + ## package constants base_url <- "https://api.fitbit.com" diff --git a/R/activity.R b/R/activity.R index 7d7eae1..c377c0e 100755 --- a/R/activity.R +++ b/R/activity.R @@ -1,31 +1,31 @@ #' Activity Summary #' #' See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for more details. +#' #' @param date The date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @importFrom dplyr bind_rows mutate select everything #' @importFrom httr content #' @importFrom janitor clean_names +#' #' @examples #' \dontrun{ #' date <- lubridate::today() -#' activity_summary(date) +#' get_activity_summary(date) #' } #' @return A tibble with the `date` and a number of activity summary metrics for the day. #' @export -activity_summary <- function(date) { - check_token_exists() +get_activity_summary <- function(date) { + validate_date(date) url <- sprintf( "%s/1/user/%s/activities/date/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), date ) - r <- get( - url = url, - .example_identifier = "activity summary" - ) + r <- perform_get(url) r %>% content(as = "parsed", type = "application/json") %>% @@ -35,48 +35,50 @@ activity_summary <- function(date) { bind_rows() %>% mutate( date = as.Date(date), - across(-.data$date, as.numeric) + across(-"date", as.numeric) ) %>% - select(date, everything()) %>% + select("date", everything()) %>% clean_names() } #' Activity Time Series #' #' See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for more details. +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param resource_path The resource path. See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for options +#' #' @importFrom purrr flatten_dfr #' @importFrom rlang := +#' @importFrom dplyr rename +#' #' @noRd -activity_time_series <- function(start_date, end_date, resource_path, .example_identifier) { - check_token_exists() +get_activity_time_series <- function(start_date, end_date, resource_path) { + validate_date(start_date) + validate_date(end_date) url <- sprintf( "%s/1/user/%s/activities/%s/date/%s/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), resource_path, start_date, end_date ) - r <- get( - url = url, - .example_identifier = .example_identifier - ) + r <- perform_get(url) r %>% content(as = "parsed", type = "application/json") %>% flatten_dfr() %>% rename( - date = .data$dateTime, - !!resource_path := .data$value + date = "dateTime", + !!resource_path := "value" ) %>% mutate( date = as.Date(.data$date), - across(-.data$date, as.numeric) + across(-"date", as.numeric) ) %>% clean_names() } @@ -84,259 +86,277 @@ activity_time_series <- function(start_date, end_date, resource_path, .example_i #' Calories Time Series #' #' Resource path /activities/calories +#' + #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() #' calories(date) #' } +#' #' @return A tibble with two columns: `date` and `calories` #' @export -calories <- function(start_date, end_date) { - activity_time_series( +get_calories <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "calories", - .example_identifier = "activity time series" + resource_path = "calories" ) } #' Calories BMR Time Series #' #' Resource path /activities/caloriesBMR +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' calories_bmr(date) +#' get_calories_bmr(date) #' } +#' #' @return A tibble with two columns: `date` and `calories_bmr` #' @export -calories_bmr <- function(start_date, end_date) { - activity_time_series( +get_calories_bmr <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "caloriesBMR", - .example_identifier = "activity time series" + resource_path = "caloriesBMR" ) } #' Steps Time Series #' #' Resource path /activities/steps +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' steps(date) +#' get_steps(date) #' } +#' #' @return A tibble with two columns: `date` and `steps` #' @export -steps <- function(start_date, end_date) { - activity_time_series( +get_steps <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "steps", - .example_identifier = "activity time series" + resource_path = "steps" ) } #' Distance Time Series #' #' Resource path /activities/distance +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' distance(date) +#' get_distance(date) #' } +#' #' @return A tibble with two columns: `date` and `distance` #' @export -distance <- function(start_date, end_date) { - activity_time_series( +get_distance <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "distance", - .example_identifier = "activity time series" + resource_path = "distance" ) } #' Floors Time Series #' #' Resource path /activities/floors +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' floors(date) +#' get_floors(date) #' } +#' #' @return A tibble with two columns: `date` and `floors` #' @export -floors <- function(start_date, end_date) { - activity_time_series( +get_floors <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "floors", - .example_identifier = "activity time series" + resource_path = "floors" ) } #' Elevation Time Series #' #' Resource path /activities/elevation +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' elevation(date) +#' get_elevation(date) #' } +#' #' @return A tibble with two columns: `date` and `elevation` #' @export -elevation <- function(start_date, end_date) { - activity_time_series( +get_elevation <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "elevation", - .example_identifier = "activity time series" + resource_path = "elevation" ) } #' Minutes Sedentary Time Series #' #' Resource path /activities/minutesSedentary +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' minutes_sedentary(date) +#' get_minutes_sedentary(date) #' } +#' #' @return A tibble with two columns: `date` and `minutes_sedentary` #' @export -minutes_sedentary <- function(start_date, end_date) { - activity_time_series( +get_minutes_sedentary <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "minutesSedentary", - .example_identifier = "activity time series" + resource_path = "minutesSedentary" ) } #' Minutes Lightly Active Time Series #' #' Resource path /activities/minutesLightlyActive +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' minutes_lightly_active(date) +#' get_minutes_lightly_active(date) #' } +#' #' @return A tibble with two columns: `date` and `minutes_lightly_active` #' @export -minutes_lightly_active <- function(start_date, end_date) { - activity_time_series( +get_minutes_lightly_active <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "minutesLightlyActive", - .example_identifier = "activity time series" + resource_path = "minutesLightlyActive" ) } #' Minutes Fairly Active Time Series #' #' Resource path /activities/minutesFairlyActive +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' minutes_fairly_active(date) +#' get_minutes_fairly_active(date) #' } +#' #' @return A tibble with two columns: `date` and `minutes_fairly_active` #' @export -minutes_fairly_active <- function(start_date, end_date) { - activity_time_series( +get_minutes_fairly_active <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "minutesFairlyActive", - .example_identifier = "activity time series" + resource_path = "minutesFairlyActive" ) } #' Minutes Very Active Time Series #' #' Resource path /activities/minutesVeryActive +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' minutes_very_active(date) +#' get_minutes_very_active(date) #' } +#' #' @return A tibble with two columns: `date` and `minutes_very_active` #' @export -minutes_very_active <- function(start_date, end_date) { - activity_time_series( +get_minutes_very_active <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "minutesVeryActive", - .example_identifier = "activity time series" + resource_path = "minutesVeryActive" ) } #' Activity Calories Time Series #' #' Resource path /activities/activityCalories +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() -#' activity_calories(date) +#' get_activity_calories(date) #' } +#' #' @return A tibble with two columns: `date` and `activity_calories` #' @export -activity_calories <- function(start_date, end_date) { - activity_time_series( +get_activity_calories <- function(start_date, end_date) { + get_activity_time_series( start_date, end_date, - resource_path = "activityCalories", - .example_identifier = "activity time series" + resource_path = "activityCalories" ) } #' @noRd get_bests_and_totals <- function(best, tracker) { - check_token_exists() - url <- sprintf( "%s/1/user/%s/activities.json", base_url, - .fitbitr_token$credentials$user_id + fetch_user_id() ) - r <- get( - url = url, - .example_identifier = "bests and totals" - ) + r <- perform_get(url) r %>% content(as = "parsed", type = "application/json") %>% @@ -348,87 +368,100 @@ get_bests_and_totals <- function(best, tracker) { #' Tracker Totals #' #' Retrieve tracker total distance, floors, and steps +#' +#' #' @examples #' \dontrun{ -#' tracker_totals() +#' get_tracker_totals() #' } +#' #' @export #' @return A tibble of all-time tracker totals (i.e. the total `distance`, `floors`, and `steps` tracked by your tracker) -tracker_totals <- function() { +get_tracker_totals <- function() { get_bests_and_totals( best = FALSE, tracker = TRUE ) %>% bind_rows() %>% select( - .data$distance, - .data$floors, - .data$steps + "distance", + "floors", + "steps" ) } #' Lifetime Totals #' #' Retrieve lifetime total distance, floors, and steps +#' +#' #' @examples #' \dontrun{ -#' lifetime_totals() +#' get_lifetime_totals() #' } +#' #' @return A tibble of all-time totals across trackers (i.e. the total `distance`, `floors`, and `steps` tracked across all of your trackers) #' @export -lifetime_totals <- function() { +get_lifetime_totals <- function() { get_bests_and_totals( best = FALSE, tracker = FALSE ) %>% bind_rows() %>% select( - .data$distance, - .data$floors, - .data$steps + "distance", + "floors", + "steps" ) } #' Tracker Bests #' #' Retrieve tracker best distance, floors, and steps +#' +#' #' @examples #' \dontrun{ -#' tracker_bests() +#' get_tracker_bests() #' } +#' #' @importFrom tidyr unnest_wider #' @importFrom tibble enframe +#' #' @return A tibble the best `distance`, `floors`, and `steps` (by date) tracked on your tracker #' @export -tracker_bests <- function() { +get_tracker_bests <- function() { get_bests_and_totals( best = TRUE, tracker = TRUE ) %>% enframe() %>% - unnest_wider(.data$value) %>% + unnest_wider("value") %>% rename( - metric = .data$name + metric = "name" ) } #' Lifetime Bests #' #' Retrieve lifetime best distance, floors, and steps +#' +#' #' @examples #' \dontrun{ -#' lifetime_bests() +#' get_lifetime_bests() #' } +#' #' @return A tibble the best `distance`, `floors`, and `steps` (by date) tracked on any of your trackers #' @export -lifetime_bests <- function() { +get_lifetime_bests <- function() { get_bests_and_totals( best = TRUE, tracker = FALSE ) %>% enframe() %>% - unnest_wider(.data$value) %>% + unnest_wider("value") %>% rename( - metric = .data$name + metric = "name" ) } diff --git a/R/api.R b/R/api.R index f6dc02e..313ae06 100644 --- a/R/api.R +++ b/R/api.R @@ -1,46 +1,85 @@ -#' @importFrom httr GET add_headers stop_for_status -#' @importFrom utils askYesNo -#' @param url the endpoint -#' @param .example_identifier An internal identifier to choose which example to run -#' @noRd -get <- function(url, .example_identifier) { - if (Sys.getenv("FITBITR_ENVIRONMENT") == "testing mode") { - r <- get_example_response(url, .example_identifier) +#' @importFrom rlang abort +fetch_user_id <- function() { + if (is.null(.fitbitr_token)) { + abort("No token provided.") + } + + if (class(.fitbitr_token)[1] != "Token2.0") { + abort("You must provide a token of class `Token2.0`") + } + + user_id <- pluck(.fitbitr_token, "credentials", "user_id", .default = NULL) + if (is.null(user_id)) { + abort("The token you provided had no associated `user_id`. Maybe it was empty? Please supply a valid token.") + } + + user_id +} + +#' @importFrom jsonlite toJSON fromJSON validate +stop_for_status <- function(response) { + status_code <- response$status_code + if (status_code == 200) { + response } else { - r <- GET( - url, - add_headers( - .headers = c( - Authorization = paste0("Bearer ", .fitbitr_token$credentials$access_token) - ) - ) - ) + response <- content(response) + + to_print <- if (!is.null(pluck(response, "errors"))) { + toJSON(response$errors, pretty = TRUE, auto_unbox = TRUE) + } else if (validate(response)) { + toJSON(fromJSON(response), pretty = TRUE, auto_unbox = TRUE) + } else { + as.character(response) + } - if (check_token_expiry(r)) { - tryCatch( - { - inform("Token expired. Trying to refresh...\n\n ...\n") - .fitbitr_token$refresh() - }, - error = function(e) { - ask_refresh("Refresh failed", e) - } + abort( + c( + sprintf("Fitbit API request failed with status code %s", status_code), + "*" = "Error text below:", + to_print ) + ) + } +} - return(get(url, .example_identifier)) - } +#' Perform a GET request +#' +#' @rdname get +#' +#' @importFrom httr GET add_headers +#' +#' @param url The URL to make the request to +#' @param \dots Additional arguments (not currently used) +#' +#' @return The response +#' @export +perform_get <- function(url, ...) { + if (is.null(.fitbitr_token)) { + abort("No token found. Please run `generate_fitbitr_token()` to create one.") + } - if (check_rate_limit(r)) { - abort("Fitbit API rate limit exceeded. For details, see https://dev.fitbit.com/build/reference/web-api/basics/#rate-limits.") - } + if (class(.fitbitr_token)[1] != "Token2.0") { + abort("You must provide a token of class `Token2.0`") + } - tryCatch( - stop_for_status(r), - error = function(e) { - ask_refresh("Failed to query the API with your token", e) - } + response <- GET( + url, + add_headers( + .headers = c( + Authorization = paste0("Bearer ", .fitbitr_token$credentials$access_token) + ) ) + ) + + if (check_token_expiry(response)) { + abort("Token expired. Please generate a new one with `generate_fitbitr_token()") + } + + if (check_rate_limit(response)) { + abort("Fitbit API rate limit exceeded. For details, see https://dev.fitbit.com/build/reference/web-api/basics/#rate-limits.") } + + stop_for_status(response) } #' @noRd @@ -65,28 +104,3 @@ check_rate_limit <- function(r) { } } -#' @noRd -#' @param reason A string reason for why the request failed -#' @param error_message the error message -#' @importFrom rlang inform -#' @return No return value. Called for side effects -ask_refresh <- function(reason, error_message) { - inform(sprintf("%s. Error message: \n\n", reason)) - inform(error_message$message) - inform("\n") - - do_refresh <- askYesNo( - "Would you like to generate a new token?", - default = FALSE, - prompts = c("y", "n", "c") - ) - - if (do_refresh & !is.na(do_refresh)) { - inform("Trying to generate a new token...") - .fitbitr_token$init_credentials() - } else { - abort("No token was found, and a new one was not generated.") - } - - invisible() -} diff --git a/R/example-responses.R b/R/example-responses.R deleted file mode 100644 index a42d633..0000000 --- a/R/example-responses.R +++ /dev/null @@ -1,454 +0,0 @@ -response_class <- utils::getFromNamespace("response", "httr") - -#' GET example response, given a URL -#' -#' @noRd -#' @param url the url to get an example response for -#' @param .example_identifier An internal identifier to choose which example to run -#' @importFrom lubridate today -#' @importFrom rlang warn -get_example_response <- function(url, .example_identifier) { - warn( - "Heads up: You're in `fitbitr` testing mode. \n\nYou're seeing this message because you have the `FITBITR_ENVIRONMENT` env var set to 'testing mode'. \nIf you don't want to be in testing mode, please change the value of this environment variable.", - .frequency = "once", - .frequency_id = "0b69edb2-1c01-4014-8954-1a65136647cf" - ) - - response_class( - url = "test", - status_code = 200, - cookies = NULL, - header = list( - date = today(), - `content-type` = "application/octet-stream", - `content-length` = "1", - `www-authenticate` = "local", - `x-frame-options` = "SAMEORIGIN", - via = "1.1 google", - `cf-cache-status` = "DYNAMIC", - `cf-request-id` = "xxxxxxxxxxxxxxxx", - `expect-ct` = "max-age=604800, report-uri=\"testing", - server = "local", - `cf-ray` = "xxxxxxxx" - ), - content = switch(.example_identifier, - "activity summary" = activity_summary_example_response, - "activity time series" = activity_ts_example_response, - "bests and totals" = bests_and_totals_example_response, - "hr zones" = hr_zones_example, - "hr intraday" = hr_intraday_example, - "sleep summary" = sleep_summary_example, - "sleep stage" = sleep_stage_example - ) %>% charToRaw() - ) -} - -## Example responses -activity_summary_example_response <- ' -{ - "activities": [ - { - "activityId": 51007, - "activityParentId": 90019, - "calories": 230, - "description": "7mph", - "distance": 2.04, - "duration": 1097053, - "hasStartTime": true, - "isFavorite": true, - "logId": 1154701, - "name": "Treadmill, 0% Incline", - "startTime": "00:25", - "steps": 3783 - } - ], - "goals": { - "caloriesOut": 2826, - "distance": 8.05, - "floors": 150, - "steps": 10000 - }, - "summary": { - "activityCalories": 230, - "caloriesBMR": 1913, - "caloriesOut": 2143, - "distances": [ - { "activity": "tracker", "distance": 1.32 }, - { "activity": "loggedActivities", "distance": 0 }, - { "activity": "total", "distance": 1.32 }, - { "activity": "veryActive", "distance": 0.51 }, - { "activity": "moderatelyActive", "distance": 0.51 }, - { "activity": "lightlyActive", "distance": 0.51 }, - { "activity": "sedentaryActive", "distance": 0.51 }, - { "activity": "Treadmill, 0% Incline", "distance": 3.28 } - ], - "elevation": 48.77, - "fairlyActiveMinutes": 0, - "floors": 16, - "lightlyActiveMinutes": 0, - "marginalCalories": 200, - "sedentaryMinutes": 1166, - "steps": 0, - "veryActiveMinutes": 0 - } -} -' - -activity_ts_example_response <- ' -{ - "example": [ - { "dateTime": "2011-04-27", "value": 5490 }, - { "dateTime": "2011-04-28", "value": 2344 }, - { "dateTime": "2011-04-29", "value": 2779 }, - { "dateTime": "2011-04-30", "value": 9196 }, - { "dateTime": "2011-05-01", "value": 15828 }, - { "dateTime": "2011-05-02", "value": 1945 }, - { "dateTime": "2011-05-03", "value": 366 } - ] -} -' - -bests_and_totals_example_response <- ' -{ - "best": { - "total": { - "distance": { - "date": "2012-01-07", - "value": 20.31597 - }, - "floors": { - "date": "2012-01-29", - "value": 14 - }, - "steps": { - "date": "2012-01-07", - "value": 26901 - } - }, - "tracker": { - "distance": { - "date": "2012-01-07", - "value": 20.31597 - }, - "floors": { - "date": "2012-01-29", - "value": 14 - }, - "steps": { - "date": "2012-01-07", - "value": 26901 - } - } - }, - "lifetime": { - "total": { - "distance": 2711.62, - "floors": 2500, - "steps": 203300 - }, - "tracker": { - "distance": 2579.82, - "floors": 2500, - "steps": 106934 - } - } -} -' - -hr_zones_example <- ' -{ - "activities-heart": [ - { - "dateTime": "2015-08-04", - "value": { - "customHeartRateZones": [], - "heartRateZones": [ - { - "caloriesOut": 740.15264, - "max": 94, - "min": 30, - "minutes": 593, - "name": "Out of Range" - }, - { - "caloriesOut": 249.66204, - "max": 132, - "min": 94, - "minutes": 46, - "name": "Fat Burn" - }, - { - "caloriesOut": 0, - "max": 160, - "min": 132, - "minutes": 0, - "name": "Cardio" - }, - { - "caloriesOut": 0, - "max": 220, - "min": 160, - "minutes": 0, - "name": "Peak" - } - ], - "restingHeartRate": 68 - } - } - ] -} -' - -hr_intraday_example <- ' -{ - "activities-heart": [ - { - "customHeartRateZones": [], - "dateTime": "today", - "heartRateZones": [ - { - "caloriesOut": 2.3246, - "max": 94, - "min": 30, - "minutes": 2, - "name": "Out of Range" - }, - { - "caloriesOut": 0, - "max": 132, - "min": 94, - "minutes": 0, - "name": "Fat Burn" - }, - { - "caloriesOut": 0, - "max": 160, - "min": 132, - "minutes": 0, - "name": "Cardio" - }, - { - "caloriesOut": 0, - "max": 220, - "min": 160, - "minutes": 0, - "name": "Peak" - } - ], - "value": "64.2" - } - ], - "activities-heart-intraday": { - "dataset": [ - { - "time": "00:00:00", - "value": 64 - }, - { - "time": "00:00:10", - "value": 63 - }, - { - "time": "00:00:20", - "value": 64 - }, - { - "time": "00:00:30", - "value": 65 - }, - { - "time": "00:00:45", - "value": 65 - } - ], - "datasetInterval": 1, - "datasetType": "second" - } -} -' - -sleep_summary_example <- ' -{ - "sleep": [ - { - "dateOfSleep": "2017-04-02", - "duration": 100, - "efficiency": 50, - "isMainSleep": true, - "levels": { - "summary": { - "deep": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "light": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "rem": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "wake": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - } - }, - "data": [ - { - "datetime": "2017-04-01T23:58:30.000", - "level": "wake", - "seconds": 50 - }, - { - "datetime": "2017-04-02T00:16:30.000", - "level": "rem", - "seconds": 50 - } - ], - "shortData": [ - { - "datetime": "2017-04-02T05:58:30.000", - "level": "wake", - "seconds": 50 - }, - { - "datetime": "2017-04-02T06:58:30.000", - "level": "wake", - "seconds": 50 - } - ] - }, - "logId": 50, - "minutesAfterWakeup": 50, - "minutesAsleep": 50, - "minutesAwake": 50, - "minutesToFallAsleep": 50, // this is generally 0 for autosleep created sleep logs - "startTime": "2017-04-01T23:58:30.000", - "endTime": "2017-04-01T08:18:30.000", - "timeInBed": 2, - "type": "stages" - }, - { - "dateOfSleep": "2017-04-02", - "duration": 100, - "efficiency": 50, - "isMainSleep": false, - "levels": { - "data": [ - { - "dateTime": "2017-04-02T12:06:00.000", - "level": "asleep", - "seconds": 50 - }, - { - "dateTime": "2017-04-02T12:13:00.000", - "level": "restless", - "seconds": 50 - }, - { - "dateTime": "2017-04-02T12:14:00.000", - "level": "awake", - "seconds": 50 - } - ], - "summary": { - "asleep": { - "count": 0, // this field should not be used for "asleep" summary info - "minutes": 50 - }, - "awake": { - "count": 50, - "minutes": 50 - }, - "restless": { - "count": 50, - "minutes": 50 - } - } - }, - "logId": 50, - "minutesAfterWakeup": 50, - "minutesAsleep": 50, - "minutesAwake": 50, - "minutesToFallAsleep": 50, // this is generally 0 for autosleep created sleep logs - "startTime": "2017-04-02T12:06:00.000", - "timeInBed": 2, - "type": "classic" - } - ], - "summary": { - "totalMinutesAsleep": 50, - "totalSleepRecords": 2, - "totalTimeInBed": 2 - } -} -' - -sleep_stage_example <- ' -{ - "sleep": [ - { - "dateOfSleep": "2017-04-02", - "duration": 100, - "efficiency": 50, - "isMainSleep": , - "levels": { - "summary": { - "deep": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "light": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "rem": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - }, - "wake": { - "count": 50, - "minutes": 50, - "thirtyDayAvgMinutes": 50 - } - }, - "data": [ - { - "datetime": "2017-04-01T23:58:30.000", - "level": "wake", - "seconds": 50 - }, - { - "datetime": "2017-04-02T00:16:30.000", - "level": "light", - "seconds": 50 - } - ], - "shortData": [ - { - "datetime": "2017-04-02T05:58:30.000", - "level": "wake", - "seconds": 50 - } - ] - }, - "logId": 50, - "minutesAfterWakeup": 50, - "minutesAsleep": 50, - "minutesAwake": 50, - "minutesToFallAsleep": 50, // this is generally 0 for autosleep created sleep logs - "startTime": "2017-04-01T23:58:30.000", - "timeInBed": 2, - "type": "stages" - } - ] -} -' diff --git a/R/heart-rate.R b/R/heart-rate.R index 6ad4ecb..84a022c 100755 --- a/R/heart-rate.R +++ b/R/heart-rate.R @@ -1,88 +1,34 @@ -#' Heart Rate Intraday -#' -#' Returns heart rate data for the specified day -#' -#' @param date The start date of records to be returned in "yyyy-mm-dd" or date(time) format -#' @param minutes a boolean for whether data should be returned in minutes (TRUE) or seconds (FALSE) -#' @importFrom dplyr rename -#' @importFrom lubridate as_datetime -#' @importFrom rlang .data -#' @return A tibble of the `time` and your `heart_rate` at that time. -#' @details -#' See \url{https://dev.fitbit.com/reference/web-api/heart-rate/#get-heart-rate-time-series} for more details. -#' -#' @examples -#' \dontrun{ -#' date <- lubridate::today() -#' -#' ## get minute by minute data -#' heart_rate_intraday(date, minutes = TRUE) -#' -#' ## get more granular data -#' ## (not necessarily by second, but more granular than minutes) -#' heart_rate_intraday(date, minutes = FALSE) -#' } -#' @export -heart_rate_intraday <- function(date, minutes = TRUE) { - check_token_exists() - - url <- sprintf( - "%s/1/user/%s/activities/heart/date/%s/1d/%s.json", - base_url, - .fitbitr_token$credentials$user_id, - date, - ifelse(minutes, "1min", "1sec") - ) - - r <- get( - url = url, - .example_identifier = "hr intraday" - ) - - r %>% - content(as = "parsed", type = "application/json") %>% - pluck("activities-heart-intraday") %>% - pluck("dataset") %>% - bind_rows() %>% - mutate( - time = as_datetime(paste0(date, .data$time)) - ) %>% - rename( - heart_rate = .data$value - ) %>% - clean_names() -} - #' Heart Rate Zones #' #' See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for more details. +#' #' @importFrom purrr map_chr +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() #' -#' heart_rate_zones(start_date, end_date) +#' get_heart_rate_zones(start_date, end_date) #' } #' @return A tibble of the date, the heart rate zone (`zone`), the minimum heart rate in that zone (`min_hr`), the maximum heart rate in that zone (`max_hr`), the minutes in that zone (`minutes_in_zone`), and the calories burned in that zone (`calories_out`) #' @export -heart_rate_zones <- function(start_date, end_date = start_date) { - check_token_exists() +get_heart_rate_zones <- function(start_date, end_date = start_date) { + validate_date(start_date) + validate_date(end_date) url <- sprintf( "%s/1/user/%s/activities/heart/date/%s/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), start_date, end_date ) - r <- get( - url = url, - .example_identifier = "hr zones" - ) + r <- perform_get(url) hr_data <- r %>% content(as = "parsed", type = "application/json") %>% @@ -107,11 +53,11 @@ heart_rate_zones <- function(start_date, end_date = start_date) { date = dates %>% map(rep, 4) %>% unlist() %>% as.Date() ) %>% select( - date, - zone = .data$name, - min_hr = .data$min, - max_hr = .data$max, - minutes_in_zone = .data$minutes, - calories_out = .data$caloriesOut + "date", + zone = "name", + min_hr = "min", + max_hr = "max", + minutes_in_zone = "minutes", + calories_out = "caloriesOut" ) } diff --git a/R/internals.R b/R/internals.R deleted file mode 100644 index 5e484b8..0000000 --- a/R/internals.R +++ /dev/null @@ -1,14 +0,0 @@ -#' @noRd -#' @return TRUE (invisibly) on success -#' @importFrom rlang abort -check_token_exists <- function() { - if (Sys.getenv("FITBITR_ENVIRONMENT") == "testing mode") { - invisible(TRUE) - } else if (is.null(.fitbitr_token)) { - abort("No Fitbit API token found. You can generate one with `generate_token()` or load one from a cache with `load_cached_token()`.") - } else if (!.fitbitr_token$can_refresh()) { - abort("Your Fitbit API token cannot be refreshed. Please generate a new one with `generate_token()`") - } else { - invisible(TRUE) - } -} diff --git a/R/intraday.R b/R/intraday.R new file mode 100644 index 0000000..d0ccad0 --- /dev/null +++ b/R/intraday.R @@ -0,0 +1,361 @@ +stop_for_one_sided_interval <- function(start_time, end_time) { + if (xor(is.null(start_time), is.null(end_time))) { + abort("If you provide either `start_time` or `end_time`, both must be provided.") + } +} + +#' Get an intraday time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @param resource The resource to get +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @importFrom lubridate as_datetime +#' @importFrom rlang .data +#' +#' @return A tibble with two columns: `time` and `{{resource}}` +get_intraday_time_series <- function( + resource = c("active-zone-minutes", "calories", "distance", "elevation", "floors", "heart", "steps"), + date, + detail_level, + start_time, + end_time +) { + + resource <- match.arg(resource) + + if (!is.null(start_time)) { + url_suffix <- sprintf("/time/%s/%s.json", start_time, end_time) + } else { + url_suffix <- ".json" + } + + url <- sprintf( + "%s/1/user/%s/activities/%s/date/%s/1d/%s%s", + base_url, + fetch_user_id(), + resource, + date, + detail_level, + url_suffix + ) + + r <- perform_get(url) + + r %>% + content(as = "parsed", type = "application/json") %>% + pluck( + sprintf("activities-%s-intraday", resource), + "dataset" + ) %>% + bind_rows() %>% + dplyr::transmute( + time = as_datetime(paste(date, .data$time)), + !!resource := .data$value + ) %>% + arrange("time") +} + +#' Get intraday calories time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_calories_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_calories_intraday(detail_level = "1min") +#' } +#' +#' @return A tibble with two columns: `time` and `calories` +#' @export +get_calories_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "calories", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) +} + +#' Get intraday distance time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_distance_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_distance_intraday(detail_level = "1min") +#' } +#' +#' @return A tibble with two columns: `time` and `distance` +#' @export +get_distance_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "distance", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) +} + +#' Get intraday floors time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_floors_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_floors_intraday(detail_level = "1min") +#' } +#' +#' @return A tibble with two columns: `time` and `floors` +#' @export +get_floors_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "floors", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) +} + +#' Get intraday steps time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_steps_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_steps_intraday(detail_level = "1min") +#' } +#' +#' @return A tibble with two columns: `time` and `steps` +#' @export +get_steps_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "steps", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) +} + +#' Get intraday elevation time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_elevation_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_elevation_intraday(detail_level = "1min") +#' } +#' +#' @return A tibble with two columns: `time` and `elevation` +#' @export +get_elevation_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "elevation", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) +} + +#' Get intraday heart time series +#' +#' See the \href{https://dev.fitbit.com/reference/web-api/heart-rate/#get-heart-rate-time-series}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1sec"`, `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_heart_rate_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_heart_rate_intraday(detail_level = "1min") +#' } +#' @export +get_heart_rate_intraday <- function( + date = lubridate::today(), + detail_level = c("1sec", "1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "heart", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) %>% + rename(heart_rate = "heart") +} + +#' Get intraday active zone minutes time series +#' +#' See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-azm-intraday-by-date/}{API documentation} for +#' more detailed explanations of parameters and more usage information and examples. +#' +#' @family intraday +#' +#' @param date A date to get data for +#' @param detail_level The detail level. One of `"1min"`, `"5min"`, or `"15min"` +#' @param start_time The start time of the time window. Default: `NULL` gets the whole day +#' @param end_time The end time of the time window. Default: `NULL` gets the whole day +#' +#' @examples +#' \dontrun{ +#' date <- lubridate::today() +#' +#' ## get minute by minute data +#' get_active_zone_minutes_intraday(detail_level = "15min") +#' +#' ## get more granular data +#' get_active_zone_minutes_intraday(detail_level = "1min") +#' } +#' @export +get_active_zone_minutes_intraday <- function( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) { + detail_level <- match.arg(detail_level) + stop_for_one_sided_interval(start_time, end_time) + + get_intraday_time_series( + resource = "active-zone-minutes", + date = date, + detail_level = detail_level, + start_time = start_time, + end_time = end_time + ) %>% + clean_names() +} diff --git a/R/setup.R b/R/setup.R index 125417c..46190d4 100644 --- a/R/setup.R +++ b/R/setup.R @@ -1,34 +1,62 @@ -#' Generate a Fitbit API token +#' Generate an Oauth token #' -#' Simplify the setup process by persisting your Fitbit client_id and secret in the `.fitbitr-oauth` file. +#' Performs the OAuth 2.0 dance to create a token to use with the Fitbit API. #' #' @importFrom httr oauth_app oauth2.0_token -#' @param client_id your Fitbit client ID -#' @param client_secret your Fitbit client secret -#' @param callback your Fitbit redirect URL -#' @param scope the scopes to enable +#' +#' @param app_name The name of your OAuth app. Default: `fitbitr` +#' @param client_id Your Fitbit client ID +#' @param client_secret Your Fitbit client secret +#' @param callback Your Fitbit redirect URL +#' @param scope The scopes to enable #' @param cache Do you want to cache your token? See \link[httr]{oauth2.0_token} for details #' @param use_basic_auth A boolean for whether or not to use basic auth in \link[httr]{oauth2.0_token}. Defaults to `TRUE` #' @param ... additional arguments to be passed to \link[httr]{oauth2.0_token} +#' #' @examples #' \dontrun{ -#' generate_token( +#' generate_fitbitr_token( #' client_id = #' client_secret = , #' cache = TRUE #' ) #' } -#' @return No return value. This function generates a token and saves it (hidden) in the global environment to be used for the remainder of the R session. You can cache this token with `cache = TRUE` or explicitly setting a filepath to cache to. See \link[httr]{oauth2.0_token} for details. +#' +#' @return Returns an OAuth 2.0 token that can be used to authorize requests to the Fitbit API #' @export -generate_token <- function(client_id, - client_secret, - callback = "http://localhost:1410/", - scope = c("sleep", "activity", "heartrate", "location", "nutrition", "profile", "settings", "social", "weight"), - cache = FALSE, - use_basic_auth = TRUE, - ...) { +generate_fitbitr_token <- function( + app_name = "fitbitr", + client_id = Sys.getenv("FITBIT_CLIENT_ID"), + client_secret = Sys.getenv("FITBIT_CLIENT_SECRET"), + callback = Sys.getenv("FITBIT_CALLBACK", "https://localhost:1410/"), + scope = c( + "activity", + "cardio_fitness", + "electrocardiogram", + "heartrate", + "location", + "nutrition", + "oxygen_saturation", + "profile", + "respiratory_rate", + "settings", + "sleep", + "social", + "temperature", + "weight" + ), + cache = TRUE, + use_basic_auth = TRUE, + ... +) { + endpoint <- create_endpoint() - myapp <- oauth_app("r-package-test", key = client_id, secret = client_secret, redirect_uri = callback) + myapp <- oauth_app( + appname = app_name, + key = client_id, + secret = client_secret, + redirect_uri = callback + ) .fitbitr_token <<- oauth2.0_token( endpoint, @@ -39,28 +67,7 @@ generate_token <- function(client_id, ... ) - invisible() -} - -#' Load a token from the cache -#' -#' @importFrom purrr keep -#' @param path the path to the file where the token is stored -#' @examples -#' \dontrun{ -#' load_cached_token() -#' } -#' @return No return value. The token is stored in the global environment as a hidden variable named `.fitbitr_token` -#' @export -load_cached_token <- function(path = ".httr-oauth") { - .fitbitr_token <<- path %>% - readRDS() %>% - keep( - ~ grepl("fitbit", .x$endpoint$request) - ) %>% - pluck(1) - - invisible() + invisible(.fitbitr_token) } create_endpoint <- function() { diff --git a/R/sleep.R b/R/sleep.R index 267c046..bdc9f5b 100755 --- a/R/sleep.R +++ b/R/sleep.R @@ -1,36 +1,38 @@ #' Nightly Sleep Summary #' #' Returns a tibble of summary by night +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @importFrom purrr pluck list_modify map flatten #' @importFrom tibble enframe #' @importFrom tidyr pivot_wider #' @importFrom dplyr arrange across +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() #' -#' sleep_summary(start_date, end_date) +#' get_sleep_summary(start_date, end_date) #' } +#' #' @return A tibble of a variety of sleep summary data by day #' @export -sleep_summary <- function(start_date, end_date = start_date) { - check_token_exists() +get_sleep_summary <- function(start_date, end_date = start_date) { + validate_date(start_date) + validate_date(end_date) url <- sprintf( "%s/1.2/user/%s/sleep/date/%s/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), start_date, end_date ) - r <- get( - url = url, - .example_identifier = "sleep summary" - ) + r <- perform_get(url) r %>% content(as = "parsed", type = "application/json") %>% @@ -39,26 +41,26 @@ sleep_summary <- function(start_date, end_date = start_date) { function(x) list_modify(x, "levels" = NULL) ) %>% bind_rows() %>% - arrange(.data$dateOfSleep) %>% + arrange("dateOfSleep") %>% mutate( date = as.Date(.data$dateOfSleep) ) %>% clean_names() %>% select( - .data$log_id, - .data$date, - .data$start_time, - .data$end_time, - .data$duration, - .data$efficiency, - .data$minutes_to_fall_asleep, - .data$minutes_asleep, - .data$minutes_awake, - .data$minutes_after_wakeup, - .data$time_in_bed + "log_id", + "date", + "start_time", + "end_time", + "duration", + "efficiency", + "minutes_to_fall_asleep", + "minutes_asleep", + "minutes_awake", + "minutes_after_wakeup", + "time_in_bed" ) %>% mutate( - across(c(.data$start_time, .data$end_time), as_datetime) + across(c("start_time", "end_time"), as_datetime) ) } @@ -66,36 +68,38 @@ sleep_summary <- function(start_date, end_date = start_date) { #' #' Returns a tibble of nightly sleep stage data. #' Minutes in each stage, count of times in each stage, and a thirty day average for the number of minutes in each stage. +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @importFrom purrr pluck list_modify map_dfr #' @importFrom tibble enframe #' @importFrom tidyr pivot_wider #' @importFrom dplyr arrange +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() #' -#' sleep_stage_summary(start_date, end_date) +#' get_sleep_stage_summary(start_date, end_date) #' } +#' #' @return A tibble of a variety of sleep stage summary data, by day #' @export -sleep_stage_summary <- function(start_date, end_date = start_date) { - check_token_exists() +get_sleep_stage_summary <- function(start_date, end_date = start_date) { + validate_date(start_date) + validate_date(end_date) url <- sprintf( "%s/1.2/user/%s/sleep/date/%s/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), start_date, end_date ) - r <- get( - url = url, - .example_identifier = "sleep stage" - ) %>% + r <- perform_get(url) %>% content(as = "parsed", type = "application/json") %>% pluck("sleep") @@ -116,11 +120,11 @@ sleep_stage_summary <- function(start_date, end_date = start_date) { } ) %>% select( - .data$date, - .data$stage, - .data$count, - .data$minutes, - thirty_day_avg_minutes = .data$thirtyDayAvgMinutes + "date", + "stage", + "count", + "minutes", + thirty_day_avg_minutes = "thirtyDayAvgMinutes" ) } @@ -128,50 +132,48 @@ sleep_stage_summary <- function(start_date, end_date = start_date) { #' #' Returns a tibble of nightly sleep stage data. #' Very granular. Returns blocks of time spent in each phase. +#' #' @param start_date The start date of records to be returned in "yyyy-mm-dd" or date(time) format #' @param end_date The end date of records to be returned in "yyyy-mm-dd" or date(time) format +#' #' @importFrom purrr pluck list_modify #' @importFrom tibble enframe #' @importFrom tidyr pivot_wider #' @importFrom dplyr arrange +#' #' @examples #' \dontrun{ #' start_date <- lubridate::today() - lubridate::weeks(1) #' end_date <- lubridate::today() #' -#' sleep_stage_granular(start_date, end_date) +#' get_sleep_stage_granular(start_date, end_date) #' } -#' @return A tibble of granular sleep stage data. This method is more granular than \link[fitbitr]{sleep_stage_summary}, and returns blocks of time that you spent in each zone throughout the night. +#' +#' @return A tibble of granular sleep stage data. This method is more granular than \link[fitbitr]{get_sleep_stage_summary}, and returns blocks of time that you spent in each zone throughout the night. #' @export -sleep_stage_granular <- function(start_date, end_date = start_date) { - check_token_exists() +get_sleep_stage_granular <- function(start_date, end_date = start_date) { + validate_date(start_date) + validate_date(end_date) url <- sprintf( "%s/1.2/user/%s/sleep/date/%s/%s.json", base_url, - .fitbitr_token$credentials$user_id, + fetch_user_id(), start_date, end_date ) - r <- get( - url = url, - .example_identifier = "sleep stage" - ) %>% + r <- perform_get(url) %>% content(as = "parsed", type = "application/json") %>% pluck("sleep") r %>% - map( - pluck, "levels" - ) %>% - map( - pluck, "data" + map_dfr( + pluck, "levels", "data" ) %>% - bind_rows() %>% rename( - time = .data$dateTime + time = "dateTime" ) %>% mutate( time = as_datetime(.data$time) diff --git a/R/validation.R b/R/validation.R new file mode 100644 index 0000000..3a639bd --- /dev/null +++ b/R/validation.R @@ -0,0 +1,20 @@ +validate_date <- function(date) { + can_coerce <- tryCatch( + { + as.Date(date) + return(TRUE) + }, + error = function(e) return(FALSE) + ) + + if (!isTRUE(can_coerce) || !inherits(date, "Date")) { + abort( + c( + "Date validation failed.", + "i" = "Please make sure that all of your date fields are either:", + "i" = " 1. A date object", + "i" = " 2. Coercible to a date, such as '2020-01-01'" + ) + ) + } +} diff --git a/README.Rmd b/README.Rmd index 73ee56b..b454c3d 100644 --- a/README.Rmd +++ b/README.Rmd @@ -19,7 +19,6 @@ knitr::opts_chunk$set( [![R-CMD-check](https://github.com/mrkaye97/fitbitr/workflows/R-CMD-check/badge.svg)](https://github.com/mrkaye97/fitbitr/actions) [![CRAN Version](http://www.r-pkg.org/badges/version/fitbitr)](https://CRAN.R-project.org/package=fitbitr) ![](http://cranlogs.r-pkg.org/badges/grand-total/fitbitr) [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) -[![Codecov test coverage](https://codecov.io/gh/mrkaye97/fitbitr/branch/master/graph/badge.svg)](https://codecov.io/gh/mrkaye97/fitbitr?branch=master) @@ -63,7 +62,8 @@ There are a few steps you'll need to do before you can start pulling your Fitbit ```r library(fitbitr) - generate_token( + .fitbitr_token <- generate_oauth_token( + oauth_app_name = , client_id = , client_secret = callback = @@ -73,5 +73,33 @@ There are a few steps you'll need to do before you can start pulling your Fitbit * If you want to edit the scopes that are enabled, you can do so with the `scopes = c('scopes', 'you', 'want', 'enabled')` argument. You can find information on the available scope options [here](https://dev.fitbit.com/build/reference/web-api/oauth2/#scope). * If you want to cache your token, you can do so by specifying either `cache = TRUE` or `cache = `. See the docs on `httr::oauth2.0_token()` for details. -5. And that's it! You now have your Fitbit API credentials set up. `fitbitr` tracks them behind the scenes for you, so all that you need to do at the start of each R session is either `generate_token()` or `load_cached_token()`, and you'll be off and running. +5. And that's it! You now have your Fitbit API credentials set up. `fitbitr` tracks them behind the scenes for you, so all that you need to do at the start of each R session is `generate_token()`. On a session restart, `generate_token()` will try to laod a token from your `.httr-oauth` if it exists. + +Using `fitbitr` +------------ +Once you have a token, using `fitbitr` is very straightforward: + + ```r + > get_steps("2020-05-21", "2020-05-28") + # A tibble: 8 × 2 + date steps + + 1 2020-05-21 3734 + 2 2020-05-22 5107 + 3 2020-05-23 5640 + 4 2020-05-24 6595 + 5 2020-05-25 8466 + 6 2020-05-26 5833 + 7 2020-05-27 8616 + 8 2020-05-28 3161 + ``` + +`fitbitr` tries to return a useful, tidy object back to you from the API. + +Known Issues / Futute Work +------------ + +Given the structure of the Fitbit API, it doesn't appear to be currently possible to use `fitbitr` _outside_ of an interactive session. And furthermore, it doesn't help that Fitbit refresh tokens only last 8 hours, at which point you'd need another interactive session to generate a new one. + +For these reasons, it is not advised to try to use `fitbitr` non-interactively, such as in a Shiny app server, a CI/CD process, a background job, etc. diff --git a/README.md b/README.md index 9d77fb8..9fb7f36 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,6 @@ Version](http://www.r-pkg.org/badges/version/fitbitr)](https://CRAN.R-project.org/package=fitbitr) ![](http://cranlogs.r-pkg.org/badges/grand-total/fitbitr) [![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) -[![Codecov test -coverage](https://codecov.io/gh/mrkaye97/fitbitr/branch/master/graph/badge.svg)](https://codecov.io/gh/mrkaye97/fitbitr?branch=master) `fitbitr` makes it easy to interface with Fitbit data in R. @@ -52,24 +50,58 @@ your Fitbit data: ``` r library(fitbitr) - generate_token( + .fitbitr_token <- generate_oauth_token( + oauth_app_name = , client_id = , client_secret = callback = ) ``` - - If you want to edit the scopes that are enabled, you can do so - with the `scopes = c('scopes', 'you', 'want', 'enabled')` - argument. You can find information on the available scope - options - [here](https://dev.fitbit.com/build/reference/web-api/oauth2/#scope). - - If you want to cache your token, you can do so by specifying - either `cache = TRUE` or `cache = `. See the - docs on `httr::oauth2.0_token()` for details. + - If you want to edit the scopes that are enabled, you can do so + with the `scopes = c('scopes', 'you', 'want', 'enabled')` + argument. You can find information on the available scope options + [here](https://dev.fitbit.com/build/reference/web-api/oauth2/#scope). + - If you want to cache your token, you can do so by specifying + either `cache = TRUE` or `cache = `. See the docs + on `httr::oauth2.0_token()` for details. 5. And that’s it! You now have your Fitbit API credentials set up. `fitbitr` tracks them behind the scenes for you, so all that you - need to do at the start of each R session is either - `generate_token()` or `load_cached_token()`, and you’ll be off and - running. + need to do at the start of each R session is `generate_token()`. On + a session restart, `generate_token()` will try to laod a token from + your `.httr-oauth` if it exists. + +## Using `fitbitr` + +Once you have a token, using `fitbitr` is very straightforward: + + ```r + > get_steps("2020-05-21", "2020-05-28") + # A tibble: 8 × 2 + date steps + + 1 2020-05-21 3734 + 2 2020-05-22 5107 + 3 2020-05-23 5640 + 4 2020-05-24 6595 + 5 2020-05-25 8466 + 6 2020-05-26 5833 + 7 2020-05-27 8616 + 8 2020-05-28 3161 + ``` + +`fitbitr` tries to return a useful, tidy object back to you from the +API. + +## Known Issues / Futute Work + +Given the structure of the Fitbit API, it doesn’t appear to be currently +possible to use `fitbitr` *outside* of an interactive session. And +furthermore, it doesn’t help that Fitbit refresh tokens only last 8 +hours, at which point you’d need another interactive session to generate +a new one. + +For these reasons, it is not advised to try to use `fitbitr` +non-interactively, such as in a Shiny app server, a CI/CD process, a +background job, etc. diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 0000000..0523248 --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,9 @@ +## R CMD check results + +0 errors | 0 warnings | 0 notes + +* Overhauls function naming to be more verb-centric a la tidy convention +* Removes some unused / not useful functions +* Adds significant test coverage hitting the actual API, since it turns out that Fitbit's example API responses in their docs don't actually match what the API gives back... +* Improves error messaging by trying to parse JSON messages in a custom `stop_for_status()` implementation +* Adds some new methods for intraday time series (i.e. your heart rate every 1 minute for a day) diff --git a/man/generate_fitbitr_token.Rd b/man/generate_fitbitr_token.Rd new file mode 100644 index 0000000..f399bec --- /dev/null +++ b/man/generate_fitbitr_token.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/setup.R +\name{generate_fitbitr_token} +\alias{generate_fitbitr_token} +\title{Generate an Oauth token} +\usage{ +generate_fitbitr_token( + app_name = "fitbitr", + client_id = Sys.getenv("FITBIT_CLIENT_ID"), + client_secret = Sys.getenv("FITBIT_CLIENT_SECRET"), + callback = Sys.getenv("FITBIT_CALLBACK", "https://localhost:1410/"), + scope = c("activity", "cardio_fitness", "electrocardiogram", "heartrate", "location", + "nutrition", "oxygen_saturation", "profile", "respiratory_rate", "settings", "sleep", + "social", "temperature", "weight"), + cache = TRUE, + use_basic_auth = TRUE, + ... +) +} +\arguments{ +\item{app_name}{The name of your OAuth app. Default: \code{fitbitr}} + +\item{client_id}{Your Fitbit client ID} + +\item{client_secret}{Your Fitbit client secret} + +\item{callback}{Your Fitbit redirect URL} + +\item{scope}{The scopes to enable} + +\item{cache}{Do you want to cache your token? See \link[httr]{oauth2.0_token} for details} + +\item{use_basic_auth}{A boolean for whether or not to use basic auth in \link[httr]{oauth2.0_token}. Defaults to \code{TRUE}} + +\item{...}{additional arguments to be passed to \link[httr]{oauth2.0_token}} +} +\value{ +Returns an OAuth 2.0 token that can be used to authorize requests to the Fitbit API +} +\description{ +Performs the OAuth 2.0 dance to create a token to use with the Fitbit API. +} +\examples{ +\dontrun{ +generate_fitbitr_token( + client_id = + client_secret = , + cache = TRUE +) +} + +} diff --git a/man/generate_token.Rd b/man/generate_token.Rd deleted file mode 100644 index 55c9d0f..0000000 --- a/man/generate_token.Rd +++ /dev/null @@ -1,47 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/setup.R -\name{generate_token} -\alias{generate_token} -\title{Generate a Fitbit API token} -\usage{ -generate_token( - client_id, - client_secret, - callback = "http://localhost:1410/", - scope = c("sleep", "activity", "heartrate", "location", "nutrition", "profile", - "settings", "social", "weight"), - cache = FALSE, - use_basic_auth = TRUE, - ... -) -} -\arguments{ -\item{client_id}{your Fitbit client ID} - -\item{client_secret}{your Fitbit client secret} - -\item{callback}{your Fitbit redirect URL} - -\item{scope}{the scopes to enable} - -\item{cache}{Do you want to cache your token? See \link[httr]{oauth2.0_token} for details} - -\item{use_basic_auth}{A boolean for whether or not to use basic auth in \link[httr]{oauth2.0_token}. Defaults to \code{TRUE}} - -\item{...}{additional arguments to be passed to \link[httr]{oauth2.0_token}} -} -\value{ -No return value. This function generates a token and saves it (hidden) in the global environment to be used for the remainder of the R session. You can cache this token with \code{cache = TRUE} or explicitly setting a filepath to cache to. See \link[httr]{oauth2.0_token} for details. -} -\description{ -Simplify the setup process by persisting your Fitbit client_id and secret in the \code{.fitbitr-oauth} file. -} -\examples{ -\dontrun{ -generate_token( - client_id = - client_secret = , - cache = TRUE -) -} -} diff --git a/man/get.Rd b/man/get.Rd new file mode 100644 index 0000000..403d484 --- /dev/null +++ b/man/get.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/api.R +\name{perform_get} +\alias{perform_get} +\title{Perform a GET request} +\usage{ +perform_get(url, ...) +} +\arguments{ +\item{url}{The URL to make the request to} + +\item{\dots}{Additional arguments (not currently used)} +} +\value{ +The response +} +\description{ +Perform a GET request +} diff --git a/man/get_active_zone_minutes_intraday.Rd b/man/get_active_zone_minutes_intraday.Rd new file mode 100644 index 0000000..7486fcf --- /dev/null +++ b/man/get_active_zone_minutes_intraday.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_active_zone_minutes_intraday} +\alias{get_active_zone_minutes_intraday} +\title{Get intraday active zone minutes time series} +\usage{ +get_active_zone_minutes_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-azm-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_active_zone_minutes_intraday(detail_level = "15min") + +## get more granular data +get_active_zone_minutes_intraday(detail_level = "1min") +} +} +\seealso{ +Other intraday: +\code{\link{get_calories_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_heart_rate_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/activity_calories.Rd b/man/get_activity_calories.Rd similarity index 81% rename from man/activity_calories.Rd rename to man/get_activity_calories.Rd index 6492b89..604374a 100644 --- a/man/activity_calories.Rd +++ b/man/get_activity_calories.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{activity_calories} -\alias{activity_calories} +\name{get_activity_calories} +\alias{get_activity_calories} \title{Activity Calories Time Series} \usage{ -activity_calories(start_date, end_date) +get_activity_calories(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/activityCalories \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -activity_calories(date) +get_activity_calories(date) } + } diff --git a/man/activity_summary.Rd b/man/get_activity_summary.Rd similarity index 80% rename from man/activity_summary.Rd rename to man/get_activity_summary.Rd index e67280e..b7e2c6b 100644 --- a/man/activity_summary.Rd +++ b/man/get_activity_summary.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{activity_summary} -\alias{activity_summary} +\name{get_activity_summary} +\alias{get_activity_summary} \title{Activity Summary} \usage{ -activity_summary(date) +get_activity_summary(date) } \arguments{ \item{date}{The date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -18,6 +18,6 @@ See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for more deta \examples{ \dontrun{ date <- lubridate::today() -activity_summary(date) +get_activity_summary(date) } } diff --git a/man/calories.Rd b/man/get_calories.Rd similarity index 88% rename from man/calories.Rd rename to man/get_calories.Rd index c2e9fe4..1149f99 100644 --- a/man/calories.Rd +++ b/man/get_calories.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{calories} -\alias{calories} +\name{get_calories} +\alias{get_calories} \title{Calories Time Series} \usage{ -calories(start_date, end_date) +get_calories(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -23,4 +23,5 @@ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() calories(date) } + } diff --git a/man/calories_bmr.Rd b/man/get_calories_bmr.Rd similarity index 83% rename from man/calories_bmr.Rd rename to man/get_calories_bmr.Rd index 84c74da..94998a8 100644 --- a/man/calories_bmr.Rd +++ b/man/get_calories_bmr.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{calories_bmr} -\alias{calories_bmr} +\name{get_calories_bmr} +\alias{get_calories_bmr} \title{Calories BMR Time Series} \usage{ -calories_bmr(start_date, end_date) +get_calories_bmr(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/caloriesBMR \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -calories_bmr(date) +get_calories_bmr(date) } + } diff --git a/man/get_calories_intraday.Rd b/man/get_calories_intraday.Rd new file mode 100644 index 0000000..00713ce --- /dev/null +++ b/man/get_calories_intraday.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_calories_intraday} +\alias{get_calories_intraday} +\title{Get intraday calories time series} +\usage{ +get_calories_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{calories} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_calories_intraday(detail_level = "15min") + +## get more granular data +get_calories_intraday(detail_level = "1min") +} + +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_heart_rate_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/distance.Rd b/man/get_distance.Rd similarity index 85% rename from man/distance.Rd rename to man/get_distance.Rd index f6d1c8b..9029909 100644 --- a/man/distance.Rd +++ b/man/get_distance.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{distance} -\alias{distance} +\name{get_distance} +\alias{get_distance} \title{Distance Time Series} \usage{ -distance(start_date, end_date) +get_distance(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/distance \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -distance(date) +get_distance(date) } + } diff --git a/man/get_distance_intraday.Rd b/man/get_distance_intraday.Rd new file mode 100644 index 0000000..940c4a4 --- /dev/null +++ b/man/get_distance_intraday.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_distance_intraday} +\alias{get_distance_intraday} +\title{Get intraday distance time series} +\usage{ +get_distance_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{distance} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_distance_intraday(detail_level = "15min") + +## get more granular data +get_distance_intraday(detail_level = "1min") +} + +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_calories_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_heart_rate_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/elevation.Rd b/man/get_elevation.Rd similarity index 85% rename from man/elevation.Rd rename to man/get_elevation.Rd index 2608faf..316d333 100644 --- a/man/elevation.Rd +++ b/man/get_elevation.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{elevation} -\alias{elevation} +\name{get_elevation} +\alias{get_elevation} \title{Elevation Time Series} \usage{ -elevation(start_date, end_date) +get_elevation(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/elevation \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -elevation(date) +get_elevation(date) } + } diff --git a/man/get_elevation_intraday.Rd b/man/get_elevation_intraday.Rd new file mode 100644 index 0000000..65b4165 --- /dev/null +++ b/man/get_elevation_intraday.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_elevation_intraday} +\alias{get_elevation_intraday} +\title{Get intraday elevation time series} +\usage{ +get_elevation_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{elevation} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_elevation_intraday(detail_level = "15min") + +## get more granular data +get_elevation_intraday(detail_level = "1min") +} + +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_calories_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_heart_rate_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/floors.Rd b/man/get_floors.Rd similarity index 86% rename from man/floors.Rd rename to man/get_floors.Rd index 428602a..46cbc24 100644 --- a/man/floors.Rd +++ b/man/get_floors.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{floors} -\alias{floors} +\name{get_floors} +\alias{get_floors} \title{Floors Time Series} \usage{ -floors(start_date, end_date) +get_floors(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/floors \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -floors(date) +get_floors(date) } + } diff --git a/man/get_floors_intraday.Rd b/man/get_floors_intraday.Rd new file mode 100644 index 0000000..1199df6 --- /dev/null +++ b/man/get_floors_intraday.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_floors_intraday} +\alias{get_floors_intraday} +\title{Get intraday floors time series} +\usage{ +get_floors_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{floors} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_floors_intraday(detail_level = "15min") + +## get more granular data +get_floors_intraday(detail_level = "1min") +} + +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_calories_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_heart_rate_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/get_heart_rate_intraday.Rd b/man/get_heart_rate_intraday.Rd new file mode 100644 index 0000000..63f2268 --- /dev/null +++ b/man/get_heart_rate_intraday.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_heart_rate_intraday} +\alias{get_heart_rate_intraday} +\title{Get intraday heart time series} +\usage{ +get_heart_rate_intraday( + date = lubridate::today(), + detail_level = c("1sec", "1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1sec"}, \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\description{ +See the \href{https://dev.fitbit.com/reference/web-api/heart-rate/#get-heart-rate-time-series}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_heart_rate_intraday(detail_level = "15min") + +## get more granular data +get_heart_rate_intraday(detail_level = "1min") +} +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_calories_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_steps_intraday}()} +} +\concept{intraday} diff --git a/man/heart_rate_zones.Rd b/man/get_heart_rate_zones.Rd similarity index 84% rename from man/heart_rate_zones.Rd rename to man/get_heart_rate_zones.Rd index 2360038..fac8917 100644 --- a/man/heart_rate_zones.Rd +++ b/man/get_heart_rate_zones.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/heart-rate.R -\name{heart_rate_zones} -\alias{heart_rate_zones} +\name{get_heart_rate_zones} +\alias{get_heart_rate_zones} \title{Heart Rate Zones} \usage{ -heart_rate_zones(start_date, end_date = start_date) +get_heart_rate_zones(start_date, end_date = start_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -22,6 +22,6 @@ See \url{https://dev.fitbit.com/build/reference/web-api/activity/} for more deta start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -heart_rate_zones(start_date, end_date) +get_heart_rate_zones(start_date, end_date) } } diff --git a/man/get_intraday_time_series.Rd b/man/get_intraday_time_series.Rd new file mode 100644 index 0000000..d974928 --- /dev/null +++ b/man/get_intraday_time_series.Rd @@ -0,0 +1,33 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_intraday_time_series} +\alias{get_intraday_time_series} +\title{Get an intraday time series} +\usage{ +get_intraday_time_series( + resource = c("active-zone-minutes", "calories", "distance", "elevation", "floors", + "heart", "steps"), + date, + detail_level, + start_time, + end_time +) +} +\arguments{ +\item{resource}{The resource to get} + +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{{{resource}}} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} diff --git a/man/lifetime_bests.Rd b/man/get_lifetime_bests.Rd similarity index 77% rename from man/lifetime_bests.Rd rename to man/get_lifetime_bests.Rd index ffebf90..b597677 100644 --- a/man/lifetime_bests.Rd +++ b/man/get_lifetime_bests.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{lifetime_bests} -\alias{lifetime_bests} +\name{get_lifetime_bests} +\alias{get_lifetime_bests} \title{Lifetime Bests} \usage{ -lifetime_bests() +get_lifetime_bests() } \value{ A tibble the best \code{distance}, \code{floors}, and \code{steps} (by date) tracked on any of your trackers @@ -14,6 +14,7 @@ Retrieve lifetime best distance, floors, and steps } \examples{ \dontrun{ -lifetime_bests() +get_lifetime_bests() } + } diff --git a/man/lifetime_totals.Rd b/man/get_lifetime_totals.Rd similarity index 78% rename from man/lifetime_totals.Rd rename to man/get_lifetime_totals.Rd index 49dff8d..f426325 100644 --- a/man/lifetime_totals.Rd +++ b/man/get_lifetime_totals.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{lifetime_totals} -\alias{lifetime_totals} +\name{get_lifetime_totals} +\alias{get_lifetime_totals} \title{Lifetime Totals} \usage{ -lifetime_totals() +get_lifetime_totals() } \value{ A tibble of all-time totals across trackers (i.e. the total \code{distance}, \code{floors}, and \code{steps} tracked across all of your trackers) @@ -14,6 +14,7 @@ Retrieve lifetime total distance, floors, and steps } \examples{ \dontrun{ -lifetime_totals() +get_lifetime_totals() } + } diff --git a/man/minutes_fairly_active.Rd b/man/get_minutes_fairly_active.Rd similarity index 80% rename from man/minutes_fairly_active.Rd rename to man/get_minutes_fairly_active.Rd index f18e5c0..0e7c5c8 100644 --- a/man/minutes_fairly_active.Rd +++ b/man/get_minutes_fairly_active.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{minutes_fairly_active} -\alias{minutes_fairly_active} +\name{get_minutes_fairly_active} +\alias{get_minutes_fairly_active} \title{Minutes Fairly Active Time Series} \usage{ -minutes_fairly_active(start_date, end_date) +get_minutes_fairly_active(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/minutesFairlyActive \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -minutes_fairly_active(date) +get_minutes_fairly_active(date) } + } diff --git a/man/minutes_lightly_active.Rd b/man/get_minutes_lightly_active.Rd similarity index 79% rename from man/minutes_lightly_active.Rd rename to man/get_minutes_lightly_active.Rd index 508719b..206c48f 100644 --- a/man/minutes_lightly_active.Rd +++ b/man/get_minutes_lightly_active.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{minutes_lightly_active} -\alias{minutes_lightly_active} +\name{get_minutes_lightly_active} +\alias{get_minutes_lightly_active} \title{Minutes Lightly Active Time Series} \usage{ -minutes_lightly_active(start_date, end_date) +get_minutes_lightly_active(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/minutesLightlyActive \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -minutes_lightly_active(date) +get_minutes_lightly_active(date) } + } diff --git a/man/minutes_sedentary.Rd b/man/get_minutes_sedentary.Rd similarity index 81% rename from man/minutes_sedentary.Rd rename to man/get_minutes_sedentary.Rd index 17c3e18..69b838d 100644 --- a/man/minutes_sedentary.Rd +++ b/man/get_minutes_sedentary.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{minutes_sedentary} -\alias{minutes_sedentary} +\name{get_minutes_sedentary} +\alias{get_minutes_sedentary} \title{Minutes Sedentary Time Series} \usage{ -minutes_sedentary(start_date, end_date) +get_minutes_sedentary(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/minutesSedentary \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -minutes_sedentary(date) +get_minutes_sedentary(date) } + } diff --git a/man/minutes_very_active.Rd b/man/get_minutes_very_active.Rd similarity index 80% rename from man/minutes_very_active.Rd rename to man/get_minutes_very_active.Rd index 25e5c47..c79246f 100644 --- a/man/minutes_very_active.Rd +++ b/man/get_minutes_very_active.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{minutes_very_active} -\alias{minutes_very_active} +\name{get_minutes_very_active} +\alias{get_minutes_very_active} \title{Minutes Very Active Time Series} \usage{ -minutes_very_active(start_date, end_date) +get_minutes_very_active(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/minutesVeryActive \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -minutes_very_active(date) +get_minutes_very_active(date) } + } diff --git a/man/sleep_stage_granular.Rd b/man/get_sleep_stage_granular.Rd similarity index 68% rename from man/sleep_stage_granular.Rd rename to man/get_sleep_stage_granular.Rd index 6c231b4..3a70099 100644 --- a/man/sleep_stage_granular.Rd +++ b/man/get_sleep_stage_granular.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/sleep.R -\name{sleep_stage_granular} -\alias{sleep_stage_granular} +\name{get_sleep_stage_granular} +\alias{get_sleep_stage_granular} \title{Granular Sleep Stage Data} \usage{ -sleep_stage_granular(start_date, end_date = start_date) +get_sleep_stage_granular(start_date, end_date = start_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -12,7 +12,7 @@ sleep_stage_granular(start_date, end_date = start_date) \item{end_date}{The end date of records to be returned in "yyyy-mm-dd" or date(time) format} } \value{ -A tibble of granular sleep stage data. This method is more granular than \link[fitbitr]{sleep_stage_summary}, and returns blocks of time that you spent in each zone throughout the night. +A tibble of granular sleep stage data. This method is more granular than \link[fitbitr]{get_sleep_stage_summary}, and returns blocks of time that you spent in each zone throughout the night. } \description{ Returns a tibble of nightly sleep stage data. @@ -23,6 +23,7 @@ Very granular. Returns blocks of time spent in each phase. start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -sleep_stage_granular(start_date, end_date) +get_sleep_stage_granular(start_date, end_date) } + } diff --git a/man/sleep_stage_summary.Rd b/man/get_sleep_stage_summary.Rd similarity index 80% rename from man/sleep_stage_summary.Rd rename to man/get_sleep_stage_summary.Rd index fc5165b..0719081 100644 --- a/man/sleep_stage_summary.Rd +++ b/man/get_sleep_stage_summary.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/sleep.R -\name{sleep_stage_summary} -\alias{sleep_stage_summary} +\name{get_sleep_stage_summary} +\alias{get_sleep_stage_summary} \title{Nightly Sleep Stage Summary Data} \usage{ -sleep_stage_summary(start_date, end_date = start_date) +get_sleep_stage_summary(start_date, end_date = start_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -23,6 +23,7 @@ Minutes in each stage, count of times in each stage, and a thirty day average fo start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -sleep_stage_summary(start_date, end_date) +get_sleep_stage_summary(start_date, end_date) } + } diff --git a/man/sleep_summary.Rd b/man/get_sleep_summary.Rd similarity index 79% rename from man/sleep_summary.Rd rename to man/get_sleep_summary.Rd index f7ba3f4..821b935 100644 --- a/man/sleep_summary.Rd +++ b/man/get_sleep_summary.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/sleep.R -\name{sleep_summary} -\alias{sleep_summary} +\name{get_sleep_summary} +\alias{get_sleep_summary} \title{Nightly Sleep Summary} \usage{ -sleep_summary(start_date, end_date = start_date) +get_sleep_summary(start_date, end_date = start_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -22,6 +22,7 @@ Returns a tibble of summary by night start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -sleep_summary(start_date, end_date) +get_sleep_summary(start_date, end_date) } + } diff --git a/man/steps.Rd b/man/get_steps.Rd similarity index 86% rename from man/steps.Rd rename to man/get_steps.Rd index 69ca0a3..5a7e197 100644 --- a/man/steps.Rd +++ b/man/get_steps.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{steps} -\alias{steps} +\name{get_steps} +\alias{get_steps} \title{Steps Time Series} \usage{ -steps(start_date, end_date) +get_steps(start_date, end_date) } \arguments{ \item{start_date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} @@ -21,6 +21,7 @@ Resource path /activities/steps \dontrun{ start_date <- lubridate::today() - lubridate::weeks(1) end_date <- lubridate::today() -steps(date) +get_steps(date) } + } diff --git a/man/get_steps_intraday.Rd b/man/get_steps_intraday.Rd new file mode 100644 index 0000000..67e324b --- /dev/null +++ b/man/get_steps_intraday.Rd @@ -0,0 +1,51 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/intraday.R +\name{get_steps_intraday} +\alias{get_steps_intraday} +\title{Get intraday steps time series} +\usage{ +get_steps_intraday( + date = lubridate::today(), + detail_level = c("1min", "5min", "15min"), + start_time = NULL, + end_time = NULL +) +} +\arguments{ +\item{date}{A date to get data for} + +\item{detail_level}{The detail level. One of \code{"1min"}, \code{"5min"}, or \code{"15min"}} + +\item{start_time}{The start time of the time window. Default: \code{NULL} gets the whole day} + +\item{end_time}{The end time of the time window. Default: \code{NULL} gets the whole day} +} +\value{ +A tibble with two columns: \code{time} and \code{steps} +} +\description{ +See the \href{https://dev.fitbit.com/build/reference/web-api/intraday/get-activity-intraday-by-date/}{API documentation} for +more detailed explanations of parameters and more usage information and examples. +} +\examples{ +\dontrun{ +date <- lubridate::today() + +## get minute by minute data +get_steps_intraday(detail_level = "15min") + +## get more granular data +get_steps_intraday(detail_level = "1min") +} + +} +\seealso{ +Other intraday: +\code{\link{get_active_zone_minutes_intraday}()}, +\code{\link{get_calories_intraday}()}, +\code{\link{get_distance_intraday}()}, +\code{\link{get_elevation_intraday}()}, +\code{\link{get_floors_intraday}()}, +\code{\link{get_heart_rate_intraday}()} +} +\concept{intraday} diff --git a/man/tracker_bests.Rd b/man/get_tracker_bests.Rd similarity index 77% rename from man/tracker_bests.Rd rename to man/get_tracker_bests.Rd index fb794eb..1f5e5d5 100644 --- a/man/tracker_bests.Rd +++ b/man/get_tracker_bests.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{tracker_bests} -\alias{tracker_bests} +\name{get_tracker_bests} +\alias{get_tracker_bests} \title{Tracker Bests} \usage{ -tracker_bests() +get_tracker_bests() } \value{ A tibble the best \code{distance}, \code{floors}, and \code{steps} (by date) tracked on your tracker @@ -14,6 +14,7 @@ Retrieve tracker best distance, floors, and steps } \examples{ \dontrun{ -tracker_bests() +get_tracker_bests() } + } diff --git a/man/tracker_totals.Rd b/man/get_tracker_totals.Rd similarity index 78% rename from man/tracker_totals.Rd rename to man/get_tracker_totals.Rd index 80d6395..b95c159 100644 --- a/man/tracker_totals.Rd +++ b/man/get_tracker_totals.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/activity.R -\name{tracker_totals} -\alias{tracker_totals} +\name{get_tracker_totals} +\alias{get_tracker_totals} \title{Tracker Totals} \usage{ -tracker_totals() +get_tracker_totals() } \value{ A tibble of all-time tracker totals (i.e. the total \code{distance}, \code{floors}, and \code{steps} tracked by your tracker) @@ -14,6 +14,7 @@ Retrieve tracker total distance, floors, and steps } \examples{ \dontrun{ -tracker_totals() +get_tracker_totals() } + } diff --git a/man/heart_rate_intraday.Rd b/man/heart_rate_intraday.Rd deleted file mode 100644 index d50b3fc..0000000 --- a/man/heart_rate_intraday.Rd +++ /dev/null @@ -1,34 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/heart-rate.R -\name{heart_rate_intraday} -\alias{heart_rate_intraday} -\title{Heart Rate Intraday} -\usage{ -heart_rate_intraday(date, minutes = TRUE) -} -\arguments{ -\item{date}{The start date of records to be returned in "yyyy-mm-dd" or date(time) format} - -\item{minutes}{a boolean for whether data should be returned in minutes (TRUE) or seconds (FALSE)} -} -\value{ -A tibble of the \code{time} and your \code{heart_rate} at that time. -} -\description{ -Returns heart rate data for the specified day -} -\details{ -See \url{https://dev.fitbit.com/reference/web-api/heart-rate/#get-heart-rate-time-series} for more details. -} -\examples{ -\dontrun{ -date <- lubridate::today() - -## get minute by minute data -heart_rate_intraday(date, minutes = TRUE) - -## get more granular data -## (not necessarily by second, but more granular than minutes) -heart_rate_intraday(date, minutes = FALSE) -} -} diff --git a/man/load_cached_token.Rd b/man/load_cached_token.Rd deleted file mode 100644 index cd87e3e..0000000 --- a/man/load_cached_token.Rd +++ /dev/null @@ -1,22 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/setup.R -\name{load_cached_token} -\alias{load_cached_token} -\title{Load a token from the cache} -\usage{ -load_cached_token(path = ".httr-oauth") -} -\arguments{ -\item{path}{the path to the file where the token is stored} -} -\value{ -No return value. The token is stored in the global environment as a hidden variable named \code{.fitbitr_token} -} -\description{ -Load a token from the cache -} -\examples{ -\dontrun{ -load_cached_token() -} -} diff --git a/renv.lock b/renv.lock new file mode 100644 index 0000000..039b105 --- /dev/null +++ b/renv.lock @@ -0,0 +1,783 @@ +{ + "R": { + "Version": "4.1.2", + "Repositories": [ + { + "Name": "CRAN", + "URL": "https://cran.rstudio.com" + } + ] + }, + "Packages": { + "R6": { + "Package": "R6", + "Version": "2.5.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "470851b6d5d0ac559e9d01bb352b4021" + }, + "Rcpp": { + "Package": "Rcpp", + "Version": "1.0.10", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e749cae40fa9ef469b6050959517453c" + }, + "askpass": { + "Package": "askpass", + "Version": "1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e8a22846fff485f0be3770c2da758713" + }, + "backports": { + "Package": "backports", + "Version": "1.4.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c39fbec8a30d23e721980b8afb31984c" + }, + "base64enc": { + "Package": "base64enc", + "Version": "0.1-3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "543776ae6848fde2f48ff3816d0628bc" + }, + "brew": { + "Package": "brew", + "Version": "1.0-6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "92a5f887f9ae3035ac7afde22ba73ee9" + }, + "brio": { + "Package": "brio", + "Version": "1.1.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "976cf154dfb043c012d87cddd8bca363" + }, + "bslib": { + "Package": "bslib", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a7fbf03946ad741129dc81098722fca1" + }, + "cachem": { + "Package": "cachem", + "Version": "1.0.7", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "cda74447c42f529de601fe4d4050daef" + }, + "callr": { + "Package": "callr", + "Version": "3.7.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "9b2191ede20fa29828139b9900922e51" + }, + "checkmate": { + "Package": "checkmate", + "Version": "2.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a667800d5f0350371bedeb8b8b950289" + }, + "cli": { + "Package": "cli", + "Version": "3.6.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "3177a5a16c243adc199ba33117bd9657" + }, + "clipr": { + "Package": "clipr", + "Version": "0.7.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "ebaa97ac99cc2daf04e77eecc7b781d7" + }, + "commonmark": { + "Package": "commonmark", + "Version": "1.9.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d691c61bff84bd63c383874d2d0c3307" + }, + "covr": { + "Package": "covr", + "Version": "3.6.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a861cee34fbb4b107a73dd414ef56724" + }, + "cpp11": { + "Package": "cpp11", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "fa53ce256cd280f468c080a58ea5ba8c" + }, + "crayon": { + "Package": "crayon", + "Version": "1.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "0a6a65d92bd45b47b94b84244b528d17" + }, + "credentials": { + "Package": "credentials", + "Version": "1.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "93762d0a34d78e6a025efdbfb5c6bb41" + }, + "curl": { + "Package": "curl", + "Version": "4.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "022c42d49c28e95d69ca60446dbabf88" + }, + "desc": { + "Package": "desc", + "Version": "1.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6b9602c7ebbe87101a9c8edb6e8b6d21" + }, + "devtools": { + "Package": "devtools", + "Version": "2.4.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "ea5bc8b4a6a01e4f12d98b58329930bb" + }, + "diffobj": { + "Package": "diffobj", + "Version": "0.3.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8" + }, + "digest": { + "Package": "digest", + "Version": "0.6.31", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "8b708f296afd9ae69f450f9640be8990" + }, + "downlit": { + "Package": "downlit", + "Version": "0.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "79bf3f66590752ffbba20f8d2da94c7c" + }, + "dplyr": { + "Package": "dplyr", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d3c34618017e7ae252d46d79a1b9ec32" + }, + "ellipsis": { + "Package": "ellipsis", + "Version": "0.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" + }, + "evaluate": { + "Package": "evaluate", + "Version": "0.20", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4b68aa51edd89a0e044a66e75ae3cc6c" + }, + "fansi": { + "Package": "fansi", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "1d9e7ad3c8312a192dea7d3db0274fde" + }, + "fastmap": { + "Package": "fastmap", + "Version": "1.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f7736a18de97dea803bde0a2daaafb27" + }, + "fontawesome": { + "Package": "fontawesome", + "Version": "0.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e80750aec5717dedc019ad7ee40e4a7c" + }, + "fs": { + "Package": "fs", + "Version": "1.6.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f4dcd23b67e33d851d2079f703e8b985" + }, + "generics": { + "Package": "generics", + "Version": "0.1.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "15e9634c0fcd294799e9b2e929ed1b86" + }, + "gert": { + "Package": "gert", + "Version": "1.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "8fddce7cbd59467106266a6e93e253b4" + }, + "gh": { + "Package": "gh", + "Version": "1.3.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "38c2580abbda249bd6afeec00d14f531" + }, + "gitcreds": { + "Package": "gitcreds", + "Version": "0.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f3aefccc1cc50de6338146b62f115de8" + }, + "glue": { + "Package": "glue", + "Version": "1.6.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e" + }, + "highr": { + "Package": "highr", + "Version": "0.10", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "06230136b2d2b9ba5805e1963fa6e890" + }, + "htmltools": { + "Package": "htmltools", + "Version": "0.5.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "9d27e99cc90bd701c0a7a63e5923f9b7" + }, + "htmlwidgets": { + "Package": "htmlwidgets", + "Version": "1.6.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a865aa85bcb2697f47505bfd70422471" + }, + "httpuv": { + "Package": "httpuv", + "Version": "1.6.9", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "1046aa31a57eae8b357267a56a0b6d8b" + }, + "httr": { + "Package": "httr", + "Version": "1.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a525aba14184fec243f9eaec62fbed43" + }, + "hunspell": { + "Package": "hunspell", + "Version": "3.0.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "656219b6f3f605499d7cdbe208656639" + }, + "ini": { + "Package": "ini", + "Version": "0.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6154ec2223172bce8162d4153cda21f7" + }, + "janitor": { + "Package": "janitor", + "Version": "2.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6de84a8c67fb247e721166049c84695f" + }, + "jquerylib": { + "Package": "jquerylib", + "Version": "0.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5aab57a3bd297eee1c1d862735972182" + }, + "jsonlite": { + "Package": "jsonlite", + "Version": "1.8.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a4269a09a9b865579b2635c77e572374" + }, + "knitr": { + "Package": "knitr", + "Version": "1.42", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "8329a9bcc82943c8069104d4be3ee22d" + }, + "later": { + "Package": "later", + "Version": "1.3.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "7e7b457d7766bc47f2a5f21cc2984f8e" + }, + "lazyeval": { + "Package": "lazyeval", + "Version": "0.2.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d908914ae53b04d4c0c0fd72ecc35370" + }, + "lifecycle": { + "Package": "lifecycle", + "Version": "1.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "001cecbeac1cff9301bdc3775ee46a86" + }, + "lubridate": { + "Package": "lubridate", + "Version": "1.8.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "2ff5eedb6ee38fb1b81205c73be1be5a" + }, + "magrittr": { + "Package": "magrittr", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "7ce2733a9826b3aeb1775d56fd305472" + }, + "memoise": { + "Package": "memoise", + "Version": "2.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e2817ccf4a065c5d9d7f2cfbe7c1d78c" + }, + "mime": { + "Package": "mime", + "Version": "0.12", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + }, + "miniUI": { + "Package": "miniUI", + "Version": "0.1.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "fec5f52652d60615fdb3957b3d74324a" + }, + "openssl": { + "Package": "openssl", + "Version": "2.0.6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "0f7cd2962e3044bb940cca4f4b5cecbe" + }, + "pillar": { + "Package": "pillar", + "Version": "1.8.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f2316df30902c81729ae9de95ad5a608" + }, + "pkgbuild": { + "Package": "pkgbuild", + "Version": "1.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "66d2adfed274daf81ccfe77d974c3b9b" + }, + "pkgconfig": { + "Package": "pkgconfig", + "Version": "2.0.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "01f28d4278f15c76cddbea05899c5d6f" + }, + "pkgdown": { + "Package": "pkgdown", + "Version": "2.0.7", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "16fa15449c930bf3a7761d3c68f8abf9" + }, + "pkgload": { + "Package": "pkgload", + "Version": "1.3.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6b0c222c5071efe0f3baf3dae9aa40e2" + }, + "praise": { + "Package": "praise", + "Version": "1.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a555924add98c99d2f411e37e7d25e9f" + }, + "prettyunits": { + "Package": "prettyunits", + "Version": "1.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "95ef9167b75dde9d2ccc3c7528393e7e" + }, + "processx": { + "Package": "processx", + "Version": "3.8.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a33ee2d9bf07564efb888ad98410da84" + }, + "profvis": { + "Package": "profvis", + "Version": "0.3.7", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "e9d21e79848e02e524bea6f5bd53e7e4" + }, + "promises": { + "Package": "promises", + "Version": "1.2.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4ab2c43adb4d4699cf3690acd378d75d" + }, + "ps": { + "Package": "ps", + "Version": "1.6.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "32620e2001c1dce1af49c49dccbb9420" + }, + "purrr": { + "Package": "purrr", + "Version": "1.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "d71c815267c640f17ddbf7f16144b4bb" + }, + "ragg": { + "Package": "ragg", + "Version": "1.2.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "690bc058ea2b1b8a407d3cfe3dce3ef9" + }, + "rappdirs": { + "Package": "rappdirs", + "Version": "0.3.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5e3c5dc0b071b21fa128676560dbe94d" + }, + "rcmdcheck": { + "Package": "rcmdcheck", + "Version": "1.4.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "8f25ebe2ec38b1f2aef3b0d2ef76f6c4" + }, + "rematch2": { + "Package": "rematch2", + "Version": "2.1.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "76c9e04c712a05848ae7a23d2f170a40" + }, + "remotes": { + "Package": "remotes", + "Version": "2.4.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "227045be9aee47e6dda9bb38ac870d67" + }, + "renv": { + "Package": "renv", + "Version": "0.13.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "079cb1f03ff972b30401ed05623cbe92" + }, + "rex": { + "Package": "rex", + "Version": "1.2.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "ae34cd56890607370665bee5bd17812f" + }, + "rlang": { + "Package": "rlang", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "dc079ccd156cde8647360f473c1fa718" + }, + "rmarkdown": { + "Package": "rmarkdown", + "Version": "2.20", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "716fde5382293cc94a71f68c85b78d19" + }, + "roxygen2": { + "Package": "roxygen2", + "Version": "7.2.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "7b153c746193b143c14baa072bae4e27" + }, + "rprojroot": { + "Package": "rprojroot", + "Version": "2.0.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "249d8cd1e74a8f6a26194a91b47f21d1" + }, + "rstudioapi": { + "Package": "rstudioapi", + "Version": "0.13", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "06c85365a03fdaf699966cc1d3cf53ea" + }, + "rversions": { + "Package": "rversions", + "Version": "2.1.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "f88fab00907b312f8b23ec13e2d437cb" + }, + "sass": { + "Package": "sass", + "Version": "0.4.5", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "2bb4371a4c80115518261866eab6ab11" + }, + "sessioninfo": { + "Package": "sessioninfo", + "Version": "1.2.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "3f9796a8d0a0e8c6eb49a4b029359d1f" + }, + "shiny": { + "Package": "shiny", + "Version": "1.7.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c2eae3d8c670fa9dfa35a12066f4a1d5" + }, + "snakecase": { + "Package": "snakecase", + "Version": "0.11.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "4079070fc210c7901c0832a3aeab894f" + }, + "sourcetools": { + "Package": "sourcetools", + "Version": "0.1.7-1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "5f5a7629f956619d519205ec475fe647" + }, + "spelling": { + "Package": "spelling", + "Version": "2.2", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "b8c899a5c83f0d897286550481c91798" + }, + "stringi": { + "Package": "stringi", + "Version": "1.7.12", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "ca8bd84263c77310739d2cf64d84d7c9" + }, + "stringr": { + "Package": "stringr", + "Version": "1.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "671a4d384ae9d32fc47a14e98bfa3dc8" + }, + "sys": { + "Package": "sys", + "Version": "3.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "b227d13e29222b4574486cfcbde077fa" + }, + "systemfonts": { + "Package": "systemfonts", + "Version": "1.0.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "90b28393209827327de889f49935140a" + }, + "testthat": { + "Package": "testthat", + "Version": "3.1.7", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "7eb5fd202a61d2fb78af5869b6c08998" + }, + "textshaping": { + "Package": "textshaping", + "Version": "0.3.6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "1ab6223d3670fac7143202cb6a2d43d5" + }, + "tibble": { + "Package": "tibble", + "Version": "3.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "37695ff125982007d42a59ad10982ff2" + }, + "tidyr": { + "Package": "tidyr", + "Version": "1.1.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c8fbdbd9fcac223d6c6fe8e406f368e1" + }, + "tidyselect": { + "Package": "tidyselect", + "Version": "1.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "79540e5fcd9e0435af547d885f184fd5" + }, + "tinytex": { + "Package": "tinytex", + "Version": "0.44", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c0f007e2eeed7722ce13d42b84a22e07" + }, + "urlchecker": { + "Package": "urlchecker", + "Version": "1.0.1", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "409328b8e1253c8d729a7836fe7f7a16" + }, + "usethis": { + "Package": "usethis", + "Version": "2.1.6", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a67a22c201832b12c036cc059f1d137d" + }, + "utf8": { + "Package": "utf8", + "Version": "1.2.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "1fe17157424bb09c48a8b3b550c753bc" + }, + "vctrs": { + "Package": "vctrs", + "Version": "0.6.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "7e877404388794361277be95d8445de8" + }, + "waldo": { + "Package": "waldo", + "Version": "0.4.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "035fba89d0c86e2113120f93301b98ad" + }, + "whisker": { + "Package": "whisker", + "Version": "0.4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "ca970b96d894e90397ed20637a0c1bbe" + }, + "withr": { + "Package": "withr", + "Version": "2.5.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c0e49a9760983e81e55cdd9be92e7182" + }, + "xfun": { + "Package": "xfun", + "Version": "0.37", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "a6860e1400a8fd1ddb6d9b4230cc34ab" + }, + "xml2": { + "Package": "xml2", + "Version": "1.3.3", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "40682ed6a969ea5abfd351eb67833adc" + }, + "xopen": { + "Package": "xopen", + "Version": "1.0.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "6c85f015dee9cc7710ddd20f86881f58" + }, + "xtable": { + "Package": "xtable", + "Version": "1.8-4", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "b8acdf8af494d9ec19ccb2481a9b11c2" + }, + "yaml": { + "Package": "yaml", + "Version": "2.3.7", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "0d0056cc5383fbc240ccd0cb584bf436" + }, + "zip": { + "Package": "zip", + "Version": "2.2.0", + "Source": "Repository", + "Repository": "CRAN", + "Hash": "c7eef2996ac270a18c2715c997a727c5" + } + } +} diff --git a/renv/.gitignore b/renv/.gitignore new file mode 100644 index 0000000..2129631 --- /dev/null +++ b/renv/.gitignore @@ -0,0 +1,5 @@ +library/ +local/ +lock/ +python/ +staging/ diff --git a/renv/activate.R b/renv/activate.R new file mode 100644 index 0000000..b852628 --- /dev/null +++ b/renv/activate.R @@ -0,0 +1,654 @@ + +local({ + + # the requested version of renv + version <- "0.13.2" + + # the project directory + project <- getwd() + + # avoid recursion + if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) + return(invisible(TRUE)) + + # signal that we're loading renv during R startup + Sys.setenv("RENV_R_INITIALIZING" = "true") + on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) + + # signal that we've consented to use renv + options(renv.consent = TRUE) + + # load the 'utils' package eagerly -- this ensures that renv shims, which + # mask 'utils' packages, will come first on the search path + library(utils, lib.loc = .Library) + + # check to see if renv has already been loaded + if ("renv" %in% loadedNamespaces()) { + + # if renv has already been loaded, and it's the requested version of renv, + # nothing to do + spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") + if (identical(spec[["version"]], version)) + return(invisible(TRUE)) + + # otherwise, unload and attempt to load the correct version of renv + unloadNamespace("renv") + + } + + # load bootstrap tools + bootstrap <- function(version, library) { + + # attempt to download renv + tarball <- tryCatch(renv_bootstrap_download(version), error = identity) + if (inherits(tarball, "error")) + stop("failed to download renv ", version) + + # now attempt to install + status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) + if (inherits(status, "error")) + stop("failed to install renv ", version) + + } + + renv_bootstrap_tests_running <- function() { + getOption("renv.tests.running", default = FALSE) + } + + renv_bootstrap_repos <- function() { + + # check for repos override + repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) + if (!is.na(repos)) + return(repos) + + # if we're testing, re-use the test repositories + if (renv_bootstrap_tests_running()) + return(getOption("renv.tests.repos")) + + # retrieve current repos + repos <- getOption("repos") + + # ensure @CRAN@ entries are resolved + repos[repos == "@CRAN@"] <- getOption( + "renv.repos.cran", + "https://cloud.r-project.org" + ) + + # add in renv.bootstrap.repos if set + default <- c(FALLBACK = "https://cloud.r-project.org") + extra <- getOption("renv.bootstrap.repos", default = default) + repos <- c(repos, extra) + + # remove duplicates that might've snuck in + dupes <- duplicated(repos) | duplicated(names(repos)) + repos[!dupes] + + } + + renv_bootstrap_download <- function(version) { + + # if the renv version number has 4 components, assume it must + # be retrieved via github + nv <- numeric_version(version) + components <- unclass(nv)[[1]] + + methods <- if (length(components) == 4L) { + list( + renv_bootstrap_download_github + ) + } else { + list( + renv_bootstrap_download_cran_latest, + renv_bootstrap_download_cran_archive + ) + } + + for (method in methods) { + path <- tryCatch(method(version), error = identity) + if (is.character(path) && file.exists(path)) + return(path) + } + + stop("failed to download renv ", version) + + } + + renv_bootstrap_download_impl <- function(url, destfile) { + + mode <- "wb" + + # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 + fixup <- + Sys.info()[["sysname"]] == "Windows" && + substring(url, 1L, 5L) == "file:" + + if (fixup) + mode <- "w+b" + + utils::download.file( + url = url, + destfile = destfile, + mode = mode, + quiet = TRUE + ) + + } + + renv_bootstrap_download_cran_latest <- function(version) { + + spec <- renv_bootstrap_download_cran_latest_find(version) + + message("* Downloading renv ", version, " ... ", appendLF = FALSE) + + type <- spec$type + repos <- spec$repos + + info <- tryCatch( + utils::download.packages( + pkgs = "renv", + destdir = tempdir(), + repos = repos, + type = type, + quiet = TRUE + ), + condition = identity + ) + + if (inherits(info, "condition")) { + message("FAILED") + return(FALSE) + } + + # report success and return + message("OK (downloaded ", type, ")") + info[1, 2] + + } + + renv_bootstrap_download_cran_latest_find <- function(version) { + + # check whether binaries are supported on this system + binary <- + getOption("renv.bootstrap.binary", default = TRUE) && + !identical(.Platform$pkgType, "source") && + !identical(getOption("pkgType"), "source") && + Sys.info()[["sysname"]] %in% c("Darwin", "Windows") + + types <- c(if (binary) "binary", "source") + + # iterate over types + repositories + for (type in types) { + for (repos in renv_bootstrap_repos()) { + + # retrieve package database + db <- tryCatch( + as.data.frame( + utils::available.packages(type = type, repos = repos), + stringsAsFactors = FALSE + ), + error = identity + ) + + if (inherits(db, "error")) + next + + # check for compatible entry + entry <- db[db$Package %in% "renv" & db$Version %in% version, ] + if (nrow(entry) == 0) + next + + # found it; return spec to caller + spec <- list(entry = entry, type = type, repos = repos) + return(spec) + + } + } + + # if we got here, we failed to find renv + fmt <- "renv %s is not available from your declared package repositories" + stop(sprintf(fmt, version)) + + } + + renv_bootstrap_download_cran_archive <- function(version) { + + name <- sprintf("renv_%s.tar.gz", version) + repos <- renv_bootstrap_repos() + urls <- file.path(repos, "src/contrib/Archive/renv", name) + destfile <- file.path(tempdir(), name) + + message("* Downloading renv ", version, " ... ", appendLF = FALSE) + + for (url in urls) { + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (identical(status, 0L)) { + message("OK") + return(destfile) + } + + } + + message("FAILED") + return(FALSE) + + } + + renv_bootstrap_download_github <- function(version) { + + enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") + if (!identical(enabled, "TRUE")) + return(FALSE) + + # prepare download options + pat <- Sys.getenv("GITHUB_PAT") + if (nzchar(Sys.which("curl")) && nzchar(pat)) { + fmt <- "--location --fail --header \"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "curl", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { + fmt <- "--header=\"Authorization: token %s\"" + extra <- sprintf(fmt, pat) + saved <- options("download.file.method", "download.file.extra") + options(download.file.method = "wget", download.file.extra = extra) + on.exit(do.call(base::options, saved), add = TRUE) + } + + message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) + + url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) + name <- sprintf("renv_%s.tar.gz", version) + destfile <- file.path(tempdir(), name) + + status <- tryCatch( + renv_bootstrap_download_impl(url, destfile), + condition = identity + ) + + if (!identical(status, 0L)) { + message("FAILED") + return(FALSE) + } + + message("OK") + return(destfile) + + } + + renv_bootstrap_install <- function(version, tarball, library) { + + # attempt to install it into project library + message("* Installing renv ", version, " ... ", appendLF = FALSE) + dir.create(library, showWarnings = FALSE, recursive = TRUE) + + # invoke using system2 so we can capture and report output + bin <- R.home("bin") + exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" + r <- file.path(bin, exe) + args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) + output <- system2(r, args, stdout = TRUE, stderr = TRUE) + message("Done!") + + # check for successful install + status <- attr(output, "status") + if (is.numeric(status) && !identical(status, 0L)) { + header <- "Error installing renv:" + lines <- paste(rep.int("=", nchar(header)), collapse = "") + text <- c(header, lines, output) + writeLines(text, con = stderr()) + } + + status + + } + + renv_bootstrap_platform_prefix <- function() { + + # construct version prefix + version <- paste(R.version$major, R.version$minor, sep = ".") + prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") + + # include SVN revision for development versions of R + # (to avoid sharing platform-specific artefacts with released versions of R) + devel <- + identical(R.version[["status"]], "Under development (unstable)") || + identical(R.version[["nickname"]], "Unsuffered Consequences") + + if (devel) + prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") + + # build list of path components + components <- c(prefix, R.version$platform) + + # include prefix if provided by user + prefix <- renv_bootstrap_platform_prefix_impl() + if (!is.na(prefix) && nzchar(prefix)) + components <- c(prefix, components) + + # build prefix + paste(components, collapse = "/") + + } + + renv_bootstrap_platform_prefix_impl <- function() { + + # if an explicit prefix has been supplied, use it + prefix <- Sys.getenv("RENV_PATHS_PREFIX", unset = NA) + if (!is.na(prefix)) + return(prefix) + + # if the user has requested an automatic prefix, generate it + auto <- Sys.getenv("RENV_PATHS_PREFIX_AUTO", unset = NA) + if (auto %in% c("TRUE", "True", "true", "1")) + return(renv_bootstrap_platform_prefix_auto()) + + # empty string on failure + "" + + } + + renv_bootstrap_platform_prefix_auto <- function() { + + prefix <- tryCatch(renv_bootstrap_platform_os(), error = identity) + if (inherits(prefix, "error") || prefix %in% "unknown") { + + msg <- paste( + "failed to infer current operating system", + "please file a bug report at https://github.com/rstudio/renv/issues", + sep = "; " + ) + + warning(msg) + + } + + prefix + + } + + renv_bootstrap_platform_os <- function() { + + sysinfo <- Sys.info() + sysname <- sysinfo[["sysname"]] + + # handle Windows + macOS up front + if (sysname == "Windows") + return("windows") + else if (sysname == "Darwin") + return("macos") + + # check for os-release files + for (file in c("/etc/os-release", "/usr/lib/os-release")) + if (file.exists(file)) + return(renv_bootstrap_platform_os_via_os_release(file, sysinfo)) + + # check for redhat-release files + if (file.exists("/etc/redhat-release")) + return(renv_bootstrap_platform_os_via_redhat_release()) + + "unknown" + + } + + renv_bootstrap_platform_os_via_os_release <- function(file, sysinfo) { + + # read /etc/os-release + release <- utils::read.table( + file = file, + sep = "=", + quote = c("\"", "'"), + col.names = c("Key", "Value"), + comment.char = "#", + stringsAsFactors = FALSE + ) + + vars <- as.list(release$Value) + names(vars) <- release$Key + + # get os name + os <- tolower(sysinfo[["sysname"]]) + + # read id + id <- "unknown" + for (field in c("ID", "ID_LIKE")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + id <- vars[[field]] + break + } + } + + # read version + version <- "unknown" + for (field in c("UBUNTU_CODENAME", "VERSION_CODENAME", "VERSION_ID", "BUILD_ID")) { + if (field %in% names(vars) && nzchar(vars[[field]])) { + version <- vars[[field]] + break + } + } + + # join together + paste(c(os, id, version), collapse = "-") + + } + + renv_bootstrap_platform_os_via_redhat_release <- function() { + + # read /etc/redhat-release + contents <- readLines("/etc/redhat-release", warn = FALSE) + + # infer id + id <- if (grepl("centos", contents, ignore.case = TRUE)) + "centos" + else if (grepl("redhat", contents, ignore.case = TRUE)) + "redhat" + else + "unknown" + + # try to find a version component (very hacky) + version <- "unknown" + + parts <- strsplit(contents, "[[:space:]]")[[1L]] + for (part in parts) { + + nv <- tryCatch(numeric_version(part), error = identity) + if (inherits(nv, "error")) + next + + version <- nv[1, 1] + break + + } + + paste(c("linux", id, version), collapse = "-") + + } + + renv_bootstrap_library_root_name <- function(project) { + + # use project name as-is if requested + asis <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT_ASIS", unset = "FALSE") + if (asis) + return(basename(project)) + + # otherwise, disambiguate based on project's path + id <- substring(renv_bootstrap_hash_text(project), 1L, 8L) + paste(basename(project), id, sep = "-") + + } + + renv_bootstrap_library_root <- function(project) { + + path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) + if (!is.na(path)) + return(path) + + path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) + if (!is.na(path)) { + name <- renv_bootstrap_library_root_name(project) + return(file.path(path, name)) + } + + prefix <- renv_bootstrap_profile_prefix() + paste(c(project, prefix, "renv/library"), collapse = "/") + + } + + renv_bootstrap_validate_version <- function(version) { + + loadedversion <- utils::packageDescription("renv", fields = "Version") + if (version == loadedversion) + return(TRUE) + + # assume four-component versions are from GitHub; three-component + # versions are from CRAN + components <- strsplit(loadedversion, "[.-]")[[1]] + remote <- if (length(components) == 4L) + paste("rstudio/renv", loadedversion, sep = "@") + else + paste("renv", loadedversion, sep = "@") + + fmt <- paste( + "renv %1$s was loaded from project library, but this project is configured to use renv %2$s.", + "Use `renv::record(\"%3$s\")` to record renv %1$s in the lockfile.", + "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", + sep = "\n" + ) + + msg <- sprintf(fmt, loadedversion, version, remote) + warning(msg, call. = FALSE) + + FALSE + + } + + renv_bootstrap_hash_text <- function(text) { + + hashfile <- tempfile("renv-hash-") + on.exit(unlink(hashfile), add = TRUE) + + writeLines(text, con = hashfile) + tools::md5sum(hashfile) + + } + + renv_bootstrap_load <- function(project, libpath, version) { + + # try to load renv from the project library + if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) + return(FALSE) + + # warn if the version of renv loaded does not match + renv_bootstrap_validate_version(version) + + # load the project + renv::load(project) + + TRUE + + } + + renv_bootstrap_profile_load <- function(project) { + + # if RENV_PROFILE is already set, just use that + profile <- Sys.getenv("RENV_PROFILE", unset = NA) + if (!is.na(profile) && nzchar(profile)) + return(profile) + + # check for a profile file (nothing to do if it doesn't exist) + path <- file.path(project, "renv/local/profile") + if (!file.exists(path)) + return(NULL) + + # read the profile, and set it if it exists + contents <- readLines(path, warn = FALSE) + if (length(contents) == 0L) + return(NULL) + + # set RENV_PROFILE + profile <- contents[[1L]] + if (nzchar(profile)) + Sys.setenv(RENV_PROFILE = profile) + + profile + + } + + renv_bootstrap_profile_prefix <- function() { + profile <- renv_bootstrap_profile_get() + if (!is.null(profile)) + return(file.path("renv/profiles", profile)) + } + + renv_bootstrap_profile_get <- function() { + profile <- Sys.getenv("RENV_PROFILE", unset = "") + renv_bootstrap_profile_normalize(profile) + } + + renv_bootstrap_profile_set <- function(profile) { + profile <- renv_bootstrap_profile_normalize(profile) + if (is.null(profile)) + Sys.unsetenv("RENV_PROFILE") + else + Sys.setenv(RENV_PROFILE = profile) + } + + renv_bootstrap_profile_normalize <- function(profile) { + + if (is.null(profile) || profile %in% c("", "default")) + return(NULL) + + profile + + } + + # load the renv profile, if any + renv_bootstrap_profile_load(project) + + # construct path to library root + root <- renv_bootstrap_library_root(project) + + # construct library prefix for platform + prefix <- renv_bootstrap_platform_prefix() + + # construct full libpath + libpath <- file.path(root, prefix) + + # attempt to load + if (renv_bootstrap_load(project, libpath, version)) + return(TRUE) + + # load failed; inform user we're about to bootstrap + prefix <- paste("# Bootstrapping renv", version) + postfix <- paste(rep.int("-", 77L - nchar(prefix)), collapse = "") + header <- paste(prefix, postfix) + message(header) + + # perform bootstrap + bootstrap(version, libpath) + + # exit early if we're just testing bootstrap + if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) + return(TRUE) + + # try again to load + if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { + message("* Successfully installed and loaded renv ", version, ".") + return(renv::load()) + } + + # failed to download or load renv; warn the user + msg <- c( + "Failed to find an renv installation: the project will not be loaded.", + "Use `renv::activate()` to re-initialize the project." + ) + + warning(paste(msg, collapse = "\n"), call. = FALSE) + +}) diff --git a/renv/settings.dcf b/renv/settings.dcf new file mode 100644 index 0000000..fc4e479 --- /dev/null +++ b/renv/settings.dcf @@ -0,0 +1,8 @@ +external.libraries: +ignored.packages: +package.dependency.fields: Imports, Depends, LinkingTo +r.version: +snapshot.type: implicit +use.cache: TRUE +vcs.ignore.library: TRUE +vcs.ignore.local: TRUE diff --git a/tests/spelling.R b/tests/spelling.R deleted file mode 100644 index 13f77d9..0000000 --- a/tests/spelling.R +++ /dev/null @@ -1,6 +0,0 @@ -if (requireNamespace("spelling", quietly = TRUE)) { - spelling::spell_check_test( - vignettes = TRUE, error = FALSE, - skip_on_cran = TRUE - ) -} diff --git a/tests/testthat.R b/tests/testthat.R index 1b60f8e..17ea097 100644 --- a/tests/testthat.R +++ b/tests/testthat.R @@ -1,11 +1,4 @@ library(testthat) library(fitbitr) -if (interactive()) { - Sys.setenv( - "FITBITR_CACHE_LOC" = system.file(".httr-oauth", package = "fitbitr"), - "FITBITR_ENVIRONMENT" = "testing mode" - ) -} - test_check("fitbitr") diff --git a/tests/testthat/.gitignore b/tests/testthat/.gitignore new file mode 100644 index 0000000..b49c113 --- /dev/null +++ b/tests/testthat/.gitignore @@ -0,0 +1 @@ +.httr-oauth diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R new file mode 100644 index 0000000..6a411cb --- /dev/null +++ b/tests/testthat/setup.R @@ -0,0 +1,6 @@ +start_date <- date <- lubridate::as_date("2020-05-21") +end_date <- start_date + lubridate::days(7) + +if (interactive()) { + .fitbitr_token <<- generate_fitbitr_token() +} diff --git a/tests/testthat/test-aaa-token-loads.R b/tests/testthat/test-aaa-token-loads.R deleted file mode 100644 index e4d6a9b..0000000 --- a/tests/testthat/test-aaa-token-loads.R +++ /dev/null @@ -1,11 +0,0 @@ -test_that("Token loads", { - skip_on_cran() - skip_on_ci() - - cache_loc <- Sys.getenv("FITBITR_CACHE_LOC") - - load_cached_token(cache_loc) - tmp <- check_token_exists() - - expect_true(tmp) -}) diff --git a/tests/testthat/test-activities.R b/tests/testthat/test-activities.R index 7cb3ab7..b1849f4 100644 --- a/tests/testthat/test-activities.R +++ b/tests/testthat/test-activities.R @@ -1,118 +1,73 @@ -start_date <- date <- "2021-05-21" -end_date <- "2021-05-22" - test_that("Activites summary downloads", { skip_on_cran() + skip_on_ci() - - tmp <- activity_summary(date) + tmp <- get_activity_summary(date) expect_equal(nrow(tmp), 1) - expect_equal(ncol(tmp), 12) + expect_equal(ncol(tmp), 14) expect_equal(colnames(tmp)[1], "date") checkmate::expect_date(tmp$date) }) -test_that("Activity calories downloads", { - skip_on_cran() - - - tmp <- activity_calories(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "activity_calories")) - checkmate::expect_date(tmp$date) -}) - -test_that("BMR calories downloads", { - skip_on_cran() - - - tmp <- calories_bmr(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "calories_bmr")) - checkmate::expect_date(tmp$date) -}) - -test_that("Total calories downloads", { - skip_on_cran() - - - tmp <- calories(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "calories")) - checkmate::expect_date(tmp$date) -}) - -test_that("Distance downloads", { - skip_on_cran() - - - tmp <- distance(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "distance")) - checkmate::expect_date(tmp$date) -}) - -test_that("Elevation downloads", { +test_that("Activity time series download correctly", { skip_on_cran() - - - tmp <- elevation(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "elevation")) - checkmate::expect_date(tmp$date) + skip_on_ci() + + run_many <- function(f, start_date) { + purrr::walk( + round(runif(1, 1, 100)), + ~ { + response <- f(start_date, start_date + lubridate::days(.x)) + + expect_equal(nrow(response), .x + 1) + expect_equal(ncol(response), 2) + expect_equal(colnames(response)[1], "date") + checkmate::expect_date(response$date) + } + ) + } + + run_many(get_activity_calories, lubridate::as_date(start_date)) + run_many(get_calories_bmr, lubridate::as_date(start_date)) + run_many(get_calories, lubridate::as_date(start_date)) + run_many(get_distance, lubridate::as_date(start_date)) + run_many(get_elevation, lubridate::as_date(start_date)) + run_many(get_floors, lubridate::as_date(start_date)) + run_many(get_minutes_sedentary, lubridate::as_date(start_date)) + run_many(get_minutes_lightly_active, lubridate::as_date(start_date)) + run_many(get_minutes_fairly_active, lubridate::as_date(start_date)) + run_many(get_minutes_very_active, lubridate::as_date(start_date)) + run_many(get_steps, lubridate::as_date(start_date)) }) -test_that("Floors downloads", { - skip_on_cran() - - - tmp <- floors(start_date, end_date) - - expect_equal(nrow(tmp), 7) - expect_equal(ncol(tmp), 2) - expect_equal(colnames(tmp), c("date", "floors")) - checkmate::expect_date(tmp$date) -}) -test_that("Minutes downloads", { +test_that("Bests and totals", { skip_on_cran() + skip_on_ci() + tracker_best <- get_bests_and_totals(TRUE, TRUE) + tracker_total <- get_bests_and_totals(FALSE, TRUE) + lifetime_best <- get_bests_and_totals(TRUE, FALSE) + lifetime_total <- get_bests_and_totals(FALSE, FALSE) - sedentary <- minutes_sedentary(start_date, end_date) - lightly <- minutes_lightly_active(start_date, end_date) - fairly <- minutes_fairly_active(start_date, end_date) - very <- minutes_very_active(start_date, end_date) - - all_data <- list( - sedentary, - lightly, - fairly, - very + purrr::walk( + names(tracker_best), + ~ expect_true(.x %in% names(tracker_total)) ) - all_data %>% - purrr::map( - ~ expect_equal(nrow(.x), 7) - ) + purrr::walk( + names(lifetime_best), + ~ expect_true(.x %in% names(lifetime_total)) + ) - all_data %>% - purrr::map( - ~ expect_equal(ncol(.x), 2) - ) + expect_identical( + names(tracker_best), + names(lifetime_best) + ) - all_data %>% - purrr::map( - ~ checkmate::expect_date(.x$date) - ) + expect_identical( + names(tracker_total), + names(lifetime_total) + ) }) diff --git a/tests/testthat/test-heartrate.R b/tests/testthat/test-heartrate.R index 7208d02..43962ae 100644 --- a/tests/testthat/test-heartrate.R +++ b/tests/testthat/test-heartrate.R @@ -1,36 +1,8 @@ -date <- start_date <- "2021-05-20" -end_date <- "2021-05-21" - -test_that("Heart rate by minute works", { - skip_on_cran() - - tmp <- heart_rate_intraday( - date = date, - minutes = TRUE - ) - - expect_equal(colnames(tmp), c("time", "heart_rate")) - expect_equal(nrow(tmp), 5) - checkmate::expect_posixct(tmp$time) -}) - -test_that("Heart rate by second works", { - skip_on_cran() - - tmp <- heart_rate_intraday( - date = date, - minutes = FALSE - ) - - expect_equal(colnames(tmp), c("time", "heart_rate")) - expect_equal(nrow(tmp), 5) - checkmate::expect_posixct(tmp$time) -}) - test_that("Heart rate zones works", { skip_on_cran() + skip_on_ci() - tmp <- heart_rate_zones(date) + tmp <- get_heart_rate_zones(date) expect_equal(nrow(tmp), 4) expect_equal(colnames(tmp), c("date", "zone", "min_hr", "max_hr", "minutes_in_zone", "calories_out")) diff --git a/tests/testthat/test-intraday.R b/tests/testthat/test-intraday.R new file mode 100644 index 0000000..42c97f9 --- /dev/null +++ b/tests/testthat/test-intraday.R @@ -0,0 +1,65 @@ +test_that("Heart rate by minute works", { + skip_on_cran() + skip_on_ci() + + heart <- get_heart_rate_intraday( + date = date + ) + + expect_equal(colnames(heart), c("time", "heart_rate")) + expect_gt(nrow(heart), 1000) + checkmate::expect_posixct(heart$time) + + heart <- get_heart_rate_intraday( + date = date, + detail_level = "1min", + start_time = "00:00:00", + end_time = "00:02:00" + ) + + expect_equal(colnames(heart), c("time", "heart_rate")) + expect_equal(nrow(heart), 3) + checkmate::expect_posixct(heart$time) +}) + +test_that("Intradays work", { + skip_on_cran() + skip_on_ci() + + test_one_min_gran <- function(f, col) { + x <- f(date, detail_level = "1min") + + expect_equal(colnames(x), c("time", col)) + expect_gt(nrow(x), 1000) + checkmate::expect_posixct(x$time) + expect_true(x$time[1] == x$time[2] - lubridate::minutes(1)) + } + + test_fifteen_min_gran <- function(f, col) { + x <- f(date, detail_level = "15min") + + expect_equal(colnames(x), c("time", col)) + expect_gt(nrow(x), 10) + checkmate::expect_posixct(x$time) + expect_true(x$time[1] == x$time[2] - lubridate::minutes(15)) + } + + resources <- list( + calories = get_calories_intraday, + distance = get_distance_intraday, + elevation = get_elevation_intraday, + floors = get_floors_intraday, + heart_rate = get_heart_rate_intraday, + steps = get_steps_intraday + ) + + purrr::iwalk( + resources, + test_one_min_gran + ) + + purrr::iwalk( + resources, + test_fifteen_min_gran + ) +}) diff --git a/tests/testthat/test-sleep.R b/tests/testthat/test-sleep.R index 34e9a72..453a2a1 100644 --- a/tests/testthat/test-sleep.R +++ b/tests/testthat/test-sleep.R @@ -1,10 +1,8 @@ -date <- start_date <- "2021-05-20" -end_date <- "2021-05-21" - test_that("Sleep works", { skip_on_cran() + skip_on_ci() - tmp <- sleep_summary( + tmp <- get_sleep_summary( start_date = start_date, end_date = end_date ) @@ -16,3 +14,43 @@ test_that("Sleep works", { checkmate::expect_date(tmp$date) }) + +test_that("Sleep stage granular works", { + skip_on_cran() + skip_on_ci() + + sleep <- get_sleep_stage_granular( + start_date, + end_date + ) + + expect_equal(max(lubridate::as_date(sleep$time)), end_date) + expect_true(dplyr::between(min(lubridate::as_date(sleep$time)), start_date - lubridate::days(1), start_date)) + expect_equal( + colnames(sleep), + c("time", "level", "seconds") + ) + expect_setequal( + unique(sleep$level), + c("rem", "deep", "light", "wake") + ) +}) + +test_that("Sleep stage summary works", { + skip_on_cran() + skip_on_ci() + + sleep <- get_sleep_stage_summary( + start_date, + end_date + ) + + expect_identical( + colnames(sleep), + c("date", "stage", "count", "minutes", "thirty_day_avg_minutes") + ) + expect_setequal( + sleep$stage, + c("deep", "light", "rem", "wake") + ) +}) diff --git a/tests/testthat/test-validation.R b/tests/testthat/test-validation.R new file mode 100644 index 0000000..37b7f25 --- /dev/null +++ b/tests/testthat/test-validation.R @@ -0,0 +1,19 @@ +test_that("Date validation works", { + skip_on_cran() + skip_on_ci() + + expect_error( + get_steps("foobar"), + "Date validation failed" + ) + + expect_error( + validate_date("foo"), + "Date validation failed" + ) + + expect_true(validate_date("2021-02-01")) + expect_true(validate_date("1/2/22")) + expect_true(validate_date("1/2/2022")) + expect_true(validate_date(lubridate::today())) +}) diff --git a/tests/testthat/test-zzz-teardown.R b/tests/testthat/test-zzz-teardown.R new file mode 100644 index 0000000..1584c7d --- /dev/null +++ b/tests/testthat/test-zzz-teardown.R @@ -0,0 +1,8 @@ +test_that("Torn down", { + skip_on_cran() + skip_on_ci() + + unlink(".httr-oauth") + expect_false(file.exists(".httr-oauth")) +}) +