-
-
Notifications
You must be signed in to change notification settings - Fork 45
898 save app state version 4 #1048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
d5add57
rebuild filter manager to retrieve/create/store key objects
d819972
rebuild snapshot manager to retrieve/create/store key objects
f987c04
rebuild state manager to retrieve/create/store key objects
13b0967
introduce GlobalSetter object
3f79bb2
delete commented code
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
#' Set global objects | ||
#' | ||
#' Class to manage global objects and bring them into scope of `shiny` modules. | ||
#' | ||
#' Singleton object to pass variables between modules. Instantiated in the package namespace. | ||
#' Global objects are stored in the `session$userData` environment. | ||
#' Every `shiny` module can register a setter functions for a global variable to be taken from storage | ||
#' or created from scratch. Then, that function can be called anywhere in the app. | ||
#' | ||
#' @docType class | ||
#' @name GlobalSetter | ||
#' @rdname GlobalSetter | ||
#' | ||
#' @examples | ||
#' GlobalSetter <- getFromNamespace("GlobalSetter", "teal") | ||
#' setter <- GlobalSetter$new() | ||
#' setter$add_setter("romans", function() { | ||
#' assign("romans", .romans, envir = .GlobalEnv) | ||
#' .romans | ||
#' }) | ||
#' setter | ||
#' setter$set_global("romans") | ||
#' exists("romans", envir = .GlobalEnv, inherits = TRUE) | ||
#' | ||
#' @keywords internal | ||
#' | ||
GlobalSetter <- R6::R6Class( | ||
classname = "GlobalSetter", | ||
|
||
# __Public Methods ---- | ||
public = list( | ||
|
||
#' @description | ||
#' Set object. | ||
#' @param obj (`character(1)`) Name of object to set. | ||
#' @return | ||
#' Whatever the particular setter function returns, usually the global object. | ||
set_global = function(obj) { | ||
if (!shiny::isRunning()) { | ||
message("no shiny, no objects") | ||
return(NULL) | ||
} | ||
checkmate::assert_string(obj) | ||
checkmate::assert_choice(obj, private$objects) | ||
|
||
ind <- match(obj, private$objects) | ||
private$functions[[ind]]() | ||
}, | ||
|
||
#' @description | ||
#' Add setter function for global object. | ||
#' @param obj (`character(1)`) Name of object. | ||
#' @param fun (`function`) Setter function. | ||
add_setter = function(obj, fun) { | ||
checkmate::assert_string(obj) | ||
checkmate::assert_function(fun) | ||
|
||
ind <- match(obj, private$objects, nomatch = length(private$objects) + 1L) | ||
|
||
if (is.element(obj, private$objects)) { | ||
if (identical(fun, private$functions[[ind]])) { | ||
message("this setter already exists") | ||
return(invisible(NULL)) | ||
} else { | ||
message(sprintf("overwriting setter function for object '%s'", obj)) | ||
} | ||
} | ||
|
||
private$objects[ind] <- obj | ||
private$functions[[ind]] <- fun | ||
invisible(NULL) | ||
}, | ||
|
||
#' @noRd | ||
format = function(...) { | ||
paste( | ||
c( | ||
"Global Object Setter", | ||
if (length(private$objects)) sprintf(" objects: %s", toString(private$objects)) else " empty" | ||
), | ||
collapse = "\n" | ||
) | ||
} | ||
), | ||
|
||
# __Private Members ---- | ||
private = list( | ||
objects = character(0L), # objects for which setter functions have been defined | ||
functions = list() # setter functions | ||
), | ||
cloneable = FALSE | ||
) | ||
|
||
GS <- GlobalSetter$new() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -113,43 +113,22 @@ filter_manager_srv <- function(id, filtered_data_list, filter) { | |||||||
|
||||||||
is_module_specific <- isTRUE(attr(filter, "module_specific")) | ||||||||
|
||||||||
# Register setters for global objects | ||||||||
GS$add_setter("slices_global", setter_slices_global) | ||||||||
GS$add_setter("filtered_data_list", setter_filtered_data_list) | ||||||||
GS$add_setter("mapping_matrix", setter_mapping_matrix) | ||||||||
|
||||||||
# Create global list of slices. | ||||||||
# Contains all available teal_slice objects available to all modules. | ||||||||
# Passed whole to instances of FilteredData used for individual modules. | ||||||||
# Down there a subset that pertains to the data sets used in that module is applied and displayed. | ||||||||
slices_global <- reactiveVal(filter) | ||||||||
|
||||||||
filtered_data_list <- | ||||||||
if (!is_module_specific) { | ||||||||
# Retrieve the first FilteredData from potentially nested list. | ||||||||
# List of length one is named "global_filters" because that name is forbidden for a module label. | ||||||||
list(global_filters = unlist(filtered_data_list)[[1]]) | ||||||||
} else { | ||||||||
# Flatten potentially nested list of FilteredData objects while maintaining useful names. | ||||||||
# Simply using `unlist` would result in concatenated names. | ||||||||
flatten_nested <- function(x, name = NULL) { | ||||||||
if (inherits(x, "FilteredData")) { | ||||||||
setNames(list(x), name) | ||||||||
} else { | ||||||||
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name))) | ||||||||
} | ||||||||
} | ||||||||
flatten_nested(filtered_data_list) | ||||||||
} | ||||||||
|
||||||||
# Create mapping fo filters to modules in matrix form (presented as data.frame). | ||||||||
# Modules get NAs for filters that cannot be set for them. | ||||||||
mapping_matrix <- reactive({ | ||||||||
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id") | ||||||||
mapping_smooth <- lapply(filtered_data_list, function(x) { | ||||||||
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id") | ||||||||
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id") | ||||||||
states_active <- state_ids_global %in% state_ids_local | ||||||||
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA) | ||||||||
}) | ||||||||
|
||||||||
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE) | ||||||||
}) | ||||||||
slices_global <- GS$set_global("slices_global") | ||||||||
# Prepare FilteredData object according to app type (global/module-specific). | ||||||||
filtered_data_list <- GS$set_global("filtered_data_list") | ||||||||
# Represent mapping of modules to filters. | ||||||||
mapping_matrix <- GS$set_global("mapping_matrix") | ||||||||
# The three objects above are also stored in the app session's userData. | ||||||||
# They will be used in other modules and stored when bookmarking. | ||||||||
|
||||||||
output$slices_table <- renderTable( | ||||||||
expr = { | ||||||||
|
@@ -182,9 +161,9 @@ filter_manager_srv <- function(id, filtered_data_list, filter) { | |||||||
}) | ||||||||
|
||||||||
# Call snapshot manager. | ||||||||
snapshot_history <- snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list) | ||||||||
snapshot_manager_srv("snapshot_manager") | ||||||||
# Call state manager. | ||||||||
state_manager_srv("state_manager", slices_global, mapping_matrix, filtered_data_list, snapshot_history) | ||||||||
state_manager_srv("state_manager") | ||||||||
|
||||||||
modules_out # returned for testing purpose | ||||||||
}) | ||||||||
|
@@ -252,3 +231,96 @@ filter_manager_module_srv <- function(id, module_fd, slices_global) { | |||||||
slices_module # returned for testing purpose | ||||||||
}) | ||||||||
} | ||||||||
|
||||||||
|
||||||||
|
||||||||
# utility functions ---- | ||||||||
|
||||||||
# Functions that prepare objects. | ||||||||
# Objects are first scoped in sesion (userData) and if not found, created and stored. | ||||||||
|
||||||||
# Obtain appropriate structure of `FilteredData` objects. | ||||||||
# For global app, list of length 1 named "global_filters". | ||||||||
# For module-specific app, list of length one-per-module, named after modules. | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
#' | ||||||||
create_filtered_data_list <- function(filtered_data_list, module_specific) { | ||||||||
if (!module_specific) { | ||||||||
# Retrieve the first FilteredData from potentially nested list. | ||||||||
# List of length one is named "global_filters" because that name is forbidden for a module label. | ||||||||
list(global_filters = unlist(filtered_data_list)[[1]]) | ||||||||
} else { | ||||||||
flatten_nested(filtered_data_list) | ||||||||
} | ||||||||
} | ||||||||
# Flatten potentially nested list of FilteredData objects while maintaining useful names. | ||||||||
# Simply using `unlist` would result in concatenated names. | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
#' | ||||||||
flatten_nested <- function(x, name = NULL) { | ||||||||
if (inherits(x, "FilteredData")) { | ||||||||
setNames(list(x), name) | ||||||||
} else { | ||||||||
unlist(lapply(names(x), function(name) flatten_nested(x[[name]], name))) | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
# Create mapping fo filters to modules in matrix form (presented as data.frame). | ||||||||
# Modules get NAs for filters that cannot be set for them. | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
#' | ||||||||
create_mapping_matrix <- function(filtered_data_list, slices_global) { | ||||||||
reactive({ | ||||||||
state_ids_global <- vapply(slices_global(), `[[`, character(1L), "id") | ||||||||
mapping_smooth <- lapply(filtered_data_list, function(x) { | ||||||||
state_ids_local <- vapply(x$get_filter_state(), `[[`, character(1L), "id") | ||||||||
state_ids_allowed <- vapply(x$get_available_teal_slices()(), `[[`, character(1L), "id") | ||||||||
states_active <- state_ids_global %in% state_ids_local | ||||||||
ifelse(state_ids_global %in% state_ids_allowed, states_active, NA) | ||||||||
}) | ||||||||
|
||||||||
as.data.frame(mapping_smooth, row.names = state_ids_global, check.names = FALSE) | ||||||||
}) | ||||||||
} | ||||||||
|
||||||||
|
||||||||
|
||||||||
# setter for global variable ---- | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
setter_slices_global <- function() { | ||||||||
sesh <- getDefaultReactiveDomain() | ||||||||
if (is.null(sesh$userData$slices_global)) { | ||||||||
filter <- dynGet("filter") | ||||||||
sesh$userData$slices_global <- reactiveVal(filter) | ||||||||
} else { | ||||||||
sesh$userData$slices_global | ||||||||
} | ||||||||
} | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
setter_filtered_data_list <- function() { | ||||||||
sesh <- getDefaultReactiveDomain() | ||||||||
if (is.null(sesh$userData$filtered_data_list)) { | ||||||||
filtered_data_list <- dynGet("filtered_data_list") | ||||||||
is_module_specific <- dynGet("is_module_specific") | ||||||||
sesh$userData$filtered_data_list <- create_filtered_data_list(filtered_data_list, is_module_specific) | ||||||||
} else { | ||||||||
sesh$userData$filtered_data_list | ||||||||
} | ||||||||
} | ||||||||
#' @keywords internal | ||||||||
#' @noRd | ||||||||
setter_mapping_matrix <- function() { | ||||||||
sesh <- getDefaultReactiveDomain() | ||||||||
if (is.null(sesh$userData$mapping_matrix)) { | ||||||||
filtered_data_list <- dynGet("filtered_data_list") | ||||||||
slices_global <- dynGet("slices_global") | ||||||||
sesh$userData$mapping_matrix <- create_mapping_matrix(filtered_data_list, slices_global) | ||||||||
} else { | ||||||||
sesh$userData$mapping_matrix | ||||||||
} | ||||||||
Comment on lines
+323
to
+325
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Existence of
Suggested change
|
||||||||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seemingly independent function requires always to be called in the environment where
filtered_data_list
andslices_global
exists (or will exist in the moment of a evaluation). This is not just unsafe but also too complicated.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where did you get that idea? 😆
All of this is extremely impure but If we are to share objects between modules, there is no way to do it in a pure way.