Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
138 changes: 135 additions & 3 deletions R/module-loadpage-server.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,143 @@
#'
#' @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
#'
#' @export
#' @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.
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)
})
}
else {
local_big_file_path <- reactive({ NULL })
}

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$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
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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')

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
}
})
Comment on lines +100 to +143
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the definitions of the bottom UI functions
fd -t f -e R . | xargs rg -l "create_diann_large_bottom_ui|create_spectronaut_large_bottom_ui"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Search for these function definitions
rg -n "create_diann_large_bottom_ui\s*<-|create_diann_large_bottom_ui\s*=" R/ --type=r -A 30

Repository: Vitek-Lab/MSstatsShiny

Length of output: 2591


🏁 Script executed:

#!/bin/bash
# Search for spectronaut function
rg -n "create_spectronaut_large_bottom_ui\s*<-|create_spectronaut_large_bottom_ui\s*=" R/ --type=r -A 30

Repository: Vitek-Lab/MSstatsShiny

Length of output: 2487


Shared input IDs between DIANN and Spectronaut large-file options cause stale defaults when switching file types.

Both create_diann_large_bottom_ui() and create_spectronaut_large_bottom_ui() create inputs with identical namespaced IDs: max_feature_count, filter_unique_peptides, aggregate_psms, and filter_few_obs. When a user switches from DIANN big-file mode (e.g., setting max_feature_count to 100) to Spectronaut big-file mode, the if (is.null(input$max_feature_count)) guard on lines 107 and 130 fails because the input is not NULL—it retains the DIANN value of 100. As a result, Spectronaut's default of 20 is never applied, and the user sees an unexpected carryover value.

While this is a minor UX issue in practice (switching between file types is an uncommon workflow), consider isolating the input IDs per file type (e.g., "diann_max_feature_count" vs "spec_max_feature_count") to prevent state leakage.

🤖 Prompt for AI Agents
In `@R/module-loadpage-server.R` around lines 100 - 143, The shared input IDs
(max_feature_count, filter_unique_peptides, aggregate_psms, filter_few_obs) used
by create_diann_large_bottom_ui() and create_spectronaut_large_bottom_ui() cause
state leakage between DIANN and Spectronaut; fix by giving each UI its own
namespaced IDs (e.g., diann_max_feature_count, diann_filter_unique_peptides,
diann_aggregate_psms, diann_filter_few_obs for DIANN and spec_max_feature_count,
spec_filter_unique_peptides, spec_aggregate_psms, spec_filter_few_obs for
Spectronaut) inside those functions and update all references in
module-loadpage-server.R (the input checks and local default variables used in
output$diann_options_ui and output$spectronaut_options_ui) to read the new
per-tool input names so defaults are applied correctly when switching file
types.


# toggle ui (DDA DIA SRM)
observe({
print("bio")
Expand Down Expand Up @@ -103,15 +231,19 @@ 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_big_file_path()) > 0
if((spec_regular_file_ok || spec_big_file_ok) && !is.null(input$sep_specdata)) {
enable("proceed1")
}
} else if (input$filetype == "ump") {
if(!is.null(input$fragSummary) && !is.null(input$peptideSummary) && !is.null(input$protSummary)) { #&& !is.null(input$annot2)
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")
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Expand Down
132 changes: 119 additions & 13 deletions R/module-loadpage-ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand Down Expand Up @@ -271,22 +271,128 @@ 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"))
Comment thread
tonywu1999 marked this conversation as resolved.
)
}

#' 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"),
fileInput(ns('specdata'), "", multiple = FALSE, accept = NULL),
create_separator_buttons(ns, "sep_specdata")
tagList(
uiOutput(ns("spectronaut_header_ui")),
uiOutput(ns("spectronaut_file_selection_ui")),
uiOutput(ns("spectronaut_options_ui"))
)
}

#' Create Spectronaut header
#' @noRd
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("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"))
)
}

#' 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 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) {
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 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)
)
}

Expand Down Expand Up @@ -486,14 +592,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
Expand Down
2 changes: 1 addition & 1 deletion R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading