New public API for working with paged result sets. Replaces the private methods 'get_paged_list' and 'init_list_objects'; these are retained for backward compatibility but deprecated.
This commit is contained in:
Hong Ooi 2021-04-24 00:56:05 +10:00 коммит произвёл GitHub
Родитель 17f6546e43
Коммит 38bec8bb15
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
28 изменённых файлов: 807 добавлений и 124 удалений

Просмотреть файл

@ -12,6 +12,8 @@ export(call_graph_endpoint)
export(call_graph_url)
export(create_graph_login)
export(delete_graph_login)
export(extract_list_values)
export(find_class_generator)
export(format_public_fields)
export(format_public_methods)
export(get_graph_login)
@ -26,6 +28,7 @@ export(is_service_principal)
export(is_user)
export(list_graph_logins)
export(ms_graph)
export(ms_graph_pager)
export(ms_object)
export(named_list)
export(register_graph_class)

Просмотреть файл

@ -1,7 +1,16 @@
# AzureGraph 1.2.2.9000
- New API for working with paged result sets:
- New `ms_graph_pager` R6 class, which is an _iterator_ for the pages in the result.
- The `ms_object` base class now has a `get_list_pager()` method which returns an object of class `ms_graph_pager`.
- New `extract_list_values()` function to get all or part of the results from a paged result set.
- The current (private) `ms_object$get_paged_list()` and `ms_object$init_list_objects()` methods are retained for backward compatibility, but are otherwise deprecated.
- The `ms_graph$get_user()` method can now get a user by email or display name.
- Fix a bug in retrieving a paged list of values as a data frame, when `n` (the maximum number of rows) is supplied.
- New `ms_graph$get_aad_object()` method to retrieve an Azure Active Directory object by ID. Mostly intended for use with the `list_object_memberships()` and `list_group_memberships()` methods, which return only IDs and not full object information.
- All `list_*` methods now have an `n` argument to set a cap on the number of results; the default value is `n=Inf`. If this is set to NULL, the `ms_graph_pager` iterator object is returned instead to allow manual iteration over the results.
- Export the `find_class_generator()` function.
- New "Batching and paging" vignette describing these APIs.
# AzureGraph 1.2.2

Просмотреть файл

@ -15,9 +15,7 @@
#' - `update(...)`: Update the app data in Azure Active Directory. For what properties can be updated, consult the REST API documentation link below.
#' - `do_operation(...)`: Carry out an arbitrary operation on the app.
#' - `sync_fields()`: Synchronise the R object with the app data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this app is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this app is a member of.
#' - `list_owners(type=c("user", "group", "application", "servicePrincipal"))`: Return a list of all owners of this app. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_owners(type=c("user", "group", "application", "servicePrincipal"), n=Inf)`: Return a list of all owners of this app. Specify the `type` argument to filter the result for specific object type(s). `n` is the number of results to return; set this to NULL to return the `ms_graph_pager` iterator object for the result set.
#' - `create_service_principal(...)`: Create a service principal for this app, by default in the current tenant.
#' - `get_service_principal()`: Get the service principal for this app.
#' - `delete_service_principal(confirm=TRUE)`: Delete the service principal for this app. By default, ask for confirmation first.
@ -159,10 +157,10 @@ public=list(
self$update(keyCredentials=creds[-idx])
},
list_owners=function(type=c("user", "group", "application", "servicePrincipal"))
list_owners=function(type=c("user", "group", "application", "servicePrincipal"), n=Inf)
{
res <- private$get_paged_list(self$do_operation("owners"))
private$init_list_objects(res, type)
pager <- self$get_list_pager(self$do_operation("owners"), type_filter=type)
extract_list_values(pager, n)
},
create_service_principal=function(...)

Просмотреть файл

@ -14,8 +14,6 @@
#' - `update(...)`: Update the device information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the device.
#' - `sync_fields()`: Synchronise the R object with the app data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this device is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this device is a member of.
#'
#' @section Initialization:
#' Create objects of this class via the `list_registered_devices()` and `list_owned_devices()` methods of the `az_user` class.

Просмотреть файл

@ -14,6 +14,7 @@
#' - `update(...)`: Update the item's properties in Microsoft Graph.
#' - `do_operation(...)`: Carry out an arbitrary operation on the item.
#' - `sync_fields()`: Synchronise the R object with the item metadata in Microsoft Graph.
#' - `list_members(n=Inf)`: Return a list of all members of this group. `n` is the number of results to return; set this to NULL to return the `ms_graph_pager` iterator object for the result set.
#'
#' @section Initialization:
#' Currently support for directory roles is limited. Objects of this class should not be initialized directly.
@ -37,10 +38,10 @@ public=list(
super$initialize(token, tenant, properties)
},
list_members=function()
list_members=function(n=Inf)
{
res <- private$get_paged_list(self$do_operation("members"))
private$init_list_objects(res)
pager <- self$get_list_pager(self$do_operation("members"))
extract_list_values(pager, n)
},
print=function(...)

Просмотреть файл

@ -14,10 +14,8 @@
#' - `update(...)`: Update the group information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the group.
#' - `sync_fields()`: Synchronise the R object with the app data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this group is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this group is a member of.
#' - `list_members(type=c("user", "group", "application", "servicePrincipal"))`: Return a list of all members of this group. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_owners(type=c("user", "group", "application", "servicePrincipal"))`: Return a list of all owners of this group. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_members(type=c("user", "group", "application", "servicePrincipal"), n=Inf)`: Return a list of all members of this group. Specify the `type` argument to filter the result for specific object type(s). `n` is the number of results to return; set this to NULL to return the `ms_graph_pager` iterator object for the result set.
#' - `list_owners(type=c("user", "group", "application", "servicePrincipal"), n=Inf)`: Return a list of all owners of this group. Specify the `type` argument to filter the result for specific object type(s).
#'
#' @section Initialization:
#' Creating new objects of this class should be done via the `create_group` and `get_group` methods of the [ms_graph] and [az_app] classes. Calling the `new()` method for this class only constructs the R object; it does not call the Microsoft Graph API to create the actual group.
@ -34,12 +32,19 @@
#' gr <- get_graph_login()
#' usr <- gr$get_user("myname@aadtenant.com")
#'
#' grps <- usr$list_direct_memberships()
#' grps <- usr$list_group_memberships()
#' grp <- gr$get_group(grps[1])
#'
#' grp$list_members()
#' grp$list_owners()
#'
#' # capping the number of results
#' grp$list_members(n=10)
#'
#' # get the pager object for a listing method
#' pager <- grp$list_members(n=NULL)
#' pager$value
#'
#' }
#' @format An R6 object of class `az_group`, inheriting from `az_object`.
#' @export
@ -54,16 +59,16 @@ public=list(
super$initialize(token, tenant, properties)
},
list_members=function(type=c("user", "group", "application", "servicePrincipal"))
list_members=function(type=c("user", "group", "application", "servicePrincipal"), n=Inf)
{
res <- private$get_paged_list(self$do_operation("members"))
private$init_list_objects(res, type)
pager <- self$get_list_pager(self$do_operation("members"), type_filter=type)
extract_list_values(pager, n)
},
list_owners=function(type=c("user", "group", "application", "servicePrincipal"))
list_owners=function(type=c("user", "group", "application", "servicePrincipal"), n=Inf)
{
res <- private$get_paged_list(self$do_operation("owners"))
private$init_list_objects(res, type)
pager <- self$get_list_pager(self$do_operation("owners"), type_filter=type)
extract_list_values(pager, n)
},
print=function(...)

Просмотреть файл

@ -14,8 +14,8 @@
#' - `update(...)`: Update the object information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the object.
#' - `sync_fields()`: Synchronise the R object with the data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this object is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this object is a member of.
#' - `list_group_memberships(security_only=FALSE, n=Inf)`: Return the IDs of all groups this object is a member of. If `security_only` is TRUE, only security group IDs are returned. `n` is the number of results to return; set this to NULL to return the `ms_graph_pager` iterator object for the result set.
#' - `list_object_memberships(security_only=FALSE, n=Inf)`: Return the IDs of all groups, administrative units and directory roles this object is a member of.
#'
#' @section Initialization:
#' Objects of this class should not be created directly. Instead, create an object of the appropriate subclass: [az_app], [az_service_principal], [az_user], [az_group].
@ -32,20 +32,20 @@ az_object <- R6::R6Class("az_object", inherit=ms_object,
public=list(
list_object_memberships=function()
list_object_memberships=function(security_only=FALSE, n=Inf)
{
lst <- self$do_operation("getMemberObjects", body=list(securityEnabledOnly=TRUE),
encode="json", http_verb="POST")
unlist(private$get_paged_list(lst))
body <- list(securityEnabledOnly=security_only)
pager <- self$get_list_pager(self$do_operation("getMemberObjects", body=body, http_verb="POST"),
generate_objects=FALSE)
unlist(extract_list_values(pager, n))
},
list_group_memberships=function()
list_group_memberships=function(security_only=FALSE, n=Inf)
{
lst <- self$do_operation("getMemberGroups", body=list(securityEnabledOnly=TRUE),
encode="json", http_verb="POST")
unlist(private$get_paged_list(lst))
body <- list(securityEnabledOnly=security_only)
pager <- self$get_list_pager(self$do_operation("getMemberGroups", body=body, http_verb="POST"),
generate_objects=FALSE)
unlist(extract_list_values(pager, n))
},
print=function(...)

Просмотреть файл

@ -14,8 +14,6 @@
#' - `update(...)`: Update the service principal information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the service principal.
#' - `sync_fields()`: Synchronise the R object with the service principal data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this service principal is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this service principal is a member of.
#'
#' @section Initialization:
#' Creating new objects of this class should be done via the `create_service_principal` and `get_service_principal` methods of the [ms_graph] and [az_app] classes. Calling the `new()` method for this class only constructs the R object; it does not call the Microsoft Graph API to create the actual service principal.

Просмотреть файл

@ -14,13 +14,11 @@
#' - `update(...)`: Update the user information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the user account.
#' - `sync_fields()`: Synchronise the R object with the app data in Azure Active Directory.
#' - `list_group_memberships()`: Return the IDs of all groups this user is a member of.
#' - `list_object_memberships()`: Return the IDs of all groups, administrative units and directory roles this user is a member of.
#' - `list_direct_memberships(id_only=TRUE)`: List the groups and directory roles this user is a direct member of. Set `id_only=TRUE` to return only a vector of IDs (the default), or `id_only=FALSE` to return a list of group objects.
#' - `list_owned_objects(type=c("user", "group", "application", "servicePrincipal"))`: List directory objects (groups/apps/service principals) owned by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_created_objects(type=c("user", "group", "application", "servicePrincipal"))`: List directory objects (groups/apps/service principals) created by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_owned_devices()`: List the devices owned by this user.
#' - `list_registered_devices()`: List the devices registered by this user.
#' - `list_direct_memberships(n=Inf)`: List the groups and directory roles this user is a direct member of. `n` is the number of results to return; set this to NULL to return the `ms_graph_pager` iterator object for the result set.
#' - `list_owned_objects(type=c("user", "group", "application", "servicePrincipal"), n=Inf)`: List directory objects (groups/apps/service principals) owned by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_created_objects(type=c("user", "group", "application", "servicePrincipal"), n=Inf)`: List directory objects (groups/apps/service principals) created by this user. Specify the `type` argument to filter the result for specific object type(s).
#' - `list_owned_devices(n=Inf)`: List the devices owned by this user.
#' - `list_registered_devices(n=Inf)`: List the devices registered by this user.
#' - `reset_password(password=NULL, force_password_change=TRUE)`: Resets a user password. By default the new password will be randomly generated, and must be changed at next login.
#'
#' @section Initialization:
@ -52,6 +50,13 @@
#' # owned apps and service principals
#' usr$list_owned_objects(type=c("application", "servicePrincipal"))
#'
#' # first 5 objects
#' usr$list_owned_objects(n=5)
#'
#' # get the pager object
#' pager <- usr$list_owned_objects(n=NULL)
#' pager$value
#'
#' }
#' @format An R6 object of class `az_user`, inheriting from `az_object`.
#' @export
@ -88,31 +93,28 @@ public=list(
password
},
list_owned_objects=function(type=c("user", "group", "application", "servicePrincipal"))
list_owned_objects=function(type=c("user", "group", "application", "servicePrincipal"), n=Inf)
{
res <- private$get_paged_list(self$do_operation("ownedObjects"))
private$init_list_objects(res, type)
pager <- self$get_list_pager(self$do_operation("ownedObjects"), type_filter=type)
extract_list_values(pager, n)
},
list_created_objects=function(type=c("user", "group", "application", "servicePrincipal"))
list_created_objects=function(type=c("user", "group", "application", "servicePrincipal"), n=Inf)
{
res <- private$get_paged_list(self$do_operation("createdObjects"))
private$init_list_objects(res, type)
pager <- self$get_list_pager(self$do_operation("createdObjects"), type_filter=type)
extract_list_values(pager, n)
},
list_owned_devices=function()
list_owned_devices=function(n=Inf)
{
res <- private$get_paged_list(self$do_operation("ownedDevices"))
private$init_list_objects(res, "device")
pager <- self$get_list_pager(self$do_operation("ownedDevices"))
extract_list_values(pager, n)
},
list_direct_memberships=function(id_only=TRUE)
list_direct_memberships=function(n=Inf)
{
res <- private$get_paged_list(self$do_operation("memberOf"))
if(id_only)
sapply(res, function(grp) grp$id)
else private$init_list_objects(res)
pager <- self$get_list_pager(self$do_operation("memberOf"))
extract_list_values(pager, n)
},
print=function(...)

Просмотреть файл

@ -19,6 +19,7 @@
#' - `delete_group(group_id, confirm=TRUE)`: Deletes a group.
#' - `call_graph_endpoint(op="", ...)`: Calls the Microsoft Graph API using this object's token and tenant as authentication arguments. See [call_graph_endpoint].
#' - `call_batch_endpoint(requests=list(), ...)`: Calls the batch endpoint with a list of individual requests. See [call_batch_endpoint].
#' - `get_aad_object(id)`: Retrieves an arbitrary Azure Active Directory object by ID.
#'
#' @section Authentication:
#' The recommended way to authenticate with Microsoft Graph is via the [create_graph_login] function, which creates a new instance of this class.
@ -53,7 +54,7 @@
#' \dontrun{
#'
#' # start a new Graph session
#' gr <- ms_graph$new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")
#' gr <- ms_graph$new(tenant="myaadtenant.onmicrosoft.com")
#'
#' # authenticate with credentials in a file
#' gr <- ms_graph$new(config_file="creds.json")
@ -76,7 +77,7 @@
#' cert <- readLines("mycert.cer")
#' gr$create_app("mycertapp", password=FALSE, certificate=cert)
#'
#' # retrieving your own user details
#' # retrieving your own user details (assuming interactive authentication)
#' gr$get_user()
#'
#' # retrieving another user's details
@ -84,6 +85,10 @@
#' gr$get_user(email="firstname.lastname@mycompany.com")
#' gr$get_user(name="Hong Ooi")
#'
#' # get an AAD object (a group)
#' id <- gr$get_user()$list_group_memberships()[1]
#' gr$get_aad_object(id)
#'
#' }
#' @format An R6 object of class `ms_graph`.
#' @export
@ -299,6 +304,13 @@ public=list(
call_batch_endpoint(self$token, requests, ...)
},
get_aad_object=function(id)
{
res <- self$call_graph_endpoint(file.path("directoryObjects", id))
gen <- find_class_generator(res, type_filter=NULL)
gen$new(self$token, self$tenant, res)
},
print=function(...)
{
cat("<Microsoft Graph client>\n")

193
R/ms_graph_pager.R Normal file
Просмотреть файл

@ -0,0 +1,193 @@
#' Pager object for Graph list results
#'
#' Class representing an _iterator_ for a set of paged query results.
#'
#' @docType class
#' @section Fields:
#' - `token`: The token used to authenticate with the Graph host.
#' - `output`: What the pager should yield on each iteration, either "data.frame","list" or "object". See 'Value' below.
#' @section Methods:
#' - `new(...)`: Initialize a new user object. See 'Initialization' below.
#' - `has_data()`: Returns TRUE if there are pages remaining in the iterator, or FALSE otherwise.
#' @section Active bindings:
#' - `value`: The returned value on each iteration of the pager.
#'
#' @section Initialization:
#' The recommended way to create objects of this class is via the `ms_object$get_list_pager()` method, but it can also be initialized directly. The arguments to the `new()` method are:
#' - `token`: The token used to authenticate with the Graph host.
#' - `first_page`: A list containing the first page of results, generally from a call to `call_graph_endpoint()` or the `do_operation()` method of an AzureGraph R6 object.
#' - `next_link_name,value_name`: The names of the components of `first_page` containing the link to the next page, and the set of values for the page respectively. The default values are `@odata.nextLink` and `value`.
#' - `generate_objects`: Whether the iterator should return a list containing the parsed JSON for the page values, or convert it into a list of R6 objects. See 'Value' below.
#' - `type_filter`: Any extra arguments required to initialise the returned objects. Only used if `generate_objects` is TRUE.
#' - `...`: Any extra arguments required to initialise the returned objects. Only used if `generate_objects` is TRUE.
#'
#' @section Value:
#' The `value` active binding returns the page values for each iteration of the pager. This can take one of 3 forms, based on the initial format of the first page and the `generate_objects` argument.
#'
#' If the first page of results is a data frame (each item has been converted into a row), then the pager will return results as data frames. In this case, the `output` field is automatically set to "data.frame" and the `generate_objects` initialization argument is ignored. Usually this will be the case when the results are meant to represent external data, eg items in a SharePoint list.
#'
#' If the first page of results is a list, the `generate_objects` argument sets whether to convert the items in each page into R6 objects defined by the AzureGraph class framework. If `generate_objects` is TRUE, the `output` field is set to "object", and if `generate_objects` is FALSE, the `output` field is set to "list".
#'
#' @seealso
#' [ms_object], [extract_list_values]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [Paging documentation](https://docs.microsoft.com/en-us/graph/paging)
#'
#' @examples
#' \dontrun{
#'
#' # list direct memberships
#' firstpage <- call_graph_endpoint(token, "me/memberOf")
#'
#' pager <- ms_graph_pager$new(token, firstpage)
#' pager$has_data()
#' pager$value
#'
#' # once all the pages have been returned
#' isFALSE(pager$has_data())
#' is.null(pager$value)
#'
#' # returning items, 1 per page, as raw lists of properties
#' firstpage <- call_graph_endpoint(token, "me/memberOf", options=list(`$top`=1))
#' pager <- ms_graph_pager$new(token, firstpage, generate_objects=FALSE)
#' lst <- NULL
#' while(pager$has_data())
#' lst <- c(lst, pager$value)
#'
#' # returning items as a data frame
#' firstdf <- call_graph_endpoint(token, "me/memberOf", options=list(`$top`=1),
#' simplify=TRUE)
#' pager <- ms_graph_pager$new(token, firstdf)
#' df <- NULL
#' while(pager$has_data())
#' df <- vctrs::vec_rbin(df, pager$value)
#'
#' }
#' @format An R6 object of class `ms_graph_pager`.
#' @export
ms_graph_pager <- R6::R6Class("ms_graph_pager",
public=list(
token=NULL,
output=NULL,
initialize=function(token, first_page, next_link_name="@odata.nextLink", value_name="value",
generate_objects=TRUE, type_filter=NULL, ...)
{
self$token <- token
private$value_name <- value_name
private$next_link_name <- next_link_name
private$type_filter <- type_filter
private$init_args <- list(...)
self$output <- if(is.data.frame(first_page$value))
"data.frame"
else if(generate_objects)
"object"
else "list"
private$next_link <- first_page[[next_link_name]]
private$next_value <- first_page[[value_name]]
},
has_data=function()
{
!is.null(private$next_value)
}
),
active=list(
value=function()
{
val <- private$next_value
private$next_value <- if(!is.null(private$next_link))
{
page <- call_graph_url(self$token, private$next_link, simplify=(self$output == "data.frame"))
private$next_link <- page[[private$next_link_name]]
page[[private$value_name]]
}
else NULL
if(self$output == "object")
private$make_objects(val)
else val
}
),
private=list(
next_link_name=NULL,
value_name=NULL,
next_link=NULL,
next_value=NULL,
type_filter=NULL,
init_args=NULL,
make_objects=function(page)
{
if(is_empty(page))
return(list())
page <- lapply(page, function(obj)
{
class_gen <- find_class_generator(obj, private$type_filter)
if(is.null(class_gen))
NULL
else do.call(class_gen$new, c(list(self$token, self$tenant, obj), private$init_args))
})
page[!sapply(page, is.null)]
}
))
#' Get the list of values from a Graph pager object
#'
#' @param pager An object of class `ms_graph_pager`, which is an iterator for a list of paged query results.
#' @param n The number of items from the list to return. Note this is _not_ the number of _pages_ (each page will usually contain multiple items). The default value of `Inf` extracts all the values from the list, leaving the pager empty. If this is NULL, the pager itself is returned.
#'
#' @details
#' This is a convenience function to perform the common task of extracting all or some of the items from a paged response.
#'
#' @return
#' If `n` is `Inf` or a number, the items from the paged query results. The format of the returned value depends on the pager settings. This will either be a raw list containing the properties for each of the items; a list of R6 objects; or a data frame. If the pager is empty, the returned value will be NULL.
#'
#' If `n` is NULL, the pager itself is returned.
#'
#' @seealso
#' [ms_graph_pager], [ms_object], [call_graph_endpoint]
#'
#' @examples
#' \dontrun{
#'
#' firstpage <- call_graph_endpoint(token, "me/memberOf")
#' pager <- ms_graph_pager$new(token, firstpage)
#' extract_list_values(pager)
#'
#' # trying to extract values a 2nd time returns NULL
#' is.null(extract_list_values(pager))
#'
#' }
#' @export
extract_list_values <- function(pager, n=Inf)
{
if(is.null(n))
return(pager)
if(!pager$has_data())
stop("Pager is empty", call.=FALSE)
bind_fn <- if(pager$output != "data.frame")
base::c
else if(requireNamespace("vctrs", quietly=TRUE))
vctrs::vec_rbind
else base::rbind
res <- NULL
while(pager$has_data() && NROW(res) < n) # not nrow()
res <- bind_fn(res, pager$value)
if(NROW(res) > n)
utils::head(res, n)
else res
}

Просмотреть файл

@ -1,4 +1,4 @@
#' Azure Active Directory object
#' Microsoft Graph object
#'
#' Base class representing a object in Microsoft Graph. All other Graph object classes ultimately inherit from this class.
#'
@ -14,25 +14,38 @@
#' - `update(...)`: Update the object information in Azure Active Directory.
#' - `do_operation(...)`: Carry out an arbitrary operation on the object.
#' - `sync_fields()`: Synchronise the R object with the data in Azure Active Directory.
#'
#' The following methods are private, and are intended for package authors extending AzureGraph to define their own objects.
#' - `get_paged_list(lst, next_link_name, value_name, simplify, n)`: Used to process API calls that return lists of objects. Microsoft Graph returns lists in pages, with each page containing a subset of objects and a link to the next page. This method reconstructs the list, given the first page. Its arguments are:
#' - `lst`: Object containing the initial page of results, generally the result of a call to `do_operation`. Should be a list with names corresponding to the arguments `next_link_name` and `value_name`.
#' - `next_link_name`: The name of the component of `lst` containing the link to the next page. Defaults to `@odata.nextLink`.
#' - `value_name`: The name of the component of `lst` containing the first page of results. Defaults to `value`.
#' - `simplify`: Whether to turn the list of objects into a data frame. This is useful if the list is intended to be a rectangular data structure, eg a SharePoint list or OneDrive file listing. Note that the vctrs package must be installed to return a data frame, if the objects in the list can have varying structures (which will often be the case).
#' - `n`: Optionally, limit the list to this many objects.
#' - `init_list_objects(lst, type_filter, default_generator, ...)`: `get_paged_list` returns a raw list, the result of parsing the JSON response from the Graph host. This method converts the list into actual R6 objects. Its arguments are:
#' - `lst`: The input list.
#' - `type_filter`: The possible types of objects that the list contains. The default is NULL.
#' - `default_generator`: An R6 class generator object to use, if `init_list_objects` is unable to detect the type of an object. It's recommended to change this from the default value of `ms_object`, if you know that all objects in the list will have the same class.
#' - `...`: Further arguments to pass to the generator's `initialize` method.
#' - `get_list_pager(...)`: Returns a pager object, which is an _iterator_ for a set of paged query results. See 'Paged results' below.
#'
#' @section Initialization:
#' Objects of this class should not be created directly. Instead, create an object of the appropriate subclass.
#'
#' @section Paged results:
#' Microsoft Graph returns lists in pages, with each page containing a subset of objects and a link to the next page. AzureGraph provides an iterator-based API that lets you access each page individually, or collect them all into a single object.
#'
#' To create a new pager object, call the `get_list_pager()` method with the following arguments:
#' - `lst`: A list containing the first page of results, generally from a call to the `do_operation()` method.
#' - `next_link_name,value_name`: The names of the components of `first_page` containing the link to the next page, and the set of values for the page respectively. The default values are `@odata.nextLink` and `value`.
#' - `generate_objects`: Whether the iterator should return a list containing the parsed JSON for the page values, or convert it into a list of R6 objects.
#' - `type_filter`: Any extra arguments required to initialise the returned objects. Only used if `generate_objects` is TRUE.
#' - `...`: Any extra arguments required to initialise the returned objects. Only used if `generate_objects` is TRUE.
#'
#' This returns an object of class [ms_graph_pager], which is an _iterator_ for the set of paged results. Each call to the object's `value` active binding yields the next page. When all pages have been returned, `value` contains NULL.
#'
#' The format of the returned values can take one of 3 forms, based on the initial format of the first page and the `generate_objects` argument.
#'
#' If the first page of results is a data frame (each item has been converted into a row), then the pager will return results as data frames. In this case, the `output` field is automatically set to "data.frame" and the `generate_objects` initialization argument is ignored. Usually this will be the case when the results are meant to represent external data, eg items in a SharePoint list.
#'
#' If the first page of results is a list, the `generate_objects` argument sets whether to convert the items in each page into R6 objects defined by the AzureGraph class framework. If `generate_objects` is TRUE, the `output` field is set to "object", and if `generate_objects` is FALSE, the `output` field is set to "list".
#'
#' You can also call the `extract_list_values()` function to get all or some of the values from a pager, without having to manually combine the pages together.
#'
#' @section Deprecated methods:
#' The following methods are private and **deprecated**, and form the older AzureGraph API for accessing paged results. They will eventually be removed.
#' - `get_paged_list(lst, next_link_name, value_name, simplify, n)`: This method reconstructs the list, given the first page.
#' - `init_list_objects(lst, type_filter, default_generator, ...)`: `get_paged_list` returns a raw list, the result of parsing the JSON response from the Graph host. This method converts the list into actual R6 objects.
#'
#' @seealso
#' [ms_graph], [az_object]
#' [ms_graph], [az_object], [ms_graph_pager], [extract_list_values]
#'
#' [Microsoft Graph overview](https://docs.microsoft.com/en-us/graph/overview),
#' [REST API reference](https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0)
@ -97,6 +110,12 @@ public=list(
call_graph_endpoint(self$token, op, ...)
},
get_list_pager=function(lst, next_link_name="@odata.nextLink", value_name="value", generate_objects=TRUE,
type_filter=NULL, ...)
{
ms_graph_pager$new(self$token, lst, next_link_name, value_name, generate_objects, type_filter, ...)
},
print=function(...)
{
cat("<Graph directory object '", self$properties$displayName, "'>\n", sep="")

Просмотреть файл

@ -55,7 +55,18 @@ register_graph_class("device", az_device,
register_graph_class("directoryRole", az_directory_role,
function(props) !is.null(props$roleTemplateId))
find_class_generator <- function(props, type_filter, default_generator)
#' Find the R6 class for a Graph object
#'
#' @param props A list of object properties, generally the result of a Graph API call.
#' @param type_filter An optional vector of types by which to filter the result.
#' @param default_generator The default class generator to use, if a match couldn't be found.
#' @details
#' This function maps Graph objects to AzureGraph classes.
#' @return
#' An R6 class generator for the appropriate AzureGraph class. If no matching R6 class could be found, the default generator is returned. If `type_filter` is provided, but the matching R6 class isn't in the filter, NULL is returned.
#' @export
find_class_generator <- function(props, type_filter=NULL, default_generator=ms_object)
{
# use ODATA metadata if available
if(!is.null(props$`@odata.type`))

Просмотреть файл

@ -29,9 +29,7 @@ Base class representing an AAD app.
\item \code{update(...)}: Update the app data in Azure Active Directory. For what properties can be updated, consult the REST API documentation link below.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the app.
\item \code{sync_fields()}: Synchronise the R object with the app data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this app is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this app is a member of.
\item \code{list_owners(type=c("user", "group", "application", "servicePrincipal"))}: Return a list of all owners of this app. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_owners(type=c("user", "group", "application", "servicePrincipal"), n=Inf)}: Return a list of all owners of this app. Specify the \code{type} argument to filter the result for specific object type(s). \code{n} is the number of results to return; set this to NULL to return the \code{ms_graph_pager} iterator object for the result set.
\item \code{create_service_principal(...)}: Create a service principal for this app, by default in the current tenant.
\item \code{get_service_principal()}: Get the service principal for this app.
\item \code{delete_service_principal(confirm=TRUE)}: Delete the service principal for this app. By default, ask for confirmation first.

Просмотреть файл

@ -28,8 +28,6 @@ Class representing a registered device.
\item \code{update(...)}: Update the device information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the device.
\item \code{sync_fields()}: Synchronise the R object with the app data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this device is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this device is a member of.
}
}

Просмотреть файл

@ -28,6 +28,7 @@ Class representing a role in Azure Active Directory.
\item \code{update(...)}: Update the item's properties in Microsoft Graph.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the item.
\item \code{sync_fields()}: Synchronise the R object with the item metadata in Microsoft Graph.
\item \code{list_members(n=Inf)}: Return a list of all members of this group. \code{n} is the number of results to return; set this to NULL to return the \code{ms_graph_pager} iterator object for the result set.
}
}

Просмотреть файл

@ -28,10 +28,8 @@ Class representing an AAD group.
\item \code{update(...)}: Update the group information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the group.
\item \code{sync_fields()}: Synchronise the R object with the app data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this group is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this group is a member of.
\item \code{list_members(type=c("user", "group", "application", "servicePrincipal"))}: Return a list of all members of this group. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_owners(type=c("user", "group", "application", "servicePrincipal"))}: Return a list of all owners of this group. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_members(type=c("user", "group", "application", "servicePrincipal"), n=Inf)}: Return a list of all members of this group. Specify the \code{type} argument to filter the result for specific object type(s). \code{n} is the number of results to return; set this to NULL to return the \code{ms_graph_pager} iterator object for the result set.
\item \code{list_owners(type=c("user", "group", "application", "servicePrincipal"), n=Inf)}: Return a list of all owners of this group. Specify the \code{type} argument to filter the result for specific object type(s).
}
}
@ -46,12 +44,19 @@ Creating new objects of this class should be done via the \code{create_group} an
gr <- get_graph_login()
usr <- gr$get_user("myname@aadtenant.com")
grps <- usr$list_direct_memberships()
grps <- usr$list_group_memberships()
grp <- gr$get_group(grps[1])
grp$list_members()
grp$list_owners()
# capping the number of results
grp$list_members(n=10)
# get the pager object for a listing method
pager <- grp$list_members(n=NULL)
pager$value
}
}
\seealso{

Просмотреть файл

@ -28,8 +28,8 @@ Base class representing an Azure Active Directory object in Microsoft Graph.
\item \code{update(...)}: Update the object information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the object.
\item \code{sync_fields()}: Synchronise the R object with the data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this object is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this object is a member of.
\item \code{list_group_memberships(security_only=FALSE, n=Inf)}: Return the IDs of all groups this object is a member of. If \code{security_only} is TRUE, only security group IDs are returned. \code{n} is the number of results to return; set this to NULL to return the \code{ms_graph_pager} iterator object for the result set.
\item \code{list_object_memberships(security_only=FALSE, n=Inf)}: Return the IDs of all groups, administrative units and directory roles this object is a member of.
}
}

Просмотреть файл

@ -28,8 +28,6 @@ Class representing an AAD service principal.
\item \code{update(...)}: Update the service principal information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the service principal.
\item \code{sync_fields()}: Synchronise the R object with the service principal data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this service principal is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this service principal is a member of.
}
}

Просмотреть файл

@ -28,13 +28,11 @@ Class representing an AAD user account.
\item \code{update(...)}: Update the user information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the user account.
\item \code{sync_fields()}: Synchronise the R object with the app data in Azure Active Directory.
\item \code{list_group_memberships()}: Return the IDs of all groups this user is a member of.
\item \code{list_object_memberships()}: Return the IDs of all groups, administrative units and directory roles this user is a member of.
\item \code{list_direct_memberships(id_only=TRUE)}: List the groups and directory roles this user is a direct member of. Set \code{id_only=TRUE} to return only a vector of IDs (the default), or \code{id_only=FALSE} to return a list of group objects.
\item \code{list_owned_objects(type=c("user", "group", "application", "servicePrincipal"))}: List directory objects (groups/apps/service principals) owned by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_created_objects(type=c("user", "group", "application", "servicePrincipal"))}: List directory objects (groups/apps/service principals) created by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_owned_devices()}: List the devices owned by this user.
\item \code{list_registered_devices()}: List the devices registered by this user.
\item \code{list_direct_memberships(n=Inf)}: List the groups and directory roles this user is a direct member of. \code{n} is the number of results to return; set this to NULL to return the \code{ms_graph_pager} iterator object for the result set.
\item \code{list_owned_objects(type=c("user", "group", "application", "servicePrincipal"), n=Inf)}: List directory objects (groups/apps/service principals) owned by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_created_objects(type=c("user", "group", "application", "servicePrincipal"), n=Inf)}: List directory objects (groups/apps/service principals) created by this user. Specify the \code{type} argument to filter the result for specific object type(s).
\item \code{list_owned_devices(n=Inf)}: List the devices owned by this user.
\item \code{list_registered_devices(n=Inf)}: List the devices registered by this user.
\item \code{reset_password(password=NULL, force_password_change=TRUE)}: Resets a user password. By default the new password will be randomly generated, and must be changed at next login.
}
}
@ -64,6 +62,13 @@ usr$list_owned_objects()
# owned apps and service principals
usr$list_owned_objects(type=c("application", "servicePrincipal"))
# first 5 objects
usr$list_owned_objects(n=5)
# get the pager object
pager <- usr$list_owned_objects(n=NULL)
pager$value
}
}
\seealso{

Просмотреть файл

@ -0,0 +1,39 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ms_graph_pager.R
\name{extract_list_values}
\alias{extract_list_values}
\title{Get the list of values from a Graph pager object}
\usage{
extract_list_values(pager, n = Inf)
}
\arguments{
\item{pager}{An object of class \code{ms_graph_pager}, which is an iterator for a list of paged query results.}
\item{n}{The number of items from the list to return. Note this is \emph{not} the number of \emph{pages} (each page will usually contain multiple items). The default value of \code{Inf} extracts all the values from the list, leaving the pager empty. If this is NULL, the pager itself is returned.}
}
\value{
If \code{n} is \code{Inf} or a number, the items from the paged query results. The format of the returned value depends on the pager settings. This will either be a raw list containing the properties for each of the items; a list of R6 objects; or a data frame. If the pager is empty, the returned value will be NULL.
If \code{n} is NULL, the pager itself is returned.
}
\description{
Get the list of values from a Graph pager object
}
\details{
This is a convenience function to perform the common task of extracting all or some of the items from a paged response.
}
\examples{
\dontrun{
firstpage <- call_graph_endpoint(token, "me/memberOf")
pager <- ms_graph_pager$new(token, firstpage)
extract_list_values(pager)
# trying to extract values a 2nd time returns NULL
is.null(extract_list_values(pager))
}
}
\seealso{
\link{ms_graph_pager}, \link{ms_object}, \link{call_graph_endpoint}
}

Просмотреть файл

@ -0,0 +1,24 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/zzz_class_directory.R
\name{find_class_generator}
\alias{find_class_generator}
\title{Find the R6 class for a Graph object}
\usage{
find_class_generator(props, type_filter = NULL, default_generator = ms_object)
}
\arguments{
\item{props}{A list of object properties, generally the result of a Graph API call.}
\item{type_filter}{An optional vector of types by which to filter the result.}
\item{default_generator}{The default class generator to use, if a match couldn't be found.}
}
\value{
An R6 class generator for the appropriate AzureGraph class. If no matching R6 class could be found, the default generator is returned. If \code{type_filter} is provided, but the matching R6 class isn't in the filter, NULL is returned.
}
\description{
Find the R6 class for a Graph object
}
\details{
This function maps Graph objects to AzureGraph classes.
}

Просмотреть файл

@ -28,6 +28,7 @@ Base class for interacting with Microsoft Graph API.
\item \code{delete_group(group_id, confirm=TRUE)}: Deletes a group.
\item \code{call_graph_endpoint(op="", ...)}: Calls the Microsoft Graph API using this object's token and tenant as authentication arguments. See \link{call_graph_endpoint}.
\item \code{call_batch_endpoint(requests=list(), ...)}: Calls the batch endpoint with a list of individual requests. See \link{call_batch_endpoint}.
\item \code{get_aad_object(id)}: Retrieves an arbitrary Azure Active Directory object by ID.
}
}
@ -64,7 +65,7 @@ A new app will also have a service principal created for it by default. To disab
\dontrun{
# start a new Graph session
gr <- ms_graph$new(tenant="myaadtenant.onmicrosoft.com", app="app_id", password="password")
gr <- ms_graph$new(tenant="myaadtenant.onmicrosoft.com")
# authenticate with credentials in a file
gr <- ms_graph$new(config_file="creds.json")
@ -87,7 +88,7 @@ app$delete()
cert <- readLines("mycert.cer")
gr$create_app("mycertapp", password=FALSE, certificate=cert)
# retrieving your own user details
# retrieving your own user details (assuming interactive authentication)
gr$get_user()
# retrieving another user's details
@ -95,6 +96,10 @@ gr$get_user("username@myaadtenant.onmicrosoft.com")
gr$get_user(email="firstname.lastname@mycompany.com")
gr$get_user(name="Hong Ooi")
# get an AAD object (a group)
id <- gr$get_user()$list_group_memberships()[1]
gr$get_aad_object(id)
}
}
\seealso{

94
man/ms_graph_pager.Rd Normal file
Просмотреть файл

@ -0,0 +1,94 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/ms_graph_pager.R
\docType{class}
\name{ms_graph_pager}
\alias{ms_graph_pager}
\title{Pager object for Graph list results}
\format{
An R6 object of class \code{ms_graph_pager}.
}
\description{
Class representing an \emph{iterator} for a set of paged query results.
}
\section{Fields}{
\itemize{
\item \code{token}: The token used to authenticate with the Graph host.
\item \code{output}: What the pager should yield on each iteration, either "data.frame","list" or "object". See 'Value' below.
}
}
\section{Methods}{
\itemize{
\item \code{new(...)}: Initialize a new user object. See 'Initialization' below.
\item \code{has_data()}: Returns TRUE if there are pages remaining in the iterator, or FALSE otherwise.
}
}
\section{Active bindings}{
\itemize{
\item \code{value}: The returned value on each iteration of the pager.
}
}
\section{Initialization}{
The recommended way to create objects of this class is via the \code{ms_object$get_list_pager()} method, but it can also be initialized directly. The arguments to the \code{new()} method are:
\itemize{
\item \code{token}: The token used to authenticate with the Graph host.
\item \code{first_page}: A list containing the first page of results, generally from a call to \code{call_graph_endpoint()} or the \code{do_operation()} method of an AzureGraph R6 object.
\item \verb{next_link_name,value_name}: The names of the components of \code{first_page} containing the link to the next page, and the set of values for the page respectively. The default values are \verb{@odata.nextLink} and \code{value}.
\item \code{generate_objects}: Whether the iterator should return a list containing the parsed JSON for the page values, or convert it into a list of R6 objects. See 'Value' below.
\item \code{type_filter}: Any extra arguments required to initialise the returned objects. Only used if \code{generate_objects} is TRUE.
\item \code{...}: Any extra arguments required to initialise the returned objects. Only used if \code{generate_objects} is TRUE.
}
}
\section{Value}{
The \code{value} active binding returns the page values for each iteration of the pager. This can take one of 3 forms, based on the initial format of the first page and the \code{generate_objects} argument.
If the first page of results is a data frame (each item has been converted into a row), then the pager will return results as data frames. In this case, the \code{output} field is automatically set to "data.frame" and the \code{generate_objects} initialization argument is ignored. Usually this will be the case when the results are meant to represent external data, eg items in a SharePoint list.
If the first page of results is a list, the \code{generate_objects} argument sets whether to convert the items in each page into R6 objects defined by the AzureGraph class framework. If \code{generate_objects} is TRUE, the \code{output} field is set to "object", and if \code{generate_objects} is FALSE, the \code{output} field is set to "list".
}
\examples{
\dontrun{
# list direct memberships
firstpage <- call_graph_endpoint(token, "me/memberOf")
pager <- ms_graph_pager$new(token, firstpage)
pager$has_data()
pager$value
# once all the pages have been returned
isFALSE(pager$has_data())
is.null(pager$value)
# returning items, 1 per page, as raw lists of properties
firstpage <- call_graph_endpoint(token, "me/memberOf", options=list(`$top`=1))
pager <- ms_graph_pager$new(token, firstpage, generate_objects=FALSE)
lst <- NULL
while(pager$has_data())
lst <- c(lst, pager$value)
# returning items as a data frame
firstdf <- call_graph_endpoint(token, "me/memberOf", options=list(`$top`=1),
simplify=TRUE)
pager <- ms_graph_pager$new(token, firstdf)
df <- NULL
while(pager$has_data())
df <- vctrs::vec_rbin(df, pager$value)
}
}
\seealso{
\link{ms_object}, \link{extract_list_values}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/paging}{Paging documentation}
}

Просмотреть файл

@ -3,7 +3,7 @@
\docType{class}
\name{ms_object}
\alias{ms_object}
\title{Azure Active Directory object}
\title{Microsoft Graph object}
\format{
An R6 object of class \code{ms_object}.
}
@ -28,25 +28,7 @@ Base class representing a object in Microsoft Graph. All other Graph object clas
\item \code{update(...)}: Update the object information in Azure Active Directory.
\item \code{do_operation(...)}: Carry out an arbitrary operation on the object.
\item \code{sync_fields()}: Synchronise the R object with the data in Azure Active Directory.
}
The following methods are private, and are intended for package authors extending AzureGraph to define their own objects.
\itemize{
\item \code{get_paged_list(lst, next_link_name, value_name, simplify, n)}: Used to process API calls that return lists of objects. Microsoft Graph returns lists in pages, with each page containing a subset of objects and a link to the next page. This method reconstructs the list, given the first page. Its arguments are:
\itemize{
\item \code{lst}: Object containing the initial page of results, generally the result of a call to \code{do_operation}. Should be a list with names corresponding to the arguments \code{next_link_name} and \code{value_name}.
\item \code{next_link_name}: The name of the component of \code{lst} containing the link to the next page. Defaults to \verb{@odata.nextLink}.
\item \code{value_name}: The name of the component of \code{lst} containing the first page of results. Defaults to \code{value}.
\item \code{simplify}: Whether to turn the list of objects into a data frame. This is useful if the list is intended to be a rectangular data structure, eg a SharePoint list or OneDrive file listing. Note that the vctrs package must be installed to return a data frame, if the objects in the list can have varying structures (which will often be the case).
\item \code{n}: Optionally, limit the list to this many objects.
}
\item \code{init_list_objects(lst, type_filter, default_generator, ...)}: \code{get_paged_list} returns a raw list, the result of parsing the JSON response from the Graph host. This method converts the list into actual R6 objects. Its arguments are:
\itemize{
\item \code{lst}: The input list.
\item \code{type_filter}: The possible types of objects that the list contains. The default is NULL.
\item \code{default_generator}: An R6 class generator object to use, if \code{init_list_objects} is unable to detect the type of an object. It's recommended to change this from the default value of \code{ms_object}, if you know that all objects in the list will have the same class.
\item \code{...}: Further arguments to pass to the generator's \code{initialize} method.
}
\item \code{get_list_pager(...)}: Returns a pager object, which is an \emph{iterator} for a set of paged query results. See 'Paged results' below.
}
}
@ -55,8 +37,41 @@ The following methods are private, and are intended for package authors extendin
Objects of this class should not be created directly. Instead, create an object of the appropriate subclass.
}
\section{Paged results}{
Microsoft Graph returns lists in pages, with each page containing a subset of objects and a link to the next page. AzureGraph provides an iterator-based API that lets you access each page individually, or collect them all into a single object.
To create a new pager object, call the \code{get_list_pager()} method with the following arguments:
\itemize{
\item \code{lst}: A list containing the first page of results, generally from a call to the \code{do_operation()} method.
\item \verb{next_link_name,value_name}: The names of the components of \code{first_page} containing the link to the next page, and the set of values for the page respectively. The default values are \verb{@odata.nextLink} and \code{value}.
\item \code{generate_objects}: Whether the iterator should return a list containing the parsed JSON for the page values, or convert it into a list of R6 objects.
\item \code{type_filter}: Any extra arguments required to initialise the returned objects. Only used if \code{generate_objects} is TRUE.
\item \code{...}: Any extra arguments required to initialise the returned objects. Only used if \code{generate_objects} is TRUE.
}
This returns an object of class \link{ms_graph_pager}, which is an \emph{iterator} for the set of paged results. Each call to the object's \code{value} active binding yields the next page. When all pages have been returned, \code{value} contains NULL.
The format of the returned values can take one of 3 forms, based on the initial format of the first page and the \code{generate_objects} argument.
If the first page of results is a data frame (each item has been converted into a row), then the pager will return results as data frames. In this case, the \code{output} field is automatically set to "data.frame" and the \code{generate_objects} initialization argument is ignored. Usually this will be the case when the results are meant to represent external data, eg items in a SharePoint list.
If the first page of results is a list, the \code{generate_objects} argument sets whether to convert the items in each page into R6 objects defined by the AzureGraph class framework. If \code{generate_objects} is TRUE, the \code{output} field is set to "object", and if \code{generate_objects} is FALSE, the \code{output} field is set to "list".
You can also call the \code{extract_list_values()} function to get all or some of the values from a pager, without having to manually combine the pages together.
}
\section{Deprecated methods}{
The following methods are private and \strong{deprecated}, and form the older AzureGraph API for accessing paged results. They will eventually be removed.
\itemize{
\item \code{get_paged_list(lst, next_link_name, value_name, simplify, n)}: This method reconstructs the list, given the first page.
\item \code{init_list_objects(lst, type_filter, default_generator, ...)}: \code{get_paged_list} returns a raw list, the result of parsing the JSON response from the Graph host. This method converts the list into actual R6 objects.
}
}
\seealso{
\link{ms_graph}, \link{az_object}
\link{ms_graph}, \link{az_object}, \link{ms_graph_pager}, \link{extract_list_values}
\href{https://docs.microsoft.com/en-us/graph/overview}{Microsoft Graph overview},
\href{https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0}{REST API reference}

Просмотреть файл

@ -29,10 +29,7 @@ test_that("User/group read functionality works",
grps1 <- me$list_group_memberships()
expect_true(is.character(grps1))
grps2 <- me$list_direct_memberships(id_only=TRUE)
expect_true(is.character(grps2))
grps3 <- me$list_direct_memberships(id_only=FALSE)
grps3 <- me$list_direct_memberships()
expect_true(all(sapply(grps3, function(x) is_group(x) || is_directory_role(x))))
expect_true(all(sapply(grps3, function(g) !is.null(g$properties$id))))

Просмотреть файл

@ -0,0 +1,137 @@
context("List paging")
tenant <- Sys.getenv("AZ_TEST_TENANT_ID")
user <- Sys.getenv("AZ_TEST_USERPRINCIPALNAME")
if(tenant == "" || user == "")
skip("Paging tests skipped: login credentials not set")
if(!interactive())
skip("Paging tests skipped: must be in interactive session")
scopes <- c("https://graph.microsoft.com/.default", "openid", "offline_access")
token <- AzureAuth::get_azure_token(scopes, tenant, .az_cli_app_id, version=2)
gr <- ms_graph$new(token=token)
me <- gr$get_user(user)
test_that("Paging works",
{
lst <- me$do_operation("memberOf")
expect_silent(p <- me$get_list_pager(lst, generate_objects=FALSE))
expect_is(p, "ms_graph_pager")
# assume result fits on 1 page
expect_true(p$has_data())
out <- p$value
expect_is(out, "list")
expect_null(p$value)
expect_false(p$has_data())
# return lists
lst1 <- me$do_operation("memberOf", options=list(`$top`=1))
p1 <- me$get_list_pager(lst1, generate_objects=FALSE)
expect_is(p1, "ms_graph_pager")
expect_true(p1$has_data())
for(i in seq_along(out))
expect_identical(out[i], p1$value)
expect_null(p1$value)
expect_false(p1$has_data())
})
test_that("Page result as data frame works",
{
lstdf <- me$do_operation("memberOf", simplify=TRUE)
expect_silent(pdf <- me$get_list_pager(lstdf))
expect_is(pdf, "ms_graph_pager")
expect_true(pdf$has_data())
outdf <- pdf$value
expect_is(outdf, "data.frame")
expect_null(pdf$value)
expect_false(pdf$has_data())
# return data frames
lstdf1 <- me$do_operation("memberOf", options=list(`$top`=1), simplify=TRUE)
pdf1 <- me$get_list_pager(lstdf1)
expect_is(pdf1, "ms_graph_pager")
expect_true(pdf1$has_data())
outdf1 <- list()
for(i in seq_len(nrow(outdf)))
outdf1 <- c(outdf1, list(pdf1$value))
outdf1 <- do.call(vctrs::vec_rbind, outdf1)
expect_identical(outdf, outdf1)
expect_null(pdf1$value)
expect_false(pdf1$has_data())
})
test_that("Page result as object works",
{
lstobj <- me$do_operation("memberOf")
expect_silent(pobj <- me$get_list_pager(lstobj, generate_objects=TRUE))
expect_is(pobj, "ms_graph_pager")
expect_true(pobj$has_data())
outobj <- pobj$value
expect_is(outobj, "list")
expect_true(all(sapply(outobj, inherits, "ms_object")))
expect_false(pobj$has_data())
lstobj1 <- me$do_operation("memberOf", options=list(`$top`=1))
pobj1 <- me$get_list_pager(lstobj1, generate_objects=TRUE)
expect_is(pobj1, "ms_graph_pager")
expect_true(pobj1$has_data())
outobj1 <- list()
for(i in seq_along(outobj))
expect_equal(outobj[i], pobj1$value)
expect_true(is_empty(pobj1$value))
expect_false(pobj1$has_data())
})
test_that("extract_list_values works",
{
# assume result fits on 1 page
lst <- me$do_operation("memberOf")
p <- me$get_list_pager(lst, generate_objects=FALSE)
out <- p$value
lst1 <- me$do_operation("memberOf", options=list(`$top`=1))
p1 <- me$get_list_pager(lst1, generate_objects=FALSE)
out1 <- extract_list_values(p1)
expect_identical(out, out1)
})
test_that("extract_list_values works for data frame",
{
lst <- me$do_operation("memberOf", simplify=TRUE)
p <- me$get_list_pager(lst, generate_objects=FALSE)
out <- p$value
lst1 <- me$do_operation("memberOf", options=list(`$top`=1), simplify=TRUE)
p1 <- me$get_list_pager(lst1, generate_objects=FALSE)
out1 <- extract_list_values(p1)
expect_identical(out, out1)
})
test_that("extract_list_values works for objects",
{
lst <- me$do_operation("memberOf", simplify=FALSE)
p <- me$get_list_pager(lst, generate_objects=TRUE)
out <- p$value
lst1 <- me$do_operation("memberOf", options=list(`$top`=1), simplify=FALSE)
p1 <- me$get_list_pager(lst1, generate_objects=TRUE)
out1 <- extract_list_values(p1)
expect_equal(out, out1)
})

Просмотреть файл

@ -0,0 +1,118 @@
---
title: "Batching and paging"
author: Hong Ooi
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Batching and Paging}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{utf8}
---
This vignette describes a couple of special interfaces that are available in Microsoft Graph, and how to use them in AzureGraph.
## Batching
The batch API allows you to combine multiple requests into a single batch call, potentially resulting in significant performance improvements. If all the requests are independent, they can be executed in parallel, so that they only take the same time as a single request. If the requests depend on each other, they must be executed serially, but even in this case you benefit by not having to make multiple HTTP requests with the associated network overhead.
For example, let's say you want to get the object information for all the Azure Active Directory groups, directory roles and admin units you're a member of. The `az_object$list_object_memberships()` method returns the IDs for these objects, but to get the remaining object properties, you have to call the `directoryObjects` API endpoint for each individual ID. Rather than making separate calls for every ID, let's combine them into a single batch request.
```r
gr <- get_graph_login()
me <- gr$get_user()
# get the AAD object IDs
obj_ids <- me$list_object_memberships(security_only=FALSE)
```
AzureGraph represents a single request with the `graph_request` R6 class. To create a new request object, call `graph_request$new()` with the following arguments:
- `op`: The operation to carry out, eg `/me/drives`.
- `body`: The body of the HTTPS request, if it is a PUT, POST or PATCH.
- `options`: A list containing the query parameters for the URL, for example OData parameters.
- `headers`: Any optional HTTP headers for the request.
- `encode`: If a request body is present, how it should be encoded when sending it to the endpoint. The default is `json`, meaning it will be sent as JSON text; an alternative is `raw`, for binary data.
- `http_verb`: One of "GET" (the default), "DELETE", "PUT", "POST", "HEAD", or "PATCH".
For this example, only `op` is required.
```r
obj_reqs <- lapply(obj_ids, function(id)
{
op <- file.path("directoryObjects", id)
graph_request$new(op)
})
```
To make a request to the batch endpoint, use the `call_batch_endpoint()` function, or the `ms_graph$call_batch_endpoint()` method. This takes as arguments a list of individual requests, as well as an optional named vector of dependencies. The result of the call is a list of the responses from the requests; each response will have components named `id` and `status`, and usually `body` as well.
In this case, there are no dependencies between the individual requests, so the code is very simple. Simply use the `call_batch_endpoint()` method with the request list; then run the `find_class_generator()` function to get the appropriate class generator for each list of object properties, and instantiate a new object.
```r
objs <- gr$call_batch_endpoint(obj_reqs)
lapply(objs, function(obj)
{
cls <- find_class_generator(obj)
cls$new(gr$token, gr$tenant, obj$body)
})
```
## Paging
Some Microsoft Graph calls return multiple results. In AzureGraph, these calls are generally those represented by methods starting with `list_*`, eg the `list_object_memberships` method used previously. Graph handles result sets by breaking them into _pages_, with each page containing several results.
The built-in AzureGraph methods will generally handle paging automatically, without you needing to be aware of the details. If necessary however, you can also carry out a paged request and handle the results manually.
The starting point for a paged request is a regular Graph call to an endpoint that returns a paged response. For example, let's take the `memberOf` endpoint, which returns the groups of which a user is a member. Calling this endpoint returns the first page of results, along with a link to the next page.
```r
res <- me$do_operation("memberOf")
```
Once you have the first page, you can use that to instantiate a new object of class `ms_graph_pager`. This is an _iterator_ object: each time you access data from it, you retrieve a new page of results. If you have used other programming languages such as Python, Java or C#, the concept of iterators should be familiar. If you're a user of the foreach package, you'll also have used iterators: foreach depends on the iterators package, which implements the same concept using S3 classes.
The easiest way to instantiate a new pager object is via the `get_list_pager()` method. Once instantiated, you access the `value` active binding to retrieve each page of results, starting from the first page.
```r
pager <- me$get_list_pager(res)
pager$value
# [[1]]
# <Security group 'secgroup'>
# directory id: cd806a5f-9d19-426c-b34b-3a3ec662ecf2
# description: test security group
# ---
# Methods:
# delete, do_operation, get_list_pager, list_group_memberships,
# list_members, list_object_memberships, list_owners, sync_fields,
# update
# [[2]]
# <Azure Active Directory role 'Global Administrator'>
# directory id: df643f93-3d9d-497f-8f2e-9cfd4c275e41
# description: Can manage all aspects of Azure AD and Microsoft services that use Azure
# AD identities.
# ---
# Methods:
# delete, do_operation, get_list_pager, list_group_memberships,
# list_members, list_object_memberships, sync_fields, update
```
Once there are no more pages, calling `value` returns an empty result.
```r
pager$value
# list()
```
By default, as shown above, the pager object will create new AzureGraph R6 objects from the properties for each item in the results. You can customise the output in the following ways:
- If the first page of results consists of a data frame, rather than a list of items, the pager will continue to output data frames. This is most useful when the results are meant to represent external, tabular data, eg items in a SharePoint list or files in a OneDrive folder. Some AzureGraph methods will automatically request data frame output; if you want this from a manual REST API call, specify `simplify=TRUE` in the `do_operation()` call.
- You can suppress the conversion of the item properties into an R6 object by specifying `generate_objects=FALSE` in the `get_list_pager()` call. In this case, the pager will return raw lists.
AzureGraph also provides the `extract_list_values()` function to perform the common task of getting all or some of the values from a paged result set. Rather than reading `pager$value` repeatedly until there is no data left, you can simply call:
```r
extract_list_values(pager)
```
To restrict the output to only the first N items, call `extract_list_values(pager, n=N)`. However, note that the result set from a paged query usually isn't ordered in any way, so the items you get will be arbitrary.