diff --git a/.Rbuildignore b/.Rbuildignore index 4f092c342..ad9438b2e 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -24,3 +24,5 @@ _\.new\.png$ ^tarballs$ ^revdep ^revdep$ +^manifest\.json$ +^\.rscignore$ diff --git a/DESCRIPTION b/DESCRIPTION index 00be49235..3b2a1d3ba 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: riskassessment Title: A web app designed to interface with the `riskmetric` package -Version: 3.0.0.9015 +Version: 3.0.0.9016 Authors@R: c( person("Aaron", "Clark", role = c("aut", "cre"), email = "aaron.clark@biogen.com"), person("Robert", "Krajcik", role = "aut", email = "robert.krajcik@biogen.com"), @@ -81,7 +81,8 @@ Suggests: shinytest2, spelling, testthat (>= 3.0.0), - tinytex + tinytex, + withr Config/testthat/edition: 3 Language: en-US Depends: diff --git a/NEWS.md b/NEWS.md index 157457efe..93a65ba58 100644 --- a/NEWS.md +++ b/NEWS.md @@ -15,6 +15,7 @@ * Move package upload process to a function * Updated logo in reports to lighter version to match application UI * Utilize `{archive}` to parse DESCRIPTION file in package upload process +* Incorporates table to show reverse dependencies included in the database # riskassessment 3.0.0 diff --git a/R/global.R b/R/global.R index dc673cdb3..cffa1c1bc 100644 --- a/R/global.R +++ b/R/global.R @@ -42,6 +42,7 @@ utils::globalVariables( 'downloads', 'ea_v', 'estimate', + 'explore_metrics', 'func', 'have_changed', 'Last modified', @@ -69,6 +70,7 @@ utils::globalVariables( 'rpt_choices', 'score', 'setNames', + 'status', 'succ_icon', 'text', 'token', diff --git a/R/mod_addComment.R b/R/mod_addComment.R index 59b594595..5a632c2af 100644 --- a/R/mod_addComment.R +++ b/R/mod_addComment.R @@ -57,7 +57,7 @@ addCommentServer <- function(id, metric_abrv, user, credentials, pkg_name) { observeEvent(input$submit_comment, { req(input$add_comment) - req("general_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("general_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) comment <- trimws(input$add_comment) diff --git a/R/mod_code_explorer.R b/R/mod_code_explorer.R index 646adac08..0a74d3337 100644 --- a/R/mod_code_explorer.R +++ b/R/mod_code_explorer.R @@ -73,7 +73,7 @@ mod_code_explorer_server <- function(id, selected_pkg, pkgarchive = reactiveVal( ), br(), br(), div(id = ns("comments_for_fe"), fluidRow( - if ("general_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) addCommentUI(id = ns("add_comment")), + if ("general_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) addCommentUI(id = ns("add_comment")), viewCommentsUI(id = ns("view_comments")))) ) } diff --git a/R/mod_communityMetrics.R b/R/mod_communityMetrics.R index fb1973303..c9b812e28 100644 --- a/R/mod_communityMetrics.R +++ b/R/mod_communityMetrics.R @@ -51,7 +51,7 @@ communityMetricsServer <- function(id, selected_pkg, community_metrics, user, cr plotly::plotlyOutput(NS(id, "downloads_plot"), height = "500px")))), br(), br(), div(id = "comments_for_cum", fluidRow( - if ("general_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) addCommentUI(id = session$ns("add_comment")), + if ("general_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) addCommentUI(id = session$ns("add_comment")), viewCommentsUI(id = session$ns("view_comments")))) ) } diff --git a/R/mod_decision_automation.R b/R/mod_decision_automation.R index a676e62d9..b44e1d84e 100644 --- a/R/mod_decision_automation.R +++ b/R/mod_decision_automation.R @@ -428,7 +428,7 @@ mod_decision_automation_server <- function(id, user, credentials){ #### Outputs #### purrr::walk(c("auto_dropdown", "auto_dropdown2"), ~ observeEvent(input[[.x]], { - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) showModal(modalDialog( size = "l", @@ -515,7 +515,7 @@ mod_decision_automation_server <- function(id, user, credentials){ }) output$auto_classify <- renderUI({ - if ("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) { + if ("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) { tagList( br(),br(), hr(), @@ -540,7 +540,7 @@ mod_decision_automation_server <- function(id, user, credentials){ }) output$decision_rule_div <- renderUI({ - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) tagList( div( style = "display: flex", @@ -625,7 +625,7 @@ mod_decision_automation_server <- function(id, user, credentials){ output$auto_settings <- renderUI({ - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) div( style = "float: right;", @@ -637,7 +637,7 @@ mod_decision_automation_server <- function(id, user, credentials){ output$auto_settings2 <- renderUI({ - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) div( style = "float: right;", @@ -721,7 +721,7 @@ mod_decision_automation_server <- function(id, user, credentials){ }) observeEvent(input$submit_auto, { - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) showModal(modalDialog( size = "l", @@ -752,7 +752,7 @@ mod_decision_automation_server <- function(id, user, credentials){ }) observeEvent(input$submit_color, { - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) showModal(modalDialog( size = "l", @@ -784,7 +784,7 @@ mod_decision_automation_server <- function(id, user, credentials){ }) observeEvent(input$confirm_submit_auto, { - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) req(!disable_auto_submit()) out_lst <- purrr::compact(reactiveValuesToList(auto_decision)) @@ -829,7 +829,7 @@ mod_decision_automation_server <- function(id, user, credentials){ observeEvent(input$confirm_submit_col, { - req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) selected_colors <- decision_lst %>% diff --git a/R/mod_introJS.R b/R/mod_introJS.R index 06637d662..d96d9b716 100644 --- a/R/mod_introJS.R +++ b/R/mod_introJS.R @@ -37,7 +37,7 @@ introJSServer <- function(id, text, user, credentials) { moduleServer(id, function(input, output, session) { steps <- reactive({ - if(user$admin || "weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) { + if(user$admin || "weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) { apptab_steps <- bind_rows(apptab_admn, apptab_steps) } diff --git a/R/mod_maintenanceMetrics.R b/R/mod_maintenanceMetrics.R index 79cbd8900..fafc99390 100644 --- a/R/mod_maintenanceMetrics.R +++ b/R/mod_maintenanceMetrics.R @@ -39,7 +39,7 @@ maintenanceMetricsServer <- function(id, selected_pkg, maint_metrics, user, cred metricGridUI(NS(id, 'metricGrid')), br(), br(), fluidRow(div(id = "comments_for_mm", - if ("general_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) addCommentUI(NS(id, 'add_comment')), + if ("general_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) addCommentUI(NS(id, 'add_comment')), viewCommentsUI(NS(id, 'view_comments'))) ) ) diff --git a/R/mod_packageDependencies.R b/R/mod_packageDependencies.R index b3453e396..efadb7dba 100644 --- a/R/mod_packageDependencies.R +++ b/R/mod_packageDependencies.R @@ -57,7 +57,7 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { req(selected_pkg$name() != "-") req(tabready() == 1L) - get_assess_blob(selected_pkg$name()) + get_assess_blob(selected_pkg$name(), metric_lst = c("dependencies", "suggests", "reverse_dependencies")) }) observeEvent(list(pkgref(), toggled()), { @@ -92,6 +92,7 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { if (rlang::is_empty(pkgref()$dependencies[[1]])) depends(dplyr::tibble(package = character(0), type = character(0), name = character(0))) revdeps(pkgref()$reverse_dependencies[[1]] %>% as.vector()) + # send either depends() or both to build_dep_cards(), depending on toggled() if (toggled() == 0L) { cards(build_dep_cards(data = depends(), loaded = session$userData$loaded2_db()$name, toggled = 0L)) @@ -147,24 +148,39 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { }, ignoreInit = TRUE) data_table <- eventReactive(pkg_df(), { - cbind( - pkg_df(), - data.frame( - Actions = shinyInput( - actionButton, nrow(pkg_df()), - "button_", - size = "xs", - style = "height:24px; padding-top:1px;", - label = icon("arrow-right", class = "fa-regular", lib = "font-awesome"), - onclick = paste0('Shiny.setInputValue(\"', ns("select_button"), '\", this.id, {priority: "event"})') - ) - ) - ) %>% # remove action button if there is nothing to review + add_buttons_to_table(pkg_df(), ns("select_button")) %>% + # remove action button if there is nothing to review mutate(Actions = if_else(identical(package, character(0)) | name %in% c(rownames(installed.packages(priority = "base"))), "", Actions)) %>% # if package name not yet loaded, switch the actionbutton to fa-upload mutate(Actions = if_else(!name %in% session$userData$loaded2_db()$name, gsub("fas fa-arrow-right fa-regular", "fas fa-upload fa-solid", Actions), Actions)) }) + revdeps_local <- reactive({ + df <- session$userData$loaded2_db() + req(df, df$name) + + revdeps <- + df %>% + dplyr::filter(name %in% revdeps()) %>% + dplyr::pull(name) %>% + get_assess_blob(metric_lst = "suggests") + + if (nrow(revdeps) == 0) return(dplyr::left_join(bind_rows(revdeps, list(suggests = logical())), df, by = "name")) + + revdeps %>% + dplyr::mutate(suggests = purrr::map_lgl(suggests, ~ any(grepl(selected_pkg$name(), .x$package)))) %>% + dplyr::left_join(df, by = "name") + }) + + table_revdeps_local <- reactive({ + req(revdeps_local()) + + revdeps_local() %>% + dplyr::filter(toggled() == 1L | !suggests) %>% + dplyr::select(-suggests) %>% + add_buttons_to_table(ns("go_to_revdep")) + }) + # Create metric grid card. metricGridServer(id = 'metricGrid', metrics = cards) @@ -176,7 +192,7 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { showHelperMessage() } else { req(depends()) - + fluidPage( shiny:: tagList( @@ -217,65 +233,27 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { fluidRow( column( width = 8, - DT::renderDataTable(server = FALSE, { - # Hiding name from DT table. target contains index for "name" - # The - 1 is because js uses 0 index instead of 1 like R - target <- which(names(data_table()) %in% c("name")) - 1 - - formattable::as.datatable( - formattable::formattable( - data_table(), - list( - score = formattable::formatter( - "span", - style = x ~ formattable::style( - display = "block", - "border-radius" = "4px", - "padding-right" = "4px", - "color" = "#000000", - "order" = x, - "background-color" = formattable::csscolor( - setColorPalette(100)[round(as.numeric(x)*100)] - ) - ) - ), - decision = formattable::formatter( - "span", - style = x ~ formattable::style( - display = "block", - "border-radius" = "4px", - "padding-right" = "4px", - "font-weight" = "bold", - "color" = ifelse(x %in% decision_lst, "white", "inherit"), - "background-color" = - ifelse(x %in% decision_lst, - color_lst[x], - "transparent" - ) - ) - ) - ) - ), - selection = "none", - colnames = c("Package", "Type", "Name", "Version", "Score", "Review Package"), - rownames = FALSE, - options = list( - lengthMenu = list(c(15, -1), c("15", "All")), - columnDefs = list(list(visible = FALSE, targets = target)), - searchable = FALSE - ), - style = "default" - ) %>% - DT::formatStyle(names(data_table()), textAlign = "center") - }) - ) # column - ), # fluidRow - br(), - h4(glue::glue("Reverse Dependencies: {length(revdeps())}"), style = "text-align: left;"), + DT::renderDataTable(server = FALSE, { + datatable_custom(data_table()) + }) + ) + ), br(), br(), + h3(glue::glue("All reverse Dependencies: {length(revdeps())}"), style = "text-align: left;"), + br(), fluidRow( column( width = 8, + h4(glue::glue("Reverse Dependencies available in database: {nrow(table_revdeps_local()) %||% 0}"), style = "text-align: left;"), + br(), + DT::renderDataTable({ + datatable_custom( + table_revdeps_local(), + colnames = c("Package", "Version", "Score", "Review Package"), + hide_names = NULL + ) + }), + br(), br(), wellPanel( renderText(revdeps() %>% sort()), style = "max-height: 500px; overflow: auto" @@ -287,21 +265,30 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { } }) # renderUI + # the package selected in the table to either browser to or to upload: + selected_package <- reactiveVal() pkgname <- reactiveVal() + observeEvent(input$select_button, { + req(pkg_df()) + selectedRow <- as.numeric(strsplit(input$select_button, "_")[[1]][2]) + selected_package(with(pkg_df(), name[selectedRow])) + pkgname("-") + }) + observeEvent(input$go_to_revdep, { + req(table_revdeps_local()) + selectedRow <- as.numeric(strsplit(input$go_to_revdep, "_")[[1]][2]) + selected_package(with(table_revdeps_local(), name[selectedRow])) + }) - observeEvent(input$select_button, + observeEvent(selected_package(), { - req(pkg_df()) rev_pkg(0L) - - selectedRow <- as.numeric(strsplit(input$select_button, "_")[[1]][2]) - - # grab the package name - pkg_name <- pkg_df()[selectedRow, 3] %>% pull() pkgname("-") - - if (!pkg_name %in% session$userData$loaded2_db()$name) { - pkgname(pkg_name) + # to ensure that if the same package is clicked on, this observeEvent will + # run again: + on.exit(selected_package(NULL)) + if (!selected_package() %in% session$userData$loaded2_db()$name) { + pkgname(selected_package()) shiny::showModal(modalDialog( size = "l", easyClose = TRUE, @@ -311,7 +298,7 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { fluidRow( column( width = 12, - "Please confirm to load this package: ", span(class = "text-info", input$decision), + "Please confirm to load this package: ", span(class = "text-info", selected_package()), ) ), br(), @@ -326,7 +313,7 @@ packageDependenciesServer <- function(id, selected_pkg, user, parent) { session = parent, inputId = "sidebar-select_pkg", choices = c("-", session$userData$loaded2_db()$name), - selected = pkg_name + selected = selected_package() ) } }, diff --git a/R/mod_pkg_explorer.R b/R/mod_pkg_explorer.R index 0fa0eb518..e34d4b4cf 100644 --- a/R/mod_pkg_explorer.R +++ b/R/mod_pkg_explorer.R @@ -68,7 +68,7 @@ mod_pkg_explorer_server <- function(id, selected_pkg, ), br(), br(), div(id = ns("comments_for_se"), fluidRow( - if ("general_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) addCommentUI(id = ns("add_comment")), + if ("general_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) addCommentUI(id = ns("add_comment")), viewCommentsUI(id = ns("view_comments")))), id = id ) diff --git a/R/mod_reportPreview.R b/R/mod_reportPreview.R index a78aa7717..652943946 100644 --- a/R/mod_reportPreview.R +++ b/R/mod_reportPreview.R @@ -82,7 +82,7 @@ reportPreviewServer <- function(id, selected_pkg, maint_metrics, com_metrics, br(), br(), - if ("overall_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + if ("overall_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) div(id = NS(id, "pkg-summary-grp"), # Compose pkg summary - either disabled, enabled, or pre-populated uiOutput(NS(id, "pkg_summary_ui")), diff --git a/R/mod_reweightView.R b/R/mod_reweightView.R index 4cba4e44b..4602b3b0c 100644 --- a/R/mod_reweightView.R +++ b/R/mod_reweightView.R @@ -46,7 +46,7 @@ reweightViewServer <- function(id, user, decision_list, credentials) { ) observeEvent(input$update_weight, { - req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) curr_new_wts(save$data %>% dplyr::mutate(new_weight = ifelse(name == isolate(input$metric_name), isolate(input$metric_weight), new_weight))) @@ -184,7 +184,7 @@ reweightViewServer <- function(id, user, decision_list, credentials) { # Update metric weight dropdown so that it matches the metric name. observeEvent(input$metric_name, { - req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) shinyjs::disable("update_weight") updateNumericInput(session, "metric_weight", @@ -199,14 +199,14 @@ reweightViewServer <- function(id, user, decision_list, credentials) { # Note that another of the observeEvents will update the metric weight after # the selected metric name is updated. observeEvent(input$weights_table_rows_selected, { - req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) updateSelectInput(session, "metric_name", selected = curr_new_wts()$name[input$weights_table_rows_selected]) }) # Save new weight into db. observeEvent(input$update_pkg_risk, { - req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) # if you the user goes input$back2dash, then when they return to the if(n_wts_chngd() == 0){ @@ -243,7 +243,7 @@ reweightViewServer <- function(id, user, decision_list, credentials) { # Upon confirming the risk re-calculation observeEvent(input$confirm_update_risk, { - req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("weight_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) removeModal() session$userData$trigger_events[["reset_pkg_upload"]] <- session$userData$trigger_events[["reset_pkg_upload"]] + 1 diff --git a/R/mod_sidebar.R b/R/mod_sidebar.R index 1f4356eea..24548c84a 100644 --- a/R/mod_sidebar.R +++ b/R/mod_sidebar.R @@ -224,7 +224,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { # Update db if comment is submitted. observeEvent(input$submit_overall_comment, { - req("overall_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("overall_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) current_comment <- trimws(input$overall_comment) @@ -277,7 +277,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { observeEvent(input$submit_overall_comment_yes, { req(selected_pkg$name) - req("overall_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("overall_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) dbUpdate( "UPDATE comments @@ -334,7 +334,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { observeEvent(req(input$select_ver, session$userData$trigger_events$reset_sidebar), { if (input$select_pkg != "-" && input$select_ver != "-" && (rlang::is_empty(selected_pkg$decision) || is.na(selected_pkg$decision)) && - "overall_comment" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) { + "overall_comment" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) { shinyjs::enable("overall_comment") shinyjs::enable("submit_overall_comment") @@ -345,7 +345,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { }, ignoreInit = TRUE) observeEvent(req(input$select_ver, session$userData$trigger_events$reset_sidebar), { - req("final_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("final_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) if (input$select_pkg != "-" && input$select_ver != "-" && (rlang::is_empty(selected_pkg$decision) || is.na(selected_pkg$decision))) { @@ -363,7 +363,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { if (!(input$select_pkg == "-" && input$select_ver == "-" || (rlang::is_empty(selected_pkg$decision) || is.na(selected_pkg$decision))) && - "revert_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) { + "revert_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) { shinyjs::hide("submit_decision") } else { shinyjs::show("submit_decision") @@ -375,7 +375,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { output$reset_decision_ui <- renderUI({ req(user$role) req(credentials$privileges) - req("revert_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("revert_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) req(!(input$select_pkg == "-" && input$select_ver == "-" || (rlang::is_empty(selected_pkg$decision) || is.na(selected_pkg$decision)))) @@ -385,7 +385,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { # Show a confirmation modal when submitting a decision. observeEvent(input$submit_decision, { req(input$decision) - req("final_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("final_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) showModal(modalDialog( size = "l", @@ -412,7 +412,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { # Show a confirmation modal when resetting a decision observeEvent(input$reset_decision, { req(input$decision) - req("revert_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("revert_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) showModal(modalDialog( size = "l", @@ -440,7 +440,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { # Update database info after decision is submitted. observeEvent(input$submit_confirmed_decision, { - req("final_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("final_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) dbUpdate("UPDATE package SET decision_id = {match(input$decision, golem::get_golem_options(\"decision_categories\"))}, decision_by = {user$name}, decision_date = {get_Date()} @@ -462,7 +462,7 @@ sidebarServer <- function(id, user, uploaded_pkgs, credentials) { }) observeEvent(input$reset_confirmed_decision, { - req("revert_decision" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("revert_decision" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) dbUpdate("UPDATE package SET decision_id = NULL, decision_by = '', decision_date = NULL diff --git a/R/mod_uploadPackage.R b/R/mod_uploadPackage.R index 7d3a7f55c..80e3665a8 100644 --- a/R/mod_uploadPackage.R +++ b/R/mod_uploadPackage.R @@ -94,7 +94,7 @@ uploadPackageServer <- function(id, user, auto_list, credentials, parent) { req(user$role) req(credentials$privileges) - if ("add_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) { + if ("add_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) { shinyjs::enable("pkg_lst") shinyjs::enable("add_pkgs") shinyjs::enable("uploaded_file") @@ -108,7 +108,7 @@ uploadPackageServer <- function(id, user, auto_list, credentials, parent) { }) output$upload_format_lnk <- renderUI({ - req("add_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("add_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) actionLink(NS(id, "upload_format"), "View Sample Dataset") }) @@ -119,9 +119,9 @@ uploadPackageServer <- function(id, user, auto_list, credentials, parent) { dplyr::bind_rows( upload_pkg, - if ("add_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) upload_pkg_add, - if ("delete_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) upload_pkg_delete, - if ("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) upload_pkg_dec_adj, + if ("add_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) upload_pkg_add, + if ("delete_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) upload_pkg_delete, + if ("auto_decision_adjust" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) upload_pkg_dec_adj, if (nrow(uploaded_pkgs()) > 0) upload_pkg_comp ) }) @@ -165,7 +165,7 @@ uploadPackageServer <- function(id, user, auto_list, credentials, parent) { output$rem_pkg_div <- renderUI({ req(user$role) req(credentials$privileges) - req("delete_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("delete_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) session$onFlushed(function() { shinyjs::runjs(glue::glue('$("#{NS(id, "rem_pkg_btn")}").css("margin-top", $("#{NS(id, "rem_pkg_lst")}-label")[0].scrollHeight + .5*parseFloat(getComputedStyle(document.documentElement).fontSize))')) @@ -246,7 +246,7 @@ uploadPackageServer <- function(id, user, auto_list, credentials, parent) { }) observeEvent(input$rem_pkg_btn, { - req("delete_package" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("delete_package" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) np <- length(input$rem_pkg_lst) uploaded_packages <- diff --git a/R/mod_user_roles.R b/R/mod_user_roles.R index 1ec407d9e..05c8b75b4 100644 --- a/R/mod_user_roles.R +++ b/R/mod_user_roles.R @@ -323,7 +323,7 @@ mod_user_roles_server <- function(id, user, credentials){ }) observeEvent(input$submit_changes, { - req("admin" %in% unlist(credentials$privileges[user$role], use.name = FALSE)) + req("admin" %in% unlist(credentials$privileges[user$role], use.names = FALSE)) chng_lst <- dplyr::filter(role_changes(), paste(old_role) != paste(new_role)) purrr::pmap(chng_lst, function(old_role, new_role) { diff --git a/R/utils.R b/R/utils.R index 2e22bbda9..d17be6194 100644 --- a/R/utils.R +++ b/R/utils.R @@ -541,4 +541,129 @@ shinyInput <- function(FUN, len, id, ...) { inputs[i] <- as.character(FUN(paste0(id, i), ...)) } inputs -} \ No newline at end of file +} + +#' Custom datatable +#' +#' Small helper function to create a `DT::datatable()` object in a consistent +#' style. +#' +#' @param data A data frame as input. +#' @param colnames +#' @param hide_names Character vector. Whether to hide columns in the data +#' frame. +#' @param ... Other options. Currently not in use. +#' +#' @return a DT::datatable object. +#' +#' @examples datatable_custom(mtcars, colnames = paste0("custom_", names(mtcars))) +#' +#' @noRd +#' +datatable_custom <- function( + data, + colnames = c("Package", "Type", "Name", "Version", "Score", "Review Package"), + hide_names = "name", + ... +){ + colnames <- colnames %||% character(0) + hide_names <- hide_names %||% character(0) + data <- data %||% as.data.frame(matrix(nrow = 0, ncol = pmax(length(colnames), 1) )) + stopifnot(is.data.frame(data)) + stopifnot(is.character(hide_names)) + stopifnot(is.character(colnames)) + colnames <- if(length(colnames) == 0) names(data) else colnames + if(length(colnames) != ncol(data)) { + warning("number of provided colnames unequal to number of columns in data. + Defaulting to original data frame names.") + colnames <- names(data) + } + # Hiding name from DT table. + # The - 1 is because js uses 0 index instead of 1 like R + target <- which(names(data) %in% hide_names) - 1 + + formattable::as.datatable( + formattable::formattable( + data, + list( + score = formattable::formatter( + "span", + style = x ~ formattable::style( + display = "block", + "border-radius" = "4px", + "padding-right" = "4px", + "color" = "#000000", + "order" = x, + "background-color" = formattable::csscolor( + setColorPalette(100)[round(as.numeric(x)*100)] + ) + ) + ), + decision = formattable::formatter( + "span", + style = x ~ formattable::style( + display = "block", + "border-radius" = "4px", + "padding-right" = "4px", + "font-weight" = "bold", + "color" = ifelse(x %in% decision_lst, "white", "inherit"), + "background-color" = + ifelse(x %in% decision_lst, + color_lst[x], + "transparent" + ) + ) + ) + ) + ), + selection = "none", + colnames = colnames, + rownames = FALSE, + options = list( + lengthMenu = list(c(15, -1), c("15", "All")), + columnDefs = list(list(visible = FALSE, targets = target)), + searchable = FALSE + ), + style = "default" + ) %>% + DT::formatStyle(names(data), textAlign = "center") +} + +#' Add buttons to data frame +#' +#' Small helper function to add Shiny action buttons to a data frame. +#' +#' @param data A data frame. +#' @param id Character vector. the main id of the buttons. +#' @param label Label to use for the button. +#' @param ... For future expansions. Currently not in use. +#' +#' @return A data frame with a button in each table row. +#' +#' @examples +#' add_buttons_to_table(mtcars[, 1:5], "button_id", "click me") |> +#' datatable_custom() +#' +#' @noRd +#' +add_buttons_to_table <- function( + data, + id, + label = icon("arrow-right", class = "fa-regular", lib = "font-awesome"), + ... +){ + stopifnot(is.data.frame(data)) + cbind( + data, + data.frame( + Actions = shinyInput( + actionButton, nrow(data), + "button_", + size = "xs", + style = "height:24px; padding-top:1px;", + label = label, + onclick = paste0('Shiny.setInputValue(\"', id, '\", this.id, {priority: "event"})') + ) + ) + ) +} diff --git a/R/utils_build_cards.R b/R/utils_build_cards.R index bf8252596..c4dd4b667 100644 --- a/R/utils_build_cards.R +++ b/R/utils_build_cards.R @@ -147,7 +147,7 @@ build_comm_cards <- function(data){ # get reverse dependency info - rev_deps <- get_assess_blob(data$id[1])$reverse_dependencies[[1]] + rev_deps <- get_assess_blob(data$id[1], metric_lst = "reverse_dependencies")$reverse_dependencies[[1]] comm_rev <- comm %>% filter(name == "reverse_dependencies") # new diff --git a/R/utils_get_db.R b/R/utils_get_db.R index 2a49f3aa4..353e0bd19 100644 --- a/R/utils_get_db.R +++ b/R/utils_get_db.R @@ -14,13 +14,15 @@ #' @returns a data frame #' #' @noRd -dbSelect <- function(query, db_name = golem::get_golem_options('assessment_db_name'), .envir = parent.frame()){ +dbSelect <- function(query, db_name = golem::get_golem_options('assessment_db_name'), .envir = parent.frame(), params = NULL){ errFlag <- FALSE con <- DBI::dbConnect(RSQLite::SQLite(), db_name) tryCatch( expr = { rs <- DBI::dbSendQuery(con, glue::glue_sql(query, .envir = .envir, .con = con)) + if (!is.null(params)) + DBI::dbBind(rs, params) }, warning = function(warn) { message <- glue::glue("warning:\n {query} \nresulted in\n {warn}") @@ -229,7 +231,7 @@ get_metric_data <- function(pkg_name, metric_class = 'maintenance', db_name = go #' @noRd get_depends_data <- function(pkg_name, db_name = golem::get_golem_options('assessment_db_name')){ - pkgref <- get_assess_blob(pkg_name, db_name) + pkgref <- get_assess_blob(pkg_name, db_name, metric_lst = "dependencies") if(suppressWarnings(is.null(nrow(pkgref$dependencies[[1]])) || nrow(pkgref$dependencies[[1]]) == 0)) { dplyr::tibble(package = character(0), type = character(0), name = character(0)) @@ -300,19 +302,34 @@ get_metric_weights <- function(db_name = golem::get_golem_options('assessment_db #' #' Retrieves metric name and current weight from metric table #' -#' @param pkg_name character name of the package +#' @param pkg_lst character name of the package #' @param db_name character name (and file path) of the database +#' @param metric_lst character name of metric #' #' @returns a data frame #' @noRd -get_assess_blob <- function(pkg_name, db_name = golem::get_golem_options('assessment_db_name')) { - db_table <- dbSelect("SELECT metric.name, package_metrics.encode FROM package +#' +#' @import dplyr +#' @importFrom purrr map pmap_dfc reduce +get_assess_blob <- function(pkg_lst, db_name = golem::get_golem_options('assessment_db_name'), + metric_lst = NA) { + if (length(pkg_lst) == 0) return(dplyr::tibble(name = character())) + + db_table <- dbSelect("SELECT package.name, metric.name metric, package_metrics.encode FROM package INNER JOIN package_metrics ON package.id = package_metrics.package_id INNER JOIN metric ON package_metrics.metric_id = metric.id - WHERE package.name = {pkg_name}", - db_name = db_name) + WHERE package.name = $pkg_name AND metric.name = COALESCE($metric_name, metric.name)", + db_name = db_name, + params = list(pkg_name = rep(pkg_lst, each = length(metric_lst)), + metric_name = rep(metric_lst, length(pkg_lst)))) - purrr::pmap_dfc(db_table, function(name, encode) {dplyr::tibble(unserialize(encode)) %>% purrr::set_names(name)}) + # This approach was used to avoid adding a dependency on tidyr to use pivot_wider + purrr::map(pkg_lst, ~ + db_table %>% + dplyr::filter(name == .x) %>% + purrr::pmap_dfc(function(name, metric, encode) {dplyr::tibble(!!metric := unserialize(encode))}) %>% + dplyr::mutate(name = .x, .before = 0)) %>% + purrr::reduce(dplyr::bind_rows) } diff --git a/tests/testthat/test-packageDependencies.R b/tests/testthat/test-packageDependencies.R index 483340eb3..bbb24f6c4 100644 --- a/tests/testthat/test-packageDependencies.R +++ b/tests/testthat/test-packageDependencies.R @@ -41,4 +41,51 @@ test_that("module can produce a table of package dependencies", { unlink("app_db_loc") rm(out_htm, id_strng, json, actual, expected, app_db_loc) -}) \ No newline at end of file +}) + +test_that( + "Feature 1. module packageDependencies selects all reverse dependencies + in the database. + Scenario 1. Given the selected package is 'dplyr', + and the packages names in the package database are 'dplyr' and 'dbplyr', + I expect that the package names 'plotly', 'admiral', 'dbplyr' and 'glue' are found in [revdeps], + and that [table_revdeps_local] contains the package name 'dbplyr', + and that [table_revdeps_local] contains an action button", + { + testargs <- list( + selected_pkg = list( + name = reactiveVal("dplyr") + ), + user = "test_user", + parent = reactiveValues( + input = reactiveValues( + tabs = "Package Metrics", + metric_type = "dep" + ) + ) + ) + + test_db_loc <- test_path("test-apps", "downloadHandler-app", + "dplyr_tidyr.sqlite") + temp_db_loc <- withr::local_tempfile(fileext = ".database") + file.copy(test_db_loc, temp_db_loc) + + # add test db location to the app session: + app_session <- MockShinySession$new() + app_session$options$golem_options <- list( + assessment_db_name = temp_db_loc + ) + app_session$userData$loaded2_db <- reactiveVal(dbSelect("SELECT name, version, score FROM package", temp_db_loc)) + + testServer(packageDependenciesServer, args = testargs, { + session$flushReact() + session$setInputs(incl_suggests = TRUE) + expect_true(all(c("plotly", "admiral", "dbplyr", "glue", "tidyr") %in% revdeps())) + expect_equal(table_revdeps_local()$name, "tidyr") + # the table contains an action button: + expect_true(grepl('button id=\"button_1\"',table_revdeps_local()$Actions)) + }, + session = app_session) + } +) + diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index a755a0cc0..5095b5406 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -165,3 +165,50 @@ test_that("Test that get_time() works", { test_that("Test that build_dep_cards() works", { skip("Placeholder for build_dep_cards()") }) + +test_that("add_buttons_to_table works", { + expect_no_error(add_buttons_to_table(mtcars, "testid", "click me")) +}) +test_that("add_buttons_to_table adds unique buttons to each table row", { + output <- add_buttons_to_table(mtcars, "testid", "click me") + # individual buttons are added to each row: + expected_button_ids <- paste0('