-
-
Notifications
You must be signed in to change notification settings - Fork 49
898 save app state version 3 #1011
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
Merged
Merged
Changes from 90 commits
Commits
Show all changes
134 commits
Select commit
Hold shift + click to select a range
9ee3c22
create functions to grab and restore app state
4278015
add state manager module
edfd6be
amend documentation
1b3b524
insert state manager into filter manager
1b149e8
rename one funciton
a7405d9
omit action buttons from grabs
6a0cc29
re-click until grab fully reset
130fbc8
encapsulate creating grabs
3b963fa
reorder file
634b5e8
amend documentation
d452326
remove dewclassing of grabbed values to keep dates and date times
e131b71
handle POSIXct in airDatePickerInput
7c21911
Merge e131b713094b85a6f73f85d82b97a5ff8e3bce87 into 54c683b92f845075a…
chlebowa 709c4ef
[skip actions] Restyle files
github-actions[bot] c2e0be4
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] 735650c
remove storing initial input state as always empty
f854b9d
spelling
556c1bb
spelling
696375e
Merge branch 'main' into 898_save_app_state@main
chlebowa 3e40918
use native shiny bookmarking
0c22dcd
open bookmarks in new window
b9e3e37
minor update to defauts
958c8d7
Merge branch 'main' into 898_save_app_state2@main
edb94dc
fix logic operators
67f8be5
fix state manager server definition and call
5606703
add hook to teal to set shiny option for bookmarking
5b47506
fix bookmarking and restoring callbacks
a583e23
add missing arguments in state manager and add return value in snapsh…
47cc2a5
properly call public methods of session object
81f10fb
add more missing arguments to state manager module
5bb0125
clean up docs
9e9c9de
rebuild module
e2fea5d
remove grab_state function
ca24e29
add argument checks to state manager module
3ec250d
remove filter panel exclusion
c9bbb54
don't store initial app state
63eb738
clean up comemnts
c3d40cf
init returns ui as function
ac363d2
use state manager module always
f24dd8c
Merge branch 'main' into 898_save_app_state3@main
b02fee4
Merge branch 'main' into 898_save_app_state3@main
c0f854c
all managers return
159e337
isolate manager_manager_module
0eb331e
rename manager_manager where it is used
ef6c049
move snapshot_manager and state_manager to manager manager
d25397c
rename module
b8b7b5c
shorten line
712a5c3
remove superfluous utility function
1084353
rename module to bookmark_manager
b950e31
rename grab to bookmark
a3d2147
fix typo
2149306
separate utility function in filter manager
af3ebfc
move renaming of global filtered data list
8ba143a
rename flat filtered data list and tweak utility function
6a3689a
rename filtered_data_list to datasets
fe0afb0
rename filtered_data_flat to datasets_flat
e59ab91
improve documentation for flatten_datasets
2b08206
add logging to all manager modules
3b82671
update name of programmatically clicked button
4371359
fix erroneous log
5d62ff4
remove delayed module initiation when starting from bookmark
23d408d
bug fix: missing argument value
c4b669c
fix update in example module
6d07bd2
improve logs in bookmark manager
43752bf
add bookmark exclusions
cd6c6bc
add code comment
fc3872a
modify button titles
8181ca6
remove superfluous CSS
07fa52f
change CSS class names
d896983
adjust style of bnon-first children in manager_table_row
6fbf146
Merge branch 'main' into 898_save_app_state3@main
chlebowa ed34a94
update code comment
2495fb3
simplify 'when from bookmark' condition
81f9346
Merge branch '898_save_app_state3@main' of github.com:insightsenginee…
5b5a1e2
replace bookmarkButton with action Button
debf0f2
amend documentation
24966a6
amend NEWS
ed94c39
rearrange code
38c9598
[skip style] [skip vbump] Restyle files
github-actions[bot] fa33460
fix spelling
9ac1fbd
Merge branch 'main' into 898_save_app_state3@main
chlebowa 4dcc66c
improve flow control condition
882c1cf
amend unit tests
9db31ac
Merge branch '898_save_app_state3@main' of github.com:insightsenginee…
40d7cb9
assign return value in wunder_bar server
6b99ded
simplify unit test for snapshot manager
2b81ce2
add unit tests for wunder_bar module
47f93dd
Merge branch 'main' into 898_save_app_state3@main
chlebowa 7888356
Merge branch 'main' into 898_save_app_state3@main
chlebowa 68a7a0d
Merge branch 'main' into 898_save_app_state3@main
chlebowa a766110
remove delay on starting report previewer
40bed62
exclude all buttons (app-wide) from bookmark
7e3aed9
Merge branch 'main' into 898_save_app_state3@main
chlebowa 92391a5
simplify code
a83b42c
change condition for initiating reporte previewer module
e2836b0
Merge branch 'main' into 898_save_app_state3@main
9d7d7b9
restoreValue
gogonzo 557364c
remove browser
gogonzo be642f5
trigger
d36aac6
Merge branch 'main' into 898_save_app_state3@main
55bf1e0
add documentation for restoreValue
75ffb21
add function for comparing bookmarked states
4b94872
reorganize code
72bce64
amend documentation for restoreValue
c4e7556
move storing values to respective modules
c0d1e32
correct for namespace when restoring filter in module_teal
1b716d2
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] 02a7db4
modify restoreValue
320014e
update documentation for restoreValue
5dbf498
clean up bookmark exclusions in bookmark manager
02b6710
modify storing filter state on bookmark
9e0f213
add comment headers
5c1896e
add enforcement of server-side bookmarks
ae46faa
register bookmark exclusions in snapshot manager
3423345
[skip style] [skip vbump] Restyle files
github-actions[bot] 0e25d4c
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] e1df2fd
remove agruments in bookmark manager
b01a395
fix return value
5294f7c
exclude buttons from bookmark in wunder bar
56c55a0
amend documentation for snapshot and bookmark managers
8aac98c
extend message in bookmarks_identical
bf8b39b
teal_bookmarkable flags
d4a2b4d
only set app id on filter when missing
9edf512
Bookmarking info (#1184)
gogonzo 92890d5
remove test
gogonzo 30e8b28
trigger
33a5ba7
Merge branch 'main' into 898_save_app_state3@main
chlebowa a3659db
fix docs collate
gogonzo d3a0e09
fix docs
gogonzo 7b40974
[skip roxygen] [skip vbump] Roxygen Man Pages Auto Update
dependabot-preview[bot] 0aca94c
trigger
97c2efb
adding testing
gogonzo b40bd15
Merge branch '898_save_app_state3@main' of github.com:insightsenginee…
gogonzo 6682d75
revert NEWS update
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
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
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
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
chlebowa marked this conversation as resolved.
Show resolved
Hide resolved
|
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,222 @@ | ||
#' App state management. | ||
#' | ||
#' Capture and restore the global (app) input state. | ||
#' | ||
#' This module introduces bookmarks into `teal` apps: the `shiny` bookmarking mechanism becomes enabled | ||
#' and server-side bookmarks can be created. | ||
#' | ||
#' The bookmark manager is accessed with the bookmark icon in the [`wunder_bar`]. | ||
#' The manager's header contains a title and a bookmark icon. Clicking the icon creates a bookmark. | ||
#' As bookmarks are added, they will show up as rows in a table, each being a link that, when clicked, | ||
#' will open the bookmarked application in a new window. | ||
#' | ||
#' @section Server logic: | ||
#' A bookmark is a URL that contains the app address with a `/?_state_id_=<bookmark_dir>` suffix. | ||
#' `<bookmark_dir>` is a directory created on the server, where the state of the application is saved. | ||
#' Accessing the bookmark URL opens a new session of the app that starts in the previously saved state. | ||
#' | ||
#' Bookmarks are stored in a `reactiveVal` as a named list. | ||
#' For every bookmark created a piece of HTML is created that contains a link, | ||
#' whose text is the name of the bookmark and whose href is the bookmark URL. | ||
#' | ||
#' @section Bookmark mechanics: | ||
#' When a bookmark is added, the user is prompted to name it. | ||
#' New bookmark names are validated so that thy are unique. Leading and trailing white space is trimmed. | ||
#' | ||
#' Once a bookmark name has been accepted, the app state is saved: values of all inputs, | ||
#' which are kept in the `input` slot of the `session` object, are dumped into the `input.rds` file | ||
#' in the `<bookmark_dir>` directory on the server. | ||
#' This is out of the box behavior that permeates the entire app, no adjustments to modules are necessary. | ||
#' An additional `onBookmark` callback creates a snapshot of the current filter state | ||
#' (the module has access to the filter state of the application through `slices_global` and `mapping_matrix`). | ||
#' Then that snapshot, the previous snapshot history (which is passed to this module as argument), | ||
#' and the previous bookmark history are dumped into the `values.rds` file in `<bookmark_dir>`. | ||
#' | ||
#' Finally, an `onBookmarked` callback adds the newly created bookmark to the bookmark history. | ||
#' Notably, this occurs _after_ creating the bookmark is concluded so the bookmark history that was stored | ||
#' does not include the newly added bookmark. | ||
#' | ||
#' When starting the app from a bookmark, `shiny` recognizes that the app is being restored, | ||
#' locates the bookmark directory and loads both `.rds` file. | ||
#' Values stored in `input.rds` are automatically set to their corresponding inputs. | ||
#' The filter state that the app had upon bookmarking, which was saved as a separate snapshot, is restored. | ||
#' This is done in the same manner as in the `snapshot_manager` module and thus requires access to `datasets_flat`, | ||
#' which is passed to this module as argument. | ||
#' Finally, snapshot history and bookmark history are loaded from `values.rds` and set to appropriate `reactiveVal`s. | ||
#' | ||
#' @section Note: | ||
#' All `teal` apps are inherently bookmarkable. Normal `shiny` apps require that `enableBookmarking` be set to "server", | ||
#' either by setting an argument in a `shinyApp` call or by calling a special function. In `teal` bookmarks are enabled | ||
#' by automatically setting an option when the package is loaded. | ||
#' | ||
#' @param id (`character(1)`) `shiny` module instance id. | ||
#' @inheritParams module_snapshot_manager | ||
#' @param snapshot_history (named `list`) of unlisted `teal_slices` objects, as returned by the `snapshot_manager`. | ||
#' | ||
#' @return `reactiveVal` containing a named list of bookmark URLs. | ||
#' | ||
#' @name module_bookmark_manager | ||
#' @aliases bookmark bookmark_manager bookmark_manager_module | ||
#' | ||
|
||
#' @rdname module_bookmark_manager | ||
#' @keywords internal | ||
#' | ||
bookmark_manager_ui <- function(id) { | ||
ns <- NS(id) | ||
div( | ||
class = "manager_content", | ||
div( | ||
class = "manager_table_row", | ||
span(tags$b("Bookmark manager")), | ||
actionLink(ns("bookmark_add"), NULL, icon = suppressMessages(icon("solid fa-bookmark")), title = "add bookmark"), | ||
NULL | ||
), | ||
uiOutput(ns("bookmark_list")) | ||
) | ||
} | ||
|
||
#' @rdname module_bookmark_manager | ||
#' @keywords internal | ||
#' | ||
bookmark_manager_srv <- function(id, slices_global, mapping_matrix, datasets, snapshot_history) { | ||
checkmate::assert_character(id) | ||
checkmate::assert_true(is.reactive(slices_global)) | ||
checkmate::assert_class(isolate(slices_global()), "teal_slices") | ||
checkmate::assert_true(is.reactive(mapping_matrix)) | ||
checkmate::assert_data_frame(isolate(mapping_matrix()), null.ok = TRUE) | ||
checkmate::assert_list(datasets, types = "FilteredData", any.missing = FALSE, names = "named") | ||
checkmate::assert_true(is.reactive(snapshot_history)) | ||
checkmate::assert_list(isolate(snapshot_history()), names = "unique") | ||
|
||
moduleServer(id, function(input, output, session) { | ||
logger::log_trace("bookmark_manager_srv initializing") | ||
|
||
# Set up bookmarking callbacks. | ||
app_session <- .subset2(shiny::getDefaultReactiveDomain(), "parent") | ||
# These exclusions are to ensure the right modals open in bookmarked app (first 2) and for extra security (3rd). | ||
setBookmarkExclude(c("bookmark_add", "bookmark_name", "bookmark_accept")) | ||
app_session$onBookmark(function(state) { | ||
# Add current filter state to bookmark. | ||
logger::log_trace("bookmark_manager_srv@onBookmark: storing filter state") | ||
snapshot <- as.list(slices_global(), recursive = TRUE) | ||
attr(snapshot, "mapping") <- matrix_to_mapping(mapping_matrix()) | ||
state$values$filter_state_on_bookmark <- snapshot | ||
# Add snapshot history and bookmark history to bookmark. | ||
logger::log_trace("bookmark_manager_srv@onBookmark: storing snapshot and bookmark history") | ||
state$values$snapshot_history <- snapshot_history() # isolate this? | ||
state$values$bookmark_history <- bookmark_history() # isolate this? | ||
}) | ||
app_session$onBookmarked(function(url) { | ||
logger::log_trace("bookmark_manager_srv@onBookmarked: bookmark button clicked, registering bookmark") | ||
bookmark_name <- trimws(input$bookmark_name) | ||
if (identical(bookmark_name, "")) { | ||
logger::log_trace("bookmark_manager_srv@onBookmarked: bookmark name rejected") | ||
showNotification( | ||
"Please name the bookmark.", | ||
type = "message" | ||
) | ||
updateTextInput(inputId = "bookmark_name", value = "", placeholder = "Meaningful, unique name") | ||
unlink(strsplit(url, "_state_id_=")[[1L]][[2L]], recursive = TRUE, force = TRUE, expand = FALSE) | ||
} else if (is.element(make.names(bookmark_name), make.names(names(bookmark_history())))) { | ||
logger::log_trace("bookmark_manager_srv@onBookmarked: bookmark name rejected") | ||
showNotification( | ||
"This name is in conflict with other bookmark names. Please choose a different one.", | ||
type = "message" | ||
) | ||
updateTextInput(inputId = "bookmark_name", value = "", placeholder = "Meaningful, unique name") | ||
unlink(strsplit(url, "_state_id_=")[[1L]][[2L]], recursive = TRUE, force = TRUE, expand = FALSE) | ||
} else { | ||
# Add bookmark URL to bookmark history (with name). | ||
logger::log_trace("bookmark_manager_srv@onBookmarked: bookmark name accepted, adding to history") | ||
bookmark_update <- c(bookmark_history(), list(url)) | ||
names(bookmark_update)[length(bookmark_update)] <- bookmark_name | ||
bookmark_history(bookmark_update) | ||
|
||
removeModal() | ||
} | ||
}) | ||
app_session$onRestored(function(state) { | ||
# Restore filter state. | ||
logger::log_trace("bookmark_manager_srv@onRestored: restoring filter state") | ||
snapshot <- state$values$filter_state_on_bookmark | ||
snapshot_state <- as.teal_slices(snapshot) | ||
mapping_unfolded <- unfold_mapping(attr(snapshot_state, "mapping"), names(datasets)) | ||
mapply( | ||
function(filtered_data, filter_ids) { | ||
filtered_data$clear_filter_states(force = TRUE) | ||
slices <- Filter(function(x) x$id %in% filter_ids, snapshot_state) | ||
filtered_data$set_filter_state(slices) | ||
}, | ||
filtered_data = datasets, | ||
filter_ids = mapping_unfolded | ||
) | ||
slices_global(snapshot_state) | ||
# Restore snapshot history and bookmark history. | ||
logger::log_trace("bookmark_manager_srv@onRestored: restoring snapshot and bookmark history") | ||
snapshot_history(state$values$snapshot_history) | ||
bookmark_history(state$values$bookmark_history) | ||
}) | ||
|
||
ns <- session$ns | ||
|
||
# Store input states. | ||
bookmark_history <- reactiveVal({ | ||
list() | ||
}) | ||
|
||
# Bookmark current input state - name bookmark. | ||
observeEvent(input$bookmark_add, { | ||
logger::log_trace("bookmark_manager_srv: bookmark_add button clicked") | ||
showModal( | ||
modalDialog( | ||
textInput(ns("bookmark_name"), "Name the bookmark", width = "100%", placeholder = "Meaningful, unique name"), | ||
footer = tagList( | ||
actionButton(ns("bookmark_accept"), label = "Accept", icon = icon("thumbs-up")), | ||
modalButton(label = "Cancel", icon = icon("thumbs-down")) | ||
), | ||
size = "s" | ||
) | ||
) | ||
}) | ||
|
||
# Initiate bookmarking with normal action button b/c `bookmarkButton` may not work on Windows. | ||
observeEvent(input$bookmark_accept, { | ||
app_session$doBookmark() | ||
}) | ||
|
||
# Create UI elements and server logic for the bookmark table. | ||
# Divs are tracked for a slight speed margin. | ||
divs <- reactiveValues() | ||
|
||
observeEvent(bookmark_history(), { | ||
logger::log_trace("bookmark_manager_srv: bookmark history changed, updating bookmark list") | ||
lapply(names(bookmark_history()), function(s) { | ||
id_rowme <- sprintf("rowme_%s", make.names(s)) | ||
|
||
# Create a row for the bookmark table. | ||
if (!is.element(id_rowme, names(divs))) { | ||
divs[[id_rowme]] <- div( | ||
class = "manager_table_row", | ||
a(h5(s), title = "go to bookmark", href = bookmark_history()[[s]], target = "blank") | ||
) | ||
} | ||
}) | ||
}) | ||
|
||
# Create table to display list of bookmarks and their actions. | ||
output$bookmark_list <- renderUI({ | ||
rows <- lapply(rev(reactiveValuesToList(divs)), function(d) d) | ||
if (length(rows) == 0L) { | ||
div( | ||
class = "manager_placeholder", | ||
"Bookmarks will appear here." | ||
) | ||
} else { | ||
rows | ||
} | ||
}) | ||
chlebowa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
bookmark_history | ||
}) | ||
} |
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.
Uh oh!
There was an error while loading. Please reload this page.