fix: R script and settings export missing volume unit simplification (#1190)#1228
fix: R script and settings export missing volume unit simplification (#1190)#1228Gero1999 wants to merge 25 commits into
Conversation
Replace the default flag filter in the save handler with a value-based comparison. Rows where PPSTRESU differs from PPORRESU are treated as changed, regardless of whether the change came from a user edit or an automatic simplification (e.g. volume units). This ensures the volume unit simplification from tab_data.R is captured in session$userData$units_table() and subsequently included in the exported settings and R script. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
The modal no longer adds a default column when building the table. Custom units are merged by matching on all columns except PPSTRESU and conversion_factor, without needing a tracking flag. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
The default column is no longer needed in the reactable definition (was hidden) or in the edit handler (was set to FALSE on edit). Change detection is now purely value-based. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
The units table stored in session$userData$units_table() now only contains rows where PPSTRESU != PPORRESU, so the export paths in nca_setup.R (settings download) and zip-utils.R (ZIP export) no longer need to filter by the default flag. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
The observer that syncs the local modal_units_table with the global session$userData$units_table no longer uses the default column. Merging is done by matching on all columns except PPSTRESU and conversion_factor. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
Remove the select(-any_of("default")) guard from tab_nca.R and the
save handler in units_table.R. The default column no longer exists
in the units table, so these guards are unnecessary.
Refs #1190
Co-authored-by: Ona <no-reply@ona.com>
Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
When pknca_data() is created, any units where PPSTRESU differs from PPORRESU (e.g. volume unit simplification) are automatically stored in session$userData$units_table(). This ensures the settings export and R script include these changes even if the user never opens the Units modal. Previously, session$userData$units_table() stayed NULL until the modal was opened and saved, causing settings to export units: ~. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
PKNCA can produce units rows where PPORRESU and PPSTRESU are both NA (parameters with no derivable units). The comparison NA != NA returns NA, which R treats as truthy in subsetting, causing these rows to leak into the saved units table as .na.character entries. Add explicit is.na() guards in both the auto-populate observer and the modal save handler. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
There was a problem hiding this comment.
Pull request overview
Fixes an export mismatch in the Shiny NCA workflow where automatic unit simplifications (not performed via the Units modal) were not reflected in exported settings or generated R scripts.
Changes:
- Replace the previous edit-tracking
defaultflag with value-based detection of unit changes (PPSTRESU != PPORRESU). - Remove
defaultcolumn handling across Units modal plumbing and export paths. - Add an observer to auto-populate
session$userData$units_table()frompknca_data()when units differ from defaults.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| inst/shiny/modules/tab_nca/units_table.R | Removes default flag usage; switches to value-based “changed unit” filtering. |
| inst/shiny/modules/tab_nca/nca_setup.R | Removes export-time filtering/stripping of default from units settings. |
| inst/shiny/modules/tab_nca.R | Auto-populates units_table based on PPSTRESU != PPORRESU; removes default drop guard. |
| inst/shiny/functions/zip-utils.R | Stops filtering units by default during ZIP settings export. |
| NEWS.md | Documents the bug fix and the new change-detection behavior. |
| DESCRIPTION | Bumps package version. |
Comments suppressed due to low confidence (3)
inst/shiny/modules/tab_nca/units_table.R:39
dplyr::rows_update()can error ifcustom_unitscontains columns not present indefault_units(e.g., older settings uploads or other sources adding extra cols). Elsewhere the codebase defensively subsets the update table to shared columns before callingrows_update()(seeR/PKNCA.Rwherecustom_units_table[, common_cols, drop = FALSE]is used). Consider applying the same pattern here by restrictingcustom_unitstointersect(names(default_units), names(custom_units))before the update.
if (!is.null(session$userData$units_table())) {
custom_units <- session$userData$units_table()
by_cols <- intersect(names(default_units), names(custom_units))
by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor"))
dplyr::rows_update(
default_units,
custom_units,
by = by_cols,
unmatched = "ignore"
inst/shiny/modules/tab_nca/units_table.R:212
- Same as above:
rows_update()is passedcustom_unitswithout trimming to shared columns. To avoid runtime errors whensession$userData$units_table()includes extra columns (e.g., from uploaded settings), subsetcustom_unitsto the shared column set before updatingdefault_units(consistent withR/PKNCA.R).
custom_units <- session$userData$units_table()
by_cols <- intersect(names(default_units), names(custom_units))
by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor"))
dplyr::rows_update(
default_units,
custom_units,
by = by_cols,
unmatched = "ignore"
) %>%
inst/shiny/modules/tab_nca.R:160
- When applying
custom_unitsviarows_update(), consider subsettingcustom_unitsto the shared column set (intersect(names(processed_pknca_data$units), names(custom_units))) before updating. This matches the defensive pattern inR/PKNCA.Rand avoids potential errors ifsession$userData$units_table()ever carries extra columns (e.g., from settings upload).
custom_units <- session$userData$units_table()
by_cols <- intersect(names(processed_pknca_data$units), names(custom_units))
by_cols <- setdiff(by_cols, c("PPSTRESU", "conversion_factor"))
processed_pknca_data$units <- rows_update(
processed_pknca_data$units,
custom_units,
by = by_cols,
unmatched = "ignore"
| log_trace("Applying custom units specification.") | ||
| modal_units_table() %>% | ||
| dplyr::filter(!default) %>% | ||
| dplyr::filter(!is.na(PPSTRESU), !is.na(PPORRESU), PPSTRESU != PPORRESU) %>% |
There was a problem hiding this comment.
The save handler now persists only rows where PPSTRESU != PPORRESU. This drops user changes where only conversion_factor was edited (allowed via editable = c("PPSTRESU", "conversion_factor")) while keeping the unit label unchanged, so those changes won’t be exported/applied. Consider treating a row as changed when either PPSTRESU != PPORRESU OR conversion_factor != 1 (with appropriate NA handling).
| dplyr::filter(!is.na(PPSTRESU), !is.na(PPORRESU), PPSTRESU != PPORRESU) %>% | |
| dplyr::filter( | |
| (!is.na(PPSTRESU) & !is.na(PPORRESU) & PPSTRESU != PPORRESU) | | |
| dplyr::coalesce(conversion_factor, 1) != 1 | |
| ) %>% |
…y having diffs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
js3110
left a comment
There was a problem hiding this comment.
The settings yaml now contains the full units table instead of only the changed units, which reverts the feature introduced in #900 . While I agree that just checking for manual user changes is not the best approach, we should still only show the changed units (ie PPORRESU != PPSTRESU) in the settings file.
session$userData$units_table() stores the full units table because downstream code (nca_results.R) replaces res$data$units with it and joins it to results — both require the complete table. The settings YAML and ZIP exports now filter to only rows where PPSTRESU != PPORRESU, preserving the compact settings format from #900. This applies at both export points: settings download (nca_setup.R) and ZIP export (zip-utils.R). The modal save handler now stores the full modal table instead of filtering, consistent with the auto-populate observer. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
…load The exported settings YAML only contains changed rows (PPSTRESU != PPORRESU). On import, these partial rows must be merged into the full data-derived units table, because downstream code (nca_results.R) expects the complete table. Uses rows_update to overlay the imported changes onto the base units, matching by all columns except PPSTRESU and conversion_factor. Waits for processed_pknca_data() to be available before merging. Refs #1190 Co-authored-by: Ona <no-reply@ona.com>
js3110
left a comment
There was a problem hiding this comment.
Its better than before, but now the settings show parameters that aren't currently selected or in the modal units table:
(I only had defaults selected)
While it doesn't disrupt any of the function of the app and settings upload, it would be preferable if only parameters actually selected are included in the units table...
|
hey @js3110 notice is showing all custom units (VSS parameters). This needs to be like that so in the case users decide to choose them later or for another study they use those preferred units and not the standard ones Maybe would make sense that from now on, the open source stops doing the automatic |
Hmm so you're saying that if I uploaded settings without these additional units, then the other parameters would not be converted to mL? If they aren't selected in settings surely the unit will just be the defualt (which is with the mL conversion) Is there any update on PKNCA progress to do this transformation automatically? |
|
@Gero1999 I tried uploading the settings file but with the additional Vol params removed from the settings, and it converts them to mL anyway, so I don't think its an issue... |
|
@js3110 It might work for the VSS parameters because we have hardcoded them, but this is not the point I am trying to make. The point is that the units table and the selected parameters are independent elements. Imagine a team has their own standards, and they always do |
but if they didn't select CMAX then they wouldn't have been able to change the unit because the units table only shows selected parameters? I don't think I understand your point? |
|
My point is that most of the times people will work with their own settings file as we do to include their specific HL-rules, units parameters... So they don't have to worry about manually interacting with the App for those little standardized things |
**How to test**
Re-upload the settings YAML on the same or different dataset — verify the simplified units are applied correctly
-> Bug, here it loads forever |
| # units table so downstream code never receives a partial units table, and | ||
| # clear it when no units are available to avoid stale values persisting | ||
| # across uploads. | ||
| observeEvent(pknca_data(), { |
There was a problem hiding this comment.
This observer fires on every pknca_data() invalidation and unconditionally overwrites units_table with the data-derived defaults. Meanwhile, nca_setup.R:175 has a bindEvent(processed_pknca_data(), once = TRUE) observer that merges imported settings into units_table.
If both observers flush in the same cycle (which happens when pknca_data() and processed_pknca_data() invalidate together during settings import), the execution order is undefined. If this observer runs after the settings import observer, it overwrites the merged units with plain defaults — silently discarding the imported unit overrides.
Two possible fixes:
Option A — priority ordering:
# tab_nca.R: run first (higher priority number = runs first)
observeEvent(pknca_data(), {
...
session$userData$units_table(current_pknca_data$units)
}, ignoreNULL = FALSE, priority = 10)
# nca_setup.R: run second, overwrites with merged result
# (default priority = 0, so it runs after priority = 10... but actually
# higher priority runs first in Shiny, so this would be wrong)Actually, Shiny runs higher-priority observers first. So set this observer to a lower priority so it runs first, then the settings import observer runs after and overwrites with the merged result:
# This observer: set low priority so it runs early
observeEvent(pknca_data(), {
...
}, ignoreNULL = FALSE, priority = -10)Option B (preferred) — skip when settings import is pending:
observeEvent(pknca_data(), {
current_pknca_data <- pknca_data()
if (is.null(current_pknca_data) || is.null(current_pknca_data$units)) {
session$userData$units_table(NULL)
return()
}
# Don't overwrite if a settings import already populated the table
# with user-customized units for this dataset
if (is.null(session$userData$units_table())) {
session$userData$units_table(current_pknca_data$units)
}
}, ignoreNULL = FALSE)Option B is simpler and avoids the priority dance. The settings import observer will still merge on top when processed_pknca_data() fires, and on re-upload units_table is cleared to NULL first (line 98-99), so the guard passes correctly.
| } | ||
|
|
||
| session$userData$units_table(current_pknca_data$units) | ||
| }, ignoreNULL = FALSE) |
There was a problem hiding this comment.
Nit: add a blank line between the observeEvent block and the adnca_data reactive definition for readability — matches the spacing pattern used elsewhere in this file.
We can generalize #1145 to address this, but it isn't related with this PR per ce. Regarding if the current PR will adapt to that, yes it will work fine once solved However, the normalized by Dose I would not change them personally |
Extract four helpers from the NCA calculation reactive: - .has_no_valid_parameters: parameter validation check - .notify_invalid_parameters: auto-replay vs manual notification - .run_nca_calculation: pipeline with warning capture - .finalize_nca_run: modal cleanup and auto-replay state reset Reduces cyclomatic complexity from 18 to below 15. Co-authored-by: Ona <no-reply@ona.com>
|
@Shaakon35 @js3110 we might need to solve this bug to prevent Ola from having the issue |
|
#1228 (comment) My two comments were not addressed |
…detect-changes-by-value
|
@KOBANAK In case you take over this, make sure this works: #1228 (comment) |
The auto-replay/session-restore feature (auto-NCA-run, tab navigation, filter import) was unrelated to #1190 and introduced a race condition that caused the 'Restoring session...' modal to hang indefinitely. Reverts tab_data.R, app.R, and tab_nca.R to main. Only the two units-related changes in tab_nca.R are preserved: - Auto-populate units_table from pknca_data on data load - Remove select(-any_of('default')) guard (default column removed) Co-authored-by: Ona <no-reply@ona.com>
Prevents the reactive dependency on settings_override from shifting the Shiny flush cycle timing during auto-replay, which caused the debounced NCA trigger to miss its window and the 'Restoring session' modal to hang. Co-authored-by: Ona <no-reply@ona.com>
Moves the units auto-populate logic into a helper function outside tab_nca_server to keep cyclomatic complexity under the lintr threshold. Co-authored-by: Ona <no-reply@ona.com>
Use auto_replay_active flag instead of reading settings_override. Remove ignoreNULL=FALSE to avoid firing on app startup. This eliminates the extra flush cycle that shifted auto-replay timing and caused the 'Restoring session' modal to hang. Co-authored-by: Ona <no-reply@ona.com>
Populate units_table on first NCA run when it is still NULL, instead of using an observeEvent on pknca_data() which interfered with the auto-replay flush cycle timing. Co-authored-by: Ona <no-reply@ona.com>
…e cascade Remove units from the settings() reactive in settings.R. The units field was only used for export (YAML download and ZIP), not for NCA computation. Including it in settings() created a reactive dependency that caused processed_pknca_data to re-evaluate whenever units_table changed, adding a 2500ms+ debounce cycle that pushed auto-replay beyond the 10s safety timeout. Export paths in nca_setup.R and zip-utils.R now read session$userData$units_table() directly. Co-authored-by: Ona <no-reply@ona.com>
|
Right now all is working, but in the settings file the units table is showing completely... which is against our interest |




Issue
Closes #1190
Description
The volume unit simplification applied in
tab_data.R(e.g.mg*L/mL→mg) was not being captured in the exported settings or R script. This happened becausesession$userData$units_table()only stored rows that the user explicitly edited in the Units modal — it used adefaultflag to track which rows were touched, and the automatic simplification never set that flag.Root cause: The
defaultcolumn (added in #900 / PR #946) tracked edits by flagging rows asdefault = FALSEwhen the user clicked on them in the modal. Automatic changes (like the volume simplification) mutated the PKNCAdata object directly without going through the modal, so they were never flagged.Fix: Two-part approach:
Internally,
session$userData$units_table()now stores the full units table (auto-populated frompknca_data()$unitson first NCA run, updated with user edits from the modal). This is required becausenca_results.Rreplacesres$data$unitswith it and joins it to results.On export (settings YAML download and ZIP), the table is filtered to only rows where
PPSTRESU != PPORRESU. This preserves the compact settings format from Enhancement: unit settings only manual changes and not entire units table #900 — only changed units appear in the YAML. On import, the partial rows are merged back into the full data-derived table viarows_update.The
defaultcolumn is fully removed — change detection is now value-based.Additionally,
units_tablewas decoupled from thesettings()reactive to prevent a debounce cascade that caused the "Restoring session..." modal to hang during auto-replay. Thesettings()reactive previously includedunits = session$userData$units_table(), which meant anyunits_tablechange invalidatedsettings→settings_debounced(2500ms) →processed_pknca_data→pknca_data_debounced(1000ms). Since the branch defersunits_tablepopulation until afterprocessed_pknca_datafirst evaluates, this created a second debounce cycle that pushed auto-replay beyond the 10s safety timeout. Export paths now readsession$userData$units_table()directly.Changes
tab_nca.R):res_ncaelse branch stores fullprocessed_pknca_data$unitsintosession$userData$units_table()on first NCA rununits_table.R): Stores full modal table (no filtering)units_table.R): Removeddefaultcolumn from merge logicunits_table.R): Removed hiddendefaultcolumn anddefault <- FALSEon editnca_setup.R): Readsunits_tabledirectly; filters toPPSTRESU != PPORRESUbefore exportzip-utils.R): Readsunits_tabledirectly; same filter before exportnca_setup.R): Merges partial imported units into full data-derived table viarows_updatetab_nca.R): Removedselect(-any_of("default"))guardsettings.R): Removedunits = session$userData$units_table()fromsettings()reactive to prevent debounce cascade during auto-replayDefinition of Done
session$userData$units_table()PPSTRESU != PPORRESU)custom_units_tabledefaultcolumn fully removed from the units table flowHow to test
1. Auto-replay / session restore (the main fix)
nca_ran: no— verify the modal dismisses after navigating to the saved tab2. Units round-trip
mg*L/mL→mg)PPSTRESU != PPORRESU), not the full table3. Units modal
4. R script export
custom_units_tablecontains the simplified units5. ZIP export
Contributor checklist
.scsschange was done, rundata-raw/compile_css.RNotes to reviewer
defaultcolumn was introduced in PR Enhancement: settings file saves only edited units #946 (issue Enhancement: unit settings only manual changes and not entire units table #900). This PR removes it entirely in favor of value-based detection.session$userData$units_table()stores the full table internally (needed bynca_results.R), but exports are filtered to only changed rows (preserving Enhancement: unit settings only manual changes and not entire units table #900's compact format).unitswas removed from thesettings()reactive because it only served export purposes and created a reactive dependency that caused auto-replay hangs. Export paths now readsession$userData$units_table()directly.PPSTRESUback to equalPPORRESU, that row is correctly treated as unchanged and excluded from export.PPSTRESUorPPORRESUisNAare excluded from export filters to avoid.na.characterentries in the YAML.