From 9f50f62cdde60ef2bf2c7c9136e5077c57d6e130 Mon Sep 17 00:00:00 2001 From: munozedg Date: Sat, 26 Aug 2023 14:29:11 -0500 Subject: [PATCH 1/4] testing avoiding to unlist atomic columns --- R/utils.R | 64 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/R/utils.R b/R/utils.R index 682f03e..12bdfc6 100644 --- a/R/utils.R +++ b/R/utils.R @@ -1,54 +1,54 @@ -## +## removeNULL <- function(x) { x[!sapply(x, is.null)] } redmine_get_all_pages <- function(endpoint, query = NULL, maxLimit = 100) { - + nameEndpoint <- gsub(".*/([^/.]*).*$", "\\1", endpoint) limit <- maxLimit offset <- 0 pageQuery <- paste0("limit=", limit, "&offset=", offset, "&", query) - + res <- redmine_get(endpoint = endpoint, query = pageQuery) resList <- res$content[[nameEndpoint]] - + N <- res$content$total_count - + if (!is.null(N) && N > limit) { - + offsets <- seq(from = limit, to = N, by = limit) - + for (iPage in seq_along(offsets)) { pageQuery <- paste0("limit=", limit, "&offset=", offsets[iPage], "&", query) res <- redmine_get(endpoint = endpoint, query = pageQuery) resList <- c(resList, res$content[[nameEndpoint]]) } } - + # implement 'fill' manually - allNames <- Reduce(union, lapply(resList, function(x) names(x))) - + allNames <- Reduce(union, lapply(resList, function(x) names(x))) + vectors <- lapply(resList, function(x) { res <- x namesToAdd <- setdiff(allNames, names(res)) res[namesToAdd] <- NA res[allNames] }) - + res_df <- as.data.frame(do.call(rbind, vectors), stringsAsFactors = FALSE) - + # unlist columns that are actually atomic - atomicCols <- names(res_df)[vapply(res_df, function(col) !is.list(col) || !is.list(unlist(col, recursive = F)), logical(1))] - res_df[atomicCols] <- lapply(res_df[atomicCols], unlist) + # atomicCols <- names(res_df)[vapply(res_df, function(col) !is.list(col) || !is.list(unlist(col, recursive = F)), logical(1))] + # res_df[atomicCols] <- lapply(res_df[atomicCols], unlist) attr(res_df, "endpoint") <- endpoint attr(res_df, "query") <- query class(res_df) <- append("redminer_df", class(res_df)) - + res_df - + } #' @export @@ -56,45 +56,45 @@ print.redminer_df <- function(x, cut = 20, ...) { if (!is.null(x$description) && is.character(x$description)) x$description <- ifelse(nchar(x$description) > cut, paste0(substr(x$description, 1, cut), " ..."), x$description) - - # TODO: show list columns nicer + + # TODO: show list columns nicer listCols <- names(x)[vapply(x, is.list, logical(1))] # print.data.frame(x[, grep("\\.id$", names(x), value = TRUE, invert = TRUE)]) cat("redmineR listing", - if (!is.null(attr(x, "endpoint"))) paste0(" for '", attr(x, "endpoint"), "'"), + if (!is.null(attr(x, "endpoint"))) paste0(" for '", attr(x, "endpoint"), "'"), if (!is.null(attr(x, "query"))) paste0(" [query = ", attr(x, "query"), "]"), ":\n", sep = "") - + print.data.frame(x) } #' Search id by name -#' -#' Search happens inside the 'subject' field for issues and 'name' for other +#' +#' Search happens inside the 'subject' field for issues and 'name' for other #' endpoints. #' @param name string to search for #' @param endpoint endpoint where to search ("projects", "issues", ...) -#' @param query extra query arguments -#' @return id(s) found invisibly, in addition summary of search results is -#' printed -#' +#' @param query extra query arguments +#' @return id(s) found invisibly, in addition summary of search results is +#' printed +#' #' @author Maxim Nazarov #' @export redmine_search_id <- function(name = NULL, endpoint = "projects", query = NULL) { - - enumerations <- c("issue_priorities", "time_entry_activities", + + enumerations <- c("issue_priorities", "time_entry_activities", "document_categories") if (endpoint %in% enumerations) endpoint <- paste0("enumerations/", endpoint) - + res <- redmine_get_all_pages(endpoint = endpoint, query = query) - + colSearch <- if (endpoint == "issues") "subject" else "name" - ans <- if (!is.null(name)) + ans <- if (!is.null(name)) res[grepl(name, res[[colSearch]], ignore.case = TRUE), ] else res - + if (nrow(ans) == 0) stop("No id found") print(ans) From 763a3b3ade86ba8e17543fc2efdd9477e1afc902 Mon Sep 17 00:00:00 2001 From: munozedg Date: Sat, 26 Aug 2023 17:49:09 -0500 Subject: [PATCH 2/4] It seems the problem was caused by the approach to unlist nonAtomic columns. I solved the problem by applying the tidyr::unnest_wider() function twice to get a tidy data frame. That creates a dependency on the package tidyr, but I believe it can be solved with base-like R code. --- DESCRIPTION | 2 +- R/utils.R | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6bf9117..7447952 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,7 +9,7 @@ Description: R API client for the 'Redmine' project management system. URL: https://github.com/openanalytics/redmineR, http://www.redmine.org/projects/redmine/wiki/Rest_api License: GPL-3 + file LICENSE RoxygenNote: 6.1.1 -Imports: httr, jsonlite, mime +Imports: httr, jsonlite, mime, tidyr Suggests: knitr VignetteBuilder: knitr Encoding: UTF-8 diff --git a/R/utils.R b/R/utils.R index 12bdfc6..9dd650f 100644 --- a/R/utils.R +++ b/R/utils.R @@ -43,6 +43,12 @@ redmine_get_all_pages <- function(endpoint, query = NULL, maxLimit = 100) { # atomicCols <- names(res_df)[vapply(res_df, function(col) !is.list(col) || !is.list(unlist(col, recursive = F)), logical(1))] # res_df[atomicCols] <- lapply(res_df[atomicCols], unlist) + getNonAtomicCols <- function(df) names(df)[!vapply(df, function(col) !is.list(col) || !is.list(unlist(col, recursive = F)), logical(1))] + + res_df <- res_df %>% + tidyr::unnest_wider(col = all_of(getNonAtomicCols(.)), names_sep = "_") %>% + tidyr::unnest_wider(col = all_of(getNonAtomicCols(.)), names_sep = "_") + attr(res_df, "endpoint") <- endpoint attr(res_df, "query") <- query class(res_df) <- append("redminer_df", class(res_df)) From a63fd6d29c6fdde66c400d43b07e1279506afe24 Mon Sep 17 00:00:00 2001 From: munozedg Date: Sat, 26 Aug 2023 19:29:36 -0500 Subject: [PATCH 3/4] Get rid of a weird message due to a non-existent `description` field in the resulting data frame when calling the functions without assigning the result to an object/data frame. Due to the print method of the `redminer_df` class. --- R/utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/utils.R b/R/utils.R index 9dd650f..43d6956 100644 --- a/R/utils.R +++ b/R/utils.R @@ -59,7 +59,7 @@ redmine_get_all_pages <- function(endpoint, query = NULL, maxLimit = 100) { #' @export print.redminer_df <- function(x, cut = 20, ...) { - if (!is.null(x$description) && is.character(x$description)) + if ('description' %in% names(x) && !is.null(x$description) && is.character(x$description)) x$description <- ifelse(nchar(x$description) > cut, paste0(substr(x$description, 1, cut), " ..."), x$description) From 59bd83552c5e94a37213a1f8ad9b9be53a5e6fbb Mon Sep 17 00:00:00 2001 From: munozedg Date: Sun, 27 Aug 2023 14:47:26 -0500 Subject: [PATCH 4/4] res_df as data frame --- R/utils.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/utils.R b/R/utils.R index 43d6956..ec89901 100644 --- a/R/utils.R +++ b/R/utils.R @@ -49,6 +49,8 @@ redmine_get_all_pages <- function(endpoint, query = NULL, maxLimit = 100) { tidyr::unnest_wider(col = all_of(getNonAtomicCols(.)), names_sep = "_") %>% tidyr::unnest_wider(col = all_of(getNonAtomicCols(.)), names_sep = "_") + res_df <- as.data.frame(res_df) + attr(res_df, "endpoint") <- endpoint attr(res_df, "query") <- query class(res_df) <- append("redminer_df", class(res_df))