From ebd3a1c2e22732267443cb6fa38ba9573e75cd04 Mon Sep 17 00:00:00 2001 From: Nicolas Dickinson Date: Thu, 7 Nov 2024 22:59:31 +0100 Subject: [PATCH] Adding getDatabaseRoles(), improving getDatabaseResources(), improving documentation, adding a vignette walk-through for working with grant-based roles, adding tests, improving activity info snapshotting functions, adding a suggestion for tidyr due to it appearing in the vignette, bump version number --- DESCRIPTION | 3 +- NAMESPACE | 5 + R/databases.R | 76 +- man/addDatabaseUser.Rd | 6 +- man/getDatabaseRole.Rd | 1 - man/getDatabaseRoles.Rd | 20 + .../databases-getDatabaseRoles.RDS | Bin 0 -> 796 bytes .../setup-dataframe-snapshot.RDS | Bin 0 -> 156 bytes tests/testthat/_snaps/records.md | 2016 +++++++++++++++++ tests/testthat/setup.R | 18 +- tests/testthat/test-databases.R | 21 +- tests/testthat/test-setup.R | 4 + vignettes/introduction.Rmd | 9 +- vignettes/working-with-grant-based-roles.Rmd | 712 ++++++ 14 files changed, 2857 insertions(+), 34 deletions(-) create mode 100644 man/getDatabaseRoles.Rd create mode 100644 tests/testthat/_activityInfoSnaps/databases-getDatabaseRoles.RDS create mode 100644 tests/testthat/_activityInfoSnaps/setup-dataframe-snapshot.RDS create mode 100644 tests/testthat/test-setup.R create mode 100644 vignettes/working-with-grant-based-roles.Rmd diff --git a/DESCRIPTION b/DESCRIPTION index ee6db6a..d551965 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -2,7 +2,7 @@ Package: activityinfo Type: Package Title: R interface to ActivityInfo.org, an information management software for humanitarian and development operations -Version: 4.37 +Version: 4.38 Date: 2024-10-16 Authors@R: c( person("Alex", "Bertram", email = "alex@bedatadriven.com", @@ -47,6 +47,7 @@ Suggests: markdown, withr, assertthat, + tidyr, purrr, tinytex VignetteBuilder: knitr diff --git a/NAMESPACE b/NAMESPACE index 3475f1a..9f078e8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,8 +28,12 @@ S3method(do,tbl_activityInfoRemoteRecords) S3method(do_,tbl_activityInfoRemoteRecords) S3method(filter,tbl_activityInfoRemoteRecords) S3method(filter_,tbl_activityInfoRemoteRecords) +S3method(getDatabaseResources,character) +S3method(getDatabaseResources,databaseTree) S3method(getDatabaseRole,character) S3method(getDatabaseRole,databaseTree) +S3method(getDatabaseRoles,character) +S3method(getDatabaseRoles,databaseTree) S3method(getRecords,activityInfoFormSchema) S3method(getRecords,activityInfoFormTree) S3method(getRecords,activityInfo_tbl_df) @@ -143,6 +147,7 @@ export(getBillingAccountUsers) export(getDatabaseBillingAccount) export(getDatabaseResources) export(getDatabaseRole) +export(getDatabaseRoles) export(getDatabaseSchema) export(getDatabaseTree) export(getDatabaseUser) diff --git a/R/databases.R b/R/databases.R index 6e32caf..f1ae313 100644 --- a/R/databases.R +++ b/R/databases.R @@ -106,19 +106,23 @@ getDatabaseTree <- function(databaseId) { #' #' @export getDatabaseResources <- function(database) { - if(is.character(database)) { - databaseTree <- getDatabaseTree(database) - } else if(is.list(database)) { - databaseTree <- database - } else { - stop("The `database` argument must be a database id or a databaseTree") - } + UseMethod("getDatabaseResources") +} + +#' @export +getDatabaseResources.character <- function(database) { + tree <- getDatabaseTree(database) + getDatabaseResources(tree) +} + +#' @export +getDatabaseResources.databaseTree <- function(database) { dplyr::tibble( - id = unlist(lapply(databaseTree$resources, function(x) {x$id})), - label = unlist(lapply(databaseTree$resources, function(x) {x$label})), - type = unlist(lapply(databaseTree$resources, function(x) {x$type})), - parentId = unlist(lapply(databaseTree$resources, function(x) {x$parentId})), - visibility = unlist(lapply(databaseTree$resources, function(x) {x$visibility})) + id = unlist(lapply(database$resources, function(x) {x$id})), + label = unlist(lapply(database$resources, function(x) {x$label})), + type = unlist(lapply(database$resources, function(x) {x$type})), + parentId = unlist(lapply(database$resources, function(x) {x$parentId})), + visibility = unlist(lapply(database$resources, function(x) {x$visibility})) ) } @@ -215,8 +219,7 @@ getDatabaseUsers <- function(databaseId, asDataFrame = TRUE) { version = unlist(lapply(users, function(x) {x$version})), inviteDate = as.Date(unlist(lapply(users, function(x) {x$inviteDate}))), deliveryStatus = unlist(lapply(users, function(x) {x$deliveryStatus})), - inviteAccepted = unlist(lapply(users, function(x) {x$inviteAccepted})) # , - # role = lapply(users, function(x) {x$role}) + inviteAccepted = unlist(lapply(users, function(x) {x$inviteAccepted})) ) usersDF$role <- lapply(users, function(x) {x$role}) @@ -318,7 +321,7 @@ checkUserRole <- function(databaseId, newUser, roleId, roleParameters, roleResou #' addDatabaseUser #' -#' Invites a user to a database. +#' Invites a user to a database and assigns a role #' #' @param databaseId the id of the database to which they should be added #' @param email the user's email @@ -326,7 +329,7 @@ checkUserRole <- function(databaseId, newUser, roleId, roleParameters, roleResou #' @param locale the locale ("en', "fr", "ar", etc) to use inviting the user (only used if they do not already have an ActivityInfo account) #' @param roleId the id of the role to assign to the user. #' @param roleParameters a named list containing the role parameter values -#' @param roleResources a list of folders in which this role should be assigned (or the databaseId if they should have this role in the whole database) +#' @param roleResources an optional list of optional grant-based resources assigned to the user #' #' @details #' @@ -347,6 +350,8 @@ checkUserRole <- function(databaseId, newUser, roleId, roleParameters, roleResou #' in many database templates has a `partner` parameter that is used to filter which #' records are visible to the user. The value of this parameter is the record id of the #' user's partner in the related Partner form. +#' +#' Optional grants can be specified by adding the resource id of those grants to a list and passing that to `roleResources`. #' #' @examples #' \dontrun{ @@ -415,6 +420,44 @@ addDatabaseUser <- function(databaseId, email, name, locale = NA_character_, rol } } + +#' getDatabaseRoles +#' +#' Get database roles in a data frame. +#' +#' @param database database tree using \link{getDatabaseTree} or the databaseId +#' +#' @examples +#' \dontrun{ +#' dbTree <- getDatabaseTree(databaseId = "ck3pqrp9a1z") # fetch the database tree +#' roles <- getDatabaseRoles(dbTree) # get the database roles +#' } +#' @export +#' +getDatabaseRoles <- function(database) { + UseMethod("getDatabaseRoles") +} + +#' @export +getDatabaseRoles.character <- function(database) { + tree <- getDatabaseTree(databaseId = database) + getDatabaseRoles(tree) +} + +#' @export +getDatabaseRoles.databaseTree <- function(database) { + roles <- dplyr::tibble( + id = unlist(lapply(database$roles, function(x) {x$id})), + label = unlist(lapply(database$roles, function(x) {x$label})), + permissions = lapply(database$roles, function(x) {x$permissions}), + parameters = lapply(database$roles, function(x) {x$parameters}), + filters = lapply(database$roles, function(x) {x$filters}), + grants = lapply(database$roles, function(x) {x$grants}), + version = unlist(lapply(database$roles, function(x) {x$version})), + grantBased = unlist(lapply(database$roles, function(x) {x$grantBased})) + ) +} + #' getDatabaseRole #' #' Helper method to fetch a role based on its id using the database tree or database id. @@ -428,7 +471,6 @@ addDatabaseUser <- function(databaseId, email, name, locale = NA_character_, rol #' dbTree <- getDatabaseTree(databaseId = "ck3pqrp9a1z") # fetch the database tree #' role <- getDatabaseRole(dbTree, roleId = "rp") # extract the reporting partner role #' } -#' #' @export #' getDatabaseRole <- function(database, roleId) { diff --git a/man/addDatabaseUser.Rd b/man/addDatabaseUser.Rd index e0af8c7..426a83d 100644 --- a/man/addDatabaseUser.Rd +++ b/man/addDatabaseUser.Rd @@ -27,10 +27,10 @@ addDatabaseUser( \item{roleParameters}{a named list containing the role parameter values} -\item{roleResources}{a list of folders in which this role should be assigned (or the databaseId if they should have this role in the whole database)} +\item{roleResources}{an optional list of optional grant-based resources assigned to the user} } \description{ -Invites a user to a database. +Invites a user to a database and assigns a role } \details{ This function adds a new user to a database and assigns them a role. @@ -50,6 +50,8 @@ Some roles are \emph{parameterized}. For example, the "Reporting Partner" role i in many database templates has a \code{partner} parameter that is used to filter which records are visible to the user. The value of this parameter is the record id of the user's partner in the related Partner form. + +Optional grants can be specified by adding the resource id of those grants to a list and passing that to \code{roleResources}. } \examples{ \dontrun{ diff --git a/man/getDatabaseRole.Rd b/man/getDatabaseRole.Rd index ed3dd36..77a32a0 100644 --- a/man/getDatabaseRole.Rd +++ b/man/getDatabaseRole.Rd @@ -20,5 +20,4 @@ Helper method to fetch a role based on its id using the database tree or databas dbTree <- getDatabaseTree(databaseId = "ck3pqrp9a1z") # fetch the database tree role <- getDatabaseRole(dbTree, roleId = "rp") # extract the reporting partner role } - } diff --git a/man/getDatabaseRoles.Rd b/man/getDatabaseRoles.Rd new file mode 100644 index 0000000..7aec197 --- /dev/null +++ b/man/getDatabaseRoles.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/databases.R +\name{getDatabaseRoles} +\alias{getDatabaseRoles} +\title{getDatabaseRoles} +\usage{ +getDatabaseRoles(database) +} +\arguments{ +\item{database}{database tree using \link{getDatabaseTree} or the databaseId} +} +\description{ +Get database roles in a data frame. +} +\examples{ +\dontrun{ +dbTree <- getDatabaseTree(databaseId = "ck3pqrp9a1z") # fetch the database tree +roles <- getDatabaseRoles(dbTree) # get the database roles +} +} diff --git a/tests/testthat/_activityInfoSnaps/databases-getDatabaseRoles.RDS b/tests/testthat/_activityInfoSnaps/databases-getDatabaseRoles.RDS new file mode 100644 index 0000000000000000000000000000000000000000..083ff9cb074eda47e716007191d6d9f9db3b4e45 GIT binary patch literal 796 zcmV+%1LOQ3iwFP!000002IX1JPunmQPn(91mTo`~lXi->!%jomrimTKv^4QXL}*ab zFm9D;+$vL=RCWsDw!fUsY3f7_bL79;uEfQ zgR$Rsw3cgWDgDA_sJZ)^13Kw$`-Dz+a^|E_D(IvkR`ofRD>*mkSGV7s1rl^mA{Kc! zEO_gFTK9xG6P6#_{h&xQ6E@E!5%W9^@}=V_3y|)sB!ghU1gaoP@C7HxLC{y@DC7lo z%^w6&KoY7^%xT0#M(!v3v zhGI^`l^8~SG&4Q_frxo5-{d(q*s05Nfg}E|!@wQe0>Z(!8Mx-P?ZXE>Zq7}Ha(^*= z;YX2wd1nw8%ekYZBDb?UinN{~ z(#$yPI6mLuFIp*h`PT$Z$!NC5J^w^AzROqp)E_5JNDz0>E8`Mgf9)xZb(=bN-mpd~ a8U%#QoFG=uXmv^&rREP#EIxx%8~_0PS$+Ee literal 0 HcmV?d00001 diff --git a/tests/testthat/_activityInfoSnaps/setup-dataframe-snapshot.RDS b/tests/testthat/_activityInfoSnaps/setup-dataframe-snapshot.RDS new file mode 100644 index 0000000000000000000000000000000000000000..be4cc7907cefcca1991636e989edb3707086395d GIT binary patch literal 156 zcmb2|=3oE=w(bW>2?+^l35jVb32CfGk`d0%cS>{{k`6iWDGTgpPEs?N-P$PZ@mclx zb1@aQ=||F^O%+)#w&>(06;-k2PDd*isx`c*SD34?T4s&Jfdl`TwRi&$H|2R literal 0 HcmV?d00001 diff --git a/tests/testthat/_snaps/records.md b/tests/testthat/_snaps/records.md index 7f4a856..e304425 100644 --- a/tests/testthat/_snaps/records.md +++ b/tests/testthat/_snaps/records.md @@ -3050,6 +3050,2022 @@ 499 0 500 1 +--- + + Code + rcrdsMinDf + Output + Identifier number A single select column A logical column A date column + 1 1 1_stuff True 2021-07-06 + 2 2 2_stuff True 2021-07-07 + 3 3 3_stuff False 2021-07-08 + 4 4 4_stuff False 2021-07-09 + 5 5 5_stuff False 2021-07-10 + 6 6 1_stuff False 2021-07-11 + 7 7 2_stuff False 2021-07-12 + 8 8 3_stuff False 2021-07-13 + 9 9 4_stuff False 2021-07-14 + 10 10 5_stuff False 2021-07-15 + 11 11 1_stuff False 2021-07-16 + 12 12 2_stuff False 2021-07-17 + 13 13 3_stuff False 2021-07-18 + 14 14 4_stuff False 2021-07-19 + 15 15 5_stuff False 2021-07-20 + 16 16 1_stuff False 2021-07-21 + 17 17 2_stuff False 2021-07-22 + 18 18 3_stuff False 2021-07-23 + 19 19 4_stuff False 2021-07-24 + 20 20 5_stuff False 2021-07-25 + 21 21 1_stuff True 2021-07-06 + 22 22 2_stuff True 2021-07-07 + 23 23 3_stuff True 2021-07-08 + 24 24 4_stuff False 2021-07-09 + 25 25 5_stuff False 2021-07-10 + 26 26 1_stuff False 2021-07-11 + 27 27 2_stuff False 2021-07-12 + 28 28 3_stuff False 2021-07-13 + 29 29 4_stuff False 2021-07-14 + 30 30 5_stuff False 2021-07-15 + 31 31 1_stuff False 2021-07-16 + 32 32 2_stuff False 2021-07-17 + 33 33 3_stuff False 2021-07-18 + 34 34 4_stuff False 2021-07-19 + 35 35 5_stuff False 2021-07-20 + 36 36 1_stuff False 2021-07-21 + 37 37 2_stuff False 2021-07-22 + 38 38 3_stuff False 2021-07-23 + 39 39 4_stuff False 2021-07-24 + 40 40 5_stuff False 2021-07-25 + 41 41 1_stuff False 2021-07-06 + 42 42 2_stuff True 2021-07-07 + 43 43 3_stuff True 2021-07-08 + 44 44 4_stuff True 2021-07-09 + 45 45 5_stuff False 2021-07-10 + 46 46 1_stuff False 2021-07-11 + 47 47 2_stuff False 2021-07-12 + 48 48 3_stuff False 2021-07-13 + 49 49 4_stuff False 2021-07-14 + 50 50 5_stuff False 2021-07-15 + 51 51 1_stuff False 2021-07-16 + 52 52 2_stuff False 2021-07-17 + 53 53 3_stuff False 2021-07-18 + 54 54 4_stuff False 2021-07-19 + 55 55 5_stuff False 2021-07-20 + 56 56 1_stuff False 2021-07-21 + 57 57 2_stuff False 2021-07-22 + 58 58 3_stuff False 2021-07-23 + 59 59 4_stuff False 2021-07-24 + 60 60 5_stuff False 2021-07-25 + 61 61 1_stuff False 2021-07-06 + 62 62 2_stuff False 2021-07-07 + 63 63 3_stuff True 2021-07-08 + 64 64 4_stuff True 2021-07-09 + 65 65 5_stuff True 2021-07-10 + 66 66 1_stuff False 2021-07-11 + 67 67 2_stuff False 2021-07-12 + 68 68 3_stuff False 2021-07-13 + 69 69 4_stuff False 2021-07-14 + 70 70 5_stuff False 2021-07-15 + 71 71 1_stuff False 2021-07-16 + 72 72 2_stuff False 2021-07-17 + 73 73 3_stuff False 2021-07-18 + 74 74 4_stuff False 2021-07-19 + 75 75 5_stuff False 2021-07-20 + 76 76 1_stuff False 2021-07-21 + 77 77 2_stuff False 2021-07-22 + 78 78 3_stuff False 2021-07-23 + 79 79 4_stuff False 2021-07-24 + 80 80 5_stuff False 2021-07-25 + 81 81 1_stuff False 2021-07-06 + 82 82 2_stuff False 2021-07-07 + 83 83 3_stuff False 2021-07-08 + 84 84 4_stuff True 2021-07-09 + 85 85 5_stuff True 2021-07-10 + 86 86 1_stuff True 2021-07-11 + 87 87 2_stuff False 2021-07-12 + 88 88 3_stuff False 2021-07-13 + 89 89 4_stuff False 2021-07-14 + 90 90 5_stuff False 2021-07-15 + 91 91 1_stuff False 2021-07-16 + 92 92 2_stuff False 2021-07-17 + 93 93 3_stuff False 2021-07-18 + 94 94 4_stuff False 2021-07-19 + 95 95 5_stuff False 2021-07-20 + 96 96 1_stuff False 2021-07-21 + 97 97 2_stuff False 2021-07-22 + 98 98 3_stuff False 2021-07-23 + 99 99 4_stuff False 2021-07-24 + 100 100 5_stuff False 2021-07-25 + 101 101 1_stuff False 2021-07-06 + 102 102 2_stuff False 2021-07-07 + 103 103 3_stuff False 2021-07-08 + 104 104 4_stuff False 2021-07-09 + 105 105 5_stuff True 2021-07-10 + 106 106 1_stuff True 2021-07-11 + 107 107 2_stuff True 2021-07-12 + 108 108 3_stuff False 2021-07-13 + 109 109 4_stuff False 2021-07-14 + 110 110 5_stuff False 2021-07-15 + 111 111 1_stuff False 2021-07-16 + 112 112 2_stuff False 2021-07-17 + 113 113 3_stuff False 2021-07-18 + 114 114 4_stuff False 2021-07-19 + 115 115 5_stuff False 2021-07-20 + 116 116 1_stuff False 2021-07-21 + 117 117 2_stuff False 2021-07-22 + 118 118 3_stuff False 2021-07-23 + 119 119 4_stuff False 2021-07-24 + 120 120 5_stuff False 2021-07-25 + 121 121 1_stuff False 2021-07-06 + 122 122 2_stuff False 2021-07-07 + 123 123 3_stuff False 2021-07-08 + 124 124 4_stuff False 2021-07-09 + 125 125 5_stuff False 2021-07-10 + 126 126 1_stuff True 2021-07-11 + 127 127 2_stuff True 2021-07-12 + 128 128 3_stuff True 2021-07-13 + 129 129 4_stuff False 2021-07-14 + 130 130 5_stuff False 2021-07-15 + 131 131 1_stuff False 2021-07-16 + 132 132 2_stuff False 2021-07-17 + 133 133 3_stuff False 2021-07-18 + 134 134 4_stuff False 2021-07-19 + 135 135 5_stuff False 2021-07-20 + 136 136 1_stuff False 2021-07-21 + 137 137 2_stuff False 2021-07-22 + 138 138 3_stuff False 2021-07-23 + 139 139 4_stuff False 2021-07-24 + 140 140 5_stuff False 2021-07-25 + 141 141 1_stuff False 2021-07-06 + 142 142 2_stuff False 2021-07-07 + 143 143 3_stuff False 2021-07-08 + 144 144 4_stuff False 2021-07-09 + 145 145 5_stuff False 2021-07-10 + 146 146 1_stuff False 2021-07-11 + 147 147 2_stuff True 2021-07-12 + 148 148 3_stuff True 2021-07-13 + 149 149 4_stuff True 2021-07-14 + 150 150 5_stuff False 2021-07-15 + 151 151 1_stuff False 2021-07-16 + 152 152 2_stuff False 2021-07-17 + 153 153 3_stuff False 2021-07-18 + 154 154 4_stuff False 2021-07-19 + 155 155 5_stuff False 2021-07-20 + 156 156 1_stuff False 2021-07-21 + 157 157 2_stuff False 2021-07-22 + 158 158 3_stuff False 2021-07-23 + 159 159 4_stuff False 2021-07-24 + 160 160 5_stuff False 2021-07-25 + 161 161 1_stuff False 2021-07-06 + 162 162 2_stuff False 2021-07-07 + 163 163 3_stuff False 2021-07-08 + 164 164 4_stuff False 2021-07-09 + 165 165 5_stuff False 2021-07-10 + 166 166 1_stuff False 2021-07-11 + 167 167 2_stuff False 2021-07-12 + 168 168 3_stuff True 2021-07-13 + 169 169 4_stuff True 2021-07-14 + 170 170 5_stuff True 2021-07-15 + 171 171 1_stuff False 2021-07-16 + 172 172 2_stuff False 2021-07-17 + 173 173 3_stuff False 2021-07-18 + 174 174 4_stuff False 2021-07-19 + 175 175 5_stuff False 2021-07-20 + 176 176 1_stuff False 2021-07-21 + 177 177 2_stuff False 2021-07-22 + 178 178 3_stuff False 2021-07-23 + 179 179 4_stuff False 2021-07-24 + 180 180 5_stuff False 2021-07-25 + 181 181 1_stuff False 2021-07-06 + 182 182 2_stuff False 2021-07-07 + 183 183 3_stuff False 2021-07-08 + 184 184 4_stuff False 2021-07-09 + 185 185 5_stuff False 2021-07-10 + 186 186 1_stuff False 2021-07-11 + 187 187 2_stuff False 2021-07-12 + 188 188 3_stuff False 2021-07-13 + 189 189 4_stuff True 2021-07-14 + 190 190 5_stuff True 2021-07-15 + 191 191 1_stuff True 2021-07-16 + 192 192 2_stuff False 2021-07-17 + 193 193 3_stuff False 2021-07-18 + 194 194 4_stuff False 2021-07-19 + 195 195 5_stuff False 2021-07-20 + 196 196 1_stuff False 2021-07-21 + 197 197 2_stuff False 2021-07-22 + 198 198 3_stuff False 2021-07-23 + 199 199 4_stuff False 2021-07-24 + 200 200 5_stuff False 2021-07-25 + 201 201 1_stuff False 2021-07-06 + 202 202 2_stuff False 2021-07-07 + 203 203 3_stuff False 2021-07-08 + 204 204 4_stuff False 2021-07-09 + 205 205 5_stuff False 2021-07-10 + 206 206 1_stuff False 2021-07-11 + 207 207 2_stuff False 2021-07-12 + 208 208 3_stuff False 2021-07-13 + 209 209 4_stuff False 2021-07-14 + 210 210 5_stuff True 2021-07-15 + 211 211 1_stuff True 2021-07-16 + 212 212 2_stuff True 2021-07-17 + 213 213 3_stuff False 2021-07-18 + 214 214 4_stuff False 2021-07-19 + 215 215 5_stuff False 2021-07-20 + 216 216 1_stuff False 2021-07-21 + 217 217 2_stuff False 2021-07-22 + 218 218 3_stuff False 2021-07-23 + 219 219 4_stuff False 2021-07-24 + 220 220 5_stuff False 2021-07-25 + 221 221 1_stuff False 2021-07-06 + 222 222 2_stuff False 2021-07-07 + 223 223 3_stuff False 2021-07-08 + 224 224 4_stuff False 2021-07-09 + 225 225 5_stuff False 2021-07-10 + 226 226 1_stuff False 2021-07-11 + 227 227 2_stuff False 2021-07-12 + 228 228 3_stuff False 2021-07-13 + 229 229 4_stuff False 2021-07-14 + 230 230 5_stuff False 2021-07-15 + 231 231 1_stuff True 2021-07-16 + 232 232 2_stuff True 2021-07-17 + 233 233 3_stuff True 2021-07-18 + 234 234 4_stuff False 2021-07-19 + 235 235 5_stuff False 2021-07-20 + 236 236 1_stuff False 2021-07-21 + 237 237 2_stuff False 2021-07-22 + 238 238 3_stuff False 2021-07-23 + 239 239 4_stuff False 2021-07-24 + 240 240 5_stuff False 2021-07-25 + 241 241 1_stuff False 2021-07-06 + 242 242 2_stuff False 2021-07-07 + 243 243 3_stuff False 2021-07-08 + 244 244 4_stuff False 2021-07-09 + 245 245 5_stuff False 2021-07-10 + 246 246 1_stuff False 2021-07-11 + 247 247 2_stuff False 2021-07-12 + 248 248 3_stuff False 2021-07-13 + 249 249 4_stuff False 2021-07-14 + 250 250 5_stuff False 2021-07-15 + 251 251 1_stuff False 2021-07-16 + 252 252 2_stuff True 2021-07-17 + 253 253 3_stuff True 2021-07-18 + 254 254 4_stuff True 2021-07-19 + 255 255 5_stuff False 2021-07-20 + 256 256 1_stuff False 2021-07-21 + 257 257 2_stuff False 2021-07-22 + 258 258 3_stuff False 2021-07-23 + 259 259 4_stuff False 2021-07-24 + 260 260 5_stuff False 2021-07-25 + 261 261 1_stuff False 2021-07-06 + 262 262 2_stuff False 2021-07-07 + 263 263 3_stuff False 2021-07-08 + 264 264 4_stuff False 2021-07-09 + 265 265 5_stuff False 2021-07-10 + 266 266 1_stuff False 2021-07-11 + 267 267 2_stuff False 2021-07-12 + 268 268 3_stuff False 2021-07-13 + 269 269 4_stuff False 2021-07-14 + 270 270 5_stuff False 2021-07-15 + 271 271 1_stuff False 2021-07-16 + 272 272 2_stuff False 2021-07-17 + 273 273 3_stuff True 2021-07-18 + 274 274 4_stuff True 2021-07-19 + 275 275 5_stuff True 2021-07-20 + 276 276 1_stuff False 2021-07-21 + 277 277 2_stuff False 2021-07-22 + 278 278 3_stuff False 2021-07-23 + 279 279 4_stuff False 2021-07-24 + 280 280 5_stuff False 2021-07-25 + 281 281 1_stuff False 2021-07-06 + 282 282 2_stuff False 2021-07-07 + 283 283 3_stuff False 2021-07-08 + 284 284 4_stuff False 2021-07-09 + 285 285 5_stuff False 2021-07-10 + 286 286 1_stuff False 2021-07-11 + 287 287 2_stuff False 2021-07-12 + 288 288 3_stuff False 2021-07-13 + 289 289 4_stuff False 2021-07-14 + 290 290 5_stuff False 2021-07-15 + 291 291 1_stuff False 2021-07-16 + 292 292 2_stuff False 2021-07-17 + 293 293 3_stuff False 2021-07-18 + 294 294 4_stuff True 2021-07-19 + 295 295 5_stuff True 2021-07-20 + 296 296 1_stuff True 2021-07-21 + 297 297 2_stuff False 2021-07-22 + 298 298 3_stuff False 2021-07-23 + 299 299 4_stuff False 2021-07-24 + 300 300 5_stuff False 2021-07-25 + 301 301 1_stuff False 2021-07-06 + 302 302 2_stuff False 2021-07-07 + 303 303 3_stuff False 2021-07-08 + 304 304 4_stuff False 2021-07-09 + 305 305 5_stuff False 2021-07-10 + 306 306 1_stuff False 2021-07-11 + 307 307 2_stuff False 2021-07-12 + 308 308 3_stuff False 2021-07-13 + 309 309 4_stuff False 2021-07-14 + 310 310 5_stuff False 2021-07-15 + 311 311 1_stuff False 2021-07-16 + 312 312 2_stuff False 2021-07-17 + 313 313 3_stuff False 2021-07-18 + 314 314 4_stuff False 2021-07-19 + 315 315 5_stuff True 2021-07-20 + 316 316 1_stuff True 2021-07-21 + 317 317 2_stuff True 2021-07-22 + 318 318 3_stuff False 2021-07-23 + 319 319 4_stuff False 2021-07-24 + 320 320 5_stuff False 2021-07-25 + 321 321 1_stuff False 2021-07-06 + 322 322 2_stuff False 2021-07-07 + 323 323 3_stuff False 2021-07-08 + 324 324 4_stuff False 2021-07-09 + 325 325 5_stuff False 2021-07-10 + 326 326 1_stuff False 2021-07-11 + 327 327 2_stuff False 2021-07-12 + 328 328 3_stuff False 2021-07-13 + 329 329 4_stuff False 2021-07-14 + 330 330 5_stuff False 2021-07-15 + 331 331 1_stuff False 2021-07-16 + 332 332 2_stuff False 2021-07-17 + 333 333 3_stuff False 2021-07-18 + 334 334 4_stuff False 2021-07-19 + 335 335 5_stuff False 2021-07-20 + 336 336 1_stuff True 2021-07-21 + 337 337 2_stuff True 2021-07-22 + 338 338 3_stuff True 2021-07-23 + 339 339 4_stuff False 2021-07-24 + 340 340 5_stuff False 2021-07-25 + 341 341 1_stuff False 2021-07-06 + 342 342 2_stuff False 2021-07-07 + 343 343 3_stuff False 2021-07-08 + 344 344 4_stuff False 2021-07-09 + 345 345 5_stuff False 2021-07-10 + 346 346 1_stuff False 2021-07-11 + 347 347 2_stuff False 2021-07-12 + 348 348 3_stuff False 2021-07-13 + 349 349 4_stuff False 2021-07-14 + 350 350 5_stuff False 2021-07-15 + 351 351 1_stuff False 2021-07-16 + 352 352 2_stuff False 2021-07-17 + 353 353 3_stuff False 2021-07-18 + 354 354 4_stuff False 2021-07-19 + 355 355 5_stuff False 2021-07-20 + 356 356 1_stuff False 2021-07-21 + 357 357 2_stuff True 2021-07-22 + 358 358 3_stuff True 2021-07-23 + 359 359 4_stuff True 2021-07-24 + 360 360 5_stuff False 2021-07-25 + 361 361 1_stuff False 2021-07-06 + 362 362 2_stuff False 2021-07-07 + 363 363 3_stuff False 2021-07-08 + 364 364 4_stuff False 2021-07-09 + 365 365 5_stuff False 2021-07-10 + 366 366 1_stuff False 2021-07-11 + 367 367 2_stuff False 2021-07-12 + 368 368 3_stuff False 2021-07-13 + 369 369 4_stuff False 2021-07-14 + 370 370 5_stuff False 2021-07-15 + 371 371 1_stuff False 2021-07-16 + 372 372 2_stuff False 2021-07-17 + 373 373 3_stuff False 2021-07-18 + 374 374 4_stuff False 2021-07-19 + 375 375 5_stuff False 2021-07-20 + 376 376 1_stuff False 2021-07-21 + 377 377 2_stuff False 2021-07-22 + 378 378 3_stuff True 2021-07-23 + 379 379 4_stuff True 2021-07-24 + 380 380 5_stuff True 2021-07-25 + 381 381 1_stuff False 2021-07-06 + 382 382 2_stuff False 2021-07-07 + 383 383 3_stuff False 2021-07-08 + 384 384 4_stuff False 2021-07-09 + 385 385 5_stuff False 2021-07-10 + 386 386 1_stuff False 2021-07-11 + 387 387 2_stuff False 2021-07-12 + 388 388 3_stuff False 2021-07-13 + 389 389 4_stuff False 2021-07-14 + 390 390 5_stuff False 2021-07-15 + 391 391 1_stuff False 2021-07-16 + 392 392 2_stuff False 2021-07-17 + 393 393 3_stuff False 2021-07-18 + 394 394 4_stuff False 2021-07-19 + 395 395 5_stuff False 2021-07-20 + 396 396 1_stuff False 2021-07-21 + 397 397 2_stuff False 2021-07-22 + 398 398 3_stuff False 2021-07-23 + 399 399 4_stuff True 2021-07-24 + 400 400 5_stuff True 2021-07-25 + 401 401 1_stuff True 2021-07-06 + 402 402 2_stuff False 2021-07-07 + 403 403 3_stuff False 2021-07-08 + 404 404 4_stuff False 2021-07-09 + 405 405 5_stuff False 2021-07-10 + 406 406 1_stuff False 2021-07-11 + 407 407 2_stuff False 2021-07-12 + 408 408 3_stuff False 2021-07-13 + 409 409 4_stuff False 2021-07-14 + 410 410 5_stuff False 2021-07-15 + 411 411 1_stuff False 2021-07-16 + 412 412 2_stuff False 2021-07-17 + 413 413 3_stuff False 2021-07-18 + 414 414 4_stuff False 2021-07-19 + 415 415 5_stuff False 2021-07-20 + 416 416 1_stuff False 2021-07-21 + 417 417 2_stuff False 2021-07-22 + 418 418 3_stuff False 2021-07-23 + 419 419 4_stuff False 2021-07-24 + 420 420 5_stuff True 2021-07-25 + 421 421 1_stuff True 2021-07-06 + 422 422 2_stuff True 2021-07-07 + 423 423 3_stuff False 2021-07-08 + 424 424 4_stuff False 2021-07-09 + 425 425 5_stuff False 2021-07-10 + 426 426 1_stuff False 2021-07-11 + 427 427 2_stuff False 2021-07-12 + 428 428 3_stuff False 2021-07-13 + 429 429 4_stuff False 2021-07-14 + 430 430 5_stuff False 2021-07-15 + 431 431 1_stuff False 2021-07-16 + 432 432 2_stuff False 2021-07-17 + 433 433 3_stuff False 2021-07-18 + 434 434 4_stuff False 2021-07-19 + 435 435 5_stuff False 2021-07-20 + 436 436 1_stuff False 2021-07-21 + 437 437 2_stuff False 2021-07-22 + 438 438 3_stuff False 2021-07-23 + 439 439 4_stuff False 2021-07-24 + 440 440 5_stuff False 2021-07-25 + 441 441 1_stuff True 2021-07-06 + 442 442 2_stuff True 2021-07-07 + 443 443 3_stuff True 2021-07-08 + 444 444 4_stuff False 2021-07-09 + 445 445 5_stuff False 2021-07-10 + 446 446 1_stuff False 2021-07-11 + 447 447 2_stuff False 2021-07-12 + 448 448 3_stuff False 2021-07-13 + 449 449 4_stuff False 2021-07-14 + 450 450 5_stuff False 2021-07-15 + 451 451 1_stuff False 2021-07-16 + 452 452 2_stuff False 2021-07-17 + 453 453 3_stuff False 2021-07-18 + 454 454 4_stuff False 2021-07-19 + 455 455 5_stuff False 2021-07-20 + 456 456 1_stuff False 2021-07-21 + 457 457 2_stuff False 2021-07-22 + 458 458 3_stuff False 2021-07-23 + 459 459 4_stuff False 2021-07-24 + 460 460 5_stuff False 2021-07-25 + 461 461 1_stuff False 2021-07-06 + 462 462 2_stuff True 2021-07-07 + 463 463 3_stuff True 2021-07-08 + 464 464 4_stuff True 2021-07-09 + 465 465 5_stuff False 2021-07-10 + 466 466 1_stuff False 2021-07-11 + 467 467 2_stuff False 2021-07-12 + 468 468 3_stuff False 2021-07-13 + 469 469 4_stuff False 2021-07-14 + 470 470 5_stuff False 2021-07-15 + 471 471 1_stuff False 2021-07-16 + 472 472 2_stuff False 2021-07-17 + 473 473 3_stuff False 2021-07-18 + 474 474 4_stuff False 2021-07-19 + 475 475 5_stuff False 2021-07-20 + 476 476 1_stuff False 2021-07-21 + 477 477 2_stuff False 2021-07-22 + 478 478 3_stuff False 2021-07-23 + 479 479 4_stuff False 2021-07-24 + 480 480 5_stuff False 2021-07-25 + 481 481 1_stuff False 2021-07-06 + 482 482 2_stuff False 2021-07-07 + 483 483 3_stuff True 2021-07-08 + 484 484 4_stuff True 2021-07-09 + 485 485 5_stuff True 2021-07-10 + 486 486 1_stuff False 2021-07-11 + 487 487 2_stuff False 2021-07-12 + 488 488 3_stuff False 2021-07-13 + 489 489 4_stuff False 2021-07-14 + 490 490 5_stuff False 2021-07-15 + 491 491 1_stuff False 2021-07-16 + 492 492 2_stuff False 2021-07-17 + 493 493 3_stuff False 2021-07-18 + 494 494 4_stuff False 2021-07-19 + 495 495 5_stuff False 2021-07-20 + 496 496 1_stuff False 2021-07-21 + 497 497 2_stuff False 2021-07-22 + 498 498 3_stuff False 2021-07-23 + 499 499 4_stuff False 2021-07-24 + 500 500 5_stuff False 2021-07-25 + Children + 1 0 + 2 1 + 3 0 + 4 1 + 5 0 + 6 1 + 7 0 + 8 1 + 9 0 + 10 1 + 11 0 + 12 1 + 13 0 + 14 1 + 15 0 + 16 1 + 17 0 + 18 1 + 19 0 + 20 1 + 21 0 + 22 1 + 23 0 + 24 1 + 25 0 + 26 1 + 27 0 + 28 1 + 29 0 + 30 1 + 31 0 + 32 1 + 33 0 + 34 1 + 35 0 + 36 1 + 37 0 + 38 1 + 39 0 + 40 1 + 41 0 + 42 1 + 43 0 + 44 1 + 45 0 + 46 1 + 47 0 + 48 1 + 49 0 + 50 1 + 51 0 + 52 1 + 53 0 + 54 1 + 55 0 + 56 1 + 57 0 + 58 1 + 59 0 + 60 1 + 61 0 + 62 1 + 63 0 + 64 1 + 65 0 + 66 1 + 67 0 + 68 1 + 69 0 + 70 1 + 71 0 + 72 1 + 73 0 + 74 1 + 75 0 + 76 1 + 77 0 + 78 1 + 79 0 + 80 1 + 81 0 + 82 1 + 83 0 + 84 1 + 85 0 + 86 1 + 87 0 + 88 1 + 89 0 + 90 1 + 91 0 + 92 1 + 93 0 + 94 1 + 95 0 + 96 1 + 97 0 + 98 1 + 99 0 + 100 1 + 101 0 + 102 1 + 103 0 + 104 1 + 105 0 + 106 1 + 107 0 + 108 1 + 109 0 + 110 1 + 111 0 + 112 1 + 113 0 + 114 1 + 115 0 + 116 1 + 117 0 + 118 1 + 119 0 + 120 1 + 121 0 + 122 1 + 123 0 + 124 1 + 125 0 + 126 1 + 127 0 + 128 1 + 129 0 + 130 1 + 131 0 + 132 1 + 133 0 + 134 1 + 135 0 + 136 1 + 137 0 + 138 1 + 139 0 + 140 1 + 141 0 + 142 1 + 143 0 + 144 1 + 145 0 + 146 1 + 147 0 + 148 1 + 149 0 + 150 1 + 151 0 + 152 1 + 153 0 + 154 1 + 155 0 + 156 1 + 157 0 + 158 1 + 159 0 + 160 1 + 161 0 + 162 1 + 163 0 + 164 1 + 165 0 + 166 1 + 167 0 + 168 1 + 169 0 + 170 1 + 171 0 + 172 1 + 173 0 + 174 1 + 175 0 + 176 1 + 177 0 + 178 1 + 179 0 + 180 1 + 181 0 + 182 1 + 183 0 + 184 1 + 185 0 + 186 1 + 187 0 + 188 1 + 189 0 + 190 1 + 191 0 + 192 1 + 193 0 + 194 1 + 195 0 + 196 1 + 197 0 + 198 1 + 199 0 + 200 1 + 201 0 + 202 1 + 203 0 + 204 1 + 205 0 + 206 1 + 207 0 + 208 1 + 209 0 + 210 1 + 211 0 + 212 1 + 213 0 + 214 1 + 215 0 + 216 1 + 217 0 + 218 1 + 219 0 + 220 1 + 221 0 + 222 1 + 223 0 + 224 1 + 225 0 + 226 1 + 227 0 + 228 1 + 229 0 + 230 1 + 231 0 + 232 1 + 233 0 + 234 1 + 235 0 + 236 1 + 237 0 + 238 1 + 239 0 + 240 1 + 241 0 + 242 1 + 243 0 + 244 1 + 245 0 + 246 1 + 247 0 + 248 1 + 249 0 + 250 1 + 251 0 + 252 1 + 253 0 + 254 1 + 255 0 + 256 1 + 257 0 + 258 1 + 259 0 + 260 1 + 261 0 + 262 1 + 263 0 + 264 1 + 265 0 + 266 1 + 267 0 + 268 1 + 269 0 + 270 1 + 271 0 + 272 1 + 273 0 + 274 1 + 275 0 + 276 1 + 277 0 + 278 1 + 279 0 + 280 1 + 281 0 + 282 1 + 283 0 + 284 1 + 285 0 + 286 1 + 287 0 + 288 1 + 289 0 + 290 1 + 291 0 + 292 1 + 293 0 + 294 1 + 295 0 + 296 1 + 297 0 + 298 1 + 299 0 + 300 1 + 301 0 + 302 1 + 303 0 + 304 1 + 305 0 + 306 1 + 307 0 + 308 1 + 309 0 + 310 1 + 311 0 + 312 1 + 313 0 + 314 1 + 315 0 + 316 1 + 317 0 + 318 1 + 319 0 + 320 1 + 321 0 + 322 1 + 323 0 + 324 1 + 325 0 + 326 1 + 327 0 + 328 1 + 329 0 + 330 1 + 331 0 + 332 1 + 333 0 + 334 1 + 335 0 + 336 1 + 337 0 + 338 1 + 339 0 + 340 1 + 341 0 + 342 1 + 343 0 + 344 1 + 345 0 + 346 1 + 347 0 + 348 1 + 349 0 + 350 1 + 351 0 + 352 1 + 353 0 + 354 1 + 355 0 + 356 1 + 357 0 + 358 1 + 359 0 + 360 1 + 361 0 + 362 1 + 363 0 + 364 1 + 365 0 + 366 1 + 367 0 + 368 1 + 369 0 + 370 1 + 371 0 + 372 1 + 373 0 + 374 1 + 375 0 + 376 1 + 377 0 + 378 1 + 379 0 + 380 1 + 381 0 + 382 1 + 383 0 + 384 1 + 385 0 + 386 1 + 387 0 + 388 1 + 389 0 + 390 1 + 391 0 + 392 1 + 393 0 + 394 1 + 395 0 + 396 1 + 397 0 + 398 1 + 399 0 + 400 1 + 401 0 + 402 1 + 403 0 + 404 1 + 405 0 + 406 1 + 407 0 + 408 1 + 409 0 + 410 1 + 411 0 + 412 1 + 413 0 + 414 1 + 415 0 + 416 1 + 417 0 + 418 1 + 419 0 + 420 1 + 421 0 + 422 1 + 423 0 + 424 1 + 425 0 + 426 1 + 427 0 + 428 1 + 429 0 + 430 1 + 431 0 + 432 1 + 433 0 + 434 1 + 435 0 + 436 1 + 437 0 + 438 1 + 439 0 + 440 1 + 441 0 + 442 1 + 443 0 + 444 1 + 445 0 + 446 1 + 447 0 + 448 1 + 449 0 + 450 1 + 451 0 + 452 1 + 453 0 + 454 1 + 455 0 + 456 1 + 457 0 + 458 1 + 459 0 + 460 1 + 461 0 + 462 1 + 463 0 + 464 1 + 465 0 + 466 1 + 467 0 + 468 1 + 469 0 + 470 1 + 471 0 + 472 1 + 473 0 + 474 1 + 475 0 + 476 1 + 477 0 + 478 1 + 479 0 + 480 1 + 481 0 + 482 1 + 483 0 + 484 1 + 485 0 + 486 1 + 487 0 + 488 1 + 489 0 + 490 1 + 491 0 + 492 1 + 493 0 + 494 1 + 495 0 + 496 1 + 497 0 + 498 1 + 499 0 + 500 1 + +--- + + Code + rcrdsMinDf + Output + Identifier number A single select column A logical column A date column + 1 1 1_stuff True 2021-07-06 + 2 2 2_stuff True 2021-07-07 + 3 3 3_stuff False 2021-07-08 + 4 4 4_stuff False 2021-07-09 + 5 5 5_stuff False 2021-07-10 + 6 6 1_stuff False 2021-07-11 + 7 7 2_stuff False 2021-07-12 + 8 8 3_stuff False 2021-07-13 + 9 9 4_stuff False 2021-07-14 + 10 10 5_stuff False 2021-07-15 + 11 11 1_stuff False 2021-07-16 + 12 12 2_stuff False 2021-07-17 + 13 13 3_stuff False 2021-07-18 + 14 14 4_stuff False 2021-07-19 + 15 15 5_stuff False 2021-07-20 + 16 16 1_stuff False 2021-07-21 + 17 17 2_stuff False 2021-07-22 + 18 18 3_stuff False 2021-07-23 + 19 19 4_stuff False 2021-07-24 + 20 20 5_stuff False 2021-07-25 + 21 21 1_stuff True 2021-07-06 + 22 22 2_stuff True 2021-07-07 + 23 23 3_stuff True 2021-07-08 + 24 24 4_stuff False 2021-07-09 + 25 25 5_stuff False 2021-07-10 + 26 26 1_stuff False 2021-07-11 + 27 27 2_stuff False 2021-07-12 + 28 28 3_stuff False 2021-07-13 + 29 29 4_stuff False 2021-07-14 + 30 30 5_stuff False 2021-07-15 + 31 31 1_stuff False 2021-07-16 + 32 32 2_stuff False 2021-07-17 + 33 33 3_stuff False 2021-07-18 + 34 34 4_stuff False 2021-07-19 + 35 35 5_stuff False 2021-07-20 + 36 36 1_stuff False 2021-07-21 + 37 37 2_stuff False 2021-07-22 + 38 38 3_stuff False 2021-07-23 + 39 39 4_stuff False 2021-07-24 + 40 40 5_stuff False 2021-07-25 + 41 41 1_stuff False 2021-07-06 + 42 42 2_stuff True 2021-07-07 + 43 43 3_stuff True 2021-07-08 + 44 44 4_stuff True 2021-07-09 + 45 45 5_stuff False 2021-07-10 + 46 46 1_stuff False 2021-07-11 + 47 47 2_stuff False 2021-07-12 + 48 48 3_stuff False 2021-07-13 + 49 49 4_stuff False 2021-07-14 + 50 50 5_stuff False 2021-07-15 + 51 51 1_stuff False 2021-07-16 + 52 52 2_stuff False 2021-07-17 + 53 53 3_stuff False 2021-07-18 + 54 54 4_stuff False 2021-07-19 + 55 55 5_stuff False 2021-07-20 + 56 56 1_stuff False 2021-07-21 + 57 57 2_stuff False 2021-07-22 + 58 58 3_stuff False 2021-07-23 + 59 59 4_stuff False 2021-07-24 + 60 60 5_stuff False 2021-07-25 + 61 61 1_stuff False 2021-07-06 + 62 62 2_stuff False 2021-07-07 + 63 63 3_stuff True 2021-07-08 + 64 64 4_stuff True 2021-07-09 + 65 65 5_stuff True 2021-07-10 + 66 66 1_stuff False 2021-07-11 + 67 67 2_stuff False 2021-07-12 + 68 68 3_stuff False 2021-07-13 + 69 69 4_stuff False 2021-07-14 + 70 70 5_stuff False 2021-07-15 + 71 71 1_stuff False 2021-07-16 + 72 72 2_stuff False 2021-07-17 + 73 73 3_stuff False 2021-07-18 + 74 74 4_stuff False 2021-07-19 + 75 75 5_stuff False 2021-07-20 + 76 76 1_stuff False 2021-07-21 + 77 77 2_stuff False 2021-07-22 + 78 78 3_stuff False 2021-07-23 + 79 79 4_stuff False 2021-07-24 + 80 80 5_stuff False 2021-07-25 + 81 81 1_stuff False 2021-07-06 + 82 82 2_stuff False 2021-07-07 + 83 83 3_stuff False 2021-07-08 + 84 84 4_stuff True 2021-07-09 + 85 85 5_stuff True 2021-07-10 + 86 86 1_stuff True 2021-07-11 + 87 87 2_stuff False 2021-07-12 + 88 88 3_stuff False 2021-07-13 + 89 89 4_stuff False 2021-07-14 + 90 90 5_stuff False 2021-07-15 + 91 91 1_stuff False 2021-07-16 + 92 92 2_stuff False 2021-07-17 + 93 93 3_stuff False 2021-07-18 + 94 94 4_stuff False 2021-07-19 + 95 95 5_stuff False 2021-07-20 + 96 96 1_stuff False 2021-07-21 + 97 97 2_stuff False 2021-07-22 + 98 98 3_stuff False 2021-07-23 + 99 99 4_stuff False 2021-07-24 + 100 100 5_stuff False 2021-07-25 + 101 101 1_stuff False 2021-07-06 + 102 102 2_stuff False 2021-07-07 + 103 103 3_stuff False 2021-07-08 + 104 104 4_stuff False 2021-07-09 + 105 105 5_stuff True 2021-07-10 + 106 106 1_stuff True 2021-07-11 + 107 107 2_stuff True 2021-07-12 + 108 108 3_stuff False 2021-07-13 + 109 109 4_stuff False 2021-07-14 + 110 110 5_stuff False 2021-07-15 + 111 111 1_stuff False 2021-07-16 + 112 112 2_stuff False 2021-07-17 + 113 113 3_stuff False 2021-07-18 + 114 114 4_stuff False 2021-07-19 + 115 115 5_stuff False 2021-07-20 + 116 116 1_stuff False 2021-07-21 + 117 117 2_stuff False 2021-07-22 + 118 118 3_stuff False 2021-07-23 + 119 119 4_stuff False 2021-07-24 + 120 120 5_stuff False 2021-07-25 + 121 121 1_stuff False 2021-07-06 + 122 122 2_stuff False 2021-07-07 + 123 123 3_stuff False 2021-07-08 + 124 124 4_stuff False 2021-07-09 + 125 125 5_stuff False 2021-07-10 + 126 126 1_stuff True 2021-07-11 + 127 127 2_stuff True 2021-07-12 + 128 128 3_stuff True 2021-07-13 + 129 129 4_stuff False 2021-07-14 + 130 130 5_stuff False 2021-07-15 + 131 131 1_stuff False 2021-07-16 + 132 132 2_stuff False 2021-07-17 + 133 133 3_stuff False 2021-07-18 + 134 134 4_stuff False 2021-07-19 + 135 135 5_stuff False 2021-07-20 + 136 136 1_stuff False 2021-07-21 + 137 137 2_stuff False 2021-07-22 + 138 138 3_stuff False 2021-07-23 + 139 139 4_stuff False 2021-07-24 + 140 140 5_stuff False 2021-07-25 + 141 141 1_stuff False 2021-07-06 + 142 142 2_stuff False 2021-07-07 + 143 143 3_stuff False 2021-07-08 + 144 144 4_stuff False 2021-07-09 + 145 145 5_stuff False 2021-07-10 + 146 146 1_stuff False 2021-07-11 + 147 147 2_stuff True 2021-07-12 + 148 148 3_stuff True 2021-07-13 + 149 149 4_stuff True 2021-07-14 + 150 150 5_stuff False 2021-07-15 + 151 151 1_stuff False 2021-07-16 + 152 152 2_stuff False 2021-07-17 + 153 153 3_stuff False 2021-07-18 + 154 154 4_stuff False 2021-07-19 + 155 155 5_stuff False 2021-07-20 + 156 156 1_stuff False 2021-07-21 + 157 157 2_stuff False 2021-07-22 + 158 158 3_stuff False 2021-07-23 + 159 159 4_stuff False 2021-07-24 + 160 160 5_stuff False 2021-07-25 + 161 161 1_stuff False 2021-07-06 + 162 162 2_stuff False 2021-07-07 + 163 163 3_stuff False 2021-07-08 + 164 164 4_stuff False 2021-07-09 + 165 165 5_stuff False 2021-07-10 + 166 166 1_stuff False 2021-07-11 + 167 167 2_stuff False 2021-07-12 + 168 168 3_stuff True 2021-07-13 + 169 169 4_stuff True 2021-07-14 + 170 170 5_stuff True 2021-07-15 + 171 171 1_stuff False 2021-07-16 + 172 172 2_stuff False 2021-07-17 + 173 173 3_stuff False 2021-07-18 + 174 174 4_stuff False 2021-07-19 + 175 175 5_stuff False 2021-07-20 + 176 176 1_stuff False 2021-07-21 + 177 177 2_stuff False 2021-07-22 + 178 178 3_stuff False 2021-07-23 + 179 179 4_stuff False 2021-07-24 + 180 180 5_stuff False 2021-07-25 + 181 181 1_stuff False 2021-07-06 + 182 182 2_stuff False 2021-07-07 + 183 183 3_stuff False 2021-07-08 + 184 184 4_stuff False 2021-07-09 + 185 185 5_stuff False 2021-07-10 + 186 186 1_stuff False 2021-07-11 + 187 187 2_stuff False 2021-07-12 + 188 188 3_stuff False 2021-07-13 + 189 189 4_stuff True 2021-07-14 + 190 190 5_stuff True 2021-07-15 + 191 191 1_stuff True 2021-07-16 + 192 192 2_stuff False 2021-07-17 + 193 193 3_stuff False 2021-07-18 + 194 194 4_stuff False 2021-07-19 + 195 195 5_stuff False 2021-07-20 + 196 196 1_stuff False 2021-07-21 + 197 197 2_stuff False 2021-07-22 + 198 198 3_stuff False 2021-07-23 + 199 199 4_stuff False 2021-07-24 + 200 200 5_stuff False 2021-07-25 + 201 201 1_stuff False 2021-07-06 + 202 202 2_stuff False 2021-07-07 + 203 203 3_stuff False 2021-07-08 + 204 204 4_stuff False 2021-07-09 + 205 205 5_stuff False 2021-07-10 + 206 206 1_stuff False 2021-07-11 + 207 207 2_stuff False 2021-07-12 + 208 208 3_stuff False 2021-07-13 + 209 209 4_stuff False 2021-07-14 + 210 210 5_stuff True 2021-07-15 + 211 211 1_stuff True 2021-07-16 + 212 212 2_stuff True 2021-07-17 + 213 213 3_stuff False 2021-07-18 + 214 214 4_stuff False 2021-07-19 + 215 215 5_stuff False 2021-07-20 + 216 216 1_stuff False 2021-07-21 + 217 217 2_stuff False 2021-07-22 + 218 218 3_stuff False 2021-07-23 + 219 219 4_stuff False 2021-07-24 + 220 220 5_stuff False 2021-07-25 + 221 221 1_stuff False 2021-07-06 + 222 222 2_stuff False 2021-07-07 + 223 223 3_stuff False 2021-07-08 + 224 224 4_stuff False 2021-07-09 + 225 225 5_stuff False 2021-07-10 + 226 226 1_stuff False 2021-07-11 + 227 227 2_stuff False 2021-07-12 + 228 228 3_stuff False 2021-07-13 + 229 229 4_stuff False 2021-07-14 + 230 230 5_stuff False 2021-07-15 + 231 231 1_stuff True 2021-07-16 + 232 232 2_stuff True 2021-07-17 + 233 233 3_stuff True 2021-07-18 + 234 234 4_stuff False 2021-07-19 + 235 235 5_stuff False 2021-07-20 + 236 236 1_stuff False 2021-07-21 + 237 237 2_stuff False 2021-07-22 + 238 238 3_stuff False 2021-07-23 + 239 239 4_stuff False 2021-07-24 + 240 240 5_stuff False 2021-07-25 + 241 241 1_stuff False 2021-07-06 + 242 242 2_stuff False 2021-07-07 + 243 243 3_stuff False 2021-07-08 + 244 244 4_stuff False 2021-07-09 + 245 245 5_stuff False 2021-07-10 + 246 246 1_stuff False 2021-07-11 + 247 247 2_stuff False 2021-07-12 + 248 248 3_stuff False 2021-07-13 + 249 249 4_stuff False 2021-07-14 + 250 250 5_stuff False 2021-07-15 + 251 251 1_stuff False 2021-07-16 + 252 252 2_stuff True 2021-07-17 + 253 253 3_stuff True 2021-07-18 + 254 254 4_stuff True 2021-07-19 + 255 255 5_stuff False 2021-07-20 + 256 256 1_stuff False 2021-07-21 + 257 257 2_stuff False 2021-07-22 + 258 258 3_stuff False 2021-07-23 + 259 259 4_stuff False 2021-07-24 + 260 260 5_stuff False 2021-07-25 + 261 261 1_stuff False 2021-07-06 + 262 262 2_stuff False 2021-07-07 + 263 263 3_stuff False 2021-07-08 + 264 264 4_stuff False 2021-07-09 + 265 265 5_stuff False 2021-07-10 + 266 266 1_stuff False 2021-07-11 + 267 267 2_stuff False 2021-07-12 + 268 268 3_stuff False 2021-07-13 + 269 269 4_stuff False 2021-07-14 + 270 270 5_stuff False 2021-07-15 + 271 271 1_stuff False 2021-07-16 + 272 272 2_stuff False 2021-07-17 + 273 273 3_stuff True 2021-07-18 + 274 274 4_stuff True 2021-07-19 + 275 275 5_stuff True 2021-07-20 + 276 276 1_stuff False 2021-07-21 + 277 277 2_stuff False 2021-07-22 + 278 278 3_stuff False 2021-07-23 + 279 279 4_stuff False 2021-07-24 + 280 280 5_stuff False 2021-07-25 + 281 281 1_stuff False 2021-07-06 + 282 282 2_stuff False 2021-07-07 + 283 283 3_stuff False 2021-07-08 + 284 284 4_stuff False 2021-07-09 + 285 285 5_stuff False 2021-07-10 + 286 286 1_stuff False 2021-07-11 + 287 287 2_stuff False 2021-07-12 + 288 288 3_stuff False 2021-07-13 + 289 289 4_stuff False 2021-07-14 + 290 290 5_stuff False 2021-07-15 + 291 291 1_stuff False 2021-07-16 + 292 292 2_stuff False 2021-07-17 + 293 293 3_stuff False 2021-07-18 + 294 294 4_stuff True 2021-07-19 + 295 295 5_stuff True 2021-07-20 + 296 296 1_stuff True 2021-07-21 + 297 297 2_stuff False 2021-07-22 + 298 298 3_stuff False 2021-07-23 + 299 299 4_stuff False 2021-07-24 + 300 300 5_stuff False 2021-07-25 + 301 301 1_stuff False 2021-07-06 + 302 302 2_stuff False 2021-07-07 + 303 303 3_stuff False 2021-07-08 + 304 304 4_stuff False 2021-07-09 + 305 305 5_stuff False 2021-07-10 + 306 306 1_stuff False 2021-07-11 + 307 307 2_stuff False 2021-07-12 + 308 308 3_stuff False 2021-07-13 + 309 309 4_stuff False 2021-07-14 + 310 310 5_stuff False 2021-07-15 + 311 311 1_stuff False 2021-07-16 + 312 312 2_stuff False 2021-07-17 + 313 313 3_stuff False 2021-07-18 + 314 314 4_stuff False 2021-07-19 + 315 315 5_stuff True 2021-07-20 + 316 316 1_stuff True 2021-07-21 + 317 317 2_stuff True 2021-07-22 + 318 318 3_stuff False 2021-07-23 + 319 319 4_stuff False 2021-07-24 + 320 320 5_stuff False 2021-07-25 + 321 321 1_stuff False 2021-07-06 + 322 322 2_stuff False 2021-07-07 + 323 323 3_stuff False 2021-07-08 + 324 324 4_stuff False 2021-07-09 + 325 325 5_stuff False 2021-07-10 + 326 326 1_stuff False 2021-07-11 + 327 327 2_stuff False 2021-07-12 + 328 328 3_stuff False 2021-07-13 + 329 329 4_stuff False 2021-07-14 + 330 330 5_stuff False 2021-07-15 + 331 331 1_stuff False 2021-07-16 + 332 332 2_stuff False 2021-07-17 + 333 333 3_stuff False 2021-07-18 + 334 334 4_stuff False 2021-07-19 + 335 335 5_stuff False 2021-07-20 + 336 336 1_stuff True 2021-07-21 + 337 337 2_stuff True 2021-07-22 + 338 338 3_stuff True 2021-07-23 + 339 339 4_stuff False 2021-07-24 + 340 340 5_stuff False 2021-07-25 + 341 341 1_stuff False 2021-07-06 + 342 342 2_stuff False 2021-07-07 + 343 343 3_stuff False 2021-07-08 + 344 344 4_stuff False 2021-07-09 + 345 345 5_stuff False 2021-07-10 + 346 346 1_stuff False 2021-07-11 + 347 347 2_stuff False 2021-07-12 + 348 348 3_stuff False 2021-07-13 + 349 349 4_stuff False 2021-07-14 + 350 350 5_stuff False 2021-07-15 + 351 351 1_stuff False 2021-07-16 + 352 352 2_stuff False 2021-07-17 + 353 353 3_stuff False 2021-07-18 + 354 354 4_stuff False 2021-07-19 + 355 355 5_stuff False 2021-07-20 + 356 356 1_stuff False 2021-07-21 + 357 357 2_stuff True 2021-07-22 + 358 358 3_stuff True 2021-07-23 + 359 359 4_stuff True 2021-07-24 + 360 360 5_stuff False 2021-07-25 + 361 361 1_stuff False 2021-07-06 + 362 362 2_stuff False 2021-07-07 + 363 363 3_stuff False 2021-07-08 + 364 364 4_stuff False 2021-07-09 + 365 365 5_stuff False 2021-07-10 + 366 366 1_stuff False 2021-07-11 + 367 367 2_stuff False 2021-07-12 + 368 368 3_stuff False 2021-07-13 + 369 369 4_stuff False 2021-07-14 + 370 370 5_stuff False 2021-07-15 + 371 371 1_stuff False 2021-07-16 + 372 372 2_stuff False 2021-07-17 + 373 373 3_stuff False 2021-07-18 + 374 374 4_stuff False 2021-07-19 + 375 375 5_stuff False 2021-07-20 + 376 376 1_stuff False 2021-07-21 + 377 377 2_stuff False 2021-07-22 + 378 378 3_stuff True 2021-07-23 + 379 379 4_stuff True 2021-07-24 + 380 380 5_stuff True 2021-07-25 + 381 381 1_stuff False 2021-07-06 + 382 382 2_stuff False 2021-07-07 + 383 383 3_stuff False 2021-07-08 + 384 384 4_stuff False 2021-07-09 + 385 385 5_stuff False 2021-07-10 + 386 386 1_stuff False 2021-07-11 + 387 387 2_stuff False 2021-07-12 + 388 388 3_stuff False 2021-07-13 + 389 389 4_stuff False 2021-07-14 + 390 390 5_stuff False 2021-07-15 + 391 391 1_stuff False 2021-07-16 + 392 392 2_stuff False 2021-07-17 + 393 393 3_stuff False 2021-07-18 + 394 394 4_stuff False 2021-07-19 + 395 395 5_stuff False 2021-07-20 + 396 396 1_stuff False 2021-07-21 + 397 397 2_stuff False 2021-07-22 + 398 398 3_stuff False 2021-07-23 + 399 399 4_stuff True 2021-07-24 + 400 400 5_stuff True 2021-07-25 + 401 401 1_stuff True 2021-07-06 + 402 402 2_stuff False 2021-07-07 + 403 403 3_stuff False 2021-07-08 + 404 404 4_stuff False 2021-07-09 + 405 405 5_stuff False 2021-07-10 + 406 406 1_stuff False 2021-07-11 + 407 407 2_stuff False 2021-07-12 + 408 408 3_stuff False 2021-07-13 + 409 409 4_stuff False 2021-07-14 + 410 410 5_stuff False 2021-07-15 + 411 411 1_stuff False 2021-07-16 + 412 412 2_stuff False 2021-07-17 + 413 413 3_stuff False 2021-07-18 + 414 414 4_stuff False 2021-07-19 + 415 415 5_stuff False 2021-07-20 + 416 416 1_stuff False 2021-07-21 + 417 417 2_stuff False 2021-07-22 + 418 418 3_stuff False 2021-07-23 + 419 419 4_stuff False 2021-07-24 + 420 420 5_stuff True 2021-07-25 + 421 421 1_stuff True 2021-07-06 + 422 422 2_stuff True 2021-07-07 + 423 423 3_stuff False 2021-07-08 + 424 424 4_stuff False 2021-07-09 + 425 425 5_stuff False 2021-07-10 + 426 426 1_stuff False 2021-07-11 + 427 427 2_stuff False 2021-07-12 + 428 428 3_stuff False 2021-07-13 + 429 429 4_stuff False 2021-07-14 + 430 430 5_stuff False 2021-07-15 + 431 431 1_stuff False 2021-07-16 + 432 432 2_stuff False 2021-07-17 + 433 433 3_stuff False 2021-07-18 + 434 434 4_stuff False 2021-07-19 + 435 435 5_stuff False 2021-07-20 + 436 436 1_stuff False 2021-07-21 + 437 437 2_stuff False 2021-07-22 + 438 438 3_stuff False 2021-07-23 + 439 439 4_stuff False 2021-07-24 + 440 440 5_stuff False 2021-07-25 + 441 441 1_stuff True 2021-07-06 + 442 442 2_stuff True 2021-07-07 + 443 443 3_stuff True 2021-07-08 + 444 444 4_stuff False 2021-07-09 + 445 445 5_stuff False 2021-07-10 + 446 446 1_stuff False 2021-07-11 + 447 447 2_stuff False 2021-07-12 + 448 448 3_stuff False 2021-07-13 + 449 449 4_stuff False 2021-07-14 + 450 450 5_stuff False 2021-07-15 + 451 451 1_stuff False 2021-07-16 + 452 452 2_stuff False 2021-07-17 + 453 453 3_stuff False 2021-07-18 + 454 454 4_stuff False 2021-07-19 + 455 455 5_stuff False 2021-07-20 + 456 456 1_stuff False 2021-07-21 + 457 457 2_stuff False 2021-07-22 + 458 458 3_stuff False 2021-07-23 + 459 459 4_stuff False 2021-07-24 + 460 460 5_stuff False 2021-07-25 + 461 461 1_stuff False 2021-07-06 + 462 462 2_stuff True 2021-07-07 + 463 463 3_stuff True 2021-07-08 + 464 464 4_stuff True 2021-07-09 + 465 465 5_stuff False 2021-07-10 + 466 466 1_stuff False 2021-07-11 + 467 467 2_stuff False 2021-07-12 + 468 468 3_stuff False 2021-07-13 + 469 469 4_stuff False 2021-07-14 + 470 470 5_stuff False 2021-07-15 + 471 471 1_stuff False 2021-07-16 + 472 472 2_stuff False 2021-07-17 + 473 473 3_stuff False 2021-07-18 + 474 474 4_stuff False 2021-07-19 + 475 475 5_stuff False 2021-07-20 + 476 476 1_stuff False 2021-07-21 + 477 477 2_stuff False 2021-07-22 + 478 478 3_stuff False 2021-07-23 + 479 479 4_stuff False 2021-07-24 + 480 480 5_stuff False 2021-07-25 + 481 481 1_stuff False 2021-07-06 + 482 482 2_stuff False 2021-07-07 + 483 483 3_stuff True 2021-07-08 + 484 484 4_stuff True 2021-07-09 + 485 485 5_stuff True 2021-07-10 + 486 486 1_stuff False 2021-07-11 + 487 487 2_stuff False 2021-07-12 + 488 488 3_stuff False 2021-07-13 + 489 489 4_stuff False 2021-07-14 + 490 490 5_stuff False 2021-07-15 + 491 491 1_stuff False 2021-07-16 + 492 492 2_stuff False 2021-07-17 + 493 493 3_stuff False 2021-07-18 + 494 494 4_stuff False 2021-07-19 + 495 495 5_stuff False 2021-07-20 + 496 496 1_stuff False 2021-07-21 + 497 497 2_stuff False 2021-07-22 + 498 498 3_stuff False 2021-07-23 + 499 499 4_stuff False 2021-07-24 + 500 500 5_stuff False 2021-07-25 + Children + 1 0 + 2 1 + 3 0 + 4 1 + 5 0 + 6 1 + 7 0 + 8 1 + 9 0 + 10 1 + 11 0 + 12 1 + 13 0 + 14 1 + 15 0 + 16 1 + 17 0 + 18 1 + 19 0 + 20 1 + 21 0 + 22 1 + 23 0 + 24 1 + 25 0 + 26 1 + 27 0 + 28 1 + 29 0 + 30 1 + 31 0 + 32 1 + 33 0 + 34 1 + 35 0 + 36 1 + 37 0 + 38 1 + 39 0 + 40 1 + 41 0 + 42 1 + 43 0 + 44 1 + 45 0 + 46 1 + 47 0 + 48 1 + 49 0 + 50 1 + 51 0 + 52 1 + 53 0 + 54 1 + 55 0 + 56 1 + 57 0 + 58 1 + 59 0 + 60 1 + 61 0 + 62 1 + 63 0 + 64 1 + 65 0 + 66 1 + 67 0 + 68 1 + 69 0 + 70 1 + 71 0 + 72 1 + 73 0 + 74 1 + 75 0 + 76 1 + 77 0 + 78 1 + 79 0 + 80 1 + 81 0 + 82 1 + 83 0 + 84 1 + 85 0 + 86 1 + 87 0 + 88 1 + 89 0 + 90 1 + 91 0 + 92 1 + 93 0 + 94 1 + 95 0 + 96 1 + 97 0 + 98 1 + 99 0 + 100 1 + 101 0 + 102 1 + 103 0 + 104 1 + 105 0 + 106 1 + 107 0 + 108 1 + 109 0 + 110 1 + 111 0 + 112 1 + 113 0 + 114 1 + 115 0 + 116 1 + 117 0 + 118 1 + 119 0 + 120 1 + 121 0 + 122 1 + 123 0 + 124 1 + 125 0 + 126 1 + 127 0 + 128 1 + 129 0 + 130 1 + 131 0 + 132 1 + 133 0 + 134 1 + 135 0 + 136 1 + 137 0 + 138 1 + 139 0 + 140 1 + 141 0 + 142 1 + 143 0 + 144 1 + 145 0 + 146 1 + 147 0 + 148 1 + 149 0 + 150 1 + 151 0 + 152 1 + 153 0 + 154 1 + 155 0 + 156 1 + 157 0 + 158 1 + 159 0 + 160 1 + 161 0 + 162 1 + 163 0 + 164 1 + 165 0 + 166 1 + 167 0 + 168 1 + 169 0 + 170 1 + 171 0 + 172 1 + 173 0 + 174 1 + 175 0 + 176 1 + 177 0 + 178 1 + 179 0 + 180 1 + 181 0 + 182 1 + 183 0 + 184 1 + 185 0 + 186 1 + 187 0 + 188 1 + 189 0 + 190 1 + 191 0 + 192 1 + 193 0 + 194 1 + 195 0 + 196 1 + 197 0 + 198 1 + 199 0 + 200 1 + 201 0 + 202 1 + 203 0 + 204 1 + 205 0 + 206 1 + 207 0 + 208 1 + 209 0 + 210 1 + 211 0 + 212 1 + 213 0 + 214 1 + 215 0 + 216 1 + 217 0 + 218 1 + 219 0 + 220 1 + 221 0 + 222 1 + 223 0 + 224 1 + 225 0 + 226 1 + 227 0 + 228 1 + 229 0 + 230 1 + 231 0 + 232 1 + 233 0 + 234 1 + 235 0 + 236 1 + 237 0 + 238 1 + 239 0 + 240 1 + 241 0 + 242 1 + 243 0 + 244 1 + 245 0 + 246 1 + 247 0 + 248 1 + 249 0 + 250 1 + 251 0 + 252 1 + 253 0 + 254 1 + 255 0 + 256 1 + 257 0 + 258 1 + 259 0 + 260 1 + 261 0 + 262 1 + 263 0 + 264 1 + 265 0 + 266 1 + 267 0 + 268 1 + 269 0 + 270 1 + 271 0 + 272 1 + 273 0 + 274 1 + 275 0 + 276 1 + 277 0 + 278 1 + 279 0 + 280 1 + 281 0 + 282 1 + 283 0 + 284 1 + 285 0 + 286 1 + 287 0 + 288 1 + 289 0 + 290 1 + 291 0 + 292 1 + 293 0 + 294 1 + 295 0 + 296 1 + 297 0 + 298 1 + 299 0 + 300 1 + 301 0 + 302 1 + 303 0 + 304 1 + 305 0 + 306 1 + 307 0 + 308 1 + 309 0 + 310 1 + 311 0 + 312 1 + 313 0 + 314 1 + 315 0 + 316 1 + 317 0 + 318 1 + 319 0 + 320 1 + 321 0 + 322 1 + 323 0 + 324 1 + 325 0 + 326 1 + 327 0 + 328 1 + 329 0 + 330 1 + 331 0 + 332 1 + 333 0 + 334 1 + 335 0 + 336 1 + 337 0 + 338 1 + 339 0 + 340 1 + 341 0 + 342 1 + 343 0 + 344 1 + 345 0 + 346 1 + 347 0 + 348 1 + 349 0 + 350 1 + 351 0 + 352 1 + 353 0 + 354 1 + 355 0 + 356 1 + 357 0 + 358 1 + 359 0 + 360 1 + 361 0 + 362 1 + 363 0 + 364 1 + 365 0 + 366 1 + 367 0 + 368 1 + 369 0 + 370 1 + 371 0 + 372 1 + 373 0 + 374 1 + 375 0 + 376 1 + 377 0 + 378 1 + 379 0 + 380 1 + 381 0 + 382 1 + 383 0 + 384 1 + 385 0 + 386 1 + 387 0 + 388 1 + 389 0 + 390 1 + 391 0 + 392 1 + 393 0 + 394 1 + 395 0 + 396 1 + 397 0 + 398 1 + 399 0 + 400 1 + 401 0 + 402 1 + 403 0 + 404 1 + 405 0 + 406 1 + 407 0 + 408 1 + 409 0 + 410 1 + 411 0 + 412 1 + 413 0 + 414 1 + 415 0 + 416 1 + 417 0 + 418 1 + 419 0 + 420 1 + 421 0 + 422 1 + 423 0 + 424 1 + 425 0 + 426 1 + 427 0 + 428 1 + 429 0 + 430 1 + 431 0 + 432 1 + 433 0 + 434 1 + 435 0 + 436 1 + 437 0 + 438 1 + 439 0 + 440 1 + 441 0 + 442 1 + 443 0 + 444 1 + 445 0 + 446 1 + 447 0 + 448 1 + 449 0 + 450 1 + 451 0 + 452 1 + 453 0 + 454 1 + 455 0 + 456 1 + 457 0 + 458 1 + 459 0 + 460 1 + 461 0 + 462 1 + 463 0 + 464 1 + 465 0 + 466 1 + 467 0 + 468 1 + 469 0 + 470 1 + 471 0 + 472 1 + 473 0 + 474 1 + 475 0 + 476 1 + 477 0 + 478 1 + 479 0 + 480 1 + 481 0 + 482 1 + 483 0 + 484 1 + 485 0 + 486 1 + 487 0 + 488 1 + 489 0 + 490 1 + 491 0 + 492 1 + 493 0 + 494 1 + 495 0 + 496 1 + 497 0 + 498 1 + 499 0 + 500 1 + # Reference field with shallow reference table should provide field based names Code diff --git a/tests/testthat/setup.R b/tests/testthat/setup.R index 2f574c5..a3f0660 100644 --- a/tests/testthat/setup.R +++ b/tests/testthat/setup.R @@ -59,19 +59,21 @@ canonicalizeActivityInfoObject <- function(tree, replaceId = TRUE, replaceDate = x[n] <- lapply(x[n], function(y) { if (is.recursive(y)) { # y - list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")) + rep(list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")), length(y)) } else if (is.list(y)) { # yReturn <- list(rep("", length(y))) # names(yReturn) <- names(y) - list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")) + rep(list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")), length(y)) } else { # rep("", length(y)) - list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")) + rep(list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")), length(y)) } }) n <- grepl(pattern = "resources", names(x)) & lengths(x) == 1 - x[n] <- list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")) + if (sum(n)>0) { + x[n] <- list(list(id = "", note = "Empty resources until we can ensure a sort order in the API.")) + } } @@ -107,7 +109,7 @@ compare_recursively <- function(a, b, path = list()) { if (!identical(a,b)) { message(sprintf("Field with name/key '%s' value has changed", paste(path, collapse="'->'"))) } - expect_identical(object = b, expected = a) + testthat::expect_identical(object = b, expected = a) } else if (is.list(a) && is.list(b)) { additionalFields <- names(b)[!names(b) %in% names(a)] if (length(additionalFields)>0) { @@ -122,7 +124,7 @@ compare_recursively <- function(a, b, path = list()) { } } else { message(sprintf("Incompatible structures under name/key '%s'", paste(path, collapse="'->'"))) - expect_identical(object = b, expected = a) + testthat::expect_identical(object = b, expected = a) } } @@ -135,7 +137,7 @@ identicalForm <- function(a,b, b_allowed_new_fields = TRUE) { if (b_allowed_new_fields) { compare_recursively(a, b) } else { - expect_identical(object = b, expected = a) + testthat::expect_identical(object = b, expected = a) } } @@ -158,7 +160,7 @@ expectActivityInfoSnapshotCompare <- function(x, snapshotName, replaceId = TRUE, if (allowed_new_fields) { compare_recursively(y, x) } else { - expect_identical(object = x, expected = y) + testthat::expect_identical(object = x, expected = y) } } diff --git a/tests/testthat/test-databases.R b/tests/testthat/test-databases.R index 5771622..86c3553 100644 --- a/tests/testthat/test-databases.R +++ b/tests/testthat/test-databases.R @@ -363,8 +363,8 @@ testthat::test_that("deleteRole() works", { testthat::test_that("updateRole() works for both legacy and new roles", { - roleId = "rp" - roleLabel = "Reporting partner" + roleId <- "rp" + roleLabel <- "Reporting partner" # create a partner reference form partnerForm <- formSchema( @@ -508,6 +508,23 @@ testthat::test_that("updateRole() works for both legacy and new roles", { }) +testthat::test_that("getDatabaseRoles() works", { + dbTree = getDatabaseTree(database$databaseId) + dbId = dbTree$databaseId + + result1 = getDatabaseRoles(dbId) + result2 = getDatabaseRoles(dbTree) + + testthat::expect_identical(result1, result2) + + testthat::expect_identical( + result1 |> filter(id == "rp") |> nrow(), + 1L + ) + + expectActivityInfoSnapshotCompare(result1, snapshotName = "databases-getDatabaseRoles", allowed_new_fields = TRUE) +}) + testthat::test_that("getDatabaseRole() works", { dbTree = getDatabaseTree(database$databaseId) dbId = dbTree$databaseId diff --git a/tests/testthat/test-setup.R b/tests/testthat/test-setup.R new file mode 100644 index 0000000..32e85e5 --- /dev/null +++ b/tests/testthat/test-setup.R @@ -0,0 +1,4 @@ +testthat::test_that("Snapshots work with data frames", { + expectActivityInfoSnapshotCompare(data.frame(x = 1:26), snapshotName = "setup-dataframe-snapshot", allowed_new_fields = TRUE) + expectActivityInfoSnapshotCompare(data.frame(x = 1:26, y = letters), snapshotName = "setup-dataframe-snapshot", allowed_new_fields = TRUE) +}) diff --git a/vignettes/introduction.Rmd b/vignettes/introduction.Rmd index d03734d..dd24cd6 100644 --- a/vignettes/introduction.Rmd +++ b/vignettes/introduction.Rmd @@ -66,7 +66,10 @@ activityInfoToken("2d6016c9cb3be78954eb396f806a20e9") After the authentication process has been successfully completed, a prompt will ask if the token file named *`r basename(activityinfo:::credentialsFile)`* should be written to your home directory. Only write the file to your home directory if this is secure. Don't share or publish this token file and preserve securely as it contains your personal access token. -Before start working on the API calls, do not forget to restart your *R* session to have changes applied. +If you would like to avoid prompts, set `prompt = FALSE` to explicitly do this, including for Shiny server apps or non-interactive scripts. +```{r, eval=FALSE} +activityInfoToken("2d6016c9cb3be78954eb396f806a20e9", prompt = FALSE) +``` ## Databases @@ -75,8 +78,8 @@ You can list the databases that you own or have been shared with you using as a `data.frame`: ```{r, eval=FALSE} -dblist <- getDatabases() -head(dblist) +dbList <- getDatabases() +head(dbList) ``` diff --git a/vignettes/working-with-grant-based-roles.Rmd b/vignettes/working-with-grant-based-roles.Rmd new file mode 100644 index 0000000..925790e --- /dev/null +++ b/vignettes/working-with-grant-based-roles.Rmd @@ -0,0 +1,712 @@ +--- +title: "Working with grant-based roles" +author: "BeDataDriven B.V." +date: "`r Sys.Date()`" +layout: docArticle +category: tutorials +order: 0 +output: + rmarkdown::html_vignette: + toc: true + toc_depth: 2 +relatedVideos: +- /support/webinars/2023-10-19-office-hour-designing-roles +- /support/webinars/2023-10-12-best-practices-for-designing-roles +vignette: > + %\VignetteIndexEntry{Working with grant-based roles} + %\VignetteEngine{knitr::rmarkdown} + %\VignetteEncoding{UTF-8} +--- + +```{r, include = FALSE} +knitr::opts_chunk$set( + collapse = TRUE, + comment = "#>") +``` + +```{r setup} +library(activityinfo) +library(dplyr) +library(tidyr) +library(purrr) +``` + +## Introduction to user roles + +This tutorial will provide a quick introduction on how to work with [grant-based roles and permissions](https://www.activityinfo.org/support/docs/permissions/explanation.html) using the [*ActivityInfo R Package*](https://www.activityinfo.org/support/docs/R/index.html) and secure your database and ensure user's can access exactly what they need. + +Note: in order to fully follow this tutorial you must have an ActivityInfo user account or a trial account with the permission to add a new database. Setup a free trial here: + +### Exploring permissions, grants and operations + +Each database has a single database owner. This user can view and change all elements of the database. If you do not invite any users, or make any forms public, than the database owner is the only person who can access a database. + +To add a new database for this tutorial use `addDatabase(label)`. + +```{r eval=FALSE, include=TRUE} + +newDb <- addDatabase("A new ActivityInfo tutorial database!") +``` + +The database tree contains all the information we need about our database, including the owner information. + +```{r eval=FALSE, include=TRUE} + +# fetch database information from the server +dbTree <- getDatabaseTree(databaseId = newDb$databaseId) + +as_tibble(dbTree$ownerRef) + +``` + +\# A tibble: 1 × 3 + +| id | name | email | +|------------------|---------------------------|---------------------------| +| | | | +| 123456789 | [me\@example.com](mailto:me@example.com){.email} | [me\@example.com](mailto:me@example.com){.email} | + +A rapid way to take a look at the default system roles is with the `getDatabaseRoles(database)` function that can take a database tree or database id as its parameter. + +```{r eval=FALSE, include=TRUE} + +# extract roles from the tree as a data frame +roles <- getDatabaseRoles(dbTree) + +roles +``` + +\# A tibble: 3 × 8 + +| id | label | permissions | parameters | filters | grants | version | grantBased | +|---------|---------|---------|---------|---------|---------|---------|---------| +| | | | | | | | | +| dataentry | Data Entry | \ | \ | \ | \ | 0 | TRUE | +| readonly | Read only | \ | \ | \ | \ | 0 | TRUE | +| admin | Administrator | \ | \ | \ | \ | 0 | TRUE | + +#### Administrative permissions + +In this table, special database-level permissions are provided in the "permissions" columns and the Administrator has all three administrative permissions: + +- manage users, + +- manage roles, and + +- manage automations. + +A list of all administrative permissions can be expanded for users with those roles by using the tidyverse \`tidyr\` package. + +```{r eval=FALSE, include=TRUE} +library(tidyr) + +roles |> + tidyr::unnest_longer(permissions) |> + tidyr::unnest_wider(permissions) |> + select(id, label, operation) + +``` + +\# A tibble: 3 × 3 + +| id | label | operation | +|-------|---------------|--------------------| +| | | | +| admin | Administrator | MANAGE_USERS | +| admin | Administrator | MANAGE_ROLES | +| admin | Administrator | MANAGE_AUTOMATIONS | + +#### Grants + +Since 2024, all new roles are grant-based. Grants define the operations that are allowed for a specific resource. A resource can be any of the following: + +- a database, + +- a folder, or + +- a form. + +One can list all resources that are granted by expanding the grants column as follows. + +```{r eval=FALSE, include=TRUE} + +roles |> + select(id, label, grants) |> + tidyr::unnest_longer(grants) |> + tidyr::unnest_wider(grants) + +``` + +\# A tibble: 3 × 5 + +| id | label | resourceId | optional | operations | +|-----------|---------------|------------------|----------|---------------| +| | | | | | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | FALSE | \ | +| readonly | Read only | c2mhs8mm3632xsb2 | FALSE | \ | +| admin | Administrator | c2mhs8mm3632xsb2 | FALSE | \ | + +In this case, all three roles have a single resource: the entire database. Each grant defines the resource in the "resourceId" column and a list with a number of "operations" allowed on the database. + +#### Operations + +To expand the grants and see the specific operations available for each resource within the role, do the following: + +```{r eval=FALSE, include=TRUE} + +roles |> + select(id, label, grants) |> + # expand grants + tidyr::unnest_longer(grants) |> + tidyr::unnest_wider(grants) |> + # expand operations + tidyr::unnest_longer(operations) |> + tidyr::unnest_wider(operations) |> + + select(id, label, resourceId, operation) + +``` + +\# A tibble: 24 × 4 + +| id | label | resourceId | operation | +|-----------|------------|------------------|---------------| +| | | | | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | VIEW | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | DISCOVER | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | EDIT_RECORD | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | ADD_RECORD | +| dataentry | Data Entry | c2mhs8mm3632xsb2 | DELETE_RECORD | +| ... | ... | ... | ... | + +#### Retrieve and examine a single role + +A single role can be retrieved from a database by id using `getDatabaseRole(database, roleId)` where the database can be the database tree or the database id. + +```{r eval=FALSE, include=TRUE} + +# extract roles in a dataframe +readOnlyRole <- getDatabaseRoles(dbTree, "readonly") + +str(readOnlyRole) + +``` + +``` +List of 8 + $ id : chr "readonly" + $ label : chr "Read only" + $ permissions: list() + $ parameters : list() + $ filters : list() + $ grants :List of 1 + ..$ :List of 3 + .. ..$ resourceId: chr "c2mhs8mm3632xsb2" + .. ..$ optional : logi FALSE + .. ..$ operations:List of 2 + .. .. ..$ :List of 3 + .. .. .. ..$ operation : chr "VIEW" + .. .. .. ..$ filter : NULL + .. .. .. ..$ securityCategories: list() + .. .. ..$ :List of 3 + .. .. .. ..$ operation : chr "DISCOVER" + .. .. .. ..$ filter : NULL + .. .. .. ..$ securityCategories: list() + $ version : int 0 + $ grantBased : logi TRUE +``` + +#### Inspect database user roles + +We can also inspect which roles have been assigned to each user. This will not list the database owner. + +```{r eval=FALSE, include=TRUE} + +dbUserRoles <- getDatabaseUsers(dbTree$databaseId) |> unnest_wider(role, names_sep = "_") + +``` + +## Creating roles + +There are a number of helper functions that make the creation of user roles straightforward. + +There are few important principles: - The role object is created and then added to the database - There are separate functions for adding a new role `addRole()` and for updating an existing role `updateRole()` - When granting access to a resource, a resource id must be provided. Resource ids are not validated during role creation. + +### Resource-level access + +A simple role that gives resource-level access can be quickly created using a few helper functions. + +This first example role grants access to a single data entry form. It does so by defining a grant and the operations available to edit and view a form. The operations are defined in the `resourcePermissions()` function. + +```{r eval=FALSE, include=TRUE} + +dataEntryFormId <- "c12fsdkjla89" # replace with id of a form in your database + +# create a role definition +dataEntryNoDeleteRole <- role( + id = "entrynodelete", + label = "Data entry without delete", + grants = + list( + grant( + resourceId = dataEntryFormId, + permissions = resourcePermissions( + view = TRUE, + add_record = TRUE, + edit_record = TRUE, + delete_record = FALSE, + export_records = TRUE), + optional = FALSE + ) + ) + ) + +# upload role to our database +addRole(dbTree$databaseId, dataEntryNoDeleteRole) + +``` + +This second role is an admin with access to the entire database but without the "automation" permission. + +```{r eval=FALSE, include=TRUE} + +# create a role definition +adminRoleNoAutomation <- role( + id = "adminnoautomation", + label = "Admin without automation", + grants = + list( + grant( + resourceId = dbTree$databaseId, + permissions = resourcePermissions( + view = TRUE, + add_record = TRUE, + edit_record = TRUE, + delete_record = TRUE, + export_records = TRUE, + lock_records = TRUE, + add_resource = TRUE, + edit_resource = TRUE, + delete_resource = TRUE, + bulk_delete = TRUE, + manage_collection_links = TRUE, + manage_users = TRUE, + manage_roles = TRUE, + manage_reference_data = TRUE, + manage_translations = TRUE, + audit = TRUE, + share_reports = TRUE, + publish_reports = TRUE, + reviewer_only = TRUE, + discover = TRUE), + optional = FALSE + ) + ), + permissions = + databasePermissions( + manage_automations = FALSE, + manage_users = TRUE, + manage_roles = TRUE + ) + ) + + +# upload role to our database +addRole(dbTree$databaseId, adminRoleNoAutomation) + +``` + +We can modify our role so that we can optionally give access to another form. To modify a role, we need to change the definition and then use the `updateRole()` function to change the role in our database. + +```{r eval=FALSE, include=TRUE} + +optionalFormId <- "c45fadgfla72" # the optional grant we will add + +# create a role definition +dataEntryNoDeleteRole <- role( + id = "entrynodelete", + label = "Data entry without delete", + grants = + list( + grant( + resourceId = dataEntryFormId, + permissions = resourcePermissions( + view = TRUE, + add_record = TRUE, + edit_record = TRUE, + delete_record = FALSE, + export_records = TRUE), + optional = FALSE + ), + grant( + resourceId = optionalFormId, + permissions = resourcePermissions( + view = TRUE, + add_record = TRUE, + edit_record = TRUE, + delete_record = FALSE, + export_records = TRUE), + optional = TRUE + ) + ) + ) + +# upload role to our database +updateRole(dbTree$databaseId, dataEntryNoDeleteRole) + +``` + +See the documentation for `role()`, `grant()`, `resourcePermissions()` for the full description of these functions. + +### Record-level access control using parameters + +Sometimes, it is necessary to be able to specify exactly which operations are available on a row by row basis. For example, a reporting partner may be able to use a form to submit reports and they may also have the ability to view and edit their own reports. However, they should not be able to view or edit reports of other partners. + +#### Create partner form and reporting forms + +We will create a list all reporting partners that we will use to assign a partner to users. We will also create a form for submitting reports. + +```{r eval=FALSE, include=TRUE} + +## create a partner reference form +partnerForm <- formSchema( + databaseId = dbTree$databaseId, + label = "Reporting Partners") |> + addFormField( + textFieldSchema( + code = "name", + label = "Partner name", + required = TRUE)) + +# upload form +addForm(partnerForm) + +# create data frame with the name column for partners +partnerTbl <- tibble(name = c("Partner A", "Partner B", "Partner C")) + +# import partner records into the form table +importRecords(partnerForm$id, data = partnerTbl) + +``` + +We can examine the form records with `getRecords(partnerForm)`: + +``` +# Form (id): Reporting Partners (cktgbvem37c2xvj3) +# Total form records: 3 +# Table fields types: Text +# Table filter: NULL +# Table sort: NULL +# Table Window: No offset or limit + `_id` `_lastEditTime` `Partner name` + +1 cx51safm37c3f5icog 1730985535 Partner A +2 crv6qs1m37c3f5icoh 1730985535 Partner B +3 c6lra77m37c3f5icoi 1730985535 Partner C +``` + +Next we create our reporting form. + +```{r eval=FALSE, include=TRUE} +# create a reporting table with a reporting partner field +reportingForm <- formSchema( + databaseId = dbTree$databaseId, + label = "Partner reports") |> + addFormField( + referenceFieldSchema( + referencedFormId = partnerForm$id, + code = "rp", + label = "Partner", + required = TRUE)) |> + addFormField( + textFieldSchema( + label = "Report", + required = TRUE)) + +addForm(reportingForm) + +# get list of partner ids +partnerTbl <- getRecords(partnerForm) |> + collect() + +partnerIds <- partnerTbl[["_id"]] +partnerReports <- paste0("This is a report from ", partnerTbl[["Partner name"]], ".") + +# create a reports table +reportingTbl <- tibble( + Partner = partnerIds, + Report = partnerReports + ) + +# import reports +importRecords(reportingForm$id, data = reportingTbl) + +``` + +We can examine the form table using `getRecords(reportingForm)`: + +``` +# Form (id): Partner reports (cl0g9vkm37c3llt5) +# Total form records: 3 +# Table fields types: c(Partner = "Reference", Report = "Text") +# Table filter: NULL +# Table sort: NULL +# Table Window: No offset or limit + `_id` `_lastEditTime` Partner Report + +1 ctnab0jm37ccyvj20s 1730985980 cx51safm37c3f5icog This is a report from Partner A. +2 copxctim37ccyvj20t 1730985980 crv6qs1m37c3f5icoh This is a report from Partner B. +3 cclowium37ccyvj20u 1730985980 c6lra77m37c3f5icoi This is a report from Partner C. +``` + +#### Defining a reporting partner role + +Using ActivityInfo formulas, we can specify row-level access rules. We assign each user a `partner` parameter in the role definition in order to do this. This parameter will be available in formulas using `@user.partner` + +```{r eval=FALSE, include=TRUE} + +partnerParameter <- parameter(id = "partner", label = "Partner", range = partnerForm$id) + +``` + +We can then add this parameter to a "Reporting Partner" role that has access to view the partner form so that they can choose their own organization when creating a report. + +Additionally, they have row-level access to the reporting form for submitting and editing only their own reports. Optionally, they can be provided full access to the partner form and manage the full list of partners. + +```{r eval=FALSE, include=TRUE} +roleId <- "rp" +roleLabel <- "Reporting Partner" + +partnerParameter <- parameter(id = "partner", label = "Partner", range = partnerForm$id) + +# view entire database +databaseGrant <- grant(resourceId = dbTree$databaseId, + permissions = resourcePermissions( + view = TRUE, + edit_record = FALSE + )) + +# record level access: edit only records where @user.partner equals the partner form id +reportingFormGrant = grant(resourceId = reportingForm$id, + permissions = resourcePermissions( + view = sprintf("%s == @user.partner", partnerForm$id), + edit_record = sprintf("%s == @user.partner", partnerForm$id), + discover = TRUE, + export_records = TRUE)) + +# optionally grant the user the right to edit records (but not delete records) in the partner table +partnerFormGrant = grant(resourceId = partnerForm$id, + permissions = resourcePermissions( + view = TRUE, + discover = TRUE, + edit_record = TRUE), + optional = TRUE) + +# define the partner role +reportingPartnerRole <- + role( + id = roleId, + label = roleLabel, + parameters = list( + partnerParameter + ), + grants = list( + databaseGrant, + reportingFormGrant, + partnerFormGrant + ) + ) + +# upload role to our database +addRole(dbTree$databaseId, reportingPartnerRole) + +``` + +#### Assigning users a reporting partner role + +We will assign a new users to the database and give them the reporting partner roles: + +- **User A**: Assign to Reporting Partner A so they can view and edit Partner A reports only + +- **User B**: Assign to Reporting Partner B so they can view and edit Partner B reports only. In addition, we will provide editing rights for the *Reporting Partner* table via the optional grant. + +```{r eval=FALSE, include=TRUE} + + +# fetch the partner id in order to set the parameter for each user role +partnerAId <- partnerTbl |> filter(`Partner name` == "Partner A") |> pull(`_id`) +partnerBId <- partnerTbl |> filter(`Partner name` == "Partner B") |> pull(`_id`) + +# user A has access to Reporting Partner A reports only +userA <- addDatabaseUser( + databaseId = dbTree$databaseId, + email = "user.a@example.com", + name = "User A", + locale = "en", + roleId = "rp", + roleParameters = list(partner = partnerAId), + ) + +# user B has access to Partner B reports only and has been provided the optional access to the partnerForm using the partner form id and the roleResources parameter +userB <- addDatabaseUser( + databaseId = dbTree$databaseId, + email = "user.b@example.com", + name = "User B", + locale = "en", + roleId = "rp", + roleParameters = list(partner = partnerBId), + roleResources = list(partnerForm$id) + ) + +``` + +## Add users and assign roles + +```{r eval=FALSE, include=TRUE} + +addDatabaseUser( + databaseId = dbTree$databaseId, + name = "Tutorial database user", + email = "activityinfo1@example.com", + locale = "en", + roleId = "readonly" + ) +addDatabaseUser( + databaseId = dbTree$databaseId, + name = "Tutorial database user", + email = "activityinfo2@example.com", + locale = "en", + roleId = "dataentry" + ) + +# Retrieve a list of all the database users +dbUsers <- getDatabaseUsers(databaseId = newDb$databaseId) + +dbUsers + +``` + +## List roles across all databases + +It is also possible to compile a list of all roles across all databases at once. + +```{r eval=FALSE, include=TRUE} + +# get a dataframe of all the databases +databases <- getDatabases() + +# combine all the roles into a single table +allRoles <- + lapply( + 1:nrow(databases), + function(x) { + getDatabaseRoles(databases[x,]$databaseId) |> + mutate( + databaseId = databases[x,]$databaseId, + databaseLabel = databases[x,]$label + ) + } + ) |> + bind_rows() + +``` + +Using the tidyverse `purrr` package, this can be shortened further. + +```{r eval=FALSE, include=TRUE} +library(purrr) + +allRoles <- purrr::pmap_dfr(databases, function(databaseId, label, ...) { + getDatabaseRoles(databaseId) |> + dplyr::mutate( + databaseId = databaseId, + databaseLabel = label + ) +}) + +``` + +## List all users and their roles across all databases + +```{r eval=FALSE, include=TRUE} + +# get a list of database ids +dbIds = getDatabases()[["databaseId"]] + +# define a function to try to get database users as a data frame and otherwise fail gracefully by returning NULL +getUsersGracefully <- function(databaseId) { + try( + return(getDatabaseUsers(databaseId)) + ) + return(NULL) +} + +allUsers <- lapply(dbIds, getUsersGracefully) |> + bind_rows() |> + as_tibble() + + +allUserRoles <- allUsers |> unnest_wider(role, names_sep = "_") + +``` + +One can further filter all reporting partners across all databases assuming the reporting partner role has the same id in each database. + +```{r eval=FALSE, include=TRUE} + +# filter all users by the role id +allUserRoles |> filter(role_id == "rp") + +``` + +Additionally, one can filter all users by some specific operation, such as *EDIT_RECORD*. In order to do this, it is also important to use the `allRoles` data frame we defined in the previous section. + +```{r eval=FALSE, include=TRUE} + +# filter all users by the role id +userRoleDefinitions <- + allUserRoles |> + left_join(allRoles, by = c("databaseId", role_id = "id")) |> + select(databaseId, userId, name, role_id, grants) + +# expand grants and operations per resource +userOperations <- userRoleDefinitions |> + select(databaseId, userId, name, role_id, grants) |> + # expand grants + tidyr::unnest_longer(grants) |> + tidyr::unnest_wider(grants) |> + # expand operations + tidyr::unnest_longer(operations) |> + tidyr::unnest_wider(operations) + +# show users who have the EDIT_RECORD operation and on which resources +userOperations %>% filter(operation == "EDIT_RECORD") + +``` + +## Find users with legacy roles + +It is important to update all roles so that users with legacy roles have grant-based roles from 2025 when legacy roles will be phased out. It is simple to find all users with legacy roles by doing the following with the `allUserRoles` and `allRoles` variables defined in the previous two sections. + +```{r eval=FALSE, include=TRUE} + +usersWithLegacyRoles <- + allUserRoles |> + left_join(allRoles, by = c("databaseId", role_id = "id")) |> + select(databaseId, userId, name, role_id, grants, grantBased) |> + filter(grantBased == FALSE) + +``` + +## List all forms and database resources + +It is possible to access the list of all the database resources with the function `getDatabaseResources(databaseTree)`. + +```{r eval=FALSE, include=TRUE} + +# Retrieve a list of all the resources (forms, sub-forms) +dbResources <- getDatabaseResources(databaseTree = dbTree) + +dbResources + +```