Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: filter users by account_status, user_role #335

Merged
merged 16 commits into from
Nov 21, 2024
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# connectapi (development version)

## New features

- `get_users()` now supports filtering users with the `account_status` and
`user_role` parameters. For example, this allows you to find all licensed
users on a Connect server. (#311)

# connectapi 0.4.0

## New features
Expand Down
20 changes: 18 additions & 2 deletions R/connect.R
Original file line number Diff line number Diff line change
Expand Up @@ -506,12 +506,28 @@ Connect <- R6::R6Class(
#' @param page_number The page number.
#' @param prefix The search term.
#' @param page_size The page size.
users = function(page_number = 1, prefix = NULL, page_size = 500) {
#' @param user_role Filter by user role.
#' @param account_status Filter by account status.
users = function(
page_number = 1,
prefix = NULL,
page_size = 500,
user_role = NULL,
account_status = NULL
) {
path <- v1_url("users")
if (!is.null(user_role)) {
user_role <- paste(user_role, collapse = "|")
}
if (!is.null(account_status)) {
account_status <- paste(account_status, collapse = "|")
}
query <- list(
page_number = page_number,
page_size = valid_page_size(page_size),
prefix = prefix
prefix = prefix,
user_role = user_role,
account_status = account_status
)
self$GET(path, query = query)
},
Expand Down
33 changes: 29 additions & 4 deletions R/get.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
#' @param prefix Filters users by prefix (username, first name, or last name).
#' The filter is case insensitive.
#' @param limit The max number of records to return
#' @param user_role Optionally filter by user role ("administrator",
#' "publisher", "viewer"). Pass in a vector of multiple roles to match any value
#' (boolean OR). When `NULL` (the default), results are not filtered.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#' (boolean OR). When `NULL` (the default), results are not filtered.
#' (boolean OR). When `NULL` (the default), all results are returned.

Feel free to take or leave this suggestion, but is this true? That not filtered == all results? I believe yes for sure for user_role, but not sure about account_status are locked/inactive accounts included if you don't specify anything?

As for the suggestion of "all results" vs "results not filtered" either is fine, the first seems slightly more approachable to me, but I don't super strongly hold that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this is true! The default for account_status is to not filter by status when nothing's passed in — this was my understanding from reading the API docs, and I just confirmed it experimentally (by filtering for all types, which uses boolean OR, and comparing that to unfiltered results).

I agree that the current wording isn't a paragon of accessibility, but to my ear the suggested change sounds almost tautological, in the sense that "result" reads to me as meaning "the set of accounts returned by the function/API call. I wonder if this sentence is even needed — to me, the "Optionally" at the start of the parameter definition says everything that this sentence is trying to say (i.e. this is an optional parameter, and if you don't pass anything, the filtering operation described doesn't happen).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I can see arguments for both ways. I do think we should mention the default, it's not good to not say what the default is. Like I said, feel free to reject the suggestion — maybe leaning heavily on filter and what that means is enough here.

#' @param account_status Optionally filter by account status ("locked",
#' "licensed", "inactive"). Pass a vector of multiple statuses to match any
#' value (boolean OR). When `NULL` (the default), results are not filtered.

#'
#' @return
#' A tibble with the following columns:
Expand Down Expand Up @@ -34,17 +41,35 @@
#' library(connectapi)
#' client <- connect()
#'
#' # get all users
#' get_users(client, limit = Inf)
#' # Get all users
#' get_users(client)
#'
#' # Get all licensed users
#' get_users(client, account_status = "licensed")
#'
#' # Get all users who are administrators or publishers
#' get_users(client, user_role = c("administrator", "publisher"))
#' }
#'
#' @export
get_users <- function(src, page_size = 500, prefix = NULL, limit = Inf) {
get_users <- function(
src,
page_size = 500,
prefix = NULL,
limit = Inf,
user_role = NULL,
account_status = NULL
) {
validate_R6_class(src, "Connect")

res <- page_offset(
src,
src$users(page_size = page_size, prefix = prefix),
src$users(
page_size = page_size,
prefix = prefix,
user_role = user_role,
account_status = account_status
),
limit = limit
)

Expand Down
12 changes: 11 additions & 1 deletion man/PositConnect.Rd

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

27 changes: 24 additions & 3 deletions man/get_users.Rd

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

8 changes: 8 additions & 0 deletions tests/integrated/test-get.R
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ test_that("get_users works", {
purrr::map_chr(vctrs::vec_ptype(users), typeof),
purrr::map_chr(vctrs::vec_ptype(connectapi_ptypes$users), typeof)
)

# Other tests create users, so specifying the exact number here is conditional
# on the contents of other tests and the order that tests run in.
admins <- get_users(test_conn_1, user_role = "administrator")
expect_true(nrow(admins) > 0)

licensed <- get_users(test_conn_1, account_status = "licensed")
expect_true(nrow(licensed) > 0)
})

test_that("get_groups works", {
Expand Down
30 changes: 30 additions & 0 deletions tests/testthat/test-get.R
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,33 @@ with_mock_api({
expect_identical(result$name, c("connect_dev", "cool_kids_of_the_dmv"))
})
})

without_internet({
client <- Connect$new(server = "https://connect.example", api_key = "fake")
test_that("get_users() works with user_role and account_status", {
# No filter parameters specified
expect_GET(
get_users(client),
"https://connect.example/__api__/v1/users?page_number=1&page_size=500"
)

# Filter just on one parameter
expect_GET(
get_users(client, user_role = "administrator"),
"https://connect.example/__api__/v1/users?page_number=1&page_size=500&user_role=administrator"
)
toph-allen marked this conversation as resolved.
Show resolved Hide resolved

# Filter on two parameters, one requiring concatenation
expect_GET(
get_users(
client,
user_role = c("administrator", "publisher"),
account_status = "licensed"
),
paste0(
"https://connect.example/__api__/v1/users?page_number=1&page_size=500&",
"user_role=administrator%7Cpublisher&account_status=licensed"
)
)
})
})
Loading