Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
6 changes: 3 additions & 3 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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))
}

tagList(ui_elements, create_separator_buttons(session$ns, "sep_dianndata"))
})
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
129 changes: 116 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,125 @@ 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) {
fileInput(ns('dianndata'), "", 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("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 +589,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