Skip to content

Merge the feature to download dose response curve analysis results into development#180

Merged
tonywu1999 merged 3 commits intodevelfrom
feat/dose-response-curve
Mar 19, 2026
Merged

Merge the feature to download dose response curve analysis results into development#180
tonywu1999 merged 3 commits intodevelfrom
feat/dose-response-curve

Conversation

@swaraj-neu
Copy link
Copy Markdown
Contributor

@swaraj-neu swaraj-neu commented Mar 13, 2026

  • Enable downloading dose response curve analysis results

Motivation and Context

The app previously supported downloading analysis code for standard comparative proteomics but lacked support for dose–response (response-curve) studies and tools to plan future dose–response experiments. This PR adds: (1) generation of reproducible dose–response analysis code (MSstatsResponse) for users to download and run locally, (2) simulation utilities and plotting for true-positive-rate (TPR) power curves to plan future experiments, and (3) UI/server plumbing to expose these capabilities in the "Design future experiments" (Expdes) workflow.

Short summary of the solution

  • generate_analysis_code now detects a response-curve comparison mode and emits MSstatsResponse-based R code (setup_metadata, prepare_dose_response_fit, doseResponseFit, visualizeResponseProtein). When response-curve mode is detected the function returns early and skips the normal contrast/groupComparison code path.
  • New simulation utilities (run_tpr_simulation) call MSstatsResponse::futureExperimentSimulation across grids of replicate counts and concentration-count ladders and aggregate TPR results for "Strong" and "Weak" interactions.
  • New plotting helper (plot_tpr_power_curve) builds two-panel ggplot2 plots (Strong vs Weak) and converts them to a Plotly subplot for interactive display and download.
  • Expdes UI was refactored to a dynamic sidebar (uiOutput) and namespaced outputs; the Expdes server was refactored to accept preprocess_data and statmodel_contrast, run asynchronous simulations, render/export plots, and expose prepared response data.
  • Package imports, constants, and man pages were updated to support the above features.

Detailed changes

  • R/statmodel-server-download-code.R

    • Added response-curve branch in generate_analysis_code:
      • Detects response-curve mode from input.
      • Emits code to load MSstatsResponse, serialize condition names from comp_mat, call setup_metadata, prepare_dose_response_fit, and doseResponseFit with parameters wired from UI (increasing, transform_dose).
      • Emits visualizeResponseProtein call using UI options (ratio scale, transform, increasing).
      • Returns generated code early to avoid emitting the standard contrast-building and groupComparison code.
  • R/module-expdes-server.R

    • Added hardcoded concentration ladder map (CONC_MAP) for 2–9 concentration counts.
    • Implemented run_tpr_simulation(rep_range, n_proteins = 1000):
      • Sweeps replicate and concentration grids.
      • Calls MSstatsResponse::futureExperimentSimulation for each grid point.
      • Aggregates Hit_Rates_Data into a standardized data.frame with Interaction (Strong/Weak), TPR, N_rep, NumConcs.
      • Wraps individual runs in tryCatch and logs failures to stderr.
    • Implemented plot_tpr_power_curve(simulation_results):
      • Builds separated ggplot2 panels for Strong and Weak interactions with custom linetypes/colors and converts them to plotly::subplot.
      • Hides duplicated legends and sets axis/limits suitable for percent TPR.
    • Expanded expdesServer public signature to accept preprocess_data and statmodel_contrast and refactored server logic to use these inputs and to support response-curve workflows and asynchronous simulation runs.
  • R/module-expdes-ui.R

    • Replaced many static sidebar inputs with uiOutput(ns(NAMESPACE_EXPDES$sidebar_controls)).
    • Namespaced main outputs: plotlyOutput(ns(NAMESPACE_EXPDES$result_plot)) and downloadButton(ns(NAMESPACE_EXPDES$download_future)).
  • R/module-statmodel-server.R

    • Added handling for response-curve comparison mode when building contrast.matrix (build_response_curve_matrix).
    • statmodelServer exposes contrast (reactive) so other modules can consume contrast info.
  • R/server.R

    • Wired statmodel_values$contrast into the callModule(expdesServer, ...) invocation by passing preprocess_data and statmodel_contrast.
  • R/constants.R

    • Added NAMESPACE_EXPDES list with namespaced IDs for Expdes UI/server wiring.
    • Added CONSTANTS_STATMODEL comparison_mode_response_curve and plot_type_response_curve.
  • R/statmodel-server-visualization.R

    • Added response-curve UI/visualization plumbing:
      • create_response_curve_options usage in plotting options.
      • visualization_response_curve_which_drug selectInput populated from prepare_dose_response_fit(contrast.matrix).
    • create_group_comparison_plot retains a tryCatch wrapper and modal spinner behavior.
  • NAMESPACE & R/MSstatsShiny.R

    • Added importFrom(MSstatsResponse, futureExperimentSimulation) and other granular imports (dplyr::if_else, ggplot2/plotly primitives) to NAMESPACE and R/MSstatsShiny.R.
  • Documentation (man/*.Rd)

    • Added/updated Rd files: expdesServer, run_tpr_simulation, plot_tpr_power_curve, get_download_plot_filename, zip_and_copy_plot.
  • Tests

    • tests/testthat/test-utils-statmodel-server.R substantially expanded to verify generate_analysis_code behavior and contrast-building logic (detailed in next section).

Unit tests added or modified

  • tests/testthat/test-utils-statmodel-server.R
    • Adds many tests around experimental-condition detection and contrast-building helpers:
      • get_experimental_conditions: covers PTM/TMT/Condition/GROUP cases.
      • build_custom_pairwise_contrast: single and multiple comparisons and no-op behavior when same groups selected.
    • The test file (first 120 lines shown) contains new expectations used to validate contrast building and condition extraction logic that underpin both standard and response-curve code generation.
    • Note: Additional tests in this file (not shown in excerpt) assert generated-code contents for response-curve code emission (presence of setup_metadata, prepare_dose_response_fit, doseResponseFit, visualizeResponseProtein) and that standard groupComparison code is not emitted when response-curve mode is selected.

Coding guidelines / notable issues (violations or concerns)

  1. Mismatch between fitted-model and visualization ratio flag

    • generate_analysis_code hardcodes ratio_response = FALSE when calling doseResponseFit but uses the UI checkbox value for ratio_response in visualizeResponseProtein. This can produce visualizations inconsistent with the fitted model and should be reconciled.
  2. Emitted code contains hardcoded placeholders and fixed parameters

    • The generated visualizeResponseProtein call includes placeholder strings ("Enter protein name here", "Enter drug name here") and fixed parameters (show_ic50 = TRUE, add_ci = TRUE, n_samples = 1000). These could be parameterized or documented to avoid confusing users.
  3. Generated/downloaded scripts lack defensive checks and error handling

    • The code emitted for users does not validate inputs or wrap model-fitting/visualization calls in tryCatch; user-run scripts may fail silently or with confusing errors. Consider emitting minimal validation and try/catch around fitting/plotting.
  4. Documentation mismatch in man/run_tpr_simulation.Rd

    • The Rd page documents an extra argument "protein" that is not present in the function signature (run_tpr_simulation(rep_range, n_proteins = 1000)). Documentation and implementation should be reconciled.
  5. Module size and import surface

    • R/module-expdes-server.R centralizes simulation, plotting, UI wiring, and export logic resulting in a large file and many new imports in NAMESPACE. Consider extracting simulation/plot helpers into separate files and minimizing exported/imported symbols to reduce maintenance burden.
  6. Tests assert on exact emitted code strings (brittle)

    • Tests that assert the presence/absence of exact text fragments in generated R scripts are brittle to formatting or template edits. Prefer testing the semantic structure or performing round-trip checks where feasible.

@swaraj-neu swaraj-neu requested a review from tonywu1999 March 13, 2026 18:22
@swaraj-neu swaraj-neu self-assigned this Mar 13, 2026
@swaraj-neu swaraj-neu added the enhancement New feature or request label Mar 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds MSstatsResponse dose‑response code generation and tests; introduces experiment‑design TPR simulation and plotting (run_tpr_simulation, plot_tpr_power_curve); makes Expdes UI dynamic and namespaced; wires statmodel contrast/preprocess_data through server/module layers; updates NAMESPACE imports and adds related docs and download helpers.

Changes

Cohort / File(s) Summary
Response‑Curve Code Generation
R/statmodel-server-download-code.R
New branch that emits MSstatsResponse dose‑response analysis code (library load, condition names, contrast matrix, data prep, doseResponseFit, visualizeResponseProtein) and returns early in response‑curve mode.
Tests: statmodel codegen & contrasts
tests/testthat/test-utils-statmodel-server.R
Large test additions validating generated dose‑response code, parameter serialization (increasing_trend, log_xaxis, ratio_scale), condition name serialization, and preservation of non‑response comparison code paths across DDA/DIA/PTM/TMT and contrast modes.
Expdes server & simulation
R/module-expdes-server.R
Adds CONC_MAP, run_tpr_simulation, plot_tpr_power_curve; integrates futureExperimentSimulation for async TPR simulations; expands expdesServer signature to accept preprocess_data and statmodel_contrast; wires simulation UI flow, plotting, and download/export.
Expdes UI
R/module-expdes-ui.R
Replaces static sidebar controls with namespaced dynamic UI (uiOutput(ns(NAMESPACE_EXPDES$sidebar_controls))), updates plotly output and download button to namespaced IDs.
Statmodel wiring & visualization
R/module-statmodel-server.R, R/statmodel-server-visualization.R, R/MSstatsShiny.R
statmodelServer now exposes contrast reactive; visualization/download helpers updated; import futureExperimentSimulation wired.
Constants / NAMESPACE imports
R/constants.R, NAMESPACE
Adds NAMESPACE_EXPDES constant and imports (MSstatsResponse::futureExperimentSimulation, dplyr::if_else, multiple ggplot2 symbols, plotly::ggplotly/subplot).
Server integration & docs
R/server.R, man/expdesServer.Rd
Wires statmodel_contrast into expdesServer call site and documents new optional args (preprocess_data, statmodel_contrast).
Man pages for new functions & helpers
man/*.Rd (run_tpr_simulation.Rd, plot_tpr_power_curve.Rd, get_download_plot_filename.Rd, zip_and_copy_plot.Rd)
Adds roxygen‑generated documentation for new simulation/plot/download helper functions.
Return value change
R/module-statmodel-server.R
statmodelServer return extended to include contrast reactive alongside dataComparison.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UI as Expdes_UI
  participant Server as expdesServer
  participant Sim as run_tpr_simulation
  participant MSR as MSstatsResponse
  participant Plot as plot_tpr_power_curve
  participant DL as zip_and_copy_plot

  User->>UI: Click "Run simulation"
  UI->>Server: trigger run (mode, rep_range, params)
  Server->>Sim: run_tpr_simulation(rep_range, n_proteins)
  Sim->>MSR: futureExperimentSimulation(...)
  MSR-->>Sim: simulation results
  Sim-->>Server: aggregated TPR results
  Server->>Plot: plot_tpr_power_curve(simulation_results)
  Plot-->>UI: render Plotly output
  User->>UI: Click download
  UI->>Server: request zip
  Server->>DL: zip_and_copy_plot(pdf_files, dest_file)
  DL-->>Server: return .zip
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested labels

Review effort 3/5

Suggested reviewers

  • sszvetecz

Poem

🐰 I hopped through lines of code at night,
Dose by dose I set things right,
I simulated power, fit each curve,
Zipped plots and sent them with a swerve—
A tiny rabbit, plotting bright.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being merged: dose response curve analysis results download functionality, which aligns with the primary changeset introducing response-curve analysis code generation and visualization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dose-response-curve
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can enable review details to help with troubleshooting, context usage and more.

Enable the reviews.review_details setting to include review details such as the model used, the time taken for each step and more in the review comments.

@swaraj-neu swaraj-neu linked an issue Mar 13, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/testthat/test-utils-statmodel-server.R (1)

462-538: Consider adding edge case tests for robustness.

The test suite covers the main scenarios well. For additional robustness, consider adding tests for:

  1. Condition names containing special characters (quotes, backslashes)
  2. NULL or missing input parameters (e.g., modeling_response_curve_increasing_trend being NULL)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/testthat/test-utils-statmodel-server.R` around lines 462 - 538, Add two
new unit tests for generate_analysis_code: one that supplies a comp_mat with
GROUP names containing special characters (quotes and backslashes) and asserts
the resulting code contains the exact serialized names (ensuring proper
escaping, e.g., presence of the quoted strings in result); and another that sets
NAMESPACE_STATMODEL$modeling_response_curve_increasing_trend (and/or
modeling_response_curve_log_xaxis/visualization_response_curve_ratio_scale) to
NULL in mock_input and asserts generate_analysis_code still emits valid
parameter values (the expected defaults, e.g., increasing = FALSE or
transform_dose = <default>) rather than crashing or inserting "NULL". Reference
generate_analysis_code, the comp_mat construction and the
NAMESPACE_STATMODEL$... keys when adding these tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/statmodel-server-download-code.R`:
- Around line 15-16: The generated R string for condition_names directly
interpolates comp_mat$GROUP which can contain double-quote characters and break
the produced code; before building codes, escape any quotes in comp_mat$GROUP
(e.g., replace " with \" or use R's encodeString) and then use the escaped
vector in the paste that builds condition_names so the resulting R code remains
syntactically valid; update the line that constructs codes (the paste(...) that
references comp_mat$GROUP and condition_names) to use the escaped names variable
instead of comp_mat$GROUP directly.

---

Nitpick comments:
In `@tests/testthat/test-utils-statmodel-server.R`:
- Around line 462-538: Add two new unit tests for generate_analysis_code: one
that supplies a comp_mat with GROUP names containing special characters (quotes
and backslashes) and asserts the resulting code contains the exact serialized
names (ensuring proper escaping, e.g., presence of the quoted strings in
result); and another that sets
NAMESPACE_STATMODEL$modeling_response_curve_increasing_trend (and/or
modeling_response_curve_log_xaxis/visualization_response_curve_ratio_scale) to
NULL in mock_input and asserts generate_analysis_code still emits valid
parameter values (the expected defaults, e.g., increasing = FALSE or
transform_dose = <default>) rather than crashing or inserting "NULL". Reference
generate_analysis_code, the comp_mat construction and the
NAMESPACE_STATMODEL$... keys when adding these tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 072cfcab-9556-4387-9be8-f3f307320bdc

📥 Commits

Reviewing files that changed from the base of the PR and between d3fab95 and ca04912.

📒 Files selected for processing (2)
  • R/statmodel-server-download-code.R
  • tests/testthat/test-utils-statmodel-server.R

Comment on lines +15 to +16
codes = paste(codes, "condition_names = c(\"",
paste(comp_mat$GROUP, collapse = "\",\""), "\")\n", sep = "")
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider escaping special characters in condition names.

If a condition name contains a double-quote character ("), the generated R code would be syntactically invalid. While this is unlikely in typical experimental conditions, defensive escaping would prevent edge-case failures.

🛡️ Proposed fix to escape quotes in condition names
-    codes = paste(codes, "condition_names = c(\"",
-                  paste(comp_mat$GROUP, collapse = "\",\""), "\")\n", sep = "")
+    escaped_groups = gsub("\"", "\\\\\"", comp_mat$GROUP)
+    codes = paste(codes, "condition_names = c(\"",
+                  paste(escaped_groups, collapse = "\",\""), "\")\n", sep = "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
codes = paste(codes, "condition_names = c(\"",
paste(comp_mat$GROUP, collapse = "\",\""), "\")\n", sep = "")
escaped_groups = gsub("\"", "\\\\\"", comp_mat$GROUP)
codes = paste(codes, "condition_names = c(\"",
paste(escaped_groups, collapse = "\",\""), "\")\n", sep = "")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/statmodel-server-download-code.R` around lines 15 - 16, The generated R
string for condition_names directly interpolates comp_mat$GROUP which can
contain double-quote characters and break the produced code; before building
codes, escape any quotes in comp_mat$GROUP (e.g., replace " with \" or use R's
encodeString) and then use the escaped vector in the paste that builds
condition_names so the resulting R code remains syntactically valid; update the
line that constructs codes (the paste(...) that references comp_mat$GROUP and
condition_names) to use the escaped names variable instead of comp_mat$GROUP
directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@tonywu1999 Condition names are parsed from experimental design annotations and don't contain special characters. No other code generation in the codebase applies quote escaping either. So applying it here only would be inconsistent. What do you suggest?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
man/expdesServer.Rd (1)

20-38: ⚠️ Potential issue | 🟡 Minor

Document the new statmodel_contrast parameter in \arguments.

expdesServer(...) now exposes statmodel_contrast in usage, but its argument documentation is missing.

📝 Proposed Rd fix
 \item{preprocess_data}{function returning preprocessed data}
+
+\item{statmodel_contrast}{reactiveValues object carrying Statmodel contrast data (e.g., contrast matrix) for expdes workflows}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@man/expdesServer.Rd` around lines 20 - 38, The Rd file for expdesServer is
missing documentation for the new statmodel_contrast parameter; update the
\arguments section of expdesServer.Rd to add an \item{statmodel_contrast}{...}
entry describing the parameter (its type, role, expected values or structure,
and default behavior) so users understand how to pass contrasts to expdesServer;
reference the symbol name statmodel_contrast and the function expdesServer in
the description to match the usage in the code and keep the style consistent
with the other items (e.g., mention it is passed to the statmodel workflow or
used by the data_comparison/statmodel_input handlers).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-expdes-server.R`:
- Around line 107-113: Create a single composition helper (e.g., compose_panels
or build_combined_plot) that builds the combined figure by calling make_panel
for results_strong and results_weak, wrapping them with plotly::subplot(...) and
plotly::layout(...), and return that combined plot; replace the current separate
p_strong/p_weak usage so both the UI render and the PDF export call this helper
(instead of printing p_strong and p_weak separately) so the PDF device receives
the same combined subplot as shown in the app (apply same change to the other
occurrence around lines 310-320).
- Around line 76-77: The linetype mapping currently assumes at most five
replicate levels (ltypes and ltype_values) and breaks when length(rep_levels) >
5; update the logic to only map up to min(length(rep_levels), length(ltypes))
(e.g., use n_ltype <- min(length(rep_levels), length(ltypes)) and ltype_values
<- setNames(ltypes[seq_len(n_ltype)],
as.character(rep_levels[seq_len(n_ltype)]))), and for any extra rep_levels
beyond that assign a fallback aesthetic (e.g., default "solid" linetype plus a
distinct colour or size) or else cap the UI slider at 5; apply the same fix to
the other occurrences referenced (around the blocks at lines ~186-188 and
~293-294) so no replicate level is left without a mapped aesthetic.
- Around line 153-160: The response-mode selector is not wired into the
simulation: locate the reactive prepared_response_data() and the run handler
that calls run_tpr_simulation(), then pass the user's selected protein/dataset
into the simulation by (1) using the input value (e.g., input$response_mode or
the control that populates choices) to subset prepared_response_data() to a
single protein or dataset before calling prepare_dose_response_fit, (2) update
the run handler to supply that subset (and any chosen replicate range) as an
argument to run_tpr_simulation(), and (3) ensure the UI choices still come from
prepared_response_data(); if you prefer not to support this path yet,
remove/disable the response-mode selector and any code branches that assume it
exists (also apply the same wiring/removal changes at the other spots where
choices and runs are prepared and invoked).

---

Outside diff comments:
In `@man/expdesServer.Rd`:
- Around line 20-38: The Rd file for expdesServer is missing documentation for
the new statmodel_contrast parameter; update the \arguments section of
expdesServer.Rd to add an \item{statmodel_contrast}{...} entry describing the
parameter (its type, role, expected values or structure, and default behavior)
so users understand how to pass contrasts to expdesServer; reference the symbol
name statmodel_contrast and the function expdesServer in the description to
match the usage in the code and keep the style consistent with the other items
(e.g., mention it is passed to the statmodel workflow or used by the
data_comparison/statmodel_input handlers).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 034d4569-614d-4b50-84d5-a78ca424abb2

📥 Commits

Reviewing files that changed from the base of the PR and between ca04912 and 113b815.

📒 Files selected for processing (12)
  • NAMESPACE
  • R/MSstatsShiny.R
  • R/constants.R
  • R/module-expdes-server.R
  • R/module-expdes-ui.R
  • R/module-statmodel-server.R
  • R/server.R
  • man/expdesServer.Rd
  • man/get_download_plot_filename.Rd
  • man/plot_tpr_power_curve.Rd
  • man/run_tpr_simulation.Rd
  • man/zip_and_copy_plot.Rd
✅ Files skipped from review due to trivial changes (2)
  • man/plot_tpr_power_curve.Rd
  • man/run_tpr_simulation.Rd

Comment on lines +76 to +77
ltypes <- c("dotted", "dotdash", "dashed", "longdash", "solid")
ltype_values <- setNames(ltypes[seq_along(rep_levels)], as.character(rep_levels))
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 | 🟠 Major

Ranges above five replicates outgrow the line-style mapping.

The UI allows 1:10, but both plot builders only define five linetypes. Once length(rep_levels) > 5, ltypes[seq_along(rep_levels)] starts producing missing mappings, so valid selections can break or partially drop traces in both the interactive plot and the PDF export. Either cap the slider at five or switch the extra levels to another aesthetic instead of relying on linetype alone.

Also applies to: 186-188, 293-294

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 76 - 77, The linetype mapping
currently assumes at most five replicate levels (ltypes and ltype_values) and
breaks when length(rep_levels) > 5; update the logic to only map up to
min(length(rep_levels), length(ltypes)) (e.g., use n_ltype <-
min(length(rep_levels), length(ltypes)) and ltype_values <-
setNames(ltypes[seq_len(n_ltype)], as.character(rep_levels[seq_len(n_ltype)]))),
and for any extra rep_levels beyond that assign a fallback aesthetic (e.g.,
default "solid" linetype plus a distinct colour or size) or else cap the UI
slider at 5; apply the same fix to the other occurrences referenced (around the
blocks at lines ~186-188 and ~293-294) so no replicate level is left without a
mapped aesthetic.

Comment on lines +107 to +113
p_strong <- plotly::ggplotly(make_panel(results_strong, "#1b9e77", FALSE))
p_weak <- plotly::ggplotly(make_panel(results_weak, "#d95f02", TRUE))

plotly::subplot(p_strong, p_weak,
nrows = 1, shareY = TRUE, titleX = TRUE, titleY = TRUE,
margin = 0.05
) |> plotly::layout(
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

The PDF export does not reproduce the plot shown in the app.

The UI renders one side-by-side subplot, but the download path prints p_strong and p_weak separately to the PDF device. That yields two standalone pages instead of the single combined figure behind the “Download plot” button. Reusing one shared composition helper for both render and download would keep the exported artifact aligned with the UI.

Also applies to: 310-320

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 107 - 113, Create a single composition
helper (e.g., compose_panels or build_combined_plot) that builds the combined
figure by calling make_panel for results_strong and results_weak, wrapping them
with plotly::subplot(...) and plotly::layout(...), and return that combined
plot; replace the current separate p_strong/p_weak usage so both the UI render
and the PDF export call this helper (instead of printing p_strong and p_weak
separately) so the PDF device receives the same combined subplot as shown in the
app (apply same change to the other occurrence around lines 310-320).

Comment on lines +153 to +160
prepared_response_data <- reactive({
req(is_response_curve())
req(preprocess_data)
req(statmodel_contrast)
req(statmodel_contrast$matrix)
matrix <- statmodel_contrast$matrix
protein_level_data <- merge(preprocess_data()$ProteinLevelData, matrix, by = "GROUP")
prepare_dose_response_fit(protein_level_data)
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 | 🟠 Major

The response-mode selector is currently a no-op.

prepared_response_data() is only used to populate choices, and the run handler never passes either the selected protein or any prepared response data into run_tpr_simulation(). For a given replicate range, every run produces the same generic curve regardless of which protein the user picks or which dataset is loaded. Either wire that selection into the simulation or remove the control until that path exists.

Also applies to: 181-185, 212-223

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 153 - 160, The response-mode selector
is not wired into the simulation: locate the reactive prepared_response_data()
and the run handler that calls run_tpr_simulation(), then pass the user's
selected protein/dataset into the simulation by (1) using the input value (e.g.,
input$response_mode or the control that populates choices) to subset
prepared_response_data() to a single protein or dataset before calling
prepare_dose_response_fit, (2) update the run handler to supply that subset (and
any chosen replicate range) as an argument to run_tpr_simulation(), and (3)
ensure the UI choices still come from prepared_response_data(); if you prefer
not to support this path yet, remove/disable the response-mode selector and any
code branches that assume it exists (also apply the same wiring/removal changes
at the other spots where choices and runs are prepared and invoked).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
R/module-expdes-server.R (3)

296-308: Duplicated make_panel logic violates DRY.

The make_panel function defined here (lines 296-308) duplicates the implementation from plot_tpr_power_curve (lines 79-101). Extract a shared helper function to avoid divergence and reduce maintenance burden.

♻️ Proposed refactor to extract shared helper

Define a shared helper at module scope:

# At top of file, after plot_tpr_power_curve
make_tpr_panel <- function(data, title = NULL, color, k_grid, ltype_values, show_legend = FALSE) {
  p <- ggplot2::ggplot(data,
    ggplot2::aes(x = NumConcs, y = TPR, linetype = factor(N_rep))) +
    ggplot2::geom_line(linewidth = 1.2, color = color) +
    ggplot2::geom_point(size = 2, color = color) +
    ggplot2::scale_x_continuous(breaks = k_grid) +
    ggplot2::scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20)) +
    ggplot2::scale_linetype_manual(values = ltype_values) +
    ggplot2::labs(title = title, x = "Number of concentrations",
                  y = "True Positive Rate (%)", linetype = "Replicates") +
    ggplot2::theme_bw(base_size = 14) +
    ggplot2::theme(plot.title = ggplot2::element_text(face = "bold", hjust = 0.5))
  if (!show_legend) p <- p + ggplot2::theme(legend.position = "none")
  p
}

Then use it in both plot_tpr_power_curve and the download handler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 296 - 308, The duplicated plotting
logic in make_panel (used in the download handler) repeats code from
plot_tpr_power_curve; extract a shared helper (e.g., make_tpr_panel) at module
scope and have both plot_tpr_power_curve and the download handler call it. The
helper should accept parameters used in both places (data, title, color, k_grid,
ltype_values, and a show_legend flag) and apply the same ggplot layers and
theme, hiding the legend when show_legend is FALSE; replace the existing
make_panel implementation with a call to this new make_tpr_panel to avoid
duplication.

28-31: Consider adding input validation for rep_range.

The function assumes rep_range is a length-2 integer vector with rep_range[1] <= rep_range[2], but doesn't validate this. Invalid input would produce silent failures or unexpected sequences.

🛡️ Proposed defensive check
 run_tpr_simulation <- function(rep_range, n_proteins = 1000) {
+  stopifnot(
+    is.numeric(rep_range),
+    length(rep_range) == 2,
+    rep_range[1] <= rep_range[2]
+  )
   k_grid <- as.integer(names(CONC_MAP))
   rep_grid <- seq(rep_range[1], rep_range[2])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 28 - 31, Add defensive input
validation in run_tpr_simulation for the rep_range parameter: verify rep_range
is a length-2 numeric/integer vector with no NAs and that rep_range[1] <=
rep_range[2]; if needed coerce to integers (or reject non-integer values) and
stop() with a clear error message when the checks fail before constructing
rep_grid. Reference rep_range, rep_grid and run_tpr_simulation when locating
where to add the validation.

231-275: Output handlers re-assigned on each observe trigger.

Defining output[[...]] <- renderPlotly(...) inside an observe() block means the handler is re-registered every time the reactive dependencies change. While this works, it's a subtle anti-pattern that can cause unnecessary overhead. Consider using a single renderPlotly with conditional logic based on is_response_curve(), or use observeEvent with once = TRUE for registration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 231 - 275, The output handlers
(output[[NAMESPACE_EXPDES$result_plot]] and
output[[NAMESPACE_EXPDES$download_future]]) are being re-registered inside
observe(), causing repeated re-assignment each reactive trigger; move their
registration out of observe() and use a single renderPlotly and downloadHandler
that call a reactive or function (e.g., future_exp or a wrapped reactive like
future_exp_reactive) which checks is_response_curve() or returns NULL/empty when
not applicable, or alternatively register them once via observeEvent(..., once =
TRUE); ensure designSampleSizePlots(future_exp(), ...) remains the rendering
logic but the output assignment happens only once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-expdes-server.R`:
- Around line 18-28: The roxygen block for run_tpr_simulation documents a
non-existent parameter `protein`; remove the stale `@param protein` line from
the function's documentation so the signature and docs match, ensuring the
documented params (e.g., rep_range, n_proteins) accurately reflect the
run_tpr_simulation signature; if you intended to accept a protein argument
instead, add `protein` to the run_tpr_simulation function signature and use it
accordingly—choose one fix and update the roxygen tags to match.

---

Nitpick comments:
In `@R/module-expdes-server.R`:
- Around line 296-308: The duplicated plotting logic in make_panel (used in the
download handler) repeats code from plot_tpr_power_curve; extract a shared
helper (e.g., make_tpr_panel) at module scope and have both plot_tpr_power_curve
and the download handler call it. The helper should accept parameters used in
both places (data, title, color, k_grid, ltype_values, and a show_legend flag)
and apply the same ggplot layers and theme, hiding the legend when show_legend
is FALSE; replace the existing make_panel implementation with a call to this new
make_tpr_panel to avoid duplication.
- Around line 28-31: Add defensive input validation in run_tpr_simulation for
the rep_range parameter: verify rep_range is a length-2 numeric/integer vector
with no NAs and that rep_range[1] <= rep_range[2]; if needed coerce to integers
(or reject non-integer values) and stop() with a clear error message when the
checks fail before constructing rep_grid. Reference rep_range, rep_grid and
run_tpr_simulation when locating where to add the validation.
- Around line 231-275: The output handlers
(output[[NAMESPACE_EXPDES$result_plot]] and
output[[NAMESPACE_EXPDES$download_future]]) are being re-registered inside
observe(), causing repeated re-assignment each reactive trigger; move their
registration out of observe() and use a single renderPlotly and downloadHandler
that call a reactive or function (e.g., future_exp or a wrapped reactive like
future_exp_reactive) which checks is_response_curve() or returns NULL/empty when
not applicable, or alternatively register them once via observeEvent(..., once =
TRUE); ensure designSampleSizePlots(future_exp(), ...) remains the rendering
logic but the output assignment happens only once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 36d5a6c0-8744-46b5-be02-e222a7765cea

📥 Commits

Reviewing files that changed from the base of the PR and between 113b815 and 6d679c4.

📒 Files selected for processing (1)
  • R/module-expdes-server.R

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
R/module-expdes-server.R (3)

211-222: ⚠️ Potential issue | 🟠 Major

Protein selection is required but unused.

Line 212 requires protein_select to have a value, but the selected protein is never passed to run_tpr_simulation(). This creates a misleading UX where users must select a protein that has no effect on the simulation output. Either remove the requirement or wire the selection into the simulation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 211 - 222, The code currently requires
input[[NAMESPACE_EXPDES$protein_select]] in the observeEvent but never uses it;
either remove that req or pass the selected protein into run_tpr_simulation so
the selection affects results. Update the observeEvent to forward
input[[NAMESPACE_EXPDES$protein_select]] as an argument to run_tpr_simulation
(e.g., a parameter named protein or protein_select) and ensure
simulation_results(results) still receives the returned object, or alternatively
delete the req(input[[NAMESPACE_EXPDES$protein_select]]) if the simulation
should not depend on a protein selection; modify run_tpr_simulation signature if
needed to accept the new parameter.

309-319: ⚠️ Potential issue | 🟡 Minor

PDF export differs from UI: two separate pages vs. combined subplot.

The UI renders a side-by-side subplot via plotly::subplot(), but the PDF download prints p_strong and p_weak as separate pages. Users downloading the plot will get a different layout than what they see in the app.

💡 Suggested fix: Use gridExtra or patchwork for combined PDF
           pdf(file, width = 16, height = 6)
-          print(p_strong)
-          print(p_weak)
+          combined <- gridExtra::grid.arrange(p_strong, p_weak, ncol = 2)
+          print(combined)
           dev.off()

Alternatively, use patchwork::wrap_plots(p_strong, p_weak, ncol = 2).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 309 - 319, The PDF export currently
prints p_strong and p_weak on separate pages (pdf(); print(p_strong);
print(p_weak); dev.off()), which differs from the UI subplot; combine the two
ggplot objects into a single side-by-side layout (e.g., use
patchwork::wrap_plots(p_strong, p_weak, ncol = 2) or
gridExtra::grid.arrange(p_strong, p_weak, ncol = 2)) and print that combined
object between pdf() and dev.off() so the exported PDF matches the
plotly::subplot UI; ensure necessary package is imported/required before use.

152-159: ⚠️ Potential issue | 🟠 Major

prepared_response_data() is computed but never used in the simulation.

The reactive prepares dose-response fit data, but run_tpr_simulation() at line 218 doesn't receive it. The simulation runs a generic experiment regardless of the loaded dataset or selected protein.

Either wire prepared_response_data() into the simulation logic or remove it if the current generic simulation is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 152 - 159, prepared_response_data() is
computed but never used by the simulation; wire it into the simulation path by
passing the prepared dose-response data into run_tpr_simulation (or using it
inside run_tpr_simulation) instead of the generic dataset. Locate
prepared_response_data (which builds protein_level_data and calls
prepare_dose_response_fit) and change the simulation invocation at
run_tpr_simulation to accept prepared_response_data() (or update
run_tpr_simulation to read prepared_response_data()) so the simulation uses the
selected dataset/protein; if the generic simulation is intentional, remove
prepared_response_data() and its related code (protein_level_data,
prepare_dose_response_fit) to avoid dead code.
🧹 Nitpick comments (2)
R/module-expdes-server.R (2)

42-56: Consider adding error handling for individual simulation runs.

If futureExperimentSimulation fails for a specific parameter combination, the entire simulation will fail. Wrapping run_one in tryCatch would allow partial results and better diagnostics.

💡 Optional: Add resilience to individual run failures
   grid_df <- expand.grid(N_rep = rep_grid, k_conc = k_grid)
-  results <- do.call(rbind, lapply(seq_len(nrow(grid_df)), function(i) {
-    run_one(n_rep = grid_df$N_rep[i], k_conc = grid_df$k_conc[i])
-  }))
+  results <- do.call(rbind, lapply(seq_len(nrow(grid_df)), function(i) {
+    tryCatch(
+      run_one(n_rep = grid_df$N_rep[i], k_conc = grid_df$k_conc[i]),
+      error = function(e) {
+        warning(sprintf("Simulation failed for N_rep=%d, k_conc=%d: %s",
+                        grid_df$N_rep[i], grid_df$k_conc[i], conditionMessage(e)))
+        NULL
+      }
+    )
+  }))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 42 - 56, The run_one function
currently calls futureExperimentSimulation directly, so a single failure aborts
the whole batch; wrap the call to futureExperimentSimulation inside a tryCatch
in run_one (catch error and warning), and on error return a safe,
consistently-shaped data.frame (same columns as the successful branch:
Interaction, TPR/Percent renamed to TPR, N_rep, NumConcs) or an empty row with
NA values plus an extra column (e.g., ErrorMessage) containing the error text
for diagnostics; then leave the outer loop that builds results via
lapply/do.call(rbind) unchanged so partial results are combined and failures are
visible.

290-307: Duplicated make_panel helper and linetype logic.

The make_panel function and ltype_values computation are duplicated between plot_tpr_power_curve() (lines 75-100) and the download handler (lines 290-307). This increases maintenance burden and risks inconsistent behavior if one is updated without the other.

♻️ Extract shared helper to module scope

Consider extracting make_tpr_panel() as a module-level helper that both plot_tpr_power_curve() and the download handler can reuse:

# At module scope (after CONC_MAP)
make_tpr_panel <- function(data, title = NULL, color, k_grid, ltype_values, show_legend = TRUE) {
  p <- ggplot2::ggplot(data,
    ggplot2::aes(x = NumConcs, y = TPR, linetype = factor(N_rep))) +
    ggplot2::geom_line(linewidth = 1.2, color = color) +
    ggplot2::geom_point(size = 2, color = color) +
    ggplot2::scale_x_continuous(breaks = k_grid) +
    ggplot2::scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20)) +
    ggplot2::scale_linetype_manual(values = ltype_values) +
    ggplot2::labs(title = title, x = "Number of concentrations",
                  y = "True Positive Rate (%)", linetype = "Replicates") +
    ggplot2::theme_bw(base_size = 14) +
    ggplot2::theme(plot.title = ggplot2::element_text(face = "bold", hjust = 0.5))
  if (!show_legend) p <- p + ggplot2::theme(legend.position = "none")
  p
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 290 - 307, The duplicated make_panel
and ltype_values logic should be extracted to a single module-level helper
(e.g., make_tpr_panel) and reused by both plot_tpr_power_curve() and the
download handler: create make_tpr_panel(data, title = NULL, color, k_grid,
ltype_values, show_legend = TRUE) that contains the ggplot construction and
linetype scaling, remove the duplicated make_panel and ltype_values in both
places, compute k_grid and ltype_values once at module scope (using the same
rep_levels logic) and pass them into make_tpr_panel from plot_tpr_power_curve()
and the download handler, and ensure show_legend toggles legend visibility so
both call sites render correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@R/module-expdes-server.R`:
- Around line 211-222: The code currently requires
input[[NAMESPACE_EXPDES$protein_select]] in the observeEvent but never uses it;
either remove that req or pass the selected protein into run_tpr_simulation so
the selection affects results. Update the observeEvent to forward
input[[NAMESPACE_EXPDES$protein_select]] as an argument to run_tpr_simulation
(e.g., a parameter named protein or protein_select) and ensure
simulation_results(results) still receives the returned object, or alternatively
delete the req(input[[NAMESPACE_EXPDES$protein_select]]) if the simulation
should not depend on a protein selection; modify run_tpr_simulation signature if
needed to accept the new parameter.
- Around line 309-319: The PDF export currently prints p_strong and p_weak on
separate pages (pdf(); print(p_strong); print(p_weak); dev.off()), which differs
from the UI subplot; combine the two ggplot objects into a single side-by-side
layout (e.g., use patchwork::wrap_plots(p_strong, p_weak, ncol = 2) or
gridExtra::grid.arrange(p_strong, p_weak, ncol = 2)) and print that combined
object between pdf() and dev.off() so the exported PDF matches the
plotly::subplot UI; ensure necessary package is imported/required before use.
- Around line 152-159: prepared_response_data() is computed but never used by
the simulation; wire it into the simulation path by passing the prepared
dose-response data into run_tpr_simulation (or using it inside
run_tpr_simulation) instead of the generic dataset. Locate
prepared_response_data (which builds protein_level_data and calls
prepare_dose_response_fit) and change the simulation invocation at
run_tpr_simulation to accept prepared_response_data() (or update
run_tpr_simulation to read prepared_response_data()) so the simulation uses the
selected dataset/protein; if the generic simulation is intentional, remove
prepared_response_data() and its related code (protein_level_data,
prepare_dose_response_fit) to avoid dead code.

---

Nitpick comments:
In `@R/module-expdes-server.R`:
- Around line 42-56: The run_one function currently calls
futureExperimentSimulation directly, so a single failure aborts the whole batch;
wrap the call to futureExperimentSimulation inside a tryCatch in run_one (catch
error and warning), and on error return a safe, consistently-shaped data.frame
(same columns as the successful branch: Interaction, TPR/Percent renamed to TPR,
N_rep, NumConcs) or an empty row with NA values plus an extra column (e.g.,
ErrorMessage) containing the error text for diagnostics; then leave the outer
loop that builds results via lapply/do.call(rbind) unchanged so partial results
are combined and failures are visible.
- Around line 290-307: The duplicated make_panel and ltype_values logic should
be extracted to a single module-level helper (e.g., make_tpr_panel) and reused
by both plot_tpr_power_curve() and the download handler: create
make_tpr_panel(data, title = NULL, color, k_grid, ltype_values, show_legend =
TRUE) that contains the ggplot construction and linetype scaling, remove the
duplicated make_panel and ltype_values in both places, compute k_grid and
ltype_values once at module scope (using the same rep_levels logic) and pass
them into make_tpr_panel from plot_tpr_power_curve() and the download handler,
and ensure show_legend toggles legend visibility so both call sites render
correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38d6e54d-3027-4d97-ade4-1ea24fe89dbf

📥 Commits

Reviewing files that changed from the base of the PR and between 6d679c4 and b347d37.

📒 Files selected for processing (1)
  • R/module-expdes-server.R

@swaraj-neu swaraj-neu linked an issue Mar 17, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
R/module-expdes-server.R (2)

159-167: ⚠️ Potential issue | 🟠 Major

protein_select is required but not used by the simulation.

The run path currently ignores the selected protein and prepared_response_data(), so results are unchanged regardless of the chosen protein. This makes the response-mode selector effectively a no-op.

Possible wiring direction
- results <- run_tpr_simulation(
-   rep_range = input[[NAMESPACE_EXPDES$rep_range]],
-   n_proteins = 1000
- )
+ selected_protein_data <- prepared_response_data() |>
+   dplyr::filter(protein == input[[NAMESPACE_EXPDES$protein_select]])
+
+ results <- run_tpr_simulation(
+   rep_range = input[[NAMESPACE_EXPDES$rep_range]],
+   n_proteins = 1000,
+   protein_data = selected_protein_data
+ )

Also applies to: 218-228

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 159 - 167, The reactive
prepared_response_data() is building the dose-response dataset but it never
applies the selected protein (protein_select) so the simulation is invariant to
the protein choice; update prepared_response_data() to read the selected protein
(e.g., protein_select()) and filter preprocess_data()$ProteinLevelData to that
protein before merging with statmodel_contrast$matrix, then call
prepare_dose_response_fit on the filtered protein_level_data; also ensure any
downstream run path (the code referenced around the run path lines using
prepared_response_data()) consumes prepared_response_data() rather than raw
preprocess_data so the selected protein actually affects results.

316-326: ⚠️ Potential issue | 🟡 Minor

PDF export still diverges from the in-app figure layout.

The app renders a side-by-side combined plot, but the PDF path prints two separate panels/pages. Consider sharing one composition helper between render and download to keep artifacts consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 316 - 326, The PDF export prints
p_strong and p_weak on separate pages because the download path prints them
individually instead of using the same side-by-side composition used in the app;
create a single composition helper (e.g., make_composite or compose_panels) that
takes the two ggplot objects produced by make_panel and returns one combined
plot (using patchwork::`/` or `|`, cowplot::plot_grid, or
gridExtra::grid.arrange wrapped as a grob). Replace the two separate print calls
in the download code with a single print (or grid::grid.draw) of the combined
plot object and reuse that same helper from the in-app render to ensure
identical layout for both render and PDF output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@R/module-expdes-server.R`:
- Around line 27-30: Add explicit validation at the start of run_tpr_simulation
to reject malformed rep_range: check that rep_range has length 2, contains no
NA, is numeric/integer, both values are positive, and rep_range[1] <=
rep_range[2]; if any check fails call stop() with a clear error message
explaining the expected format (e.g., "rep_range must be a length-2 numeric
vector with non-NA, positive values and min <= max"). Place this guard before
k_grid and rep_grid are constructed so invalid input is rejected early.
- Around line 293-295: Inside the downloadHandler's content function (the
content = function(file) block) remove the req(simulation_results()) call and
replace it with an explicit null check on simulation_results(); if
simulation_results() is NULL, write a clear error message to the provided file
(using writeLines or similar) and return early, otherwise proceed to assign
results <- simulation_results() and continue the normal file writing; reference
symbols: downloadHandler, content = function(file), simulation_results(), and
results.

---

Duplicate comments:
In `@R/module-expdes-server.R`:
- Around line 159-167: The reactive prepared_response_data() is building the
dose-response dataset but it never applies the selected protein (protein_select)
so the simulation is invariant to the protein choice; update
prepared_response_data() to read the selected protein (e.g., protein_select())
and filter preprocess_data()$ProteinLevelData to that protein before merging
with statmodel_contrast$matrix, then call prepare_dose_response_fit on the
filtered protein_level_data; also ensure any downstream run path (the code
referenced around the run path lines using prepared_response_data()) consumes
prepared_response_data() rather than raw preprocess_data so the selected protein
actually affects results.
- Around line 316-326: The PDF export prints p_strong and p_weak on separate
pages because the download path prints them individually instead of using the
same side-by-side composition used in the app; create a single composition
helper (e.g., make_composite or compose_panels) that takes the two ggplot
objects produced by make_panel and returns one combined plot (using
patchwork::`/` or `|`, cowplot::plot_grid, or gridExtra::grid.arrange wrapped as
a grob). Replace the two separate print calls in the download code with a single
print (or grid::grid.draw) of the combined plot object and reuse that same
helper from the in-app render to ensure identical layout for both render and PDF
output.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1f63be72-bd50-44ef-baf1-092a1ee2d173

📥 Commits

Reviewing files that changed from the base of the PR and between b347d37 and e5c6baf.

📒 Files selected for processing (1)
  • R/module-expdes-server.R

Comment on lines +27 to +30
run_tpr_simulation <- function(rep_range, n_proteins = 1000) {
k_grid <- as.integer(names(CONC_MAP))
rep_grid <- seq(rep_range[1], rep_range[2])

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

Validate rep_range before building the simulation grid.

run_tpr_simulation() is exported; it should reject malformed ranges (length != 2, NA, non-positive, or min > max) early with a clear message.

Suggested guard
 run_tpr_simulation <- function(rep_range, n_proteins = 1000) {
+  if (length(rep_range) != 2 || anyNA(rep_range) ||
+      any(rep_range < 1) || rep_range[1] > rep_range[2]) {
+    stop("`rep_range` must be c(min, max) with positive integers and min <= max.")
+  }
   k_grid <- as.integer(names(CONC_MAP))
   rep_grid <- seq(rep_range[1], rep_range[2])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 27 - 30, Add explicit validation at
the start of run_tpr_simulation to reject malformed rep_range: check that
rep_range has length 2, contains no NA, is numeric/integer, both values are
positive, and rep_range[1] <= rep_range[2]; if any check fails call stop() with
a clear error message explaining the expected format (e.g., "rep_range must be a
length-2 numeric vector with non-NA, positive values and min <= max"). Place
this guard before k_grid and rep_grid are constructed so invalid input is
rejected early.

Comment on lines +293 to +295
content = function(file) {
req(simulation_results())
results <- simulation_results()
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Shiny, what is the recommended error-handling pattern inside downloadHandler(content=...) when req()/stop() would be triggered, and does rstudio/shiny#2070 still apply?

💡 Result:

Recommended pattern (when req() / stop() might be triggered)

Don’t let req()/validate()/stop() fire inside downloadHandler(content=...). Instead, run all checks + expensive/fragile logic before the download is initiated, and only have content do a “dumb” write of already-prepared data (or a known-good temp file).

Common implementation pattern:

  • User clicks an actionButton() (“Prepare download”)
  • observeEvent() does req(...)/tryCatch(...); on failure, show a modal/notification and do not trigger a download
  • On success, store the result in a reactiveVal() (or create a temp file) and then programmatically click a hidden downloadButton()
  • downloadHandler(content=...) just writes the stored result

This is the core idea shown in the Stack Overflow answer (hidden downloadButton + visible actionButton + shinyjs::click() only after success). [1]

Does rstudio/shiny#2070 still apply?

Yes—the issue is still Open (opened May 23, 2018) and describes that including stop()/req() in downloadHandler()’s filename or content “simply crashes the app.” [2]

Sources: [1] [2]


🏁 Script executed:

cd /repo && wc -l "R/module-expdes-server.R"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 121


🏁 Script executed:

cd /repo && sed -n '280,310p' "R/module-expdes-server.R"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 121


🏁 Script executed:

pwd && git rev-parse --show-toplevel 2>/dev/null || echo "Not in git repo"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 103


🏁 Script executed:

git ls-files "R/module-expdes-server.R" 2>/dev/null && wc -l "R/module-expdes-server.R"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 119


🏁 Script executed:

sed -n '280,310p' "R/module-expdes-server.R"

Repository: Vitek-Lab/MSstatsShiny

Length of output: 1434


Remove req() from inside downloadHandler(content=...) and write an error message to the file instead.

Calling req() inside the content function can crash or disconnect the Shiny session (rstudio/shiny#2070). Instead, guard with explicit checks like is.null(simulation_results()) and write an informative error message to the file so users receive meaningful feedback rather than a broken download.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@R/module-expdes-server.R` around lines 293 - 295, Inside the
downloadHandler's content function (the content = function(file) block) remove
the req(simulation_results()) call and replace it with an explicit null check on
simulation_results(); if simulation_results() is NULL, write a clear error
message to the provided file (using writeLines or similar) and return early,
otherwise proceed to assign results <- simulation_results() and continue the
normal file writing; reference symbols: downloadHandler, content =
function(file), simulation_results(), and results.

@swaraj-neu swaraj-neu force-pushed the feat/dose-response-curve branch from 2cfbade to ca04912 Compare March 18, 2026 22:29
codes = paste(codes, "library(MSstatsResponse)\n", sep = "")
codes = paste(codes, "condition_names = c(\"",
paste(comp_mat$GROUP, collapse = "\",\""), "\")\n", sep = "")
codes = paste(codes, "contrast_matrix = setup_metadata(condition_names)\n", sep = "")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's no function called setup_metadata.

Preferably, you take what the existing contrast matrix is and create a data table/frame containing those specific values.


codes = paste(codes, "\n# Prepare data for dose response fitting\n", sep = "")
codes = paste(codes, "protein_level_data = merge(summarized$ProteinLevelData, contrast_matrix, by = \"GROUP\")\n", sep = "")
codes = paste(codes, "prepared_data = prepare_dose_response_fit(protein_level_data)\n", sep = "")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Users don't have access to the prepare_dose_response_fit function because it's a private function in MSstatsShiny. This function basically does column mapping to the appropriate column name format for MSstatsResponse.

Could you review this function? I expect to see users to download code that does this column mapping explicitly, e.g. like:

protein_level_data$protein = protein_level_data$Protein
protein_level_data$response = protein_level_data$LogIntensities

Mapping the intervention_value and intervention_type will be slightly trickier and may require some refactoring of the code to make it clean.

@tonywu1999 tonywu1999 merged commit c2a30aa into devel Mar 19, 2026
2 checks passed
@tonywu1999 tonywu1999 deleted the feat/dose-response-curve branch April 9, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Statmodel] Enable downloading dose response curve analysis results

2 participants