Skip to content
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

298 reset button #859

Merged
merged 101 commits into from
Jul 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
68bb345
fix after teal.slice
gogonzo Mar 9, 2023
89626c5
Merge remote-tracking branch 'origin/main' into filter_panel_refactor…
gogonzo Mar 16, 2023
beb3b0a
187 new api@filter panel refactor@main (#825)
chlebowa Apr 14, 2023
3c44f70
filterable (#830)
gogonzo May 3, 2023
7ff713d
Merge remote-tracking branch 'origin/main' into filter_panel_refactor…
gogonzo Jun 2, 2023
cfbc3d3
Merge remote-tracking branch 'origin/main' into filter_panel_refactor…
gogonzo Jun 6, 2023
61e5c23
reduce the amount of spelling issues / WORDLIST (#843)
m7pr Jun 12, 2023
fde15a4
Module specific filter panels (#837)
gogonzo Jun 16, 2023
c312dee
Merge fde15a48ec060f20df2cdba86e2fa306b1e5c07d into c51c0b0b47d52afb2…
gogonzo Jun 16, 2023
ede832f
[skip actions] Restyle files
github-actions[bot] Jun 16, 2023
139a723
330 [Feature Request]: Option to not show module_add (#852)
kartikeyakirar Jun 21, 2023
cf43277
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jun 21, 2023
f23a746
lift function slices_field from teal.slice
Jun 26, 2023
bc6f2c0
Merge branch 'main' into filter_panel_refactor@main
gogonzo Jun 28, 2023
cc102fc
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jun 28, 2023
0c4aa11
Merge remote-tracking branch 'origin/main' into filter_panel_refactor…
gogonzo Jun 29, 2023
34c6df8
simplify to understand
Jun 29, 2023
2c51fb8
insert snapshot manager module into filter manager module
Jun 29, 2023
14befc9
improve mapping_matrix
Jun 30, 2023
ee49652
add shanpshot manager
Jul 2, 2023
b7f3254
add styling for snapshot manager
Jul 2, 2023
b938da9
restore dependency between of mapping_matrix on slices_map and make s…
Jul 4, 2023
7ee6cf4
add helper functions for transforming mapping
Jul 4, 2023
9afe4d2
properly add mapping to stored snapshots
Jul 4, 2023
c43d0c9
bug fixes
Jul 4, 2023
7985cfd
fix bug in refreshing slices_map
Jul 4, 2023
b43a492
properly set filter state when restoring snapshot
Jul 4, 2023
2700cc7
highlight code repetition
Jul 4, 2023
77c55b3
fix mapping matrix
Jul 5, 2023
c636138
remove slices_map object from filter manager
Jul 5, 2023
7f60f0d
`filter_var` and `filter_expr` to `teal_slice` (#857)
gogonzo Jul 5, 2023
e3b3a42
Merge 7f60f0d1fd18ff1f8ab274afdccfb93cbe786d71 into 603e041c477b01b96…
gogonzo Jul 5, 2023
00570d9
[skip actions] Restyle files
github-actions[bot] Jul 5, 2023
f46e4bf
Merge branch 'filter_panel_refactor@main' into 298_reset_button@filte…
Jul 6, 2023
b25a072
post merge fix
Jul 6, 2023
3ebd43f
mofe setdiff_teal_slices to teal.slice
Jul 6, 2023
63126f9
hide initial state from snapshot list
Jul 6, 2023
3f41145
patch bug in restoring filter state
Jul 6, 2023
8a4b8eb
amend documentation
Jul 6, 2023
70d4c8e
don't check names in mapping_matrix
Jul 10, 2023
e4c84ea
remove slices_field
Jul 10, 2023
d3978fe
block dataset level snapshot manager in module-specific mode
Jul 10, 2023
19c01b3
move helper function to teal.slice
Jul 10, 2023
d065973
lift internal functions from teal.slice
Jul 11, 2023
d847841
reopen filter manager after adding snapshot
Jul 11, 2023
b4498d8
adapt to change of method in FilteredData
Jul 11, 2023
b8e69e3
Merge branch 'main' into filter_panel_refactor@main
gogonzo Jul 11, 2023
6365d6d
postmerge fixes
gogonzo Jul 11, 2023
116ec2f
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jul 11, 2023
59eea14
Merge branch 'filter_panel_refactor@main' into 298_reset_button@filte…
gogonzo Jul 11, 2023
bad5853
restrore helper functions
Jul 11, 2023
720e749
Merge branch '298_reset_button@filter_panel_refactor@main' of github.…
Jul 11, 2023
326effe
remove comments
Jul 11, 2023
db9d905
change default file name
Jul 12, 2023
e9ba1c5
close modal after restoring snapshot
Jul 12, 2023
26daab6
Merge branch 'main' into 298_reset_button@filter_panel_refactor@main
Jul 17, 2023
438651e
Merge 26daab6fbcfff3efeafa3147a72c6c2cfbdb8a88 into 70e900b9dcbcbedb8…
chlebowa Jul 17, 2023
8566158
[skip actions] Restyle files
github-actions[bot] Jul 17, 2023
12de0db
Merge branch 'main' into 298_reset_button@filter_panel_refactor@main
Jul 17, 2023
9e827a5
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jul 17, 2023
1371421
solve overdue merge conflicts
Jul 18, 2023
7488c16
do not reinstantiate observers
Jul 18, 2023
0c6085d
Merge 7488c163b08ab98dc2b2c04efe01d105d9b9277a into 0c2d5142ea96d9045…
chlebowa Jul 18, 2023
be316e2
[skip actions] Restyle files
github-actions[bot] Jul 18, 2023
e0667fb
make snapshot module internal
Jul 18, 2023
f94b589
fix spelling
Jul 18, 2023
f63d6de
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jul 18, 2023
af358be
accept solution with tracking observers to avoid duplication
Jul 18, 2023
00adaec
Merge branch '298_reset_button@filter_panel_refactor@main' of github.…
Jul 18, 2023
49d525f
remove commented code
Jul 18, 2023
1498efb
add tests for snapshot manager module
Jul 18, 2023
69dc6de
modify tests for filter manager
Jul 18, 2023
74bd5c0
Merge 69dc6dec5b5c9e42fc7073692bca1c6ac48bc491 into 0c2d5142ea96d9045…
chlebowa Jul 18, 2023
8662b00
[skip actions] Restyle files
github-actions[bot] Jul 18, 2023
ac4d02b
trigger
Jul 18, 2023
723ccc1
modify unit tests for snapshot manager
Jul 19, 2023
cc536b4
update documentation
Jul 19, 2023
a000af8
amend NEWS
Jul 19, 2023
5e638ea
display mapping matrix as checkmarks
Jul 19, 2023
4afd9ed
Merge branch 'main' into 298_reset_button@filter_panel_refactor@main
chlebowa Jul 19, 2023
db2416a
adapt filter manager to run in global application
Jul 20, 2023
d9b75bb
Merge db2416a895c7c5decda7b549846c605f85c9e213 into bc37145a95ea3e483…
chlebowa Jul 20, 2023
e04267c
[skip actions] Restyle files
github-actions[bot] Jul 20, 2023
58e98e1
filter_manager button UI
gogonzo Jul 20, 2023
75d357e
change handling of mappin in teal_slices
Jul 20, 2023
66fc368
improve handling of mapping matrix in filter manager
Jul 20, 2023
441d72f
always display mapping matrix
Jul 20, 2023
c1a80f0
amend documentation
Jul 20, 2023
bf5a519
remove activation of snapshot manager in FiteredData
Jul 20, 2023
1fab7c2
improve handling mapping in teal_slices funciton
Jul 20, 2023
accf8b3
only set global filters in global mode
Jul 20, 2023
9ed7a66
[skip actions] Roxygen Man Pages Auto Update
dependabot-preview[bot] Jul 20, 2023
144778a
adjust logic in teal_slices
Jul 20, 2023
67bd2f7
amend documentation
Jul 20, 2023
7f656eb
add assertion in srv_tabs_with_filters
Jul 20, 2023
abb02f8
adjust unit tests
Jul 20, 2023
b0b4ad7
Merge branch '298_reset_button@filter_panel_refactor@main' of github.…
Jul 20, 2023
5453ac4
Merge b0b4ad7a23e9adb2f6c88ff70201742a91ce6e45 into bc37145a95ea3e483…
chlebowa Jul 20, 2023
13d5a24
[skip actions] Restyle files
github-actions[bot] Jul 20, 2023
1ff6a0d
trigger
Jul 20, 2023
46746f0
linter
Jul 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Collate:
'init.R'
'module_filter_manager.R'
'module_nested_tabs.R'
'module_snapshot_manager.R'
'module_tabs_with_filters.R'
'module_teal.R'
'module_teal_with_splash.R'
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### New features

* Enabled module specific filter panel. See `module_specific` in `teal::teal_slices` documentation.
* Enabled capturing and resetting application filter state with snapshots. See `?snapshot`.
* Enabled `reporter_previewer_module` to customize default values through `srv_args`.
* Enabled passing own `reporter_previewer_module` in a list of modules to override default one.

Expand Down
179 changes: 88 additions & 91 deletions R/module_filter_manager.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#' Filter manager modal
#'
#' Filter manager modal
#' Opens modal containing the filter manager UI.
#'
#' @name module_filter_manager_modal
#' @inheritParams filter_manager_srv
#' @examples
Expand Down Expand Up @@ -38,6 +39,7 @@
#' if (interactive()) {
#' runApp(app)
#' }
#'
#' @keywords internal
#'
NULL
Expand All @@ -62,6 +64,7 @@ filter_manager_modal_srv <- function(id, filtered_data_list, filter) {
modalDialog(
filter_manager_ui(session$ns("filter_manager")),
size = "l",
footer = NULL,
easyClose = TRUE
)
)
Expand All @@ -75,145 +78,153 @@ filter_manager_modal_srv <- function(id, filtered_data_list, filter) {
filter_manager_ui <- function(id) {
ns <- NS(id)
div(
tableOutput(ns("slices_table"))
class = "filter_manager_content",
tableOutput(ns("slices_table")),
snapshot_manager_ui(ns("snapshot_manager"))
)
}

#' Manage multiple `FilteredData` objects
#'
#' Manage multiple `FilteredData` objects
#' Oversee filter states in the whole application.
#'
#' @rdname module_filter_manager
#' @details
#' This module observes the changes of the filters in each `FilteredData` object
#' and keeps track of all filters used. Map of the filters is kept in so called
#' `slices_map` object where each `FilteredData` is linked with its active filters.
#' This map is represented in the UI as a matrix where rows are ids of the filters and
#' columns are names of the `filtered_data_list` (named after teal modules).
#' and keeps track of all filters used. A mapping of filters to modules
#' is kept in the `mapping_matrix` object (which is actually a `data.frame`)
#' that tracks which filters (rows) are active in which modules (columns).
#'
#' @param id (`character(1)`)\cr
#' `shiny` module id.
#' @param filtered_data_list (`list` of `FilteredData`)\cr
#' Names of the list should be the same as `teal_module$label`.
#' @param filtered_data_list (`named list`)\cr
#' A list, possibly nested, of `FilteredData` objects.
#' Each `FilteredData` will be served to one module in the `teal` application.
#' The structure of the list must reflect the nesting of modules in tabs
#' and names of the list must be the same as labels of their respective modules.
#' @inheritParams init
#' @return A list of `reactive`s, each holding a `teal_slices`, as returned by `filter_manager_module_srv`.
#' @keywords internal
#'
filter_manager_srv <- function(id, filtered_data_list, filter) {
moduleServer(id, function(input, output, session) {
logger::log_trace("filter_manager_srv initializing for: { paste(names(filtered_data_list), collapse = ', ')}.")

# instead of unlist which unlist with concatenating nested names with '.'
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)))
}
}
filtered_data_list <- flatten_nested(filtered_data_list)
is_module_specific <- isTRUE(attr(filter, "module_specific"))

# global list of slices (all available teal_slice)
# 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)

# create a reactive map between modules and filters
slices_map <- sapply(
names(filtered_data_list),
function(module_name) {
shiny::reactiveVal(
unlist(attr(filter, "mapping")[c(module_name, "global_filters")], use.names = FALSE)
)
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 = 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).
mapping_matrix <- reactive({
module_states <- lapply(filtered_data_list, function(x) x$get_filter_state())
mapping_ragged <- lapply(module_states, function(x) vapply(x, `[[`, character(1L), "id"))
all_names <- vapply(slices_global(), `[[`, character(1L), "id")
mapping_smooth <- lapply(mapping_ragged, is.element, el = all_names)
as.data.frame(mapping_smooth, row.names = all_names, check.names = FALSE)
})

output$slices_table <- renderTable(
expr = {
# Display logical values as UTF characters.
mm <- mapping_matrix()
mm[] <- lapply(mm, ifelse, yes = intToUtf8(9989), no = intToUtf8(10060))
if (!is_module_specific) colnames(mm) <- "Global Filters"
mm
},
align = paste(c("l", rep("c", ncol(mapping_matrix()))), collapse = ""),
rownames = TRUE
)

# Create list of module calls.
modules_out <- lapply(names(filtered_data_list), function(module_name) {
filter_manager_module_srv(
id = module_name,
module_fd = filtered_data_list[[module_name]],
slices_map_module = slices_map[[module_name]],
slices_global = slices_global
)
})

mapping_matrix <- reactive({
module_names <- names(filtered_data_list)
filter_names <- vapply(X = slices_global(), `[[`, character(1), "id")
mapping_matrix <- matrix(
FALSE,
nrow = length(filter_names),
ncol = length(module_names),
dimnames = list(filter_names, module_names)
)
for (i in module_names) {
mapping_matrix[slices_map[[i]](), i] <- TRUE
}
mapping_matrix
})

output$slices_table <- renderTable(rownames = TRUE, {
as.data.frame(mapping_matrix())
})
# Call snapshot manager.
snapshot_manager_srv("snapshot_manager", slices_global, mapping_matrix, filtered_data_list)

modules_out # returned for testing purpose
})
}

#' Module specific filter manager
#'
#' This module compares filters between single `FilteredData` settings
#' and global `teal_slices`. Updates appropriate objects `module_fd`,
#' `slices_map_module`, `slices_global` to keep them consistent.
#' Track filter states in single module.
#'
#' This module tracks the state of a single `FilteredData` object and global `teal_slices`
#' and updates both objects as necessary. Filter states added in different modules
#' Filter states added any individual module are added to global `teal_slices`
#' and from there become available in other modules
#' by setting `private$available_teal_slices` in each `FilteredData`.
#'
#' @param id (`character(1)`)\cr
#' `shiny` module id.
#' @param module_fd (`FilteredData`)\cr
#' object to filter data in the teal-module
#' @param slices_map_module (`reactiveVal` of `character`)\cr
#' `id` of the `teal_slice` objects used in the module specific `FilteredData`.
#' @param slices_global (`reactiveVal` or `teal_slices`)\cr
#' stores a list of all available filters which can be utilized in several ways, for example:
#' - to disable/enable specific filter in the module
#' - to restore filter saved settings
#' - to save current filter settings panel
#' @return shiny module returning NULL
#' @param slices_global (`reactiveVal`)\cr
#' stores `teal_slices` with all available filters; allows the following actions:
#' - to disable/enable a specific filter in a module
#' - to restore saved filter settings
#' - to save current filter panel settings
#' @return A `reactive` expression containing the slices active in this module.
#' @keywords internal
filter_manager_module_srv <- function(id, module_fd, slices_map_module, slices_global) {
#'
filter_manager_module_srv <- function(id, module_fd, slices_global) {
moduleServer(id, function(input, output, session) {
setdiff_teal_slices <- function(x, y) {
Filter(
function(xx) {
!any(vapply(y, function(yy) identical(yy, xx), logical(1)))
},
x
)
}

available_slices <- reactive(
# Only operate on slices that refer to data sets present in this module.
available_slices <- reactive({
Filter(function(slice) slice$dataname %in% module_fd$datanames(), slices_global())
)
})
module_fd$set_available_teal_slices(available_slices)

# Track filter state of this module.
slices_module <- reactive(module_fd$get_filter_state())

previous_slices <- reactiveVal(shiny::isolate(slices_module()))
# Reactive values for comparing states.
previous_slices <- reactiveVal(isolate(slices_module()))
slices_added <- reactiveVal(NULL)
slices_activated <- reactiveVal(NULL)
slices_deactivated <- reactiveVal(NULL)

observeEvent(slices_module(), {
# Observe changes in module filter state and trigger appropriate actions.
observeEvent(slices_module(), ignoreNULL = FALSE, {
logger::log_trace("filter_manager_srv@1 detecting states deltas in module: { id }.")
added <- setdiff_teal_slices(slices_module(), slices_global())
activated <- setdiff_teal_slices(slices_module(), previous_slices())
deactivated <- setdiff_teal_slices(previous_slices(), slices_module())
if (length(added)) slices_added(added)
if (length(activated)) slices_activated(activated)
if (length(deactivated)) slices_deactivated(deactivated)
previous_slices(slices_module())
})

observeEvent(slices_added(), ignoreNULL = TRUE, {
logger::log_trace("filter_manager_srv@2 added filter in module: { id }.")
# In case the new state has the same id as an existing state, add a suffix to it.
global_ids <- vapply(slices_global(), `[[`, character(1L), "id")
lapply(
slices_added(),
function(slice) {
global_ids <- vapply(slices_global(), function(x) x$id, character(1))
if (slice$id %in% global_ids) {
slice$id <- utils::tail(make.unique(c(global_ids, slice$id), sep = "_"), 1)
}
Expand All @@ -224,20 +235,6 @@ filter_manager_module_srv <- function(id, module_fd, slices_map_module, slices_g
slices_added(NULL)
})

observeEvent(slices_activated(), ignoreNULL = TRUE, {
logger::log_trace("filter_manager_srv@3 activated filter in module: { id }.")
activated_ids <- vapply(slices_activated(), `[[`, character(1), "id")
slices_map_module(c(slices_map_module(), activated_ids))
slices_activated(NULL)
})

observeEvent(slices_deactivated(), ignoreNULL = TRUE, {
logger::log_trace("filter_manager_srv@4 deactivated filter in module: { id }.")
deactivated_ids <- vapply(slices_deactivated(), `[[`, character(1), "id")
slices_map_module(setdiff(slices_map_module(), deactivated_ids))
slices_deactivated(NULL)
})

slices_module # returned for testing purpose
})
}
4 changes: 2 additions & 2 deletions R/module_nested_tabs.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
#' @param depth (`integer(1)`)\cr
#' number which helps to determine depth of the modules nesting.
#' @param is_module_specific (`logical(1)`)\cr
#' flag determining if the filter panel is global or module-specific. When `module_specific`
#' is `TRUE` then a filter panel is called inside of each module tab.
#' flag determining if the filter panel is global or module-specific.
#' When set to `TRUE`, a filter panel is called inside of each module tab.
#' @return depending on class of `modules`:
#' - `teal_module`: instantiated UI of the module
#' - `teal_modules`: `tabsetPanel` with each tab corresponding to recursively
Expand Down
Loading