Skip to content

feat: create r script from settings file#835

Merged
Gero1999 merged 32 commits intomainfrom
826-enhancement-create-r-script-from-settings-file
Feb 9, 2026
Merged

feat: create r script from settings file#835
Gero1999 merged 32 commits intomainfrom
826-enhancement-create-r-script-from-settings-file

Conversation

@Gero1999
Copy link
Copy Markdown
Collaborator

@Gero1999 Gero1999 commented Dec 16, 2025

Issue

Closes #826

Description

This PR refactors and extends the code generation utilities for replicating app sessions, introducing a new get_settings_code() function to generate scripts from settings files, and updating the template and related helpers to use a more general yaml_setts structure instead of direct session$userData references. It also improves robustness when handling tibble objects and updates documentation and tests accordingly.

Major improvements and changes:

  • Added get_settings_code() to generate a reproducible R script from a settings RDS file and data path, using a mapping and template, making it possible to script session replication without a running Shiny session. Objects that are still not part of the settings but are needed for the r-script (ratios_table, mapping) are for now set by default to standard values and need to be manually declared by the user.

  • Refactored code and template references from session$userData to yaml_setts, making code generation more flexible and decoupled from Shiny internals. This includes changes in the script template and all code substitution logic.

  • Added new tests for get_settings_code() and get_session_code() to verify script file output.

Related to other PR to be merged

  • Enhanced clean_deparse() to treat tbl_df (tibble) objects as data frames for serialization, ensuring consistent output regardless of input data frame type. With its corresponding tests.

  • read_settings() now ensures types_df is always a data frame, improving downstream robustness.

Definition of Done

  • get_settings_code() works same way as get_session_code()

How to test

  1. Make all App adjustments you want
  2. Run NCA and save the r-script
  3. Download the settings file (settings.yaml)
  4. Use the function get_settings_code() in your R console on the settings file. Indicate all inputs apart from the settings file as they were in the App (ratio_table, mapping).
  5. Check that the output r-script matches the one downloaded in the App

Note: ratio_table and mapping are aspects still not covered by the settings file. Therefore, they need for now to be explicitly declared in the function get_settings_code()

Contributor checklist

  • Code passes lintr checks
  • Code passes all unit tests
  • New logic covered by unit tests
  • New logic is documented
  • App or package changes are reflected in NEWS
  • Package version is incremented

Notes to reviewer

Better to review/merge first #789 ✔️

Copilot AI review requested due to automatic review settings December 16, 2025 11:14
@Gero1999 Gero1999 linked an issue Dec 16, 2025 that may be closed by this pull request
1 task
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces functionality to generate reproducible R scripts from Shiny app sessions and settings files, enabling users to replicate NCA analysis outputs outside the app. Additionally, it includes enhancements to the pivoting functionality and bug fixes for ratio calculations.

Key Changes

  • Added get_session_code() and get_settings_code() functions with supporting clean_deparse() methods to generate R scripts from session data
  • Enhanced pivot_wider_pknca_results() to accept flag rules and extra variables, with flagging logic moved into the function
  • Fixed calculate_table_ratios_app() to handle NULL ratio_table by wrapping with as.data.frame()

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
R/get_session_code.R New file implementing script generation from session data with clean_deparse methods for various R objects
R/ratio_calculations.R Moved ratio calculation functions from inst/shiny/functions and added as.data.frame() wrapping for NULL handling
R/pivot_wider_pknca_results.R Enhanced to accept flag_rules and extra_vars_to_keep parameters, moved flagging logic into function
inst/shiny/www/templates/script_template.R New template file for generating executable R scripts from session data
inst/shiny/modules/tab_nca/*.R Updated to store session data (settings, ratio_table, slope_rules, final_units) for script generation
inst/shiny/modules/tab_data/*.R Updated to store data_path, mapping, and applied_filters in session$userData
tests/testthat/test-get_session_code.R New tests for clean_deparse() helper function
tests/testthat/test-pivot_wider_pknca_results.R Added tests for flag_rules and extra_vars_to_keep functionality
man/*.Rd Documentation updates for new and modified exported functions
NAMESPACE Added exports for get_session_code and calculate_table_ratios_app
NEWS.md Updated to document the new R script export feature
inst/WORDLIST Added new technical terms used in documentation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread R/pivot_wider_pknca_results.R Outdated
Comment thread inst/shiny/www/templates/script_template.R Outdated
Comment thread R/ratio_calculations.R
Comment thread NEWS.md Outdated
Comment thread R/get_session_code.R Outdated
Comment thread tests/testthat/test-get_session_code.R
Comment thread inst/shiny/www/templates/script_template.R
Comment thread inst/shiny/www/templates/script_template.R Outdated
Comment thread R/get_session_code.R
Comment thread R/pivot_wider_pknca_results.R Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread R/get_session_code.R Outdated
Comment thread R/get_session_code.R Outdated
#' installed from your aNCA package version.
#' @param output_path Path to write the resulting script file.
#'
#' @importFrom yaml read_yaml
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The function-level roxygen includes @importFrom yaml read_yaml, but get_settings_code() does not call read_yaml() directly (it calls read_settings()). Consider removing this import to avoid unused-import lintr/R CMD check issues, or move the import to the function that actually uses it.

Suggested change
#' @importFrom yaml read_yaml

Copilot uses AI. Check for mistakes.

describe("get_session_code: ", {
setts_file <- testthat::test_path("data/test-settings.yaml")
data_file <- testthat::test_path("data/test-multispec-ADNCA.csv")
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

output_file is created inside the describe("get_settings_code") block but then reused in the later describe("get_session_code") block without being defined there. describe() evaluates in its own environment, so output_file may be out of scope and cause the later tests to error. Define output_file in each describe() (or use withr::local_tempfile() inside each it()).

Suggested change
data_file <- testthat::test_path("data/test-multispec-ADNCA.csv")
data_file <- testthat::test_path("data/test-multispec-ADNCA.csv")
output_file <- tempfile(fileext = ".R")

Copilot uses AI. Check for mistakes.
Comment thread man/get_settings_code.Rd
Comment on lines +16 to +19
\arguments{
\item{settings_file_path}{Path to the RDS file containing the settings list.}

\item{data_path}{Path to the data file to be referenced in the script.}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

The docs say settings_file_path points to an RDS file, but get_settings_code() calls read_settings() which reads YAML. Please update the argument documentation to reference a YAML settings file (and keep it consistent with tests using test-settings.yaml).

Copilot uses AI. Check for mistakes.
Comment thread man/get_settings_code.Rd
\item{template_path}{Path to the R script template file. By default, uses the one
installed from your aNCA package version.}

\item{mapping}{Named list mapping variable names (default: \code{default_mapping}).}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

ratio_table appears in the usage signature but is missing from the \arguments section. Please document ratio_table (and any other arguments that users can pass) so the generated Rd is complete.

Suggested change
\item{mapping}{Named list mapping variable names (default: \code{default_mapping}).}
\item{mapping}{Named list mapping variable names (default: \code{default_mapping}).}
\item{ratio_table}{Optional data frame specifying ratio information used when
generating the session script.}

Copilot uses AI. Check for mistakes.
Comment thread R/get_session_code.R Outdated
Comment thread R/get_session_code.R Outdated
Comment thread R/get_session_code.R Outdated
Comment thread tests/testthat/test-get_session_code.R Outdated
Comment on lines +190 to +198
it("writes a script R file output", {
get_settings_code(
settings_file_path = setts_file,
data_path = data_file,
output_path = output_file
)
# Check if the file was created
expect_true(file.exists(output_file))
})
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

These tests only assert that an output file exists, but not that placeholders were substituted correctly (e.g., that mapping/settings were written instead of NULL). Adding an assertion that the generated script contains expected lines/values would prevent regressions like NULL substitution bugs.

Copilot uses AI. Check for mistakes.
Comment thread R/get_session_code.R Outdated
Comment on lines +235 to +252
get_settings_code <- function(
settings_file_path,
data_path,
output_path = "settings_code.R",
template_path = system.file("shiny/www/templates/script_template.R", package = "aNCA"),
# TODO: mapping & ratio_table should be included in the settings file as well
# so they keep working as expected also from the settings file
mapping = default_mapping,
ratio_table = data.frame()
) {
settings <- read_settings(settings_file_path)
session <- list(yaml_setts = list(
settings = settings[["settings"]],
slope_rules = settings[["slope_rules"]],
data_path = data_path,
mapping = mapping,
ratio_table = ratio_table
))
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

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

get_settings_code takes settings from a YAML file (read_settings(settings_file_path)) and feeds them into get_code, which serializes them into an R script using clean_deparse. The clean_deparse.character implementation builds string literals via sprintf('"%s"', obj) without escaping embedded quotes or other metacharacters, so an attacker controlling any string in the YAML (e.g., a mapping value) can inject R code into the generated script by including a " and additional statements (for example a value like "; system('id'); #). When someone later sources the generated script, that injected code will execute; to fix this, ensure all character values are converted into valid R string literals (e.g., via a safe escaping routine or deparse) before substitution, or restrict get_settings_code to trusted, non-user-controlled inputs.

Copilot uses AI. Check for mistakes.
@Gero1999 Gero1999 marked this pull request as ready for review February 3, 2026 08:42
@Gero1999 Gero1999 removed the request for review from Shaakon35 February 3, 2026 15:08
@Gero1999 Gero1999 marked this pull request as ready for review February 3, 2026 15:26
Copy link
Copy Markdown
Collaborator

@Shaakon35 Shaakon35 left a comment

Choose a reason for hiding this comment

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

working well :)
@gerardo Please check the Copilot notes if not done yet

Copy link
Copy Markdown
Collaborator

@js3110 js3110 left a comment

Choose a reason for hiding this comment

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

Im still reviewing, so won't approve or request changes yet, but just a quick comment regarding style: setts is not an appropriate shortening of settings imo- it's not a standard abbreviation, and can be misinterpreted as sets is also a word. settings is more clear and only 4 characters longer, so I would suggest to rename all instances of setts to settings, eg yaml_settings, as this is much more clear.

Copy link
Copy Markdown
Collaborator

@js3110 js3110 left a comment

Choose a reason for hiding this comment

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

I ran the app and created the two code versions, but for both I get the same error because mapping is being initialised as NULL
Console output:

> mapping <- NULL
> names(mapping) <- gsub("select_", "", names(mapping))
Error in names(mapping) <- gsub("select_", "", names(mapping)) : 
  attempt to set an attribute on NULL

> applied_filters <- NULL
> preprocessed_adnca <- adnca_data %>%
+ 
+   # Filter the data
+   apply_filters(applied_filters) %>%
+ 
+   # Map columns to their standards
+   apply_mapping(
+     mapping = mapping,
+     desired_order =  c(
+       "STUDYID", "USUBJID", "PARAM", "PCSPEC", "ATPTREF",
+       "AVAL", "AVALU", "AFRLT", "ARRLT", "NRRLT", "NFRLT",
+       "RRLTU", "ROUTE", "DOSETRT", "DOSEA", "DOSEU", "ADOSEDUR",
+       "VOLUME", "VOLUMEU", "TRTRINT", "METABFL"
+     ),
+     silent = FALSE
+   ) %>%
+ 
+   # Derive METABFL column using PARAM metabolites
+   create_metabfl(mapping$Metabolites) %>%
+   
+   # Make sure all variables are in its correct class
+   adjust_class_and_length(metadata_nca_variables)
*  -> 
Error in if (mapping$DOSETRT == "") { : argument is of length zero
In addition: Warning message:
In apply_mapping(., mapping = mapping, desired_order = c("STUDYID",  :
  Dose duration is assumed to be 0  for all records (ADOSEDUR = 0)

I used a preclinical dataset.

@Gero1999
Copy link
Copy Markdown
Collaborator Author

Gero1999 commented Feb 5, 2026

should be fixed now @js3110

@Gero1999 Gero1999 requested a review from js3110 February 5, 2026 15:25
Copy link
Copy Markdown
Collaborator

@js3110 js3110 left a comment

Choose a reason for hiding this comment

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

Works well for me!

I will approve, but I think it would be good to add an example or a template for the mapping in the documentation, so its easy for the user to know what they should add. Currently it just says "default: default_mapping) but its not clear what that is or what it looks like.

I had to search the code to find the default_mapping, but a simple user won't necessarily have access to that.

Copy link
Copy Markdown
Collaborator

@m-kolomanski m-kolomanski left a comment

Choose a reason for hiding this comment

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

Hey, I have found some non-blocking issues, otherwise code looks fine.

I am gonna attach my complimentary warning about about coupling the package code with the application even more (with the default_mapping list). It is the application that should parse its own session object and provide the get_code() function with a list that is already in a common format - the package should not care how we named the inputs in the Shiny application. But this seems to be a design choice lately so.

suggestion/nitpick: By naming the object yaml_settings we are coupling this usage to the settings implementation (by name only, ofc). I know that we are probably not gonna migrate the settings to a different format, but if we ever wanted to move it to JSON or TOML or whatever, then this yaml_settings name would stop making sense and need refactoring. There is no reason to do that - neither the template script, not get_code() care what format the settings were parsed from - they receive a list.

Comment thread inst/shiny/modules/tab_nca/zip.R Outdated
script_template_path <- "shiny/www/templates/script_template.R"
get_session_code(
template_path = script_template_path,
template_path = system.file(script_template_path, package = "aNCA"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

suggestion: I would consider moving the template to just /inst/ folder as opposed to nesting it within /shiny/

Copy link
Copy Markdown
Collaborator Author

@Gero1999 Gero1999 Feb 9, 2026

Choose a reason for hiding this comment

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

good catch. I will open a new issue to also move template.pptx from there (#965)

Comment thread R/get_session_code.R Outdated

#' Generate a session script from settings and mapping files
#'
#' This function reads a settings RDS file and data path, and generates an R script
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue(non-blocking): The docs say RDS, but in the code the settings file is referenced as yaml.

Comment thread R/get_session_code.R
#'
#' @param settings_file_path Path to the RDS file containing the settings list.
#' @param data_path Path to the data file to be referenced in the script.
#' @param template_path Path to the R script template file. By default, uses the one
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue(non-blocking): Docs say the function uses template file by default, but the default is NULL.

Comment thread R/get_session_code.R Outdated
Comment on lines +16 to +18
if (is.null(template_path)) {
template_path = system.file("shiny/www/templates/script_template.R", package = "aNCA")
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

issue(non-blocking): If we want this to be the default, why not make it the default argument? We don't need this NULL check.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I thought for the user would be weird to read system.file... in the man but you are right

Comment thread R/get_session_code.R
#' @importFrom yaml read_yaml
#' @return Invisibly returns the output_path.
#' @export
get_settings_code <- function(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

question: The function name seems misleading to me. It seems like we should receive something related to the settings? But from the code it looks like we are receiving the template / target analysis script, not just settings. Am I missing something?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

hmm you are right. the logic here of course is that the settings file provides the key elements to produce the specific code that can replicate the App actions.

However, I understand your point that it might be confusing because indeed the true code essence is in the template. So in the end what this function does is populate the relevant inputs/key-variables on it. I have to keep thinking about this to give it a better name in the future, because I am not sure

@Gero1999
Copy link
Copy Markdown
Collaborator Author

Gero1999 commented Feb 6, 2026

It is the application that should parse its own session object and provide the get_code() function with a list that is already in a common format - the package should not care how we named the inputs in the Shiny application. But this seems to be a design choice lately so.

@m-kolomanski Would it be sufficient if I just move the function get_session_code to the server (utils-zip.R)?

@Gero1999
Copy link
Copy Markdown
Collaborator Author

Gero1999 commented Feb 6, 2026

suggestion/nitpick: By naming the object yaml_settings we are coupling this usage

maybe I can rename it again to something different like settings_list

@m-kolomanski
Copy link
Copy Markdown
Collaborator

@Gero1999 Not quite, because it seems the default mapping is referencing raw input names (select_USUBJID etc). There would need to be additional parsing step to rename this mapping to something agnostic. Currently, the template scripts does exactly that:

mapping <- yaml_settings$mapping
names(mapping) <- gsub("select_", "", names(mapping))

which means it is coupled with the select_* names. This step should be performed by an application (can be via get_session_code() migrated to zip.utils.R).

Then default_mapping should have the select_ removed from names, and it should be fine.

…zip-utils.R

 remote-tracking branch 'origin/main' into 826-enhancement-create-r-script-from-settings-file
…te.R) so slope rules logic is adapted to the new simplified logic

 remote-tracking branch 'origin/main' into 826-enhancement-create-r-script-from-settings-file
@Gero1999 Gero1999 merged commit e8bf544 into main Feb 9, 2026
10 checks passed
@Gero1999 Gero1999 deleted the 826-enhancement-create-r-script-from-settings-file branch February 9, 2026 12:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: create R script from settings file

5 participants