From affd61134f1907a4b28bf0d40ddf3c5678186eb8 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Thu, 18 Dec 2025 00:13:06 -0600 Subject: [PATCH 01/18] Adding option to handle big spec file, via MSstatsBig --- DESCRIPTION | 4 +-- R/module-loadpage-server.R | 67 ++++++++++++++++++++++++++++++++++++-- R/module-loadpage-ui.R | 18 +++++++++- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index abdfb0d..c085cbf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,8 +24,8 @@ Authors@R: c( person("Olga", "Vitek", email = "o.vitek@northeastern.edu", role = "aut")) License: Artistic-2.0 Depends: R (>= 4.2) -Imports: shiny, shinyBS, shinyjs, shinybusy, dplyr, ggplot2, plotly, data.table, Hmisc, - MSstats, MSstatsTMT, MSstatsPTM, MSstatsConvert, gplots, marray, DT, readxl, +Imports: shiny, shinyBS, shinyjs, shinybusy, dplyr, ggplot2, plotly, data.table, Hmisc, shinyFiles, + MSstats,MSstatsBig, MSstatsTMT, MSstatsPTM, MSstatsConvert, gplots, marray, DT, readxl, ggrepel, uuid, utils, stats, htmltools, methods, tidyr, grDevices, graphics, mockery, MSstatsBioNet, shinydashboard, arrow, tools, MSstatsResponse Suggests: diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index f6e1659..60f5f68 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -1,3 +1,4 @@ + #' Loadpage Server module for data selection and upload server. #' #' This function sets up the loadpage server where it consists of several, @@ -14,6 +15,33 @@ #' loadpageServer <- function(id, parent_session) { moduleServer(id, function(input, output, session) { + + # == shinyFiles LOGIC FOR LOCAL FILE BROWSER ================================= + # Define volumes for the file selection. + volumes <- shinyFiles::getVolumes()() + + # Server-side logic for the shinyFiles button + shinyFiles::shinyFileChoose(input, "specdata_big_browse", roots = volumes, session = session) + + # Reactive to parse and store the full file information (path, name, etc.) + # This is efficient because parseFilePaths is only called once. + local_spec_file_info <- reactive({ + req(is.list(input$specdata_big_browse)) + shinyFiles::parseFilePaths(volumes, input$specdata_big_browse) + }) + + # Reactive to get just the full datapath, for use in backend processing. + local_path_spec <- reactive({ + path_info <- local_spec_file_info() + if (nrow(path_info) > 0) path_info$datapath else NULL + }) + + # Render just the filename for user feedback in the UI. + output$specdata_big_path <- renderPrint({ + req(nrow(local_spec_file_info()) > 0) + cat(local_spec_file_info()$name) + }) + # toggle ui (DDA DIA SRM) observe({ print("bio") @@ -103,7 +131,9 @@ loadpageServer <- function(id, parent_session) { enable("proceed1") } } else if (input$filetype == "spec") { - if(!is.null(input$specdata) && !is.null(input$sep_specdata)) { # && !is.null(input$annot) + spec_regular_file_ok <- !isTRUE(input$big_file_spec) && !is.null(input$specdata) + spec_big_file_ok <- isTRUE(input$big_file_spec) && length(local_path_spec()) > 0 + if((spec_regular_file_ok || spec_big_file_ok) && !is.null(input$sep_specdata)) { enable("proceed1") } } else if (input$filetype == "ump") { @@ -221,7 +251,40 @@ loadpageServer <- function(id, parent_session) { get_data = eventReactive(input$proceed1, { - getData(input) + # Check if the filetype is Spectronaut AND the big file checkbox is checked + if (input$filetype == "spec" && isTRUE(input$big_file_spec)) { + # Ensure a file has been selected via the local browser + req(length(local_path_spec()) > 0) + + # Show a busy indicator as this might take time + shinybusy::show_modal_spinner( + spin = "fading-circle", + text = "Processing large Spectronaut file..." + ) + + # Define the output path using a temporary file. + #currently can't use the timeporary file because bigSpectronauttoMSstatsFormat modifies the filepath internally + #temp_output_path <- tempfile(fileext = ".csv") + + + + # Call the big file conversion function from MSstatsConvert + converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( + input = local_path_spec(), + output = "output_file.csv", + backend = "arrow" + ) + + converted_data <- dplyr::collect(converted_data) + # Remove the busy indicator + shinybusy::remove_modal_spinner() + + return(converted_data) + } else { + # For all other cases (non-spec files, or standard spec uploads), + # use the existing getData function. + getData(input) + } }) diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index 9020459..a95931d 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -285,7 +285,23 @@ create_spectronaut_uploads <- function(ns) { conditionalPanel( condition = "input['loadpage-filetype'] == 'spec' && input['loadpage-BIO'] != 'PTM'", h4("4. Upload MSstats scheme output from Spectronaut"), - fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL), + + # Checkbox to switch between upload methods + checkboxInput(ns("big_file_spec"), "Use local file browser (for large files)"), + + # Standard fileInput, shown when checkbox is NOT checked + conditionalPanel( + condition = "!input['loadpage-big_file_spec']", + fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) + ), + + # Local browser button, shown when checkbox IS checked + conditionalPanel( + condition = "input['loadpage-big_file_spec']", + shinyFiles::shinyFilesButton(ns("specdata_big_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("specdata_big_path")) + ), + create_separator_buttons(ns, "sep_specdata") ) } From 7ec393843fe496a0581b8a431c6eb4cde1e2b28c Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Thu, 25 Dec 2025 18:53:30 -0500 Subject: [PATCH 02/18] Updating the method for loadpageserver() to indicate if we are running on a server or locally Updated UI for big spec file --- R/module-loadpage-server.R | 147 +++++++++++++++++++++++++++++-------- R/module-loadpage-ui.R | 21 +++++- R/server.R | 2 +- man/loadpageServer.Rd | 4 +- 4 files changed, 139 insertions(+), 35 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 60f5f68..0a47f3a 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -1,3 +1,16 @@ +#' Decide which data loading path to use +#' +#' @param filetype The selected file type from the UI +#' @param is_big_file Boolean indicating if the 'big file' checkbox is checked +#' @return string "big_spectronaut" or "standard" +#' @noRd +.get_data_source_type <- function(filetype, is_big_file) { + if (filetype == "spec" && isTRUE(is_big_file)) { + "big_spectronaut" + } else { + "standard" + } +} #' Loadpage Server module for data selection and upload server. #' @@ -6,6 +19,7 @@ #' #' @param id namespace prefix for the module #' @param parent_session session of the main calling module +#' @param is_web_server boolean indicating if the app is running on a web server #' #' @return input object with user selected options #' @@ -13,33 +27,85 @@ #' @examples #' NA #' -loadpageServer <- function(id, parent_session) { +loadpageServer <- function(id, parent_session, is_web_server = FALSE) { moduleServer(id, function(input, output, session) { # == shinyFiles LOGIC FOR LOCAL FILE BROWSER ================================= # Define volumes for the file selection. - volumes <- shinyFiles::getVolumes()() - - # Server-side logic for the shinyFiles button - shinyFiles::shinyFileChoose(input, "specdata_big_browse", roots = volumes, session = session) - - # Reactive to parse and store the full file information (path, name, etc.) - # This is efficient because parseFilePaths is only called once. - local_spec_file_info <- reactive({ - req(is.list(input$specdata_big_browse)) - shinyFiles::parseFilePaths(volumes, input$specdata_big_browse) - }) - - # Reactive to get just the full datapath, for use in backend processing. - local_path_spec <- reactive({ - path_info <- local_spec_file_info() - if (nrow(path_info) > 0) path_info$datapath else NULL - }) + if (!is_web_server) { + volumes <- shinyFiles::getVolumes()() + + # Server-side logic for the shinyFiles button + shinyFiles::shinyFileChoose(input, "specdata_big_browse", roots = volumes, session = session) + + # Reactive to parse and store the full file information (path, name, etc.) + # This is efficient because parseFilePaths is only called once. + local_spec_file_info <- reactive({ + req(is.list(input$specdata_big_browse)) + shinyFiles::parseFilePaths(volumes, input$specdata_big_browse) + }) + + # Reactive to get just the full datapath, for use in backend processing. + local_path_spec <- reactive({ + path_info <- local_spec_file_info() + if (nrow(path_info) > 0) path_info$datapath else NULL + }) + + # Render just the filename for user feedback in the UI. + output$specdata_big_path <- renderPrint({ + req(nrow(local_spec_file_info()) > 0) + cat(local_spec_file_info()$name) + }) + } else { + local_path_spec <- reactive({ NULL }) + } - # Render just the filename for user feedback in the UI. - output$specdata_big_path <- renderPrint({ - req(nrow(local_spec_file_info()) > 0) - cat(local_spec_file_info()$name) + output$spectronaut_upload_ui <- renderUI({ + req(input$filetype == 'spec', input$BIO != 'PTM') + + if (!is_web_server) { + tagList( + h4("4. Upload MSstats scheme output from Spectronaut"), + + # Checkbox to switch between upload methods + checkboxInput(session$ns("big_file_spec"), "Use local file browser (for large files)"), + + # Standard fileInput, shown when checkbox is NOT checked + conditionalPanel( + condition = paste0("!input['", session$ns("big_file_spec"), "']"), + fileInput(session$ns('specdata'), "", multiple = FALSE, accept = NULL) + ), + + # Local browser button, shown when checkbox IS checked + conditionalPanel( + condition = paste0("input['", session$ns("big_file_spec"), "']"), + shinyFiles::shinyFilesButton(session$ns("specdata_big_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(session$ns("specdata_big_path")) + ), + + radioButtons(session$ns("sep_specdata"), + label = h5("Column separator in uploaded file", class = "icon-wrapper", + icon("question-circle", lib = "font-awesome"), + div("Choose how columns are separated in the uploaded file", class = "icon-tooltip")), + c(Comma=",", Semicolon=";", Tab="\t", Pipe="|"), + inline = TRUE + ) + ) + } else { + tagList( + h4("4. Upload MSstats scheme output from Spectronaut"), + + fileInput(session$ns('specdata'), "", multiple = FALSE, accept = NULL), + + radioButtons(session$ns("sep_specdata"), + label = h5("Column separator in uploaded file", class = "icon-wrapper", + icon("question-circle", lib = "font-awesome"), + div("Choose how columns are separated in the uploaded file", class = "icon-tooltip")), + c(Comma=",", Semicolon=";", Tab="\t", Pipe="|"), + inline = TRUE + ) + ) + } }) # toggle ui (DDA DIA SRM) @@ -251,8 +317,9 @@ loadpageServer <- function(id, parent_session) { get_data = eventReactive(input$proceed1, { - # Check if the filetype is Spectronaut AND the big file checkbox is checked - if (input$filetype == "spec" && isTRUE(input$big_file_spec)) { + data_source <- .get_data_source_type(input$filetype, input$big_file_spec) + + if (data_source == "big_spectronaut") { # Ensure a file has been selected via the local browser req(length(local_path_spec()) > 0) @@ -266,16 +333,38 @@ loadpageServer <- function(id, parent_session) { #currently can't use the timeporary file because bigSpectronauttoMSstatsFormat modifies the filepath internally #temp_output_path <- tempfile(fileext = ".csv") - # Call the big file conversion function from MSstatsConvert converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( - input = local_path_spec(), - output = "output_file.csv", - backend = "arrow" + input_file = local_path_spec(), + output_file_name = "output_file.csv", + backend = "arrow", + filter_by_excluded = input$filter_by_excluded, + filter_by_identified = input$filter_by_identified, + filter_by_qvalue = input$filter_by_qvalue, + qvalue_cutoff = input$qvalue_cutoff, + max_feature_count = input$max_feature_count, + filter_unique_peptides = input$filter_unique_peptides, + aggregate_psms = input$aggregate_psms, + filter_few_obs = input$filter_few_obs ) - converted_data <- dplyr::collect(converted_data) + # Attempt to load the data into memory. + # If the data is too large for RAM, this will catch the allocation error. + converted_data <- tryCatch({ + dplyr::collect(converted_data) + }, error = function(e) { + shinybusy::remove_modal_spinner() + showNotification( + paste("Memory Error: The dataset is too large to process in-memory.", e$message), + type = "error", + duration = NULL + ) + return(NULL) + }) + + if (is.null(converted_data)) return(NULL) + # Remove the busy indicator shinybusy::remove_modal_spinner() diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index a95931d..74dfd57 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -223,7 +223,7 @@ create_standard_uploads <- function(ns) { #' @noRd create_standard_annotation_uploads <- function(ns) { conditionalPanel( - condition = "(input['loadpage-filetype'] == 'sky' || input['loadpage-filetype'] == 'prog' || input['loadpage-filetype'] == 'PD' || input['loadpage-filetype'] == 'spec' || input['loadpage-filetype'] == 'open'|| input['loadpage-filetype'] =='spmin' || input['loadpage-filetype'] == 'phil' || input['loadpage-filetype'] == 'diann') && input['loadpage-BIO'] != 'PTM'", + condition = "(input['loadpage-filetype'] == 'sky' || input['loadpage-filetype'] == 'prog' || input['loadpage-filetype'] == 'PD' || (input['loadpage-filetype'] == 'spec' && !input['loadpage-big_file_spec']) || input['loadpage-filetype'] == 'open'|| input['loadpage-filetype'] =='spmin' || input['loadpage-filetype'] == 'phil' || input['loadpage-filetype'] == 'diann') && input['loadpage-BIO'] != 'PTM'", h4("5. Upload annotation File", class = "icon-wrapper", icon("question-circle", lib = "font-awesome"), div("Upload manually created annotation file. This file maps MS runs to experiment metadata (i.e. conditions, bioreplicates). Please see Help tab for information on creating this file.", class = "icon-tooltip")), @@ -299,7 +299,20 @@ create_spectronaut_uploads <- function(ns) { conditionalPanel( condition = "input['loadpage-big_file_spec']", shinyFiles::shinyFilesButton(ns("specdata_big_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("specdata_big_path")) + verbatimTextOutput(ns("specdata_big_path")), + tags$hr(), + h4("Options for large file processing"), + checkboxInput(ns("filter_by_excluded"), "Filter by excluded from quantification", value = FALSE), + checkboxInput(ns("filter_by_identified"), "Filter by identified", value = FALSE), + checkboxInput(ns("filter_by_qvalue"), "Filter by q-value", value = TRUE), + conditionalPanel( + condition = "input['loadpage-filter_by_qvalue']", + numericInput(ns("qvalue_cutoff"), "Q-value cutoff", value = 0.01, min = 0, max = 1, step = 0.01) + ), + numericInput(ns("max_feature_count"), "Max feature count", value = 20, min = 1), + checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = FALSE), + checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = FALSE), + checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = FALSE) ), create_separator_buttons(ns, "sep_specdata") @@ -502,14 +515,14 @@ create_tmt_options <- function(ns) { create_label_free_options <- function(ns) { tagList( conditionalPanel( - condition = "input['loadpage-filetype'] && input['loadpage-DDA_DIA'] == 'LType' && input['loadpage-filetype'] != 'sample' && input['loadpage-filetype'] != 'MRF'", + condition = "input['loadpage-filetype'] && input['loadpage-DDA_DIA'] == 'LType' && input['loadpage-filetype'] != 'sample' && input['loadpage-filetype'] != 'MRF' && (input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec'])", h4("Select the options for pre-processing"), checkboxInput(ns("unique_peptides"), "Use unique peptides", value = TRUE), checkboxInput(ns("remove"), "Remove proteins with 1 peptide and charge", value = FALSE) ), conditionalPanel( - condition = "input['loadpage-filetype'] && input['loadpage-DDA_DIA'] == 'LType' && input['loadpage-filetype'] != 'sample'", + condition = "input['loadpage-filetype'] && input['loadpage-DDA_DIA'] == 'LType' && input['loadpage-filetype'] != 'sample' && (input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec'])", checkboxInput(ns("remove"), "Remove proteins with 1 feature", value = FALSE), # Quality filtering options diff --git a/R/server.R b/R/server.R index 75d02cd..87d3b98 100644 --- a/R/server.R +++ b/R/server.R @@ -32,7 +32,7 @@ server = function(input, output, session) { selected = "Uploaddata") }) - loadpage_values = loadpageServer("loadpage", parent_session = session) + loadpage_values = loadpageServer("loadpage", parent_session = session, is_web_server = isWebServer) loadpage_input = loadpage_values$input get_data = loadpage_values$getData diff --git a/man/loadpageServer.Rd b/man/loadpageServer.Rd index 47aae4f..19d6f4e 100644 --- a/man/loadpageServer.Rd +++ b/man/loadpageServer.Rd @@ -4,12 +4,14 @@ \alias{loadpageServer} \title{Loadpage Server module for data selection and upload server.} \usage{ -loadpageServer(id, parent_session) +loadpageServer(id, parent_session, is_web_server = FALSE) } \arguments{ \item{id}{namespace prefix for the module} \item{parent_session}{session of the main calling module} + +\item{is_web_server}{boolean indicating if the app is running on a web server} } \value{ input object with user selected options From e07c645d467c1af794671431c0560aa72924d693 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Sun, 28 Dec 2025 12:58:31 -0500 Subject: [PATCH 03/18] Adding Test --- tests/testthat/test-loadpage-server.R | 21 ++++++++++++ tests/testthat/test-module-loadpage-ui.R | 41 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tests/testthat/test-loadpage-server.R diff --git a/tests/testthat/test-loadpage-server.R b/tests/testthat/test-loadpage-server.R new file mode 100644 index 0000000..12a7231 --- /dev/null +++ b/tests/testthat/test-loadpage-server.R @@ -0,0 +1,21 @@ +test_that(".get_data_source_type correctly identifies the data loading path", { + # This test checks the helper function that determines whether to use the + # standard data loader or the special loader for large Spectronaut files. + # This approach is simpler and more robust than a complex shiny::testServer test. + + # Case 1: Spectronaut file with the 'big file' checkbox checked + expect_equal(.get_data_source_type("spec", TRUE), "big_spectronaut") + + # Case 2: Spectronaut file without the 'big file' checkbox + expect_equal(.get_data_source_type("spec", FALSE), "standard") + + # Case 3: A non-Spectronaut file (should always be standard) + expect_equal(.get_data_source_type("maxq", FALSE), "standard") + + # Case 4: A non-Spectronaut file where the Spectronaut checkbox might be TRUE + # (though the UI should prevent this, the logic should be robust) + expect_equal(.get_data_source_type("maxq", TRUE), "standard") + + # Case 5: Input is NULL (initial state) + expect_equal(.get_data_source_type("spec", NULL), "standard") +}) diff --git a/tests/testthat/test-module-loadpage-ui.R b/tests/testthat/test-module-loadpage-ui.R index ee4b47a..c5fd723 100644 --- a/tests/testthat/test-module-loadpage-ui.R +++ b/tests/testthat/test-module-loadpage-ui.R @@ -392,4 +392,45 @@ test_that("file inputs have proper accept attributes", { annot_input <- create_standard_annotation_uploads(NS("test")) annot_html <- as.character(annot_input) expect_true(grepl('accept=.*csv', annot_html)) +}) + +test_that("Spectronaut big file UI is configured correctly", { + # This test checks for the existence and conditional logic of UI elements + # related to the Spectronaut big file workflow. + result <- loadpageUI("test") + html_output <- as.character(result) + + # 1. Check for the presence of the big file checkbox itself + expect_true(grepl("test-big_file_spec", html_output), + info = "Spectronaut 'big file' checkbox is missing.") + + # 2. Check that the new processing options are present in the HTML. + new_options_labels <- c( + "Filter by excluded from quantification", + "Filter by q-value", + "Max feature count", + "Aggregate PSMs to peptides" + ) + for(label in new_options_labels) { + expect_true(grepl(label, html_output), info = paste("Missing Spectronaut big file option:", label)) + } + + # 3. Verify the conditional logic for hiding/showing related panels. + # Note: HTML entities encode ' as ', && as &&, and || as || + + # Annotation upload should be hidden for big spec files. + # Condition contains: (input['loadpage-filetype'] == 'spec' && !input['loadpage-big_file_spec']) + expected_annot_condition <- "input['loadpage-filetype'] == 'spec' && !input['loadpage-big_file_spec']" + expect_true( + grepl(expected_annot_condition, html_output, fixed = TRUE), + info = "Conditional logic to hide annotation upload for big Spectronaut files is incorrect." + ) + + # Standard pre-processing should be hidden for big spec files. + # Condition contains: (input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec']) + expected_preprocess_condition <- "input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec']" + expect_true( + grepl(expected_preprocess_condition, html_output, fixed = TRUE), + info = "Conditional logic to hide pre-processing options for big Spectronaut files is incorrect." + ) }) \ No newline at end of file From 983265eee71417bd8718cb129986ea10f7e523ad Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 9 Jan 2026 16:38:35 -0600 Subject: [PATCH 04/18] Updating based on the feedback --- R/module-loadpage-server.R | 71 +++++++-------------------------- R/module-loadpage-ui.R | 81 +++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 88 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 0a47f3a..5f022bc 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -36,76 +36,35 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { volumes <- shinyFiles::getVolumes()() # Server-side logic for the shinyFiles button - shinyFiles::shinyFileChoose(input, "specdata_big_browse", roots = volumes, session = session) + shinyFiles::shinyFileChoose(input, "big_file_browse", roots = volumes, session = session) # Reactive to parse and store the full file information (path, name, etc.) # This is efficient because parseFilePaths is only called once. - local_spec_file_info <- reactive({ - req(is.list(input$specdata_big_browse)) - shinyFiles::parseFilePaths(volumes, input$specdata_big_browse) + local_file_info <- reactive({ + req(is.list(input$big_file_browse)) + shinyFiles::parseFilePaths(volumes, input$big_file_browse) }) # Reactive to get just the full datapath, for use in backend processing. - local_path_spec <- reactive({ - path_info <- local_spec_file_info() + local_big_file_path <- reactive({ + path_info <- local_file_info() if (nrow(path_info) > 0) path_info$datapath else NULL }) # Render just the filename for user feedback in the UI. output$specdata_big_path <- renderPrint({ - req(nrow(local_spec_file_info()) > 0) - cat(local_spec_file_info()$name) + req(nrow(local_file_info()) > 0) + cat(local_file_info()$name) }) - } else { - local_path_spec <- reactive({ NULL }) + } + else { + local_big_file_path <- reactive({ NULL }) } output$spectronaut_upload_ui <- renderUI({ req(input$filetype == 'spec', input$BIO != 'PTM') - if (!is_web_server) { - tagList( - h4("4. Upload MSstats scheme output from Spectronaut"), - - # Checkbox to switch between upload methods - checkboxInput(session$ns("big_file_spec"), "Use local file browser (for large files)"), - - # Standard fileInput, shown when checkbox is NOT checked - conditionalPanel( - condition = paste0("!input['", session$ns("big_file_spec"), "']"), - fileInput(session$ns('specdata'), "", multiple = FALSE, accept = NULL) - ), - - # Local browser button, shown when checkbox IS checked - conditionalPanel( - condition = paste0("input['", session$ns("big_file_spec"), "']"), - shinyFiles::shinyFilesButton(session$ns("specdata_big_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(session$ns("specdata_big_path")) - ), - - radioButtons(session$ns("sep_specdata"), - label = h5("Column separator in uploaded file", class = "icon-wrapper", - icon("question-circle", lib = "font-awesome"), - div("Choose how columns are separated in the uploaded file", class = "icon-tooltip")), - c(Comma=",", Semicolon=";", Tab="\t", Pipe="|"), - inline = TRUE - ) - ) - } else { - tagList( - h4("4. Upload MSstats scheme output from Spectronaut"), - - fileInput(session$ns('specdata'), "", multiple = FALSE, accept = NULL), - - radioButtons(session$ns("sep_specdata"), - label = h5("Column separator in uploaded file", class = "icon-wrapper", - icon("question-circle", lib = "font-awesome"), - div("Choose how columns are separated in the uploaded file", class = "icon-tooltip")), - c(Comma=",", Semicolon=";", Tab="\t", Pipe="|"), - inline = TRUE - ) - ) - } + create_spectronaut_ui_content(session$ns, is_web_server) }) # toggle ui (DDA DIA SRM) @@ -198,7 +157,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { } } else if (input$filetype == "spec") { spec_regular_file_ok <- !isTRUE(input$big_file_spec) && !is.null(input$specdata) - spec_big_file_ok <- isTRUE(input$big_file_spec) && length(local_path_spec()) > 0 + spec_big_file_ok <- isTRUE(input$big_file_spec) && length(local_big_file_path()) > 0 if((spec_regular_file_ok || spec_big_file_ok) && !is.null(input$sep_specdata)) { enable("proceed1") } @@ -321,7 +280,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { if (data_source == "big_spectronaut") { # Ensure a file has been selected via the local browser - req(length(local_path_spec()) > 0) + req(length(local_big_file_path()) > 0) # Show a busy indicator as this might take time shinybusy::show_modal_spinner( @@ -336,7 +295,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { # Call the big file conversion function from MSstatsConvert converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( - input_file = local_path_spec(), + input_file = local_big_file_path(), output_file_name = "output_file.csv", backend = "arrow", filter_by_excluded = input$filter_by_excluded, diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index 74dfd57..8ac1cf8 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -282,41 +282,58 @@ create_diann_uploads <- function(ns) { #' Create Spectronaut file uploads #' @noRd create_spectronaut_uploads <- function(ns) { - conditionalPanel( - condition = "input['loadpage-filetype'] == 'spec' && input['loadpage-BIO'] != 'PTM'", - h4("4. Upload MSstats scheme output from Spectronaut"), - - # Checkbox to switch between upload methods - checkboxInput(ns("big_file_spec"), "Use local file browser (for large files)"), - - # Standard fileInput, shown when checkbox is NOT checked - conditionalPanel( - condition = "!input['loadpage-big_file_spec']", - fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) - ), + uiOutput(ns("spectronaut_upload_ui")) +} - # Local browser button, shown when checkbox IS checked - conditionalPanel( - condition = "input['loadpage-big_file_spec']", - shinyFiles::shinyFilesButton(ns("specdata_big_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("specdata_big_path")), - tags$hr(), - h4("Options for large file processing"), - checkboxInput(ns("filter_by_excluded"), "Filter by excluded from quantification", value = FALSE), - checkboxInput(ns("filter_by_identified"), "Filter by identified", value = FALSE), - checkboxInput(ns("filter_by_qvalue"), "Filter by q-value", value = TRUE), +#' Create content for Spectronaut upload UI +#' @param ns Namespace function +#' @param is_web_server Boolean indicating if running on web server +#' @noRd +create_spectronaut_ui_content <- function(ns, is_web_server) { + if (!is_web_server) { + tagList( + h4("4. Upload MSstats scheme output from Spectronaut"), + + # Checkbox to switch between upload methods + checkboxInput(ns("big_file_spec"), "Large file mode"), + + # Standard fileInput, shown when checkbox is NOT checked conditionalPanel( - condition = "input['loadpage-filter_by_qvalue']", - numericInput(ns("qvalue_cutoff"), "Q-value cutoff", value = 0.01, min = 0, max = 1, step = 0.01) + condition = paste0("!input['", ns("big_file_spec"), "']"), + fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) ), - numericInput(ns("max_feature_count"), "Max feature count", value = 20, min = 1), - checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = FALSE), - checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = FALSE), - checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = FALSE) - ), - - create_separator_buttons(ns, "sep_specdata") - ) + + # Local browser button, shown when checkbox IS checked + conditionalPanel( + condition = paste0("input['", ns("big_file_spec"), "']"), + shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("specdata_big_path")), + tags$hr(), + h4("Options for large file processing"), + checkboxInput(ns("filter_by_excluded"), "Filter by excluded from quantification", value = FALSE), + checkboxInput(ns("filter_by_identified"), "Filter by identified", value = FALSE), + checkboxInput(ns("filter_by_qvalue"), "Filter by q-value", value = TRUE), + conditionalPanel( + condition = paste0("input['", ns("filter_by_qvalue"), "']"), + numericInput(ns("qvalue_cutoff"), "Q-value cutoff", value = 0.01, min = 0, max = 1, step = 0.01) + ), + numericInput(ns("max_feature_count"), "Max feature count", value = 20, min = 1), + checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = FALSE), + checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = FALSE), + checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = FALSE) + ), + + create_separator_buttons(ns, "sep_specdata") + ) + } else { + tagList( + h4("4. Upload MSstats scheme output from Spectronaut"), + + fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL), + + create_separator_buttons(ns, "sep_specdata") + ) + } } #' Create PTM FragPipe uploads From 2feabd146e322b719560f86a42e7285fe8909316 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Sat, 17 Jan 2026 14:11:16 -0600 Subject: [PATCH 05/18] Respoding to the comments in the PR --- R/module-loadpage-server.R | 119 +++++++++-------------- R/module-loadpage-ui.R | 107 ++++++++++---------- R/utils.R | 114 +++++++++++++++++----- tests/testthat/test-module-loadpage-ui.R | 87 +++++++++-------- tests/testthat/test-utils.R | 90 +++++++++++++++++ 5 files changed, 331 insertions(+), 186 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 5f022bc..975b6a6 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -1,17 +1,3 @@ -#' Decide which data loading path to use -#' -#' @param filetype The selected file type from the UI -#' @param is_big_file Boolean indicating if the 'big file' checkbox is checked -#' @return string "big_spectronaut" or "standard" -#' @noRd -.get_data_source_type <- function(filetype, is_big_file) { - if (filetype == "spec" && isTRUE(is_big_file)) { - "big_spectronaut" - } else { - "standard" - } -} - #' Loadpage Server module for data selection and upload server. #' #' This function sets up the loadpage server where it consists of several, @@ -61,10 +47,53 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { local_big_file_path <- reactive({ NULL }) } - output$spectronaut_upload_ui <- renderUI({ + output$spectronaut_header_ui <- renderUI({ + req(input$filetype == 'spec', input$BIO != 'PTM') + create_spectronaut_header() + }) + + output$spectronaut_file_selection_ui <- renderUI({ + req(input$filetype == 'spec', input$BIO != 'PTM') + + ui_elements <- tagList() + + if (!is_web_server) { + ui_elements <- tagList(ui_elements, create_spectronaut_mode_selector(session$ns, isTRUE(input$big_file_spec))) + + if (isTRUE(input$big_file_spec)) { + ui_elements <- tagList(ui_elements, create_spectronaut_large_file_ui(session$ns)) + } else { + ui_elements <- tagList(ui_elements, create_spectronaut_standard_ui(session$ns)) + } + } else { + ui_elements <- tagList(ui_elements, create_spectronaut_standard_ui(session$ns)) + } + + tagList(ui_elements, create_separator_buttons(session$ns, "sep_specdata")) + }) + + output$spectronaut_options_ui <- renderUI({ req(input$filetype == 'spec', input$BIO != 'PTM') - create_spectronaut_ui_content(session$ns, is_web_server) + if (!is_web_server && isTRUE(input$big_file_spec)) { + qval_def <- if (is.null(input$filter_by_qvalue)) TRUE else input$filter_by_qvalue + excluded_def <- if (is.null(input$filter_by_excluded)) FALSE else input$filter_by_excluded + identified_def <- if (is.null(input$filter_by_identified)) FALSE else input$filter_by_identified + cutoff_def <- if (is.null(input$qvalue_cutoff)) 0.01 else input$qvalue_cutoff + + max_feature_def <- if (is.null(input$max_feature_count)) 20 else input$max_feature_count + unique_peps_def <- if (is.null(input$filter_unique_peptides)) FALSE else input$filter_unique_peptides + agg_psms_def <- if (is.null(input$aggregate_psms)) FALSE else input$aggregate_psms + few_obs_def <- if (is.null(input$filter_few_obs)) FALSE else input$filter_few_obs + + tagList( + create_spectronaut_large_filter_options(session$ns, excluded_def, identified_def, qval_def), + if (qval_def) create_spectronaut_qvalue_cutoff_ui(session$ns, cutoff_def), + create_spectronaut_large_bottom_ui(session$ns, max_feature_def, unique_peps_def, agg_psms_def, few_obs_def) + ) + } else { + NULL + } }) # toggle ui (DDA DIA SRM) @@ -276,63 +305,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { get_data = eventReactive(input$proceed1, { - data_source <- .get_data_source_type(input$filetype, input$big_file_spec) - - if (data_source == "big_spectronaut") { - # Ensure a file has been selected via the local browser - req(length(local_big_file_path()) > 0) - - # Show a busy indicator as this might take time - shinybusy::show_modal_spinner( - spin = "fading-circle", - text = "Processing large Spectronaut file..." - ) - - # Define the output path using a temporary file. - #currently can't use the timeporary file because bigSpectronauttoMSstatsFormat modifies the filepath internally - #temp_output_path <- tempfile(fileext = ".csv") - - - # Call the big file conversion function from MSstatsConvert - converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( - input_file = local_big_file_path(), - output_file_name = "output_file.csv", - backend = "arrow", - filter_by_excluded = input$filter_by_excluded, - filter_by_identified = input$filter_by_identified, - filter_by_qvalue = input$filter_by_qvalue, - qvalue_cutoff = input$qvalue_cutoff, - max_feature_count = input$max_feature_count, - filter_unique_peptides = input$filter_unique_peptides, - aggregate_psms = input$aggregate_psms, - filter_few_obs = input$filter_few_obs - ) - - # Attempt to load the data into memory. - # If the data is too large for RAM, this will catch the allocation error. - converted_data <- tryCatch({ - dplyr::collect(converted_data) - }, error = function(e) { - shinybusy::remove_modal_spinner() - showNotification( - paste("Memory Error: The dataset is too large to process in-memory.", e$message), - type = "error", - duration = NULL - ) - return(NULL) - }) - - if (is.null(converted_data)) return(NULL) - - # Remove the busy indicator - shinybusy::remove_modal_spinner() - - return(converted_data) - } else { - # For all other cases (non-spec files, or standard spec uploads), - # use the existing getData function. - getData(input) - } + getData(input) }) diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index 8ac1cf8..0d06d74 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -282,58 +282,67 @@ create_diann_uploads <- function(ns) { #' Create Spectronaut file uploads #' @noRd create_spectronaut_uploads <- function(ns) { - uiOutput(ns("spectronaut_upload_ui")) + tagList( + uiOutput(ns("spectronaut_header_ui")), + uiOutput(ns("spectronaut_file_selection_ui")), + uiOutput(ns("spectronaut_options_ui")) + ) } -#' Create content for Spectronaut upload UI -#' @param ns Namespace function -#' @param is_web_server Boolean indicating if running on web server +#' Create Spectronaut header #' @noRd -create_spectronaut_ui_content <- function(ns, is_web_server) { - if (!is_web_server) { - tagList( - h4("4. Upload MSstats scheme output from Spectronaut"), - - # Checkbox to switch between upload methods - checkboxInput(ns("big_file_spec"), "Large file mode"), - - # Standard fileInput, shown when checkbox is NOT checked - conditionalPanel( - condition = paste0("!input['", ns("big_file_spec"), "']"), - fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) - ), - - # Local browser button, shown when checkbox IS checked - conditionalPanel( - condition = paste0("input['", ns("big_file_spec"), "']"), - shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("specdata_big_path")), - tags$hr(), - h4("Options for large file processing"), - checkboxInput(ns("filter_by_excluded"), "Filter by excluded from quantification", value = FALSE), - checkboxInput(ns("filter_by_identified"), "Filter by identified", value = FALSE), - checkboxInput(ns("filter_by_qvalue"), "Filter by q-value", value = TRUE), - conditionalPanel( - condition = paste0("input['", ns("filter_by_qvalue"), "']"), - numericInput(ns("qvalue_cutoff"), "Q-value cutoff", value = 0.01, min = 0, max = 1, step = 0.01) - ), - numericInput(ns("max_feature_count"), "Max feature count", value = 20, min = 1), - checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = FALSE), - checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = FALSE), - checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = FALSE) - ), - - create_separator_buttons(ns, "sep_specdata") - ) - } else { - tagList( - h4("4. Upload MSstats scheme output from Spectronaut"), - - fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL), - - create_separator_buttons(ns, "sep_specdata") - ) - } +create_spectronaut_header <- function() { + h4("4. Upload MSstats scheme output from Spectronaut") +} + +#' Create Spectronaut mode selector (Local only) +#' @noRd +create_spectronaut_mode_selector <- function(ns, selected = FALSE) { + checkboxInput(ns("big_file_spec"), "Large file mode", value = selected) +} + +#' Create Spectronaut standard file input +#' @noRd +create_spectronaut_standard_ui <- function(ns) { + fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) +} + +#' Create Spectronaut large file selection UI +#' @noRd +create_spectronaut_large_file_ui <- function(ns) { + tagList( + shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("specdata_big_path")) + ) +} + +#' Create Spectronaut large file filter options +#' @noRd +create_spectronaut_large_filter_options <- function(ns, excluded_def = FALSE, identified_def = FALSE, qval_def = TRUE) { + tagList( + tags$hr(), + h4("Options for large file processing"), + checkboxInput(ns("filter_by_excluded"), "Filter by excluded from quantification", value = excluded_def), + checkboxInput(ns("filter_by_identified"), "Filter by identified", value = identified_def), + checkboxInput(ns("filter_by_qvalue"), "Filter by q-value", value = qval_def) + ) +} + +#' Create Spectronaut Q-value cutoff input +#' @noRd +create_spectronaut_qvalue_cutoff_ui <- function(ns, cutoff_def = 0.01) { + numericInput(ns("qvalue_cutoff"), "Q-value cutoff", value = cutoff_def, min = 0, max = 1, step = 0.01) +} + +#' Create Spectronaut large file options (Bottom part) +#' @noRd +create_spectronaut_large_bottom_ui <- function(ns, max_feature_def = 20, unique_peps_def = FALSE, agg_psms_def = FALSE, few_obs_def = FALSE) { + tagList( + numericInput(ns("max_feature_count"), "Max feature count", value = max_feature_def, min = 1), + checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = unique_peps_def), + checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = agg_psms_def), + checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = few_obs_def) + ) } #' Create PTM FragPipe uploads diff --git a/R/utils.R b/R/utils.R index b09bf89..e2dee6b 100644 --- a/R/utils.R +++ b/R/utils.R @@ -501,33 +501,95 @@ getData <- function(input) { } else if(input$filetype == 'spec') { - # if (input$subset){ - # data = read.csv.sql(infile$datapath, sep="\t", - # sql = "select * from file order by random() limit 100000") - # } else { - data = read.csv(input$specdata$datapath, sep=input$sep_specdata, check.names = FALSE) - # } - # Base arguments for the Spectronaut converter - converter_args = list( - input = data, - annotation = getAnnot(input), - filter_with_Qvalue = input$q_val, - qvalue_cutoff = input$q_cutoff, - removeProtein_with1Feature = input$remove, - use_log_file = FALSE - ) - - if (isTRUE(input$calculate_anomaly_scores) && !is.null(input$run_order_file)) { - # Add anomaly score parameters only if the checkbox is checked - converter_args$calculateAnomalyScores = TRUE - converter_args$runOrder = read.csv(input$run_order_file$datapath) - converter_args$anomalyModelFeatures = c("FG.ShapeQualityScore (MS2)", "FG.ShapeQualityScore (MS1)", "EGDeltaRT") - converter_args$anomalyModelFeatureTemporal = c("mean_decrease", "mean_decrease", "dispersion_increase") - converter_args$n_trees = 100 - converter_args$max_depth = "auto" - converter_args$numberOfCores = 1 + if (isTRUE(input$big_file_spec)) { + # Logic for big Spectronaut files + # Parse the file path from shinyFiles input + volumes <- shinyFiles::getVolumes()() + path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse) + local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL + + # Validate inputs + if (!is.numeric(input$qvalue_cutoff) || is.na(input$qvalue_cutoff) || input$qvalue_cutoff < 0 || input$qvalue_cutoff > 1) { + showNotification("Error: qvalue_cutoff must be between 0 and 1.", type = "error") + shinybusy::remove_modal_spinner() + return(NULL) + } + + if (!is.numeric(input$max_feature_count) || is.na(input$max_feature_count) || input$max_feature_count <= 0) { + showNotification("Error: max_feature_count must be a positive number.", type = "error") + shinybusy::remove_modal_spinner() + return(NULL) + } + + if (is.null(local_big_file_path) || !file.exists(local_big_file_path)) { + showNotification("Error: The selected file does not exist or is not readable.", type = "error") + shinybusy::remove_modal_spinner() + return(NULL) + } + + shinybusy::update_modal_spinner(text = "Processing large Spectronaut file...") + + # Call the big file conversion function from MSstatsConvert + converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( + input_file = local_big_file_path, + output_file_name = "output_file.csv", + backend = "arrow", + filter_by_excluded = input$filter_by_excluded, + filter_by_identified = input$filter_by_identified, + filter_by_qvalue = input$filter_by_qvalue, + qvalue_cutoff = input$qvalue_cutoff, + max_feature_count = input$max_feature_count, + filter_unique_peptides = input$filter_unique_peptides, + aggregate_psms = input$aggregate_psms, + filter_few_obs = input$filter_few_obs + ) + + # Attempt to load the data into memory. + mydata <- tryCatch({ + dplyr::collect(converted_data) + }, error = function(e) { + showNotification( + paste("Memory Error: The dataset is too large to process in-memory.", e$message), + type = "error", + duration = NULL + ) + return(NULL) + }) + + if (is.null(mydata)) { + shinybusy::remove_modal_spinner() + return(NULL) + } + + } else { + # if (input$subset){ + # data = read.csv.sql(infile$datapath, sep="\t", + # sql = "select * from file order by random() limit 100000") + # } else { + data = read.csv(input$specdata$datapath, sep=input$sep_specdata, check.names = FALSE) + # } + # Base arguments for the Spectronaut converter + converter_args = list( + input = data, + annotation = getAnnot(input), + filter_with_Qvalue = input$q_val, + qvalue_cutoff = input$q_cutoff, + removeProtein_with1Feature = input$remove, + use_log_file = FALSE + ) + + if (isTRUE(input$calculate_anomaly_scores) && !is.null(input$run_order_file)) { + # Add anomaly score parameters only if the checkbox is checked + converter_args$calculateAnomalyScores = TRUE + converter_args$runOrder = read.csv(input$run_order_file$datapath) + converter_args$anomalyModelFeatures = c("FG.ShapeQualityScore (MS2)", "FG.ShapeQualityScore (MS1)", "EGDeltaRT") + converter_args$anomalyModelFeatureTemporal = c("mean_decrease", "mean_decrease", "dispersion_increase") + converter_args$n_trees = 100 + converter_args$max_depth = "auto" + converter_args$numberOfCores = 1 + } + mydata = do.call(SpectronauttoMSstatsFormat, converter_args) } - mydata = do.call(SpectronauttoMSstatsFormat, converter_args) } else if(input$filetype == 'diann') { if (getFileExtension(input$dianndata$name) %in% c("parquet", "pq")) { diff --git a/tests/testthat/test-module-loadpage-ui.R b/tests/testthat/test-module-loadpage-ui.R index c5fd723..7bc3062 100644 --- a/tests/testthat/test-module-loadpage-ui.R +++ b/tests/testthat/test-module-loadpage-ui.R @@ -394,43 +394,54 @@ test_that("file inputs have proper accept attributes", { expect_true(grepl('accept=.*csv', annot_html)) }) -test_that("Spectronaut big file UI is configured correctly", { - # This test checks for the existence and conditional logic of UI elements - # related to the Spectronaut big file workflow. - result <- loadpageUI("test") - html_output <- as.character(result) - - # 1. Check for the presence of the big file checkbox itself - expect_true(grepl("test-big_file_spec", html_output), - info = "Spectronaut 'big file' checkbox is missing.") - - # 2. Check that the new processing options are present in the HTML. - new_options_labels <- c( - "Filter by excluded from quantification", - "Filter by q-value", - "Max feature count", - "Aggregate PSMs to peptides" - ) - for(label in new_options_labels) { - expect_true(grepl(label, html_output), info = paste("Missing Spectronaut big file option:", label)) - } - - # 3. Verify the conditional logic for hiding/showing related panels. - # Note: HTML entities encode ' as ', && as &&, and || as || - - # Annotation upload should be hidden for big spec files. - # Condition contains: (input['loadpage-filetype'] == 'spec' && !input['loadpage-big_file_spec']) - expected_annot_condition <- "input['loadpage-filetype'] == 'spec' && !input['loadpage-big_file_spec']" - expect_true( - grepl(expected_annot_condition, html_output, fixed = TRUE), - info = "Conditional logic to hide annotation upload for big Spectronaut files is incorrect." - ) +# Tests for Spectronaut specific UI components +test_that("create_spectronaut_uploads creates UI outputs", { + uploads <- create_spectronaut_uploads(NS("test")) + uploads_html <- as.character(uploads) - # Standard pre-processing should be hidden for big spec files. - # Condition contains: (input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec']) - expected_preprocess_condition <- "input['loadpage-filetype'] != 'spec' || !input['loadpage-big_file_spec']" - expect_true( - grepl(expected_preprocess_condition, html_output, fixed = TRUE), - info = "Conditional logic to hide pre-processing options for big Spectronaut files is incorrect." - ) + expect_true(grepl("spectronaut_header_ui", uploads_html)) + expect_true(grepl("spectronaut_file_selection_ui", uploads_html)) + expect_true(grepl("spectronaut_options_ui", uploads_html)) +}) + +test_that("Spectronaut helper functions create correct UI elements", { + # Header + header <- create_spectronaut_header() + expect_true(grepl("Upload MSstats scheme output from Spectronaut", as.character(header))) + + # Mode selector + mode_sel <- create_spectronaut_mode_selector(NS("test")) + expect_true(grepl("Large file mode", as.character(mode_sel))) + expect_true(grepl("checkbox", as.character(mode_sel))) + + # Standard UI + std_ui <- create_spectronaut_standard_ui(NS("test")) + expect_true(grepl("file", as.character(std_ui))) + expect_true(grepl("specdata", as.character(std_ui))) + + # Large file UI + large_ui <- create_spectronaut_large_file_ui(NS("test")) + large_ui_html <- as.character(large_ui) + expect_true(grepl("Browse for local file", large_ui_html)) + expect_true(grepl("specdata_big_path", large_ui_html)) + + # Filter options + filter_opts <- create_spectronaut_large_filter_options(NS("test")) + opts_html <- as.character(filter_opts) + expect_true(grepl("Filter by excluded", opts_html)) + expect_true(grepl("Filter by identified", opts_html)) + expect_true(grepl("Filter by q-value", opts_html)) + + # Q-value cutoff + qval_ui <- create_spectronaut_qvalue_cutoff_ui(NS("test")) + expect_true(grepl("Q-value cutoff", as.character(qval_ui))) + expect_true(grepl("0.01", as.character(qval_ui))) + + # Bottom UI + bottom_ui <- create_spectronaut_large_bottom_ui(NS("test")) + bottom_html <- as.character(bottom_ui) + expect_true(grepl("Max feature count", bottom_html)) + expect_true(grepl("Use unique peptides", bottom_html)) + expect_true(grepl("Aggregate PSMs", bottom_html)) + expect_true(grepl("Filter features with few observations", bottom_html)) }) \ No newline at end of file diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index ef8cfcc..8d3d6ce 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1437,3 +1437,93 @@ describe("getData for Spectronaut input with anomaly scores", { expect_null(result_args$anomalyModelFeatures) }) }) + +describe("getData for Big Spectronaut", { + + # Common mock input for big spec + mock_input_big <- list( + filetype = "spec", + big_file_spec = TRUE, + big_file_browse = list(files = list("file.csv")), + qvalue_cutoff = 0.01, + max_feature_count = 20, + filter_by_excluded = FALSE, + filter_by_identified = FALSE, + filter_by_qvalue = TRUE, + filter_unique_peptides = TRUE, + aggregate_psms = TRUE, + filter_few_obs = TRUE, + BIO = "Protein", + DDA_DIA = "DIA" + ) + + # Mock data to return + mock_arrow_obj <- list(dummy = "arrow") + mock_df <- data.frame(ProteinName = "P1", Intensity = 100) + + test_that("Valid input returns data", { + # Mocks + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "file.exists", TRUE) + stub(getData, "MSstatsBig::bigSpectronauttoMSstatsFormat", mock_arrow_obj) + stub(getData, "dplyr::collect", mock_df) + stub(getData, "showNotification", function(...) NULL) + stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big) + expect_equal(res, mock_df) + }) + + test_that("Invalid qvalue_cutoff returns NULL", { + bad_input <- mock_input_big + bad_input$qvalue_cutoff <- 1.5 + + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "qvalue_cutoff")) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(bad_input) + expect_null(res) + }) + + test_that("Invalid max_feature_count returns NULL", { + bad_input <- mock_input_big + bad_input$max_feature_count <- 0 + + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "max_feature_count")) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(bad_input) + expect_null(res) + }) + + test_that("File not found returns NULL", { + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "nonexistent.csv")) + stub(getData, "file.exists", FALSE) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "does not exist")) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big) + expect_null(res) + }) + + test_that("Memory error returns NULL", { + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "file.exists", TRUE) + stub(getData, "MSstatsBig::bigSpectronauttoMSstatsFormat", mock_arrow_obj) + stub(getData, "dplyr::collect", function(...) stop("Memory allocation failed")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "Memory Error")) + stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big) + expect_null(res) + }) +}) From ee99a002c0f26d498fa40e3ca15a2c501c299af3 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Sun, 25 Jan 2026 14:54:21 -0600 Subject: [PATCH 06/18] Adding Big File option for DIANN --- R/module-loadpage-server.R | 52 +++++++++++++++++++++++++++++-- R/module-loadpage-ui.R | 63 ++++++++++++++++++++++++++++++++++---- R/utils.R | 52 +++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 8 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 975b6a6..ac0a2ce 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -38,7 +38,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { }) # Render just the filename for user feedback in the UI. - output$specdata_big_path <- renderPrint({ + output$big_file_path <- renderPrint({ req(nrow(local_file_info()) > 0) cat(local_file_info()$name) }) @@ -72,6 +72,52 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { tagList(ui_elements, create_separator_buttons(session$ns, "sep_specdata")) }) + output$diann_header_ui <- renderUI({ + req(input$filetype == 'diann', input$BIO != 'PTM') + create_diann_header() + }) + + output$diann_file_selection_ui <- renderUI({ + req(input$filetype == 'diann', input$BIO != 'PTM') + + ui_elements <- tagList() + + if (!is_web_server) { + ui_elements <- tagList(ui_elements, create_diann_mode_selector(session$ns, isTRUE(input$big_file_diann))) + + if (isTRUE(input$big_file_diann)) { + ui_elements <- tagList(ui_elements, create_diann_large_file_ui(session$ns)) + } else { + ui_elements <- tagList(ui_elements, create_diann_standard_ui(session$ns)) + } + } else { + ui_elements <- tagList(ui_elements, create_diann_standard_ui(session$ns)) + } + + ui_elements + }) + + output$diann_options_ui <- renderUI({ + req(input$filetype == 'diann', input$BIO != 'PTM') + + if (!is_web_server && isTRUE(input$big_file_diann)) { + mbr_def <- if (is.null(input$diann_MBR)) TRUE else input$diann_MBR + quant_col_def <- if (is.null(input$diann_quantificationColumn)) "Fragment.Quant.Corrected" else input$diann_quantificationColumn + + max_feature_def <- if (is.null(input$max_feature_count)) 100 else input$max_feature_count + unique_peps_def <- if (is.null(input$filter_unique_peptides)) FALSE else input$filter_unique_peptides + agg_psms_def <- if (is.null(input$aggregate_psms)) FALSE else input$aggregate_psms + few_obs_def <- if (is.null(input$filter_few_obs)) FALSE else input$filter_few_obs + + tagList( + create_diann_large_filter_options(session$ns, mbr_def, quant_col_def), + create_diann_large_bottom_ui(session$ns, max_feature_def, unique_peps_def, agg_psms_def, few_obs_def) + ) + } else { + NULL + } + }) + output$spectronaut_options_ui <- renderUI({ req(input$filetype == 'spec', input$BIO != 'PTM') @@ -195,7 +241,9 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { enable("proceed1") } } else if (input$filetype == "diann") { - if(!is.null(input$dianndata) && !is.null(input$sep_dianndata)) { # && !is.null(input$annot) + diann_regular_file_ok <- !isTRUE(input$big_file_diann) && !is.null(input$dianndata) && !is.null(input$sep_dianndata) + diann_big_file_ok <- isTRUE(input$big_file_diann) && length(local_big_file_path()) > 0 + if(diann_regular_file_ok || diann_big_file_ok) { enable("proceed1") } } diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index 0d06d74..2969f8f 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -271,11 +271,10 @@ create_skyline_uploads <- function(ns) { #' Create DIANN file uploads #' @noRd create_diann_uploads <- function(ns) { - conditionalPanel( - condition = "input['loadpage-filetype'] == 'diann' && input['loadpage-BIO'] != 'PTM'", - h4("4. Upload MSstats report from DIANN"), - fileInput(ns('dianndata'), "", multiple = FALSE, accept = NULL), - create_separator_buttons(ns, "sep_dianndata") + tagList( + uiOutput(ns("diann_header_ui")), + uiOutput(ns("diann_file_selection_ui")), + uiOutput(ns("diann_options_ui")) ) } @@ -295,24 +294,54 @@ create_spectronaut_header <- function() { h4("4. Upload MSstats scheme output from Spectronaut") } +#' Create DIANN header +#' @noRd +create_diann_header <- function() { + h4("4. Upload MSstats report from DIANN") +} + #' Create Spectronaut mode selector (Local only) #' @noRd create_spectronaut_mode_selector <- function(ns, selected = FALSE) { checkboxInput(ns("big_file_spec"), "Large file mode", value = selected) } +#' Create DIANN mode selector (Local only) +#' @noRd +create_diann_mode_selector <- function(ns, selected = FALSE) { + checkboxInput(ns("big_file_diann"), "Large file mode", value = selected) +} + #' Create Spectronaut standard file input #' @noRd create_spectronaut_standard_ui <- function(ns) { fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL) } +#' Create DIANN standard file input +#' @noRd +create_diann_standard_ui <- function(ns) { + tagList( + fileInput(ns('dianndata'), "", multiple = FALSE, accept = NULL), + create_separator_buttons(ns, "sep_dianndata") + ) +} + #' Create Spectronaut large file selection UI #' @noRd create_spectronaut_large_file_ui <- function(ns) { tagList( shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("specdata_big_path")) + verbatimTextOutput(ns("big_file_path")) + ) +} + +#' Create DIANN large file selection UI +#' @noRd +create_diann_large_file_ui <- function(ns) { + tagList( + shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("big_file_path")) ) } @@ -328,6 +357,17 @@ create_spectronaut_large_filter_options <- function(ns, excluded_def = FALSE, id ) } +#' Create DIANN large file filter options +#' @noRd +create_diann_large_filter_options <- function(ns, mbr_def = TRUE, quant_col_def = "Fragment.Quant.Corrected") { + tagList( + tags$hr(), + h4("Options for large file processing"), + checkboxInput(ns("diann_MBR"), "MBR Enabled", value = mbr_def), + textInput(ns("diann_quantificationColumn"), "Quantification Column", value = quant_col_def) + ) +} + #' Create Spectronaut Q-value cutoff input #' @noRd create_spectronaut_qvalue_cutoff_ui <- function(ns, cutoff_def = 0.01) { @@ -345,6 +385,17 @@ create_spectronaut_large_bottom_ui <- function(ns, max_feature_def = 20, unique_ ) } +#' Create DIANN large file options (Bottom part) +#' @noRd +create_diann_large_bottom_ui <- function(ns, max_feature_def = 100, unique_peps_def = FALSE, agg_psms_def = FALSE, few_obs_def = FALSE) { + tagList( + numericInput(ns("max_feature_count"), "Max feature count", value = max_feature_def, min = 1), + checkboxInput(ns("filter_unique_peptides"), "Use unique peptides", value = unique_peps_def), + checkboxInput(ns("aggregate_psms"), "Aggregate PSMs to peptides", value = agg_psms_def), + checkboxInput(ns("filter_few_obs"), "Filter features with few observations", value = few_obs_def) + ) +} + #' Create PTM FragPipe uploads #' @noRd create_ptm_fragpipe_uploads <- function(ns) { diff --git a/R/utils.R b/R/utils.R index e2dee6b..eed3052 100644 --- a/R/utils.R +++ b/R/utils.R @@ -592,6 +592,57 @@ getData <- function(input) { } } else if(input$filetype == 'diann') { + if (isTRUE(input$big_file_diann)) { + # Logic for big DIANN files + # Parse the file path from shinyFiles input + volumes <- shinyFiles::getVolumes()() + path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse) + local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL + + if (!is.numeric(input$max_feature_count) || is.na(input$max_feature_count) || input$max_feature_count <= 0) { + showNotification("Error: max_feature_count must be a positive number.", type = "error") + shinybusy::remove_modal_spinner() + return(NULL) + } + + if (is.null(local_big_file_path) || !file.exists(local_big_file_path)) { + showNotification("Error: The selected file does not exist or is not readable.", type = "error") + shinybusy::remove_modal_spinner() + return(NULL) + } + + shinybusy::update_modal_spinner(text = "Processing large DIANN file...") + + # Call the big file conversion function from MSstatsConvert + converted_data <- MSstatsBig::bigDIANNtoMSstatsFormat( + input_file = local_big_file_path, + output_file_name = "output_file.csv", + backend = "arrow", + MBR = isTRUE(input$diann_MBR), + quantificationColumn = input$diann_quantificationColumn, + max_feature_count = input$max_feature_count, + filter_unique_peptides = input$filter_unique_peptides, + aggregate_psms = input$aggregate_psms, + filter_few_obs = input$filter_few_obs + ) + + # Attempt to load the data into memory. + mydata <- tryCatch({ + dplyr::collect(converted_data) + }, error = function(e) { + showNotification( + paste("Memory Error: The dataset is too large to process in-memory.", e$message), + type = "error", + duration = NULL + ) + return(NULL) + }) + + if (is.null(mydata)) { + shinybusy::remove_modal_spinner() + return(NULL) + } + } else { if (getFileExtension(input$dianndata$name) %in% c("parquet", "pq")) { data = read_parquet(input$dianndata$datapath) } else { @@ -620,6 +671,7 @@ getData <- function(input) { use_log_file = FALSE, quantificationColumn = quantificationColumn ) + } print("Mydata from mstats") print(mydata) } From 8ced47e6fd836076f218cd40e817b3b1cb75a72c Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Mon, 2 Feb 2026 18:34:19 -0600 Subject: [PATCH 07/18] Updating GetDataCode() --- R/utils.R | 72 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 19 deletions(-) diff --git a/R/utils.R b/R/utils.R index eed3052..25f9ec8 100644 --- a/R/utils.R +++ b/R/utils.R @@ -773,7 +773,8 @@ library(MSstatsTMT) library(MSstatsPTM)\n", sep = "") codes = paste(codes, "\n# Package versions\n# MSstats version ", packageVersion("MSstats"), "\n# MSstatsTMT version ", packageVersion("MSstatsTMT"), - "\n# MSstatsPTM version ", packageVersion("MSstatsPTM"), sep = "") + "\n# MSstatsPTM version ", packageVersion("MSstatsPTM"), + "\n# MSstatsBig version ", tryCatch(packageVersion("MSstatsBig"), error = function(e) "Not Installed"), sep = "") codes = paste(codes, "\n\n# Read data\n", sep = "") if(input$filetype == 'sample') { if(input$BIO != "PTM" && input$DDA_DIA =='LType' && input$LabelFreeType == "SRM_PRM") { @@ -895,27 +896,60 @@ library(MSstatsPTM)\n", sep = "") } else if(input$filetype == 'spec') { - codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from Spectronaut filepath\", header = TRUE, sep = ",input$sep_specdata,")\nannot_file = read.csv(\"insert your annotation filepath\", sep='\t')#Optional\n" - , sep = "") - - codes = paste(codes, "data = SpectronauttoMSstatsFormat(data, - annotation = annot_file #Optional, - filter_with_Qvalue = TRUE, ## same as default - qvalue_cutoff = 0.01, ## same as default - fewMeasurements=\"remove\", - removeProtein_with1Feature = TRUE, - use_log_file = FALSE)\n", sep = "") + if (isTRUE(input$big_file_spec)) { + codes = paste(codes, "library(MSstatsBig)\n", sep = "") + codes = paste(codes, "data = MSstatsBig::bigSpectronauttoMSstatsFormat(\n", sep = "") + codes = paste(codes, " input_file = \"insert your large Spectronaut file path\",\n", sep = "") + codes = paste(codes, " output_file_name = \"output_file.csv\",\n", sep = "") + codes = paste(codes, " backend = \"arrow\",\n", sep = "") + codes = paste(codes, " filter_by_excluded = ", input$filter_by_excluded, ",\n", sep = "") + codes = paste(codes, " filter_by_identified = ", input$filter_by_identified, ",\n", sep = "") + codes = paste(codes, " filter_by_qvalue = ", input$filter_by_qvalue, ",\n", sep = "") + codes = paste(codes, " qvalue_cutoff = ", input$qvalue_cutoff, ",\n", sep = "") + codes = paste(codes, " max_feature_count = ", input$max_feature_count, ",\n", sep = "") + codes = paste(codes, " filter_unique_peptides = ", input$filter_unique_peptides, ",\n", sep = "") + codes = paste(codes, " aggregate_psms = ", input$aggregate_psms, ",\n", sep = "") + codes = paste(codes, " filter_few_obs = ", input$filter_few_obs, "\n", sep = "") + codes = paste(codes, ")\n", sep = "") + codes = paste(codes, "data = dplyr::collect(data)\n", sep = "") + } else { + codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from Spectronaut filepath\", header = TRUE, sep = ",input$sep_specdata,")\nannot_file = read.csv(\"insert your annotation filepath\", sep='\t')#Optional\n" + , sep = "") + codes = paste(codes, "data = SpectronauttoMSstatsFormat(data, + annotation = annot_file #Optional, + filter_with_Qvalue = TRUE, ## same as default + qvalue_cutoff = 0.01, ## same as default + fewMeasurements=\"remove\", + removeProtein_with1Feature = TRUE, + use_log_file = FALSE)\n", sep = "") + } } else if(input$filetype == 'diann') { - codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from DIANN filepath\", header = TRUE, sep = '\\t')\nannot_file = read.csv(\"insert your annotation filepath\")#Optional\n" - , sep = "") - - codes = paste(codes, "data = DIANNtoMSstatsFormat(data, - annotation = annot_file, #Optional - qvalue_cutoff = 0.01, ## same as default - removeProtein_with1Feature = TRUE, - use_log_file = FALSE)\n", sep = "") + if (isTRUE(input$big_file_diann)) { + codes = paste(codes, "library(MSstatsBig)\n", sep = "") + codes = paste(codes, "data = MSstatsBig::bigDIANNtoMSstatsFormat(\n", sep = "") + codes = paste(codes, " input_file = \"insert your large DIANN file path\",\n", sep = "") + codes = paste(codes, " output_file_name = \"output_file.csv\",\n", sep = "") + codes = paste(codes, " backend = \"arrow\",\n", sep = "") + codes = paste(codes, " MBR = ", isTRUE(input$diann_MBR), ",\n", sep = "") + codes = paste(codes, " quantificationColumn = \"", input$diann_quantificationColumn, "\",\n", sep = "") + codes = paste(codes, " max_feature_count = ", input$max_feature_count, ",\n", sep = "") + codes = paste(codes, " filter_unique_peptides = ", input$filter_unique_peptides, ",\n", sep = "") + codes = paste(codes, " aggregate_psms = ", input$aggregate_psms, ",\n", sep = "") + codes = paste(codes, " filter_few_obs = ", input$filter_few_obs, "\n", sep = "") + codes = paste(codes, ")\n", sep = "") + codes = paste(codes, "data = dplyr::collect(data)\n", sep = "") + } else { + codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from DIANN filepath\", header = TRUE, sep = '\\t')\nannot_file = read.csv(\"insert your annotation filepath\")#Optional\n" + , sep = "") + + codes = paste(codes, "data = DIANNtoMSstatsFormat(data, + annotation = annot_file, #Optional + qvalue_cutoff = 0.01, ## same as default + removeProtein_with1Feature = TRUE, + use_log_file = FALSE)\n", sep = "") + } } else if(input$filetype == 'open') { From 1fc981cce2a687cc2eb8488996277c4a7fff9bbc Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Tue, 3 Feb 2026 00:41:07 -0600 Subject: [PATCH 08/18] Adding test --- tests/testthat/test-utils.R | 75 +++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 8d3d6ce..e72f98b 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1527,3 +1527,78 @@ describe("getData for Big Spectronaut", { expect_null(res) }) }) + +describe("getData for Big DIANN", { + + # Common mock input for big diann + mock_input_big_diann <- list( + filetype = "diann", + big_file_diann = TRUE, + big_file_browse = list(files = list("file.csv")), + max_feature_count = 20, + diann_MBR = TRUE, + diann_quantificationColumn = "Fragment.Quant.Corrected", + filter_unique_peptides = TRUE, + aggregate_psms = TRUE, + filter_few_obs = TRUE, + BIO = "Protein", + DDA_DIA = "DIA" + ) + + # Mock data to return + mock_arrow_obj <- list(dummy = "arrow") + mock_df <- data.frame(ProteinName = "P1", Intensity = 100) + + test_that("Valid input returns data", { + # Mocks + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "file.exists", TRUE) + stub(getData, "MSstatsBig::bigDIANNtoMSstatsFormat", mock_arrow_obj) + stub(getData, "dplyr::collect", mock_df) + stub(getData, "showNotification", function(...) NULL) + stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big_diann) + expect_equal(res, mock_df) + }) + + test_that("Invalid max_feature_count returns NULL", { + bad_input <- mock_input_big_diann + bad_input$max_feature_count <- 0 + + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "max_feature_count")) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(bad_input) + expect_null(res) + }) + + test_that("File not found returns NULL", { + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "nonexistent.csv")) + stub(getData, "file.exists", FALSE) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "does not exist")) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big_diann) + expect_null(res) + }) + + test_that("Memory error returns NULL", { + stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) + stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "file.exists", TRUE) + stub(getData, "MSstatsBig::bigDIANNtoMSstatsFormat", mock_arrow_obj) + stub(getData, "dplyr::collect", function(...) stop("Memory allocation failed")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "Memory Error")) + stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) + stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) + + res <- getData(mock_input_big_diann) + expect_null(res) + }) +}) From bd4d39a1ecbd5eac0dc0ba117330130c1d84d1af Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Wed, 4 Feb 2026 23:38:13 -0600 Subject: [PATCH 09/18] Fixing TSV file issue --- DESCRIPTION | 2 +- R/utils.R | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c085cbf..e9a357a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: MSstatsShiny Type: Package Title: MSstats GUI for Statistical Anaylsis of Proteomics Experiments -Version: 1.13.0 +Version: 1.13.1 Description: MSstatsShiny is an R-Shiny graphical user interface (GUI) integrated with the R packages MSstats, MSstatsTMT, and MSstatsPTM. It provides a point and click end-to-end analysis pipeline applicable to a wide diff --git a/R/utils.R b/R/utils.R index 25f9ec8..a50c0f7 100644 --- a/R/utils.R +++ b/R/utils.R @@ -646,7 +646,15 @@ getData <- function(input) { if (getFileExtension(input$dianndata$name) %in% c("parquet", "pq")) { data = read_parquet(input$dianndata$datapath) } else { - data = read.csv(input$dianndata$datapath, sep=input$sep_dianndata) + sep = input$sep_dianndata + if(is.null(sep)) { + sep = "\t" + } + if (sep == "\t") { + data = read.delim(input$dianndata$datapath) + } else { + data = read.csv(input$dianndata$datapath, sep = sep) + } } qvalue_cutoff = 0.01 @@ -941,8 +949,16 @@ library(MSstatsPTM)\n", sep = "") codes = paste(codes, ")\n", sep = "") codes = paste(codes, "data = dplyr::collect(data)\n", sep = "") } else { - codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from DIANN filepath\", header = TRUE, sep = '\\t')\nannot_file = read.csv(\"insert your annotation filepath\")#Optional\n" - , sep = "") + sep = input$sep_dianndata + if(is.null(sep)) { + sep = "\t" + } + + if (sep == "\t") { + codes = paste(codes, "data = read.delim(\"insert your MSstats scheme output from DIANN filepath\")\nannot_file = read.csv(\"insert your annotation filepath\")#Optional\n", sep = "") + } else { + codes = paste(codes, "data = read.csv(\"insert your MSstats scheme output from DIANN filepath\", header = TRUE, sep = '", sep, "')\nannot_file = read.csv(\"insert your annotation filepath\")#Optional\n", sep = "") + } codes = paste(codes, "data = DIANNtoMSstatsFormat(data, annotation = annot_file, #Optional From cf54d435ca2cda607529142ac51c65df789e3cd6 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Wed, 4 Feb 2026 23:39:29 -0600 Subject: [PATCH 10/18] removing this file --- tests/testthat/test-loadpage-server.R | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 tests/testthat/test-loadpage-server.R diff --git a/tests/testthat/test-loadpage-server.R b/tests/testthat/test-loadpage-server.R deleted file mode 100644 index 12a7231..0000000 --- a/tests/testthat/test-loadpage-server.R +++ /dev/null @@ -1,21 +0,0 @@ -test_that(".get_data_source_type correctly identifies the data loading path", { - # This test checks the helper function that determines whether to use the - # standard data loader or the special loader for large Spectronaut files. - # This approach is simpler and more robust than a complex shiny::testServer test. - - # Case 1: Spectronaut file with the 'big file' checkbox checked - expect_equal(.get_data_source_type("spec", TRUE), "big_spectronaut") - - # Case 2: Spectronaut file without the 'big file' checkbox - expect_equal(.get_data_source_type("spec", FALSE), "standard") - - # Case 3: A non-Spectronaut file (should always be standard) - expect_equal(.get_data_source_type("maxq", FALSE), "standard") - - # Case 4: A non-Spectronaut file where the Spectronaut checkbox might be TRUE - # (though the UI should prevent this, the logic should be robust) - expect_equal(.get_data_source_type("maxq", TRUE), "standard") - - # Case 5: Input is NULL (initial state) - expect_equal(.get_data_source_type("spec", NULL), "standard") -}) From 5c7c74b1e79a4b509ef350d4c897f69e66e415e0 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 6 Feb 2026 21:43:53 -0600 Subject: [PATCH 11/18] Add separator buttons to DIANN big file mode UI --- R/module-loadpage-server.R | 4 ++-- R/module-loadpage-ui.R | 5 +---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index ac0a2ce..7d8bf38 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -94,7 +94,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { ui_elements <- tagList(ui_elements, create_diann_standard_ui(session$ns)) } - ui_elements + tagList(ui_elements, create_separator_buttons(session$ns, "sep_dianndata")) }) output$diann_options_ui <- renderUI({ @@ -117,7 +117,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { NULL } }) - + output$spectronaut_options_ui <- renderUI({ req(input$filetype == 'spec', input$BIO != 'PTM') diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index 2969f8f..efae2bf 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -321,10 +321,7 @@ create_spectronaut_standard_ui <- function(ns) { #' Create DIANN standard file input #' @noRd create_diann_standard_ui <- function(ns) { - tagList( - fileInput(ns('dianndata'), "", multiple = FALSE, accept = NULL), - create_separator_buttons(ns, "sep_dianndata") - ) + fileInput(ns('dianndata'), "", multiple = FALSE, accept = NULL) } #' Create Spectronaut large file selection UI From f03f00316ba0d5a411b33c08c5b6db2a9d93d72f Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 6 Feb 2026 23:24:38 -0600 Subject: [PATCH 12/18] consistent separator requirement between Spectronaut and DIANN big-file modes. --- R/module-loadpage-server.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 7d8bf38..ba8b707 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -241,9 +241,9 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { enable("proceed1") } } else if (input$filetype == "diann") { - diann_regular_file_ok <- !isTRUE(input$big_file_diann) && !is.null(input$dianndata) && !is.null(input$sep_dianndata) + diann_regular_file_ok <- !isTRUE(input$big_file_diann) && !is.null(input$dianndata) diann_big_file_ok <- isTRUE(input$big_file_diann) && length(local_big_file_path()) > 0 - if(diann_regular_file_ok || diann_big_file_ok) { + if((diann_regular_file_ok || diann_big_file_ok) && !is.null(input$sep_dianndata)) { enable("proceed1") } } From 6d06c88b36ecaadb57f14895f94752ac41c7f845 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 13 Feb 2026 13:04:42 -0600 Subject: [PATCH 13/18] Updating BigDiann function to accept annotation file. Updating MSstatsShiny.R with dependancies --- R/MSstatsShiny.R | 1 + R/utils.R | 5 +- tests/testthat/test_parallel_simulation.R | 159 ++++++++++++++++++++++ 3 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test_parallel_simulation.R diff --git a/R/MSstatsShiny.R b/R/MSstatsShiny.R index c378098..1861d5c 100644 --- a/R/MSstatsShiny.R +++ b/R/MSstatsShiny.R @@ -35,6 +35,7 @@ #' @importFrom dplyr `%>%` filter summarise n_distinct group_by ungroup select n mutate #' @importFrom tidyr unite #' @importFrom MSstatsConvert MSstatsLogsSettings +#' @importFrom MSstatsBig bigDIANNtoMSstatsFormat bigSpectronauttoMSstatsFormat #' @importFrom MSstatsPTM dataProcessPlotsPTM groupComparisonPlotsPTM MaxQtoMSstatsPTMFormat PDtoMSstatsPTMFormat FragPipetoMSstatsPTMFormat SkylinetoMSstatsPTMFormat SpectronauttoMSstatsPTMFormat #' @importFrom utils capture.output head packageVersion read.csv read.delim write.csv #' @importFrom stats aggregate diff --git a/R/utils.R b/R/utils.R index a50c0f7..4aa0094 100644 --- a/R/utils.R +++ b/R/utils.R @@ -530,7 +530,7 @@ getData <- function(input) { shinybusy::update_modal_spinner(text = "Processing large Spectronaut file...") # Call the big file conversion function from MSstatsConvert - converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( + converted_data <- bigSpectronauttoMSstatsFormat( input_file = local_big_file_path, output_file_name = "output_file.csv", backend = "arrow", @@ -614,8 +614,9 @@ getData <- function(input) { shinybusy::update_modal_spinner(text = "Processing large DIANN file...") # Call the big file conversion function from MSstatsConvert - converted_data <- MSstatsBig::bigDIANNtoMSstatsFormat( + converted_data <- bigDIANNtoMSstatsFormat( input_file = local_big_file_path, + annotation = getAnnot(input), output_file_name = "output_file.csv", backend = "arrow", MBR = isTRUE(input$diann_MBR), diff --git a/tests/testthat/test_parallel_simulation.R b/tests/testthat/test_parallel_simulation.R new file mode 100644 index 0000000..166ef4b --- /dev/null +++ b/tests/testthat/test_parallel_simulation.R @@ -0,0 +1,159 @@ +library(data.table) +library(survival) +library(parallel) + +# ------------------------------------------------------------------------- +# 1. Mock Internal MSstats Functions +# ------------------------------------------------------------------------- + +.fitSurvival <- function(data, iterations) { + tryCatch({ + # Create a real survreg object to simulate memory usage + # Use a subset of data to ensure stability/speed, as we only care about object size + fit <- survreg(Surv(newABUNDANCE, !cen, type="left") ~ 1, + data = head(data, 500), dist = "gaussian") + # Artificially bloat the object to simulate a complex model (approx 100MB) + # Real MSstats models can be very large due to model frames and environments + fit$bloat <- numeric(12.5 * 1024 * 1024) + return(fit) + }, error = function(e) return(NULL)) +} + +.isSummarizable <- function(data, remove50missing) return(data) + +.runTukey <- function(data, is_labeled, censored_symbol, remove50missing) { + return(data.table(Protein = "TestProtein", LogIntensities = mean(data$newABUNDANCE, na.rm=TRUE))) +} + +# ------------------------------------------------------------------------- +# 2. Define Functions with "Work Simulation" (Sleep) +# ------------------------------------------------------------------------- + +# LEAKY VERSION +MSstatsSummarizeSingleTMP_Leaky_Sim <- function (single_protein, impute, censored_symbol, remove50missing, aft_iterations = 90) { + # ... Setup ... + newABUNDANCE = n_obs = n_obs_run = RUN = FEATURE = LABEL = NULL + predicted = censored = NULL + cols = intersect(colnames(single_protein), c("newABUNDANCE", "cen", "RUN", "FEATURE", "ref")) + single_protein = single_protein[(n_obs > 1 & !is.na(n_obs)) & (n_obs_run > 0 & !is.na(n_obs_run))] + if (nrow(single_protein) == 0) return(list(NULL, NULL)) + single_protein[, `:=`(RUN, factor(RUN))] + single_protein[, `:=`(FEATURE, factor(FEATURE))] + + if (impute & any(single_protein[["censored"]])) { + converged = TRUE + survival_fit = withCallingHandlers({ + .fitSurvival(single_protein[LABEL == "L", cols, with = FALSE], aft_iterations) + }, warning = function(w) { if (grepl("converge", conditionMessage(w), ignore.case = TRUE)) converged <<- FALSE }) + + if (converged && !is.null(survival_fit)) { + single_protein[, `:=`(predicted, predict(survival_fit, newdata = .SD))] + } else { + single_protein[, `:=`(predicted, NA_real_)] + } + + # --- LEAK SIMULATION --- + # The object 'survival_fit' is still in memory here. + # We simulate "doing other work" (predictions, formatting) by sleeping. + Sys.sleep(1) + + # Report Memory Usage of this Worker + mem_used <- sum(gc()[,2]) + msg <- sprintf("[Worker %d] LEAKY State - Holding Memory: %.2f MB\n", Sys.getpid(), mem_used) + cat(msg) + cat(msg, file = "parallel_log.txt", append = TRUE) + + single_protein[, `:=`(predicted, ifelse(censored & (LABEL == "L"), predicted, NA))] + single_protein[, `:=`(newABUNDANCE, ifelse(censored & LABEL == "L", predicted, newABUNDANCE))] + survival = single_protein[, c(cols, "predicted"), with = FALSE] + } else { + survival = single_protein[, cols, with = FALSE] + survival[, `:=`(predicted, NA)] + } + # ... Finalize ... + single_protein = .isSummarizable(single_protein, remove50missing) + if (is.null(single_protein)) return(list(NULL, NULL)) + result = .runTukey(single_protein, TRUE, censored_symbol, remove50missing) + list(result, survival) +} + +# FIXED VERSION +MSstatsSummarizeSingleTMP_Fixed_Sim <- function (single_protein, impute, censored_symbol, remove50missing, aft_iterations = 90) { + # ... Setup ... + newABUNDANCE = n_obs = n_obs_run = RUN = FEATURE = LABEL = NULL + predicted = censored = NULL + cols = intersect(colnames(single_protein), c("newABUNDANCE", "cen", "RUN", "FEATURE", "ref")) + single_protein = single_protein[(n_obs > 1 & !is.na(n_obs)) & (n_obs_run > 0 & !is.na(n_obs_run))] + if (nrow(single_protein) == 0) return(list(NULL, NULL)) + single_protein[, `:=`(RUN, factor(RUN))] + single_protein[, `:=`(FEATURE, factor(FEATURE))] + + if (impute & any(single_protein[["censored"]])) { + converged = TRUE + survival_fit = withCallingHandlers({ + .fitSurvival(single_protein[LABEL == "L", cols, with = FALSE], aft_iterations) + }, warning = function(w) { if (grepl("converge", conditionMessage(w), ignore.case = TRUE)) converged <<- FALSE }) + + if (converged && !is.null(survival_fit)) { + single_protein[, `:=`(predicted, predict(survival_fit, newdata = .SD))] + } else { + single_protein[, `:=`(predicted, NA_real_)] + } + + # --- FIX APPLIED --- + rm(survival_fit) + + # --- FIXED SIMULATION --- + # We simulate "doing other work" by sleeping. + Sys.sleep(1) + + # Report Memory Usage of this Worker + mem_used <- sum(gc()[,2]) + msg <- sprintf("[Worker %d] FIXED State - Holding Memory: %.2f MB\n", Sys.getpid(), mem_used) + cat(msg) + cat(msg, file = "parallel_log.txt", append = TRUE) + + single_protein[, `:=`(predicted, ifelse(censored & (LABEL == "L"), predicted, NA))] + single_protein[, `:=`(newABUNDANCE, ifelse(censored & LABEL == "L", predicted, newABUNDANCE))] + survival = single_protein[, c(cols, "predicted"), with = FALSE] + } else { + survival = single_protein[, cols, with = FALSE] + survival[, `:=`(predicted, NA)] + } + # ... Finalize ... + single_protein = .isSummarizable(single_protein, remove50missing) + if (is.null(single_protein)) return(list(NULL, NULL)) + result = .runTukey(single_protein, TRUE, censored_symbol, remove50missing) + list(result, survival) +} + +# ------------------------------------------------------------------------- +# 3. Run Simulation +# ------------------------------------------------------------------------- + +set.seed(123) +n_rows <- 20000 +dt <- data.table( + newABUNDANCE = rnorm(n_rows, 20, 5), + censored = sample(c(TRUE, FALSE), n_rows, replace=TRUE, prob=c(0.3, 0.7)), + LABEL = "L", RUN = sample(1:20, n_rows, replace=TRUE), FEATURE = sample(1:500, n_rows, replace=TRUE), + n_obs = 5, n_obs_run = 5, cen = FALSE, ref = "ref" +) +dt$cen <- dt$censored +dt$newABUNDANCE[dt$censored] <- dt$newABUNDANCE[dt$censored] - 5 + +# Clear log file +file.create("parallel_log.txt") + +cat("\n--- Simulating LEAKY Parallel Execution ---\n") +# We run 2 cores. Both will hit the 'sleep' at the same time. +# Both will report HIGH memory because they haven't cleaned up yet. +invisible(mclapply(1:2, function(i) MSstatsSummarizeSingleTMP_Leaky_Sim(copy(dt), TRUE, "NA", FALSE), mc.cores = 2)) + +cat("\n--- Simulating FIXED Parallel Execution ---\n") +# We run 2 cores. Both will hit the 'sleep' at the same time. +# Both will report LOW memory because they cleaned up BEFORE sleeping. +invisible(mclapply(1:2, function(i) MSstatsSummarizeSingleTMP_Fixed_Sim(copy(dt), TRUE, "NA", FALSE), mc.cores = 2)) + +cat("\n--- Log File Content ---\n") +cat(readLines("parallel_log.txt"), sep = "\n") \ No newline at end of file From b86880d5f3523d3322199ecbb6b3fd38f454678f Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Wed, 18 Feb 2026 18:08:58 -0600 Subject: [PATCH 14/18] Updating Namespace --- NAMESPACE | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NAMESPACE b/NAMESPACE index 1690c4b..7ecb09d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,6 +37,8 @@ importFrom(DT,datatable) importFrom(DT,renderDT) importFrom(DT,renderDataTable) importFrom(Hmisc,describe) +importFrom(MSstatsBig,bigDIANNtoMSstatsFormat) +importFrom(MSstatsBig,bigSpectronauttoMSstatsFormat) importFrom(MSstatsBioNet,annotateProteinInfoFromIndra) importFrom(MSstatsBioNet,generateCytoscapeConfig) importFrom(MSstatsBioNet,getSubnetworkFromIndra) From 048b87fe7c15a92a7210d0433259ab1f5252b229 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Mon, 4 May 2026 20:47:44 -0500 Subject: [PATCH 15/18] fix for setting the right value for the intensity col --- R/utils.R | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/R/utils.R b/R/utils.R index 4aa0094..e3b8ebe 100644 --- a/R/utils.R +++ b/R/utils.R @@ -613,19 +613,32 @@ getData <- function(input) { shinybusy::update_modal_spinner(text = "Processing large DIANN file...") - # Call the big file conversion function from MSstatsConvert - converted_data <- bigDIANNtoMSstatsFormat( + big_quantificationColumn <- if (isTRUE(input$diann_2plus)) { + "auto" + } else if (!is.null(input$intensity_column) && nzchar(input$intensity_column)) { + input$intensity_column + } else { + "auto" + } + + # Call the big file conversion function from MsStatsBig + big_diann_args <- list( input_file = local_big_file_path, annotation = getAnnot(input), output_file_name = "output_file.csv", backend = "arrow", MBR = isTRUE(input$diann_MBR), - quantificationColumn = input$diann_quantificationColumn, + quantificationColumn = big_quantificationColumn, max_feature_count = input$max_feature_count, filter_unique_peptides = input$filter_unique_peptides, aggregate_psms = input$aggregate_psms, filter_few_obs = input$filter_few_obs ) + message("---- bigDIANNtoMSstatsFormat args ----") + utils::str(big_diann_args, max.level = 1, give.attr = FALSE) + message("--------------------------------------") + converted_data <- do.call(MSstatsBig::bigDIANNtoMSstatsFormat, big_diann_args) + # Attempt to load the data into memory. mydata <- tryCatch({ From d19305789da6a28694c4636b8420508d1981fd65 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 8 May 2026 21:34:37 -0500 Subject: [PATCH 16/18] Cleanup --- R/module-loadpage-server.R | 58 ++++++++++++++++++++----------------- R/module-loadpage-ui.R | 8 ++--- R/utils.R | 48 ++++++++++++++++-------------- tests/testthat/test-utils.R | 4 +-- 4 files changed, 64 insertions(+), 54 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index ba8b707..9199484 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -20,31 +20,37 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { # Define volumes for the file selection. if (!is_web_server) { volumes <- shinyFiles::getVolumes()() - - # Server-side logic for the shinyFiles button - shinyFiles::shinyFileChoose(input, "big_file_browse", roots = volumes, session = session) - - # Reactive to parse and store the full file information (path, name, etc.) - # This is efficient because parseFilePaths is only called once. - local_file_info <- reactive({ - req(is.list(input$big_file_browse)) - shinyFiles::parseFilePaths(volumes, input$big_file_browse) - }) - - # Reactive to get just the full datapath, for use in backend processing. - local_big_file_path <- reactive({ - path_info <- local_file_info() - if (nrow(path_info) > 0) path_info$datapath else NULL - }) - - # Render just the filename for user feedback in the UI. - output$big_file_path <- renderPrint({ - req(nrow(local_file_info()) > 0) - cat(local_file_info()$name) - }) - } + + # Register one shinyFiles pipeline per filetype so DIANN and Spectronaut + # selections can't leak across when the user switches filetypes mid-session. + make_big_file_path <- function(id_suffix) { + browse_id <- paste0("big_file_browse_", id_suffix) + path_id <- paste0("big_file_path_", id_suffix) + + shinyFiles::shinyFileChoose(input, browse_id, roots = volumes, session = session) + + file_info <- reactive({ + req(is.list(input[[browse_id]])) + shinyFiles::parseFilePaths(volumes, input[[browse_id]]) + }) + + output[[path_id]] <- renderPrint({ + req(nrow(file_info()) > 0) + cat(file_info()$name) + }) + + reactive({ + info <- file_info() + if (nrow(info) > 0) info$datapath else NULL + }) + } + + local_big_file_path_spec <- make_big_file_path("spec") + local_big_file_path_diann <- make_big_file_path("diann") + } else { - local_big_file_path <- reactive({ NULL }) + local_big_file_path_spec <- reactive({ NULL }) + local_big_file_path_diann <- reactive({ NULL }) } output$spectronaut_header_ui <- renderUI({ @@ -232,7 +238,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { } } else if (input$filetype == "spec") { spec_regular_file_ok <- !isTRUE(input$big_file_spec) && !is.null(input$specdata) - spec_big_file_ok <- isTRUE(input$big_file_spec) && length(local_big_file_path()) > 0 + spec_big_file_ok <- isTRUE(input$big_file_spec) && length(local_big_file_path_spec()) > 0 if((spec_regular_file_ok || spec_big_file_ok) && !is.null(input$sep_specdata)) { enable("proceed1") } @@ -242,7 +248,7 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { } } else if (input$filetype == "diann") { diann_regular_file_ok <- !isTRUE(input$big_file_diann) && !is.null(input$dianndata) - diann_big_file_ok <- isTRUE(input$big_file_diann) && length(local_big_file_path()) > 0 + diann_big_file_ok <- isTRUE(input$big_file_diann) && length(local_big_file_path_diann()) > 0 if((diann_regular_file_ok || diann_big_file_ok) && !is.null(input$sep_dianndata)) { enable("proceed1") } diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index efae2bf..cebc261 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -328,8 +328,8 @@ create_diann_standard_ui <- function(ns) { #' @noRd create_spectronaut_large_file_ui <- function(ns) { tagList( - shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("big_file_path")) + shinyFiles::shinyFilesButton(ns("big_file_browse_spec"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("big_file_path_spec")) ) } @@ -337,8 +337,8 @@ create_spectronaut_large_file_ui <- function(ns) { #' @noRd create_diann_large_file_ui <- function(ns) { tagList( - shinyFiles::shinyFilesButton(ns("big_file_browse"), "Browse for local file...", "Please select a file", multiple = FALSE), - verbatimTextOutput(ns("big_file_path")) + shinyFiles::shinyFilesButton(ns("big_file_browse_diann"), "Browse for local file...", "Please select a file", multiple = FALSE), + verbatimTextOutput(ns("big_file_path_diann")) ) } diff --git a/R/utils.R b/R/utils.R index e3b8ebe..0eef9a3 100644 --- a/R/utils.R +++ b/R/utils.R @@ -195,6 +195,20 @@ getFileExtension <- function(filename) { tolower(file_ext(basename(filename))) } +# Resolves the DIANN intensity-column name from UI inputs. Used by both the +# runtime call and the exported-script generator so they cannot drift apart. +# "auto" is a sentinel handled by MSstatsConvert::.cleanDIANNProcessQuantificationColumns +# that detects DIANN 2.0+ column renames at parse time. +resolveDiannQuantCol <- function(input) { + if (isTRUE(input$diann_2plus)) { + "auto" + } else if (!is.null(input$intensity_column) && nzchar(input$intensity_column)) { + input$intensity_column + } else { + "auto" + } +} + #' @importFrom arrow read_parquet getData <- function(input) { show_modal_spinner() @@ -502,10 +516,11 @@ getData <- function(input) { else if(input$filetype == 'spec') { if (isTRUE(input$big_file_spec)) { - # Logic for big Spectronaut files - # Parse the file path from shinyFiles input + # Each filetype has its own shinyFiles input id — see make_big_file_path() + # in module-loadpage-server.R — so a path picked under DIANN cannot + # accidentally feed the Spectronaut converter when the user switches modes. volumes <- shinyFiles::getVolumes()() - path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse) + path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse_spec) local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL # Validate inputs @@ -529,8 +544,8 @@ getData <- function(input) { shinybusy::update_modal_spinner(text = "Processing large Spectronaut file...") - # Call the big file conversion function from MSstatsConvert - converted_data <- bigSpectronauttoMSstatsFormat( + # Call the big file conversion function from MSstatsBig + converted_data <- MSstatsBig::bigSpectronauttoMSstatsFormat( input_file = local_big_file_path, output_file_name = "output_file.csv", backend = "arrow", @@ -593,10 +608,10 @@ getData <- function(input) { } else if(input$filetype == 'diann') { if (isTRUE(input$big_file_diann)) { - # Logic for big DIANN files - # Parse the file path from shinyFiles input + # Reads the DIANN-scoped shinyFiles input — see the Spectronaut branch + # above and make_big_file_path() for why ids are split per filetype. volumes <- shinyFiles::getVolumes()() - path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse) + path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse_diann) local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL if (!is.numeric(input$max_feature_count) || is.na(input$max_feature_count) || input$max_feature_count <= 0) { @@ -613,16 +628,9 @@ getData <- function(input) { shinybusy::update_modal_spinner(text = "Processing large DIANN file...") - big_quantificationColumn <- if (isTRUE(input$diann_2plus)) { - "auto" - } else if (!is.null(input$intensity_column) && nzchar(input$intensity_column)) { - input$intensity_column - } else { - "auto" - } + big_quantificationColumn <- resolveDiannQuantCol(input) - # Call the big file conversion function from MsStatsBig - big_diann_args <- list( + converted_data <- MSstatsBig::bigDIANNtoMSstatsFormat( input_file = local_big_file_path, annotation = getAnnot(input), output_file_name = "output_file.csv", @@ -634,10 +642,6 @@ getData <- function(input) { aggregate_psms = input$aggregate_psms, filter_few_obs = input$filter_few_obs ) - message("---- bigDIANNtoMSstatsFormat args ----") - utils::str(big_diann_args, max.level = 1, give.attr = FALSE) - message("--------------------------------------") - converted_data <- do.call(MSstatsBig::bigDIANNtoMSstatsFormat, big_diann_args) # Attempt to load the data into memory. @@ -955,7 +959,7 @@ library(MSstatsPTM)\n", sep = "") codes = paste(codes, " output_file_name = \"output_file.csv\",\n", sep = "") codes = paste(codes, " backend = \"arrow\",\n", sep = "") codes = paste(codes, " MBR = ", isTRUE(input$diann_MBR), ",\n", sep = "") - codes = paste(codes, " quantificationColumn = \"", input$diann_quantificationColumn, "\",\n", sep = "") + codes = paste(codes, " quantificationColumn = \"", resolveDiannQuantCol(input), "\",\n", sep = "") codes = paste(codes, " max_feature_count = ", input$max_feature_count, ",\n", sep = "") codes = paste(codes, " filter_unique_peptides = ", input$filter_unique_peptides, ",\n", sep = "") codes = paste(codes, " aggregate_psms = ", input$aggregate_psms, ",\n", sep = "") diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index e72f98b..db11b49 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1444,7 +1444,7 @@ describe("getData for Big Spectronaut", { mock_input_big <- list( filetype = "spec", big_file_spec = TRUE, - big_file_browse = list(files = list("file.csv")), + big_file_browse_spec = list(files = list("file.csv")), qvalue_cutoff = 0.01, max_feature_count = 20, filter_by_excluded = FALSE, @@ -1534,7 +1534,7 @@ describe("getData for Big DIANN", { mock_input_big_diann <- list( filetype = "diann", big_file_diann = TRUE, - big_file_browse = list(files = list("file.csv")), + big_file_browse_diann = list(files = list("file.csv")), max_feature_count = 20, diann_MBR = TRUE, diann_quantificationColumn = "Fragment.Quant.Corrected", From 61c5a2828ef304da9316068a41f1eab910b4a353 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Fri, 8 May 2026 21:44:41 -0500 Subject: [PATCH 17/18] removing the dead checkbox --- R/module-loadpage-server.R | 7 +++---- R/module-loadpage-ui.R | 5 ++--- tests/testthat/test-utils.R | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 9199484..267b3c7 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -108,15 +108,14 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { if (!is_web_server && isTRUE(input$big_file_diann)) { mbr_def <- if (is.null(input$diann_MBR)) TRUE else input$diann_MBR - quant_col_def <- if (is.null(input$diann_quantificationColumn)) "Fragment.Quant.Corrected" else input$diann_quantificationColumn - + max_feature_def <- if (is.null(input$max_feature_count)) 100 else input$max_feature_count unique_peps_def <- if (is.null(input$filter_unique_peptides)) FALSE else input$filter_unique_peptides agg_psms_def <- if (is.null(input$aggregate_psms)) FALSE else input$aggregate_psms few_obs_def <- if (is.null(input$filter_few_obs)) FALSE else input$filter_few_obs - + tagList( - create_diann_large_filter_options(session$ns, mbr_def, quant_col_def), + create_diann_large_filter_options(session$ns, mbr_def), create_diann_large_bottom_ui(session$ns, max_feature_def, unique_peps_def, agg_psms_def, few_obs_def) ) } else { diff --git a/R/module-loadpage-ui.R b/R/module-loadpage-ui.R index cebc261..7c87fdb 100644 --- a/R/module-loadpage-ui.R +++ b/R/module-loadpage-ui.R @@ -356,12 +356,11 @@ create_spectronaut_large_filter_options <- function(ns, excluded_def = FALSE, id #' Create DIANN large file filter options #' @noRd -create_diann_large_filter_options <- function(ns, mbr_def = TRUE, quant_col_def = "Fragment.Quant.Corrected") { +create_diann_large_filter_options <- function(ns, mbr_def = TRUE) { tagList( tags$hr(), h4("Options for large file processing"), - checkboxInput(ns("diann_MBR"), "MBR Enabled", value = mbr_def), - textInput(ns("diann_quantificationColumn"), "Quantification Column", value = quant_col_def) + checkboxInput(ns("diann_MBR"), "MBR Enabled", value = mbr_def) ) } diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index db11b49..4595780 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1537,7 +1537,7 @@ describe("getData for Big DIANN", { big_file_browse_diann = list(files = list("file.csv")), max_feature_count = 20, diann_MBR = TRUE, - diann_quantificationColumn = "Fragment.Quant.Corrected", + diann_2plus = TRUE, filter_unique_peptides = TRUE, aggregate_psms = TRUE, filter_few_obs = TRUE, From f90620ad9f4fd5c2e24a4c670b318e1989804517 Mon Sep 17 00:00:00 2001 From: Rudhik1904 Date: Sat, 9 May 2026 11:27:55 -0500 Subject: [PATCH 18/18] Single source of truth for big-file paths. MSstatsBig removed from Imports, added to Suggests. --- DESCRIPTION | 5 ++- NAMESPACE | 2 - R/MSstatsShiny.R | 1 - R/module-loadpage-server.R | 6 ++- R/utils.R | 35 +++++++++------ tests/testthat/test-utils.R | 90 +++++++++++++------------------------ 6 files changed, 61 insertions(+), 78 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e9a357a..5550094 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -25,10 +25,11 @@ Authors@R: c( License: Artistic-2.0 Depends: R (>= 4.2) Imports: shiny, shinyBS, shinyjs, shinybusy, dplyr, ggplot2, plotly, data.table, Hmisc, shinyFiles, - MSstats,MSstatsBig, MSstatsTMT, MSstatsPTM, MSstatsConvert, gplots, marray, DT, readxl, + MSstats, MSstatsTMT, MSstatsPTM, MSstatsConvert, gplots, marray, DT, readxl, ggrepel, uuid, utils, stats, htmltools, methods, tidyr, grDevices, graphics, mockery, MSstatsBioNet, shinydashboard, arrow, tools, MSstatsResponse -Suggests: +Suggests: + MSstatsBig, rmarkdown, tinytest, sessioninfo, diff --git a/NAMESPACE b/NAMESPACE index 7ecb09d..1690c4b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -37,8 +37,6 @@ importFrom(DT,datatable) importFrom(DT,renderDT) importFrom(DT,renderDataTable) importFrom(Hmisc,describe) -importFrom(MSstatsBig,bigDIANNtoMSstatsFormat) -importFrom(MSstatsBig,bigSpectronauttoMSstatsFormat) importFrom(MSstatsBioNet,annotateProteinInfoFromIndra) importFrom(MSstatsBioNet,generateCytoscapeConfig) importFrom(MSstatsBioNet,getSubnetworkFromIndra) diff --git a/R/MSstatsShiny.R b/R/MSstatsShiny.R index 1861d5c..c378098 100644 --- a/R/MSstatsShiny.R +++ b/R/MSstatsShiny.R @@ -35,7 +35,6 @@ #' @importFrom dplyr `%>%` filter summarise n_distinct group_by ungroup select n mutate #' @importFrom tidyr unite #' @importFrom MSstatsConvert MSstatsLogsSettings -#' @importFrom MSstatsBig bigDIANNtoMSstatsFormat bigSpectronauttoMSstatsFormat #' @importFrom MSstatsPTM dataProcessPlotsPTM groupComparisonPlotsPTM MaxQtoMSstatsPTMFormat PDtoMSstatsPTMFormat FragPipetoMSstatsPTMFormat SkylinetoMSstatsPTMFormat SpectronauttoMSstatsPTMFormat #' @importFrom utils capture.output head packageVersion read.csv read.delim write.csv #' @importFrom stats aggregate diff --git a/R/module-loadpage-server.R b/R/module-loadpage-server.R index 267b3c7..0333913 100644 --- a/R/module-loadpage-server.R +++ b/R/module-loadpage-server.R @@ -358,7 +358,11 @@ loadpageServer <- function(id, parent_session, is_web_server = FALSE) { get_data = eventReactive(input$proceed1, { - getData(input) + getData( + input, + big_file_path_spec = local_big_file_path_spec(), + big_file_path_diann = local_big_file_path_diann() + ) }) diff --git a/R/utils.R b/R/utils.R index 0eef9a3..2136ff8 100644 --- a/R/utils.R +++ b/R/utils.R @@ -210,7 +210,7 @@ resolveDiannQuantCol <- function(input) { } #' @importFrom arrow read_parquet -getData <- function(input) { +getData <- function(input, big_file_path_spec = NULL, big_file_path_diann = NULL) { show_modal_spinner() ev_maxq = getEvidence(input) pg_maxq = getProteinGroups(input) @@ -516,13 +516,17 @@ getData <- function(input) { else if(input$filetype == 'spec') { if (isTRUE(input$big_file_spec)) { - # Each filetype has its own shinyFiles input id — see make_big_file_path() - # in module-loadpage-server.R — so a path picked under DIANN cannot - # accidentally feed the Spectronaut converter when the user switches modes. - volumes <- shinyFiles::getVolumes()() - path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse_spec) - local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL - + # The resolved path is supplied by the caller (the loadpage server's + # local_big_file_path_spec reactive). See make_big_file_path() in + # module-loadpage-server.R — each filetype has its own shinyFiles input + # so a selection made under DIANN cannot leak into the Spectronaut path. + if (!requireNamespace("MSstatsBig", quietly = TRUE)) { + showNotification("Error: MSstatsBig is required for large file mode but is not installed.", type = "error", duration = NULL) + shinybusy::remove_modal_spinner() + return(NULL) + } + local_big_file_path <- big_file_path_spec + # Validate inputs if (!is.numeric(input$qvalue_cutoff) || is.na(input$qvalue_cutoff) || input$qvalue_cutoff < 0 || input$qvalue_cutoff > 1) { showNotification("Error: qvalue_cutoff must be between 0 and 1.", type = "error") @@ -608,12 +612,15 @@ getData <- function(input) { } else if(input$filetype == 'diann') { if (isTRUE(input$big_file_diann)) { - # Reads the DIANN-scoped shinyFiles input — see the Spectronaut branch - # above and make_big_file_path() for why ids are split per filetype. - volumes <- shinyFiles::getVolumes()() - path_info <- shinyFiles::parseFilePaths(volumes, input$big_file_browse_diann) - local_big_file_path <- if (nrow(path_info) > 0) path_info$datapath else NULL - + # See the Spectronaut branch above for the rationale on per-filetype + # path resolution. + if (!requireNamespace("MSstatsBig", quietly = TRUE)) { + showNotification("Error: MSstatsBig is required for large file mode but is not installed.", type = "error", duration = NULL) + shinybusy::remove_modal_spinner() + return(NULL) + } + local_big_file_path <- big_file_path_diann + if (!is.numeric(input$max_feature_count) || is.na(input$max_feature_count) || input$max_feature_count <= 0) { showNotification("Error: max_feature_count must be a positive number.", type = "error") shinybusy::remove_modal_spinner() diff --git a/tests/testthat/test-utils.R b/tests/testthat/test-utils.R index 4595780..90c27eb 100644 --- a/tests/testthat/test-utils.R +++ b/tests/testthat/test-utils.R @@ -1439,12 +1439,10 @@ describe("getData for Spectronaut input with anomaly scores", { }) describe("getData for Big Spectronaut", { - - # Common mock input for big spec + mock_input_big <- list( filetype = "spec", big_file_spec = TRUE, - big_file_browse_spec = list(files = list("file.csv")), qvalue_cutoff = 0.01, max_feature_count = 20, filter_by_excluded = FALSE, @@ -1456,85 +1454,71 @@ describe("getData for Big Spectronaut", { BIO = "Protein", DDA_DIA = "DIA" ) - - # Mock data to return + mock_arrow_obj <- list(dummy = "arrow") mock_df <- data.frame(ProteinName = "P1", Intensity = 100) - + test_that("Valid input returns data", { - # Mocks - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) stub(getData, "file.exists", TRUE) stub(getData, "MSstatsBig::bigSpectronauttoMSstatsFormat", mock_arrow_obj) stub(getData, "dplyr::collect", mock_df) stub(getData, "showNotification", function(...) NULL) stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big) + + res <- getData(mock_input_big, big_file_path_spec = "test.csv") expect_equal(res, mock_df) }) - + test_that("Invalid qvalue_cutoff returns NULL", { bad_input <- mock_input_big bad_input$qvalue_cutoff <- 1.5 - - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "qvalue_cutoff")) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - res <- getData(bad_input) + res <- getData(bad_input, big_file_path_spec = "test.csv") expect_null(res) }) - + test_that("Invalid max_feature_count returns NULL", { bad_input <- mock_input_big bad_input$max_feature_count <- 0 - - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "max_feature_count")) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(bad_input) + + res <- getData(bad_input, big_file_path_spec = "test.csv") expect_null(res) }) - + test_that("File not found returns NULL", { - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "nonexistent.csv")) stub(getData, "file.exists", FALSE) stub(getData, "showNotification", function(msg, ...) expect_match(msg, "does not exist")) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big) + + res <- getData(mock_input_big, big_file_path_spec = "nonexistent.csv") expect_null(res) }) - + test_that("Memory error returns NULL", { - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) stub(getData, "file.exists", TRUE) stub(getData, "MSstatsBig::bigSpectronauttoMSstatsFormat", mock_arrow_obj) stub(getData, "dplyr::collect", function(...) stop("Memory allocation failed")) stub(getData, "showNotification", function(msg, ...) expect_match(msg, "Memory Error")) stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big) + + res <- getData(mock_input_big, big_file_path_spec = "test.csv") expect_null(res) }) }) describe("getData for Big DIANN", { - # Common mock input for big diann mock_input_big_diann <- list( filetype = "diann", big_file_diann = TRUE, - big_file_browse_diann = list(files = list("file.csv")), max_feature_count = 20, diann_MBR = TRUE, diann_2plus = TRUE, @@ -1544,61 +1528,51 @@ describe("getData for Big DIANN", { BIO = "Protein", DDA_DIA = "DIA" ) - - # Mock data to return + mock_arrow_obj <- list(dummy = "arrow") mock_df <- data.frame(ProteinName = "P1", Intensity = 100) - + test_that("Valid input returns data", { - # Mocks - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) stub(getData, "file.exists", TRUE) stub(getData, "MSstatsBig::bigDIANNtoMSstatsFormat", mock_arrow_obj) stub(getData, "dplyr::collect", mock_df) stub(getData, "showNotification", function(...) NULL) stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big_diann) + + res <- getData(mock_input_big_diann, big_file_path_diann = "test.csv") expect_equal(res, mock_df) }) - + test_that("Invalid max_feature_count returns NULL", { bad_input <- mock_input_big_diann bad_input$max_feature_count <- 0 - - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) + stub(getData, "showNotification", function(msg, ...) expect_match(msg, "max_feature_count")) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(bad_input) + + res <- getData(bad_input, big_file_path_diann = "test.csv") expect_null(res) }) - + test_that("File not found returns NULL", { - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "nonexistent.csv")) stub(getData, "file.exists", FALSE) stub(getData, "showNotification", function(msg, ...) expect_match(msg, "does not exist")) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big_diann) + + res <- getData(mock_input_big_diann, big_file_path_diann = "nonexistent.csv") expect_null(res) }) - + test_that("Memory error returns NULL", { - stub(getData, "shinyFiles::getVolumes", function() function() c(root = "/")) - stub(getData, "shinyFiles::parseFilePaths", function(...) data.frame(datapath = "test.csv")) stub(getData, "file.exists", TRUE) stub(getData, "MSstatsBig::bigDIANNtoMSstatsFormat", mock_arrow_obj) stub(getData, "dplyr::collect", function(...) stop("Memory allocation failed")) stub(getData, "showNotification", function(msg, ...) expect_match(msg, "Memory Error")) stub(getData, "shinybusy::update_modal_spinner", function(...) NULL) stub(getData, "shinybusy::remove_modal_spinner", function(...) NULL) - - res <- getData(mock_input_big_diann) + + res <- getData(mock_input_big_diann, big_file_path_diann = "test.csv") expect_null(res) }) })