├── vignettes ├── .gitignore ├── images │ ├── bs-launch.png │ ├── bs-font-size.png │ ├── bs-theme-set.png │ ├── show_code_prepro_missing.png │ ├── show_code_prepro_present.png │ ├── teal-app-components.drawio.png │ ├── custom_module_without_reporter.png │ └── teal-app-components-hover.drawio.png ├── blueprint │ ├── images │ │ ├── reporter.png │ │ ├── showrcode.png │ │ └── filterpanel.png │ ├── _setup.Rmd │ ├── index.Rmd │ ├── in_app_data.Rmd │ ├── module_encapsulation.Rmd │ ├── dataflow.Rmd │ ├── intro.Rmd │ ├── product_map.Rmd │ ├── filter_panel.Rmd │ └── input_data.Rmd └── teal-as-a-shiny-module.Rmd ├── tests ├── testthat │ ├── setup-logger.R │ ├── _snaps │ │ └── linux-4.5 │ │ │ ├── shinytest2-init │ │ │ └── teal-001.png │ │ │ └── shinytest2-wunder_bar │ │ │ └── teal-001.png │ ├── helper-shinytest2.R │ ├── test-shinytest2-snapshot_manager.R │ ├── test-shinytest2-filter_manager.R │ ├── setup-options.R │ ├── test-teal_data_module.R │ ├── test-shinytest2-disable_src.R │ ├── test-module_session_info.R │ ├── test-shinytest2-module_bookmark_manager.R │ ├── test-shinytest2-disable_report.R │ ├── test-shinytest2-show-rcode.R │ ├── test-teal_transform_module.R │ ├── helper-testing_depth.R │ ├── test-reporter_previewer_module.R │ ├── test-zzz.R │ ├── test-shinytest2-reporter.R │ ├── test-validate_has_data.R │ ├── test-module_teal_data.R │ ├── test-teal_reporter.R │ ├── test-shinytest2-modules.R │ └── test-shinytest2-decorators.R └── testthat.R ├── man ├── figures │ ├── readme_app.gif │ ├── lifecycle-stable.svg │ ├── lifecycle-defunct.svg │ ├── lifecycle-archived.svg │ ├── lifecycle-maturing.svg │ ├── lifecycle-deprecated.svg │ ├── lifecycle-superseded.svg │ ├── lifecycle-experimental.svg │ ├── lifecycle-questioning.svg │ └── lifecycle-soft-deprecated.svg ├── dot-smart_rbind.Rd ├── dot-teal_favicon.Rd ├── get_unique_labels.Rd ├── validate_app_title_tag.Rd ├── dot-get_reporter_options.Rd ├── dot-add_signature_to_data.Rd ├── extract_transformators.Rd ├── modules_slot.Rd ├── is_arg_used.Rd ├── dot-get_hashes_code.Rd ├── deep_copy_filter.Rd ├── teal_data_to_filtered_data.Rd ├── append_module.Rd ├── modules_bookmarkable.Rd ├── decorate_err_msg.Rd ├── include_css_files.Rd ├── pluralize.Rd ├── get_client_timezone.Rd ├── include_teal_css_js.Rd ├── create_app_id.Rd ├── check_filter_datanames.Rd ├── teal_extend_server.Rd ├── build_app_title.Rd ├── module_management.Rd ├── report_card_template.Rd ├── check_modules_datanames.Rd ├── teal_data_utilities.Rd ├── restoreValue.Rd ├── slices_store.Rd ├── after.Rd ├── module_transform_data.Rd ├── reporter_previewer_module.Rd ├── check_reactive.Rd ├── dot-call_once_when.Rd ├── teal-package.Rd ├── module_session_info.Rd ├── module_data_summary.Rd ├── module_filter_data.Rd ├── module_teal_lockfile.Rd ├── module_init_data.Rd ├── add_landing_modal.Rd ├── validate_in.Rd ├── module_bookmark_manager.Rd ├── validate_one_row_per_id.Rd ├── validate_has_variable.Rd ├── module_teal.Rd ├── module_validate_error.Rd ├── validate_has_data.Rd ├── make_teal_transform_server.Rd ├── module_filter_manager.Rd ├── validate_has_elements.Rd └── validate_no_intersection.Rd ├── revdep └── .gitignore ├── inst ├── cheatsheet │ ├── cheatsheet_teal.pdf │ └── cheatsheet_thumbnail.png ├── js │ ├── busy-disable.js │ ├── extendShinyJs.js │ └── togglePanelItems.js ├── WORDLIST └── css │ ├── validation.css │ └── custom.css ├── .lintr ├── .github ├── pull_request_template.md ├── workflows │ ├── copilot-setup-steps.yml │ ├── cla.yaml │ ├── post-release.yaml │ ├── docs.yaml │ ├── scheduled.yaml │ └── release.yaml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── question.yml │ ├── feature.yml │ └── bug.yml ├── .gitlab-ci.yml ├── .gitignore ├── R ├── teal.R ├── include_css_js.R ├── checkmate.R ├── teal_data_module-within.R ├── teal_data_module-eval_code.R ├── module_session_info.R ├── zzz.R ├── teal_slices-store.R ├── teal_data_utils.R ├── teal_data_module.R └── reporter_previewer_module.R ├── teal.Rproj ├── LICENSE ├── .Rbuildignore ├── SECURITY.md ├── staged_dependencies.yaml ├── NAMESPACE └── .pre-commit-config.yaml /vignettes/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.R 3 | -------------------------------------------------------------------------------- /tests/testthat/setup-logger.R: -------------------------------------------------------------------------------- 1 | logger::log_appender(function(...) NULL, namespace = "teal") 2 | -------------------------------------------------------------------------------- /man/figures/readme_app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/man/figures/readme_app.gif -------------------------------------------------------------------------------- /vignettes/images/bs-launch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/bs-launch.png -------------------------------------------------------------------------------- /revdep/.gitignore: -------------------------------------------------------------------------------- 1 | checks 2 | library 3 | checks.noindex 4 | library.noindex 5 | cloud.noindex 6 | data.sqlite 7 | *.html 8 | -------------------------------------------------------------------------------- /vignettes/images/bs-font-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/bs-font-size.png -------------------------------------------------------------------------------- /vignettes/images/bs-theme-set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/bs-theme-set.png -------------------------------------------------------------------------------- /inst/cheatsheet/cheatsheet_teal.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/inst/cheatsheet/cheatsheet_teal.pdf -------------------------------------------------------------------------------- /.lintr: -------------------------------------------------------------------------------- 1 | linters: linters_with_defaults( 2 | line_length_linter = line_length_linter(120), 3 | object_usage_linter = NULL 4 | ) 5 | -------------------------------------------------------------------------------- /vignettes/blueprint/images/reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/blueprint/images/reporter.png -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | 4 | 5 | Fixes #nnn 6 | -------------------------------------------------------------------------------- /inst/cheatsheet/cheatsheet_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/inst/cheatsheet/cheatsheet_thumbnail.png -------------------------------------------------------------------------------- /vignettes/blueprint/images/showrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/blueprint/images/showrcode.png -------------------------------------------------------------------------------- /vignettes/blueprint/images/filterpanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/blueprint/images/filterpanel.png -------------------------------------------------------------------------------- /vignettes/images/show_code_prepro_missing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/show_code_prepro_missing.png -------------------------------------------------------------------------------- /vignettes/images/show_code_prepro_present.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/show_code_prepro_present.png -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | include: 4 | - project: 'nest/automation/gitlab-shared-library' 5 | ref: main 6 | file: R/R_NEST_min.gitlab-ci.yml 7 | -------------------------------------------------------------------------------- /vignettes/images/teal-app-components.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/teal-app-components.drawio.png -------------------------------------------------------------------------------- /vignettes/images/custom_module_without_reporter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/custom_module_without_reporter.png -------------------------------------------------------------------------------- /vignettes/images/teal-app-components-hover.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/vignettes/images/teal-app-components-hover.drawio.png -------------------------------------------------------------------------------- /tests/testthat/_snaps/linux-4.5/shinytest2-init/teal-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/tests/testthat/_snaps/linux-4.5/shinytest2-init/teal-001.png -------------------------------------------------------------------------------- /tests/testthat/helper-shinytest2.R: -------------------------------------------------------------------------------- 1 | simple_teal_data <- function() { 2 | data <- within(teal_data(), { 3 | iris <- iris 4 | mtcars <- mtcars 5 | }) 6 | data 7 | } 8 | -------------------------------------------------------------------------------- /tests/testthat/_snaps/linux-4.5/shinytest2-wunder_bar/teal-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/insightsengineering/teal/HEAD/tests/testthat/_snaps/linux-4.5/shinytest2-wunder_bar/teal-001.png -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | pkg_name <- "teal" 2 | library(pkg_name, character.only = TRUE) 3 | testthat::test_check(pkg_name) 4 | unlink(".renv", recursive = TRUE, force = TRUE) 5 | unlink("BiocManager", recursive = TRUE, force = TRUE) 6 | -------------------------------------------------------------------------------- /vignettes/blueprint/_setup.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ```{css, echo=FALSE} 3 | pre.mermaid { 4 | background: transparent; 5 | } 6 | ``` 7 | 8 | ```{r, echo=FALSE} 9 | shiny::tags$script(type = "module", "import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid/+esm'") 10 | ``` 11 | -------------------------------------------------------------------------------- /.github/workflows/copilot-setup-steps.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Copilot Agent Setup 3 | 4 | on: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | copilot-setup-steps: 9 | name: Copilot Agent Setup 10 | uses: insightsengineering/r.pkg.template/.github/workflows/copilot-setup-steps.yaml@main 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | 4 | contact_links: 5 | - name: We are hiring! 6 | url: https://careers.gene.com/ 7 | about: Genentech and Roche are hiring! 8 | - name: Pharmaverse 9 | url: https://pharmaverse.org/ 10 | about: Related projects @ Pharmaverse.org 11 | -------------------------------------------------------------------------------- /inst/js/busy-disable.js: -------------------------------------------------------------------------------- 1 | // Primarily added to make sure the user does not open multiple modals when shiny is busy. 2 | // https://github.com/rstudio/shiny/issues/4261 3 | $(document).on("shiny:busy", function () { 4 | $(".teal-busy-disable").prop("disabled", true); 5 | }); 6 | $(document).on("shiny:idle", function () { 7 | $(".teal-busy-disable").prop("disabled", false); 8 | }); 9 | -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | name: CLA 🔏 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | # For PRs that originate from forks 8 | pull_request_target: 9 | types: 10 | - opened 11 | - closed 12 | - synchronize 13 | 14 | jobs: 15 | CLA: 16 | name: CLA 📝 17 | uses: insightsengineering/.github/.github/workflows/cla.yaml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/post-release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Post release ✨ 3 | 4 | on: 5 | release: 6 | types: ["released"] 7 | 8 | jobs: 9 | vbump: 10 | name: Version Bump 🤜🤛 11 | uses: insightsengineering/r.pkg.template/.github/workflows/version-bump.yaml@main 12 | secrets: 13 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 14 | with: 15 | vbump-after-release: true 16 | -------------------------------------------------------------------------------- /man/dot-smart_rbind.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{.smart_rbind} 4 | \alias{.smart_rbind} 5 | \title{Smart \code{rbind}} 6 | \usage{ 7 | .smart_rbind(...) 8 | } 9 | \arguments{ 10 | \item{...}{(\code{data.frame})} 11 | } 12 | \description{ 13 | Combine \code{data.frame} objects which have different columns 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /man/dot-teal_favicon.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \docType{data} 4 | \name{.teal_favicon} 5 | \alias{.teal_favicon} 6 | \title{The default favicon for the teal app.} 7 | \format{ 8 | An object of class \code{character} of length 1. 9 | } 10 | \usage{ 11 | .teal_favicon 12 | } 13 | \description{ 14 | The default favicon for the teal app. 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | Biomarker 2 | CDISC 3 | Deprecations 4 | Forkers 5 | Hoffmann 6 | MAEs 7 | ORCID 8 | Shinylive 9 | TLG 10 | Transformators 11 | UI 12 | UX 13 | args 14 | bookmarkable 15 | favicon 16 | favicons 17 | funder 18 | lockfile 19 | modularized 20 | omics 21 | powerpoint 22 | pre 23 | quosure 24 | reactively 25 | reportable 26 | summarization 27 | tabset 28 | themer 29 | theming 30 | toc 31 | transformator 32 | transformators 33 | ui 34 | -------------------------------------------------------------------------------- /man/get_unique_labels.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{get_unique_labels} 4 | \alias{get_unique_labels} 5 | \title{Get unique labels} 6 | \usage{ 7 | get_unique_labels(labels) 8 | } 9 | \arguments{ 10 | \item{labels}{(\code{character}) vector of labels} 11 | } 12 | \value{ 13 | (\code{character}) vector of unique labels 14 | } 15 | \description{ 16 | Get unique labels for the modules to avoid namespace conflicts. 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.Rcheck 2 | *.html 3 | *.rprof 4 | *.sas.txt 5 | *~ 6 | .DS_Store 7 | .RData 8 | .Rhistory 9 | .Rproj.user 10 | .Ruserdata 11 | .httr-oauth 12 | .project 13 | .settings/** 14 | /.project 15 | Meta 16 | coverage.* 17 | devel/* 18 | doc 19 | docs 20 | inst/outputs/* 21 | logs 22 | packrat/lib*/ 23 | temp 24 | temp_w 25 | templates/ 26 | tmp.* 27 | vignettes/*.R 28 | vignettes/*.html 29 | vignettes/*.md 30 | inst/doc 31 | tests/testthat/_snaps/**/*.new.md 32 | tests/testthat/_snaps/**/*.new.svg 33 | teal_app.lock 34 | -------------------------------------------------------------------------------- /R/teal.R: -------------------------------------------------------------------------------- 1 | #' `teal`: Interactive exploration of clinical trials data 2 | #' 3 | #' The `teal` package provides a `shiny` based framework for creating an 4 | #' interactive data analysis environment. 5 | #' 6 | #' To learn mode about the package, visit the [project website](https://insightsengineering.github.io/teal/latest-tag/) 7 | #' or read the [init()] manual page. 8 | #' 9 | #' @keywords internal 10 | "_PACKAGE" 11 | 12 | #' @import shiny teal.data teal.slice 13 | #' @importFrom stats setNames 14 | #' @export 15 | NULL 16 | -------------------------------------------------------------------------------- /man/validate_app_title_tag.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{validate_app_title_tag} 4 | \alias{validate_app_title_tag} 5 | \title{Function for validating the title parameter of \code{teal::init}} 6 | \usage{ 7 | validate_app_title_tag(shiny_tag) 8 | } 9 | \arguments{ 10 | \item{shiny_tag}{(\code{shiny.tag}) Object to validate for a valid title.} 11 | } 12 | \description{ 13 | Checks if the input of the title from \code{teal::init} will create a valid title and favicon tag. 14 | } 15 | \keyword{internal} 16 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-snapshot_manager.R: -------------------------------------------------------------------------------- 1 | testthat::test_that("e2e: wunder_bar_srv clicking snapshot icon opens snapshot-manager modal", { 2 | skip_if_too_deep(5) 3 | app <- TealAppDriver$new( 4 | init( 5 | data = simple_teal_data(), 6 | modules = example_module(label = "Example Module") 7 | ) 8 | ) 9 | 10 | testthat::expect_null(app$get_text(".snapshot_manager_modal")) 11 | app$click("teal-snapshot_manager_panel-show_snapshot_manager") 12 | testthat::expect_match(app$get_text(".snapshot_manager_modal"), "Snapshot manager") 13 | }) 14 | -------------------------------------------------------------------------------- /teal.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageBuildBinaryArgs: --no-build-vignettes 22 | PackageCheckArgs: --as-cran 23 | PackageRoxygenize: rd,collate,namespace 24 | -------------------------------------------------------------------------------- /man/dot-get_reporter_options.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reporter_previewer_module.R 3 | \name{.get_reporter_options} 4 | \alias{.get_reporter_options} 5 | \title{Temporary function to handle server_args of the report_previewer_module before its hard 6 | deprecation.} 7 | \usage{ 8 | .get_reporter_options(args) 9 | } 10 | \arguments{ 11 | \item{args}{(\code{list})} 12 | } 13 | \description{ 14 | Temporary function to handle server_args of the report_previewer_module before its hard 15 | deprecation. 16 | } 17 | \keyword{internal} 18 | -------------------------------------------------------------------------------- /man/dot-add_signature_to_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_init_data.R 3 | \name{.add_signature_to_data} 4 | \alias{.add_signature_to_data} 5 | \title{Adds signature protection to the \code{datanames} in the data} 6 | \usage{ 7 | .add_signature_to_data(data) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{teal_data})} 11 | } 12 | \value{ 13 | \code{teal_data} with additional code that has signature of the \code{datanames} 14 | } 15 | \description{ 16 | Adds signature protection to the \code{datanames} in the data 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 F. Hoffmann-La Roche AG 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /man/extract_transformators.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_transform_module.R 3 | \name{extract_transformators} 4 | \alias{extract_transformators} 5 | \title{Extract all \code{transformators} from \code{modules}.} 6 | \usage{ 7 | extract_transformators(modules) 8 | } 9 | \arguments{ 10 | \item{modules}{\code{teal_modules} or \code{teal_module}} 11 | } 12 | \value{ 13 | A list of \code{teal_transform_module} nested in the same way as input \code{modules}. 14 | } 15 | \description{ 16 | Extract all \code{transformators} from \code{modules}. 17 | } 18 | \keyword{internal} 19 | -------------------------------------------------------------------------------- /man/modules_slot.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modules.R 3 | \name{modules_slot} 4 | \alias{modules_slot} 5 | \title{Retrieve slot from \code{teal_modules}} 6 | \usage{ 7 | modules_slot(modules, slot) 8 | } 9 | \arguments{ 10 | \item{modules}{(\code{teal_modules})} 11 | 12 | \item{slot}{(\code{character(1)})} 13 | } 14 | \value{ 15 | A \code{list} containing the \code{slot} of the modules. 16 | If the modules are nested, the function returns a nested \code{list} of values. 17 | } 18 | \description{ 19 | Retrieve slot from \code{teal_modules} 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/is_arg_used.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modules.R 3 | \name{is_arg_used} 4 | \alias{is_arg_used} 5 | \title{Does the object make use of the \code{arg}} 6 | \usage{ 7 | is_arg_used(modules, arg) 8 | } 9 | \arguments{ 10 | \item{modules}{(\code{teal_module} or \code{teal_modules}) object} 11 | 12 | \item{arg}{(\code{character(1)}) names of the arguments to be checked against formals of \code{teal} modules.} 13 | } 14 | \value{ 15 | \code{logical} whether the object makes use of \code{arg}. 16 | } 17 | \description{ 18 | Does the object make use of the \code{arg} 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/dot-get_hashes_code.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_init_data.R 3 | \name{.get_hashes_code} 4 | \alias{.get_hashes_code} 5 | \title{Get code that tests the integrity of the reproducible data} 6 | \usage{ 7 | .get_hashes_code(data, datanames = names(data)) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{teal_data}) object holding the data} 11 | 12 | \item{datanames}{(\code{character}) names of \code{datasets}} 13 | } 14 | \value{ 15 | A character vector with the code lines. 16 | } 17 | \description{ 18 | Get code that tests the integrity of the reproducible data 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/deep_copy_filter.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_slices.R 3 | \name{deep_copy_filter} 4 | \alias{deep_copy_filter} 5 | \title{Deep copy \code{teal_slices}} 6 | \usage{ 7 | deep_copy_filter(filter) 8 | } 9 | \arguments{ 10 | \item{filter}{(\code{teal_slices})} 11 | } 12 | \value{ 13 | \code{teal_slices} 14 | } 15 | \description{ 16 | it's important to create a new copy of \code{teal_slices} when 17 | starting a new \code{shiny} session. Otherwise, object will be shared 18 | by multiple users as it is created in global environment before 19 | \code{shiny} session starts. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-filter_manager.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | 4 | testthat::test_that("e2e: wunder_bar_srv clicking filter icon opens filter-manager modal", { 5 | skip_if_too_deep(5) 6 | app <- TealAppDriver$new( 7 | init( 8 | data = simple_teal_data(), 9 | modules = example_module(label = "Example Module") 10 | ) 11 | ) 12 | testthat::expect_null(app$get_text(".teal-filter-manager-modal")) 13 | app$click("teal-filter_manager_panel-show_filter_manager") 14 | testthat::expect_match(app$get_text(".teal-filter-manager-modal"), "Filter manager") 15 | }) 16 | -------------------------------------------------------------------------------- /man/teal_data_to_filtered_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{teal_data_to_filtered_data} 4 | \alias{teal_data_to_filtered_data} 5 | \title{Create a \code{FilteredData}} 6 | \usage{ 7 | teal_data_to_filtered_data(x, datanames = names(x)) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{teal_data}) object} 11 | 12 | \item{datanames}{(\code{character}) vector of data set names to include; must be subset of \code{names(x)}} 13 | } 14 | \value{ 15 | A \code{FilteredData} object. 16 | } 17 | \description{ 18 | Create a \code{FilteredData} object from a \code{teal_data} object. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/append_module.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modules.R 3 | \name{append_module} 4 | \alias{append_module} 5 | \title{Append a \code{teal_module} to \code{children} of a \code{teal_modules} object} 6 | \usage{ 7 | append_module(modules, module) 8 | } 9 | \arguments{ 10 | \item{modules}{(\code{teal_modules})} 11 | 12 | \item{module}{(\code{teal_module}) object to be appended onto the children of \code{modules}} 13 | } 14 | \value{ 15 | A \code{teal_modules} object with \code{module} appended. 16 | } 17 | \description{ 18 | Append a \code{teal_module} to \code{children} of a \code{teal_modules} object 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/modules_bookmarkable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modules.R 3 | \name{modules_bookmarkable} 4 | \alias{modules_bookmarkable} 5 | \title{Retrieve \code{teal_bookmarkable} attribute from \code{teal_modules}} 6 | \usage{ 7 | modules_bookmarkable(modules) 8 | } 9 | \arguments{ 10 | \item{modules}{(\code{teal_modules} or \code{teal_module}) object} 11 | } 12 | \value{ 13 | named list of the same structure as \code{modules} with \code{TRUE} or \code{FALSE} values indicating 14 | whether the module is bookmarkable. 15 | } 16 | \description{ 17 | Retrieve \code{teal_bookmarkable} attribute from \code{teal_modules} 18 | } 19 | \keyword{internal} 20 | -------------------------------------------------------------------------------- /man/decorate_err_msg.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checkmate.R 3 | \name{decorate_err_msg} 4 | \alias{decorate_err_msg} 5 | \title{Capture error and decorate error message.} 6 | \usage{ 7 | decorate_err_msg(x, pre = character(0), post = character(0)) 8 | } 9 | \arguments{ 10 | \item{x}{object to evaluate} 11 | 12 | \item{pre}{(\code{character(1)}) A string to prepend to error message} 13 | 14 | \item{post}{(\code{character(1)}) A string to append to error message} 15 | } 16 | \value{ 17 | \code{x} if no error, otherwise throws error with decorated message 18 | } 19 | \description{ 20 | Capture error and decorate error message. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/include_css_files.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/include_css_js.R 3 | \name{include_css_files} 4 | \alias{include_css_files} 5 | \title{Include \code{CSS} files from \verb{/inst/css/} package directory to application header} 6 | \usage{ 7 | include_css_files(pattern = "*") 8 | } 9 | \arguments{ 10 | \item{pattern}{(\code{character}) pattern of files to be included} 11 | } 12 | \value{ 13 | HTML code that includes \code{CSS} files. 14 | } 15 | \description{ 16 | \code{system.file} should not be used to access files in other packages, it does 17 | not work with \code{devtools}. Therefore, we redefine this method in each package 18 | as needed. Thus, we do not export this method. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /man/pluralize.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{pluralize} 4 | \alias{pluralize} 5 | \title{Pluralize a word depending on the size of the input} 6 | \usage{ 7 | pluralize(x, singular, plural = NULL) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{object}) to check length for plural.} 11 | 12 | \item{singular}{(\code{character}) singular form of the word.} 13 | 14 | \item{plural}{(optional \code{character}) plural form of the word. If not given an "s" 15 | is added to the singular form.} 16 | } 17 | \value{ 18 | A \code{character} that correctly represents the size of the \code{x} argument. 19 | } 20 | \description{ 21 | Pluralize a word depending on the size of the input 22 | } 23 | \keyword{internal} 24 | -------------------------------------------------------------------------------- /man/get_client_timezone.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{get_client_timezone} 4 | \alias{get_client_timezone} 5 | \title{Get client timezone} 6 | \usage{ 7 | get_client_timezone(ns) 8 | } 9 | \arguments{ 10 | \item{ns}{(\code{function}) namespace function passed from the \code{session} object in the \code{shiny} server. 11 | For \code{shiny} modules this will allow for proper name spacing of the registered input.} 12 | } 13 | \value{ 14 | \code{NULL}, invisibly. 15 | } 16 | \description{ 17 | User timezone in the browser may be different to the one on the server. 18 | This script can be run to register a \code{shiny} input which contains information about the timezone in the browser. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /inst/js/extendShinyJs.js: -------------------------------------------------------------------------------- 1 | // This file contains functions that should be executed at the start of each session, 2 | // not included in the original HTML 3 | 4 | shinyjs.autoFocusModal = function(id) { 5 | document.getElementById('shiny-modal').addEventListener( 6 | 'shown.bs.modal', 7 | () => document.getElementById(id).focus(), 8 | { once: true } 9 | ); 10 | } 11 | 12 | shinyjs.enterToSubmit = function(id, submit_id) { 13 | document.getElementById('shiny-modal').addEventListener( 14 | 'shown.bs.modal', 15 | () => document.getElementById(id).addEventListener('keyup', (e) => { 16 | if (e.key === 'Enter') { 17 | e.preventDefault(); // prevent form submission 18 | document.getElementById(submit_id).click(); 19 | } 20 | }) 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /man/include_teal_css_js.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/include_css_js.R 3 | \name{include_teal_css_js} 4 | \alias{include_teal_css_js} 5 | \title{Code to include \code{teal} \code{CSS} and \code{JavaScript} files} 6 | \usage{ 7 | include_teal_css_js() 8 | } 9 | \value{ 10 | A \code{shiny.tag.list}. 11 | } 12 | \description{ 13 | This is useful when you want to use the same \code{JavaScript} and \code{CSS} files that are 14 | used with the \code{teal} application. 15 | This is also useful for running standalone modules in \code{teal} with the correct 16 | styles. 17 | Also initializes \code{shinyjs} so you can use it. 18 | } 19 | \details{ 20 | Simply add \code{include_teal_css_js()} as one of the UI elements. 21 | } 22 | \keyword{internal} 23 | -------------------------------------------------------------------------------- /man/create_app_id.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{create_app_id} 4 | \alias{create_app_id} 5 | \title{Application ID} 6 | \usage{ 7 | create_app_id(data, modules) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{teal_data} or \code{teal_data_module}) as accepted by \code{init}} 11 | 12 | \item{modules}{(\code{teal_modules}) object as accepted by \code{init}} 13 | } 14 | \value{ 15 | A single character string. 16 | } 17 | \description{ 18 | Creates App ID used to match filter snapshots to application. 19 | } 20 | \details{ 21 | Calculate app ID that will be used to stamp filter state snapshots. 22 | App ID is a hash of the app's data and modules. 23 | See "transferring snapshots" section in ?snapshot. 24 | } 25 | \keyword{internal} 26 | -------------------------------------------------------------------------------- /man/check_filter_datanames.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{check_filter_datanames} 4 | \alias{check_filter_datanames} 5 | \title{Check \code{datanames} in filters} 6 | \usage{ 7 | check_filter_datanames(filters, datanames) 8 | } 9 | \arguments{ 10 | \item{filters}{(\code{teal_slices}) object} 11 | 12 | \item{datanames}{(\code{character}) names of datasets available in the \code{data} object} 13 | } 14 | \value{ 15 | A \code{character(1)} containing error message or TRUE if validation passes. 16 | } 17 | \description{ 18 | This function checks whether \code{datanames} in filters correspond to those in \code{data}, 19 | returning character vector with error messages or \code{TRUE} if all checks pass. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/teal_extend_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_modifiers.R 3 | \name{teal_extend_server} 4 | \alias{teal_extend_server} 5 | \title{Add a Custom Server Logic to \code{teal} Application} 6 | \usage{ 7 | teal_extend_server(x, custom_server, module_id = character(0)) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{teal_app}) A \code{teal_app} object created using the \code{init} function.} 11 | 12 | \item{custom_server}{(\verb{function(input, output, session)} or \verb{function(id, ...)}) 13 | The custom server function or server module to set.} 14 | 15 | \item{module_id}{(\code{character(1)}) The ID of the module when a module server function is passed.} 16 | } 17 | \description{ 18 | Adds a custom server function to the \code{teal} app. This function can define additional server logic. 19 | } 20 | \keyword{internal} 21 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | LICENSE 2 | ^renv$ 3 | ^renv\.lock$ 4 | CODE_OF_CONDUCT.md 5 | SECURITY.md 6 | ^.*\.Rproj$ 7 | ^Jenkinsfile$ 8 | ^Makefile$ 9 | ^Meta$ 10 | ^TODO\.md$ 11 | ^[^/]+\.R$ 12 | ^[^/]+\.Rmd$ 13 | ^[^/]+\.html$ 14 | ^[^/]+\.png$ 15 | ^\.CI-FORCE-RELEASE-VERSION$ 16 | ^\.Rprofile$ 17 | ^\.Rproj\.user$ 18 | ^\.drone\.yml$ 19 | ^\.github$ 20 | ^\.gitlab-ci\.yml$ 21 | ^\.lintr$ 22 | ^\.travis\.yml$ 23 | ^\_pkgdown\.yaml$ 24 | ^\_pkgdown\.yml 25 | ^_dev$ 26 | ^build_docker_image$ 27 | ^cache$ 28 | ^data-raw$ 29 | ^data/script\.R$ 30 | ^design$ 31 | ^dev$ 32 | ^doc$ 33 | ^docs$ 34 | ^logs$ 35 | ^man-roxygen$ 36 | ^outputdir$ 37 | ^\.pre-commit-config\.yaml$ 38 | ^scratch$ 39 | ^staged_dependencies\.yaml$ 40 | ^stubs$ 41 | ^temp$ 42 | ^templates$ 43 | ^test_full_example$ 44 | coverage.* 45 | ^pkgdown$ 46 | ^.revdeprefs\.yaml$ 47 | ^revdep$ 48 | ^\.covrignore$ 49 | ^inst/cheatsheet$ 50 | -------------------------------------------------------------------------------- /vignettes/blueprint/index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "About Technical Blueprint" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{About Technical Blueprint} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | Welcome to `teal` Technical Blueprint documentation! 12 | 13 | The purpose of this material is to aid new developer's comprehension of the fundamental principles of the `teal` framework. 14 | We will explore crucial `teal` concepts such as data flow, actors, and filter panel, among others. 15 | 16 | While this material will be presented at a high-level, we will direct you to our vignettes for a more in-depth understanding. 17 | 18 | Our hope is that this resource will provide new developers with a strong grasp of `teal` products, enabling them to contribute to code with confidence. 19 | -------------------------------------------------------------------------------- /man/build_app_title.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{build_app_title} 4 | \alias{build_app_title} 5 | \title{Build app title with favicon} 6 | \usage{ 7 | build_app_title( 8 | title = "teal app", 9 | favicon = 10 | "https://raw.githubusercontent.com/insightsengineering/hex-stickers/main/PNG/nest.png" 11 | ) 12 | } 13 | \arguments{ 14 | \item{title}{(\code{character}) The browser title for the \code{teal} app.} 15 | 16 | \item{favicon}{(\code{character}) The path for the icon for the title. 17 | The image/icon path can be remote or the static path accessible by \code{shiny}, like the \verb{www/}} 18 | } 19 | \value{ 20 | A \code{shiny.tag} containing the element that adds the title and logo to the \code{shiny} app. 21 | } 22 | \description{ 23 | A helper function to create the browser title along with a logo. 24 | } 25 | -------------------------------------------------------------------------------- /tests/testthat/setup-options.R: -------------------------------------------------------------------------------- 1 | # `opts_partial_match_old` is left for exclusions due to partial matching in dependent packages (i.e. not fixable here) 2 | # it might happen that it is not used right now, but it is left for possible future use 3 | # use with: `withr::with_options(opts_partial_match_old, { ... })` inside the test 4 | opts_partial_match_old <- list( 5 | warnPartialMatchDollar = getOption("warnPartialMatchDollar"), 6 | warnPartialMatchArgs = getOption("warnPartialMatchArgs"), 7 | warnPartialMatchAttr = getOption("warnPartialMatchAttr") 8 | ) 9 | opts_partial_match_new <- list( 10 | warnPartialMatchDollar = TRUE, 11 | warnPartialMatchArgs = TRUE, 12 | warnPartialMatchAttr = TRUE 13 | ) 14 | 15 | if (isFALSE(getFromNamespace("on_cran", "testthat")()) && requireNamespace("withr", quietly = TRUE)) { 16 | withr::local_options( 17 | opts_partial_match_new, 18 | .local_envir = testthat::teardown_env() 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /man/figures/lifecycle-stable.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclestablestable -------------------------------------------------------------------------------- /tests/testthat/test-teal_data_module.R: -------------------------------------------------------------------------------- 1 | testthat::test_that("teal_data_module returns teal_data_module", { 2 | testthat::expect_s3_class( 3 | teal_data_module(ui = function(id) tags$div(), server = function(id) NULL), 4 | "teal_data_module" 5 | ) 6 | }) 7 | 8 | testthat::test_that("teal_data_module throws when ui has other formals than id only", { 9 | testthat::expect_error( 10 | teal_data_module(ui = function(id, x) tags$div(), server = function(id) NULL), 11 | "Must have exactly 1 formal arguments" 12 | ) 13 | }) 14 | 15 | testthat::test_that("teal_data_module throws when server has other formals than id only", { 16 | testthat::expect_error( 17 | teal_data_module(ui = function(id) tags$div(), server = function(id, x) NULL), 18 | ".*exactly 1 formal.*" 19 | ) 20 | testthat::expect_error( 21 | teal_data_module(ui = function(id) tags$div(), server = function(id, x) NULL), 22 | ".*formal arguments.*" 23 | ) 24 | }) 25 | -------------------------------------------------------------------------------- /man/figures/lifecycle-defunct.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledefunctdefunct -------------------------------------------------------------------------------- /man/figures/lifecycle-archived.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclearchivedarchived -------------------------------------------------------------------------------- /man/figures/lifecycle-maturing.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclematuringmaturing -------------------------------------------------------------------------------- /man/figures/lifecycle-deprecated.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycledeprecateddeprecated -------------------------------------------------------------------------------- /man/figures/lifecycle-superseded.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclesupersededsuperseded -------------------------------------------------------------------------------- /man/figures/lifecycle-experimental.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecycleexperimentalexperimental -------------------------------------------------------------------------------- /man/figures/lifecycle-questioning.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclequestioningquestioning -------------------------------------------------------------------------------- /man/figures/lifecycle-soft-deprecated.svg: -------------------------------------------------------------------------------- 1 | lifecyclelifecyclesoft-deprecatedsoft-deprecated -------------------------------------------------------------------------------- /man/module_management.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/modules.R 3 | \name{extract_module} 4 | \alias{extract_module} 5 | \alias{drop_module} 6 | \title{Extract/Remove module(s) of specific class} 7 | \usage{ 8 | extract_module(modules, class) 9 | 10 | drop_module(modules, class) 11 | } 12 | \arguments{ 13 | \item{modules}{(\code{teal_modules})} 14 | 15 | \item{class}{The class name of \code{teal_module} to be extracted or dropped.} 16 | } 17 | \value{ 18 | \itemize{ 19 | \item For \code{extract_module}, a \code{teal_module} of class \code{class} or \code{teal_modules} containing modules of class \code{class}. 20 | \item For \code{drop_module}, the opposite, which is all \code{teal_modules} of class other than \code{class}. 21 | } 22 | 23 | \code{teal_modules} 24 | } 25 | \description{ 26 | Given a \code{teal_module} or a \code{teal_modules}, return the elements of the structure according to \code{class}. 27 | } 28 | \keyword{internal} 29 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs 📚 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "inst/templates/**" 10 | - "_pkgdown.*" 11 | - DESCRIPTION 12 | - "**.md" 13 | - "**.Rmd" 14 | - "man/**" 15 | - "LICENSE.*" 16 | - NAMESPACE 17 | pull_request: 18 | types: 19 | - opened 20 | - synchronize 21 | - reopened 22 | - ready_for_review 23 | branches: 24 | - main 25 | paths: 26 | - "inst/templates/**" 27 | - "_pkgdown.*" 28 | - DESCRIPTION 29 | - "**.md" 30 | - "**.Rmd" 31 | - "man/**" 32 | - "LICENSE.*" 33 | - NAMESPACE 34 | workflow_dispatch: 35 | 36 | jobs: 37 | docs: 38 | name: Pkgdown Docs 📚 39 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 40 | secrets: 41 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 42 | with: 43 | default-landing-page: latest-tag 44 | additional-unit-test-report-directories: unit-test-report-non-cran 45 | deps-installation-method: setup-r-dependencies 46 | -------------------------------------------------------------------------------- /man/report_card_template.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{report_card_template} 4 | \alias{report_card_template} 5 | \title{Template function for \code{TealReportCard} creation and customization} 6 | \usage{ 7 | report_card_template( 8 | title, 9 | label, 10 | description = NULL, 11 | with_filter, 12 | filter_panel_api 13 | ) 14 | } 15 | \arguments{ 16 | \item{title}{(\code{character(1)}) title of the card (unless overwritten by label)} 17 | 18 | \item{label}{(\code{character(1)}) label provided by the user when adding the card} 19 | 20 | \item{description}{(\code{character(1)}) optional, additional description} 21 | 22 | \item{with_filter}{(\code{logical(1)}) flag indicating to add filter state} 23 | 24 | \item{filter_panel_api}{(\code{FilterPanelAPI}) object with API that allows the generation 25 | of the filter state in the report} 26 | } 27 | \value{ 28 | (\code{TealReportCard}) populated with a title, description and filter state. 29 | } 30 | \description{ 31 | This function generates a report card with a title, 32 | an optional description, and the option to append the filter state list. 33 | } 34 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-disable_src.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | skip_if_too_deep(5) 3 | 4 | testthat::test_that("Show R Code button is disabled on a module", { 5 | app <- TealAppDriver$new(init( 6 | data = simple_teal_data(), 7 | modules = example_module(label = "m1") |> disable_src() 8 | )) 9 | 10 | 11 | expect_true(endsWith(app$get_attr( 12 | selector = "#teal-teal_modules-nav-m1-source_code_wrapper-source_code-button", 13 | attribute = "class" 14 | ), "shiny-bound-input disabled")) 15 | 16 | app$stop() 17 | }) 18 | 19 | testthat::test_that("Show R Code is disabled on nested modules changed with disable_src", { 20 | app <- TealAppDriver$new(init( 21 | data = simple_teal_data(), 22 | modules = modules( 23 | example_module(label = "m1"), 24 | example_module(label = "m2") 25 | ) |> disable_src() 26 | )) 27 | 28 | class <- app$get_attr( 29 | selector = "#teal-teal_modules-nav-m1-source_code_wrapper-source_code-button", 30 | attribute = "class" 31 | ) 32 | classes <- strsplit(class, split = " ", fixed = TRUE)[[1]] 33 | expect_true(all(c("shiny-bound-input", "disabled") %in% classes)) 34 | 35 | app$stop() 36 | }) 37 | -------------------------------------------------------------------------------- /vignettes/blueprint/in_app_data.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "In-App Data" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{In-App Data} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ## Building data in the App 12 | 13 | Typically the data that is passed into a `teal` application is available before the app is run. 14 | However, this is not always true and in some cases the data will be built only after the app has started. 15 | A good example is pulling the data from an external repository, like a database, or uploading a file. 16 | Additional authentication may be required. 17 | 18 | ### `teal_data_module` 19 | 20 | Preprocessing actions can be performed in-app using the `teal_data_module`. 21 | Rather than passing a `teal_data` object to the app, one may pass a _`shiny` module_ that _returns_ a `teal_data` object (wrapped in a reactive expression). 22 | This allows the app developer to include user actions data creation, fetching, and even pre-filtering modification. 23 | 24 | ## Further reading 25 | 26 | A complete explanation of using the `teal_data_module` can be found in [this `teal` vignette](../data-as-shiny-module.html) 27 | -------------------------------------------------------------------------------- /man/check_modules_datanames.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils.R 3 | \name{check_modules_datanames} 4 | \alias{check_modules_datanames} 5 | \alias{check_reserved_datanames} 6 | \alias{check_modules_datanames_html} 7 | \title{Check \code{datanames} in modules} 8 | \usage{ 9 | check_modules_datanames(modules, datanames) 10 | 11 | check_reserved_datanames(datanames) 12 | 13 | check_modules_datanames_html(modules, datanames) 14 | } 15 | \arguments{ 16 | \item{modules}{(\code{teal_modules}) object} 17 | 18 | \item{datanames}{(\code{character}) names of datasets available in the \code{data} object} 19 | } 20 | \value{ 21 | \code{TRUE} if validation passes, otherwise \code{character(1)} or \code{shiny.tag.list} 22 | } 23 | \description{ 24 | These functions check if specified \code{datanames} in modules match those in the data object, 25 | returning error messages or \code{TRUE} for successful validation. Two functions return error message 26 | in different forms: 27 | \itemize{ 28 | \item \code{check_modules_datanames} returns \code{character(1)} for basic assertion usage 29 | \item \code{check_modules_datanames_html} returns \code{shiny.tag.list} to display it in the app. 30 | } 31 | } 32 | \keyword{internal} 33 | -------------------------------------------------------------------------------- /inst/css/validation.css: -------------------------------------------------------------------------------- 1 | /* adding boarder to the validated input */ 2 | .teal_validated:has(.shiny-output-error) { 3 | border: 1px solid red; 4 | border-radius: 4px; 5 | } 6 | 7 | .teal_validated:has(.teal-output-warning) { 8 | border: 1px solid orange; 9 | border-radius: 4px; 10 | } 11 | 12 | .teal_validated .teal-output-warning { 13 | color: #888; 14 | } 15 | 16 | .teal_validated .shiny-output-error, 17 | .teal_validated .teal-output-warning { 18 | margin-top: 0.5em; 19 | } 20 | 21 | .teal_validated .teal-output-warning::before { 22 | content: "\26A0\FE0F"; 23 | } 24 | 25 | .teal_validated .shiny-output-error::before { 26 | content: "\1F6A8"; 27 | } 28 | 29 | .teal_primary_col .shiny-output-error::before { 30 | content: "\1F6A8"; 31 | } 32 | 33 | .teal_primary_col .teal-output-warning::before { 34 | content: "\26A0\FE0F"; 35 | } 36 | 37 | .teal_primary_col .teal_validated:has(.shiny-output-error), 38 | .teal_primary_col .teal_validated:has(.teal-output-warning) { 39 | margin: 1em 0 1em 0; 40 | padding: 0.5em 0 0.5em 0.5em; 41 | } 42 | 43 | .teal_primary_col > .teal_validated:has(.teal-output-warning), 44 | .teal_primary_col > .teal_validated:has(.shiny-output-error) { 45 | width: 100%; 46 | background-color: rgba(223, 70, 97, 0.1); 47 | border: 1px solid red; 48 | padding: 1em; 49 | } 50 | -------------------------------------------------------------------------------- /man/teal_data_utilities.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_data_utils.R 3 | \name{teal_data_utilities} 4 | \alias{teal_data_utilities} 5 | \alias{.append_evaluated_code} 6 | \alias{.append_modified_data} 7 | \alias{.collapse_subsequent_chunks} 8 | \title{\code{teal_data} utils} 9 | \usage{ 10 | .append_evaluated_code(data, code, filter_states) 11 | 12 | .append_modified_data(data, objects) 13 | 14 | .collapse_subsequent_chunks(card) 15 | } 16 | \arguments{ 17 | \item{data}{(\code{teal_data})} 18 | 19 | \item{code}{(\code{character}) code to append to the object's code slot.} 20 | 21 | \item{objects}{(\code{list}) objects to append to object's environment.} 22 | } 23 | \value{ 24 | modified \code{teal_data} 25 | } 26 | \description{ 27 | In \code{teal} we need to recreate the \code{teal_data} object due to two operations: 28 | \itemize{ 29 | \item we need to append filter-data code and objects which have been evaluated in \code{FilteredData} and 30 | we want to avoid double-evaluation. 31 | \item we need to subset \code{teal_data} to \code{datanames} used by the module, to shorten obtainable R-code 32 | } 33 | } 34 | \details{ 35 | Due to above recreation of \code{teal_data} object can't be done simply by using public 36 | \code{teal.code} and \code{teal.data} methods. 37 | } 38 | \keyword{internal} 39 | -------------------------------------------------------------------------------- /R/include_css_js.R: -------------------------------------------------------------------------------- 1 | #' Include `CSS` files from `/inst/css/` package directory to application header 2 | #' 3 | #' `system.file` should not be used to access files in other packages, it does 4 | #' not work with `devtools`. Therefore, we redefine this method in each package 5 | #' as needed. Thus, we do not export this method. 6 | #' 7 | #' @param pattern (`character`) pattern of files to be included 8 | #' 9 | #' @return HTML code that includes `CSS` files. 10 | #' @keywords internal 11 | include_css_files <- function(pattern = "*") { 12 | css_files <- list.files( 13 | system.file("css", package = "teal", mustWork = TRUE), 14 | pattern = pattern, full.names = TRUE 15 | ) 16 | 17 | singleton( 18 | tags$head(lapply(css_files, includeCSS)) 19 | ) 20 | } 21 | 22 | #' Code to include `teal` `CSS` and `JavaScript` files 23 | #' 24 | #' This is useful when you want to use the same `JavaScript` and `CSS` files that are 25 | #' used with the `teal` application. 26 | #' This is also useful for running standalone modules in `teal` with the correct 27 | #' styles. 28 | #' Also initializes `shinyjs` so you can use it. 29 | #' 30 | #' Simply add `include_teal_css_js()` as one of the UI elements. 31 | #' @return A `shiny.tag.list`. 32 | #' @keywords internal 33 | include_teal_css_js <- function() { 34 | tagList( 35 | shinyjs::useShinyjs(), 36 | include_css_files() 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /man/restoreValue.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_bookmark_manager.R 3 | \name{restoreValue} 4 | \alias{restoreValue} 5 | \title{Restore value from bookmark.} 6 | \usage{ 7 | restoreValue(value, default) 8 | } 9 | \arguments{ 10 | \item{value}{(\code{character(1)}) name of value to restore} 11 | 12 | \item{default}{fallback value} 13 | } 14 | \value{ 15 | In an application restored from a server-side bookmark, 16 | the variable specified by \code{value} from the \code{values} environment. 17 | Otherwise \code{default}. 18 | } 19 | \description{ 20 | Get value from bookmark or return default. 21 | } 22 | \details{ 23 | Bookmarks can store not only inputs but also arbitrary values. 24 | These values are stored by \code{onBookmark} callbacks and restored by \code{onBookmarked} callbacks, 25 | and they are placed in the \code{values} environment in the \code{session$restoreContext} field. 26 | Using \code{teal_data_module} makes it impossible to run the callbacks 27 | because the app becomes ready before modules execute and callbacks are registered. 28 | In those cases the stored values can still be recovered from the \code{session} object directly. 29 | 30 | Note that variable names in the \code{values} environment are prefixed with module name space names, 31 | therefore, when using this function in modules, \code{value} must be run through the name space function. 32 | } 33 | \keyword{internal} 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Question 3 | description: Question about usage or documentation 4 | title: "[Question]: " 5 | labels: ["question"] 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: What is your question? 10 | validations: 11 | required: true 12 | - type: checkboxes 13 | id: code-of-conduct 14 | attributes: 15 | label: Code of Conduct 16 | description: By submitting this issue, you agree to follow our [Code of Conduct.](https://insightsengineering.github.io/teal/latest-tag/CODE_OF_CONDUCT.html) 17 | options: 18 | - label: I agree to follow this project's Code of Conduct. 19 | required: true 20 | - type: checkboxes 21 | id: contributor-guidelines 22 | attributes: 23 | label: Contribution Guidelines 24 | description: By submitting this issue, you agree to follow our [Contribution Guidelines.](https://insightsengineering.github.io/teal/latest-tag/CONTRIBUTING.html) 25 | options: 26 | - label: I agree to follow this project's Contribution Guidelines. 27 | required: true 28 | - type: checkboxes 29 | id: security-policy 30 | attributes: 31 | label: Security Policy 32 | description: By submitting this issue, you agree to follow our [Security Policy.](https://github.com/insightsengineering/teal/security/policy) 33 | options: 34 | - label: I agree to follow this project's Security Policy. 35 | required: true 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | description: Request or propose a new feature 4 | title: "[Feature Request]: <title>" 5 | labels: ["enhancement"] 6 | body: 7 | - type: textarea 8 | attributes: 9 | label: Feature description 10 | validations: 11 | required: true 12 | - type: checkboxes 13 | id: code-of-conduct 14 | attributes: 15 | label: Code of Conduct 16 | description: By submitting this issue, you agree to follow our [Code of Conduct.](https://insightsengineering.github.io/teal/latest-tag/CODE_OF_CONDUCT.html) 17 | options: 18 | - label: I agree to follow this project's Code of Conduct. 19 | required: true 20 | - type: checkboxes 21 | id: contributor-guidelines 22 | attributes: 23 | label: Contribution Guidelines 24 | description: By submitting this issue, you agree to follow our [Contribution Guidelines.](https://insightsengineering.github.io/teal/latest-tag/CONTRIBUTING.html) 25 | options: 26 | - label: I agree to follow this project's Contribution Guidelines. 27 | required: true 28 | - type: checkboxes 29 | id: security-policy 30 | attributes: 31 | label: Security Policy 32 | description: By submitting this issue, you agree to follow our [Security Policy.](https://github.com/insightsengineering/teal/security/policy) 33 | options: 34 | - label: I agree to follow this project's Security Policy. 35 | required: true 36 | -------------------------------------------------------------------------------- /R/checkmate.R: -------------------------------------------------------------------------------- 1 | #' Check that argument is reactive. 2 | #' 3 | #' @inherit checkmate::check_class params return 4 | #' 5 | #' @keywords internal 6 | check_reactive <- function(x, null.ok = FALSE) { # nolint: object_name_linter. 7 | if (!isTRUE(checkmate::test_class(x, classes = "reactive", null.ok = null.ok))) { 8 | cl <- class(x) 9 | return(sprintf( 10 | "Must be a reactive (i.e. inherit from 'reactive' class) but has class%s '%s'", 11 | if (length(cl) > 1L) "es" else "", 12 | paste0(cl, collapse = "','") 13 | )) 14 | } 15 | return(TRUE) 16 | } 17 | #' @rdname check_reactive 18 | test_reactive <- function(x, null.ok = FALSE) { # nolint: object_name_linter. 19 | isTRUE(check_reactive(x, null.ok = null.ok)) 20 | } 21 | #' @rdname check_reactive 22 | assert_reactive <- checkmate::makeAssertionFunction(check_reactive) 23 | 24 | #' Capture error and decorate error message. 25 | #' 26 | #' @param x object to evaluate 27 | #' @param pre (`character(1)`) A string to prepend to error message 28 | #' @param post (`character(1)`) A string to append to error message 29 | #' 30 | #' @return `x` if no error, otherwise throws error with decorated message 31 | #' 32 | #' @keywords internal 33 | decorate_err_msg <- function(x, pre = character(0), post = character(0)) { 34 | tryCatch( 35 | x, 36 | error = function(e) { 37 | stop( 38 | "\n", 39 | pre, 40 | "\n", 41 | e$message, 42 | "\n", 43 | post, 44 | call. = FALSE 45 | ) 46 | } 47 | ) 48 | x 49 | } 50 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | If you believe you have found a security vulnerability in any of the repositories in this organization, please report it to us through coordinated disclosure. 6 | 7 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 8 | 9 | Instead, please send an email to vulnerability.management[@]roche.com. 10 | 11 | Please include as much of the information listed below as you can to help us better understand and resolve the issue: 12 | 13 | * The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) 14 | * Full paths of source file(s) related to the manifestation of the issue 15 | * The location of the affected source code (tag/branch/commit or direct URL) 16 | * Any special configuration required to reproduce the issue 17 | * Step-by-step instructions to reproduce the issue 18 | * Proof-of-concept or exploit code (if possible) 19 | * Impact of the issue, including how an attacker might exploit the issue 20 | 21 | This information will help us triage your report more quickly. 22 | 23 | ## Data Security Standards (DSS) 24 | 25 | Please make sure that while reporting issues in the form a bug, feature, or pull request, *all* sensitive information such as [PII](https://www.dhs.gov/privacy-training/what-personally-identifiable-information), [PHI](https://www.hhs.gov/hipaa/for-professionals/security/laws-regulations/index.html), and [PCI](https://www.pcisecuritystandards.org/pci_security/standards_overview) is completely removed from any text and attachments, including pictures and videos. 26 | -------------------------------------------------------------------------------- /man/slices_store.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_slices-store.R 3 | \name{slices_store} 4 | \alias{slices_store} 5 | \alias{slices_restore} 6 | \title{Store and restore \code{teal_slices} object} 7 | \usage{ 8 | slices_store(tss, file) 9 | 10 | slices_restore(file) 11 | } 12 | \arguments{ 13 | \item{tss}{(\code{teal_slices}) object to be stored.} 14 | 15 | \item{file}{(\code{character(1)}) file path where \code{teal_slices} object will be 16 | saved and restored. The file extension should be \code{".json"}.} 17 | } 18 | \value{ 19 | \code{slices_store} returns \code{NULL}, invisibly. 20 | 21 | \code{slices_restore} returns a \code{teal_slices} object restored from the file. 22 | } 23 | \description{ 24 | Functions that write a \code{teal_slices} object to a file in the \code{JSON} format, 25 | and also restore the object from disk. 26 | } 27 | \details{ 28 | Date and date time objects are stored in the following formats: 29 | \itemize{ 30 | \item \code{Date} class is converted to the \code{"ISO8601"} standard (\code{YYYY-MM-DD}). 31 | \item \code{POSIX*t} classes are converted to character by using 32 | \code{format.POSIX*t(usetz = TRUE, tz = "UTC")} (\verb{YYYY-MM-DD HH:MM:SS UTC}, where 33 | \code{UTC} is the \verb{Coordinated Universal Time} timezone short-code). 34 | } 35 | 36 | This format is assumed during \code{slices_restore}. All \code{POSIX*t} objects in 37 | \code{selected} or \code{choices} fields of \code{teal_slice} objects are always printed in 38 | \code{UTC} timezone as well. 39 | } 40 | \seealso{ 41 | \code{\link[=teal_slices]{teal_slices()}} 42 | } 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /tests/testthat/test-module_session_info.R: -------------------------------------------------------------------------------- 1 | testthat::describe("srv_session_info lockfile", { 2 | testthat::it(paste0( 3 | "creation process is invoked for teal.lockfile.mode = \"enabled\" ", 4 | "and snapshot is copied to teal_app.lock and removed after session ended" 5 | ), { 6 | testthat::skip_if_not_installed("mirai") 7 | testthat::skip_if_not_installed("renv") 8 | withr::with_options( 9 | list(teal.lockfile.mode = "enabled"), 10 | { 11 | renv_filename <- "teal_app.lock" 12 | shiny::testServer( 13 | app = srv_session_info, 14 | args = list(id = "test"), 15 | expr = { 16 | iter <- 1 17 | while (!file.exists(renv_filename) && iter <= 1000) { 18 | Sys.sleep(0.5) 19 | iter <- iter + 1 # max wait time is 500 seconds 20 | } 21 | testthat::expect_true(file.exists(renv_filename)) 22 | } 23 | ) 24 | testthat::expect_false(file.exists(renv_filename)) 25 | } 26 | ) 27 | }) 28 | testthat::it("creation process is not invoked for teal.lockfile.mode = \"disabled\"", { 29 | testthat::skip_if_not_installed("mirai") 30 | testthat::skip_if_not_installed("renv") 31 | withr::with_options( 32 | list(teal.lockfile.mode = "disabled"), 33 | { 34 | renv_filename <- "teal_app.lock" 35 | shiny::testServer( 36 | app = srv_session_info, 37 | args = list(id = "test"), 38 | expr = { 39 | testthat::expect_false(file.exists(renv_filename)) 40 | } 41 | ) 42 | } 43 | ) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /man/after.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/after.R 3 | \name{after} 4 | \alias{after} 5 | \title{Executes modifications to the result of a module} 6 | \usage{ 7 | after(x, server = function(input, output, session, data) data, ...) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{teal_module} or \code{teal_modules}).} 11 | 12 | \item{server}{(\verb{function(input, output, session, data, ...)}) function to receive output data from \code{tm$server}. 13 | Must return data} 14 | 15 | \item{...}{Additional arguments passed to the server wrapper function by matching their formal names.} 16 | } 17 | \value{ 18 | A \code{teal_module} or \code{teal_modules} object with a wrapped server. 19 | } 20 | \description{ 21 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} 22 | 23 | Exported to be able to use methods not to be used directly by module-developers or app-users. 24 | Primarily used to modify the output object of module. 25 | } 26 | \examples{ 27 | library("teal.reporter") 28 | hide_code <- function(input, output, session, data) { 29 | teal_card(data) <- Filter(function(x) !inherits(x, "code_chunk"), teal_card(data)) 30 | data 31 | } 32 | app <- init( 33 | data = teal_data(IRIS = iris, MTCARS = mtcars), 34 | modules = example_module() |> 35 | after(server = hide_code) 36 | ) 37 | 38 | if (interactive()) { 39 | runApp(app) 40 | } 41 | } 42 | \seealso{ 43 | \code{\link[=disable_src]{disable_src()}}, \code{\link[=disable_report]{disable_report()}} 44 | } 45 | \keyword{internal} 46 | -------------------------------------------------------------------------------- /R/teal_data_module-within.R: -------------------------------------------------------------------------------- 1 | #' Evaluate expression on `teal_data_module` 2 | #' 3 | #' @details 4 | #' `within` is a convenience function for evaluating inline code inside the environment of a `teal_data_module`. 5 | #' It accepts only inline expressions (both simple and compound) and allows for injecting values into `expr` through 6 | #' the `...` argument: as `name:value` pairs are passed to `...`, `name` in `expr` will be replaced with `value.` 7 | #' 8 | #' @param data (`teal_data_module`) object 9 | #' @param expr (`expression`) to evaluate. Must be inline code. See [within()] 10 | #' @param ... See `Details`. 11 | #' 12 | #' @return 13 | #' `within` returns a `teal_data_module` object with a delayed evaluation of `expr` when the module is run. 14 | #' 15 | #' @examples 16 | #' within(tdm, dataset1 <- subset(dataset1, Species == "virginica")) 17 | #' 18 | #' # use additional parameter for expression value substitution. 19 | #' valid_species <- "versicolor" 20 | #' within(tdm, dataset1 <- subset(dataset1, Species %in% species), species = valid_species) 21 | #' @include teal_data_module.R 22 | #' @name within 23 | #' @rdname teal_data_module 24 | #' 25 | #' @export 26 | #' 27 | within.teal_data_module <- function(data, expr, ...) { 28 | expr <- substitute(expr) 29 | extras <- list(...) 30 | 31 | # Add braces for consistency. 32 | if (!identical(as.list(expr)[[1L]], as.symbol("{"))) { 33 | expr <- call("{", expr) 34 | } 35 | 36 | calls <- as.list(expr)[-1] 37 | 38 | # Inject extra values into expressions. 39 | calls <- lapply(calls, function(x) do.call(substitute, list(x, env = extras))) 40 | 41 | eval_code(object = data, code = as.expression(calls)) 42 | } 43 | -------------------------------------------------------------------------------- /R/teal_data_module-eval_code.R: -------------------------------------------------------------------------------- 1 | setOldClass("teal_data_module") 2 | 3 | #' Evaluate code on `teal_data_module` 4 | #' 5 | #' @details 6 | #' `eval_code` evaluates given code in the environment of the `teal_data` object created by the `teal_data_module`. 7 | #' The code is added to the `@code` slot of the `teal_data`. 8 | #' 9 | #' @param object (`teal_data_module`) 10 | #' @inheritParams teal.code::eval_code 11 | #' 12 | #' @return 13 | #' `eval_code` returns a `teal_data_module` object with a delayed evaluation of `code` when the module is run. 14 | #' 15 | #' @examples 16 | #' eval_code(tdm, "dataset1 <- subset(dataset1, Species == 'virginica')") 17 | #' 18 | #' @include teal_data_module.R 19 | #' @name eval_code 20 | #' @rdname teal_data_module 21 | #' @aliases eval_code,teal_data_module 22 | #' @aliases \S4method{eval_code}{teal_data_module} 23 | #' 24 | #' @importFrom methods setMethod 25 | #' @importMethodsFrom teal.code eval_code 26 | #' 27 | setMethod("eval_code", signature = c(object = "teal_data_module"), function(object, code) { 28 | teal_data_module( 29 | ui = function(id) { 30 | ns <- NS(id) 31 | object$ui(ns("mutate_inner")) 32 | }, 33 | server = function(id) { 34 | moduleServer(id, function(input, output, session) { 35 | data <- object$server("mutate_inner") 36 | td <- eventReactive(data(), 37 | { 38 | if (inherits(data(), c("teal_data", "qenv.error"))) { 39 | eval_code(data(), code) 40 | } else { 41 | data() 42 | } 43 | }, 44 | ignoreNULL = FALSE 45 | ) 46 | td 47 | }) 48 | }, 49 | once = attr(object, "once") 50 | ) 51 | }) 52 | -------------------------------------------------------------------------------- /staged_dependencies.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | current_repo: 3 | repo: insightsengineering/teal 4 | host: https://github.com 5 | upstream_repos: 6 | insightsengineering/roxy.shinylive: 7 | repo: insightsengineering/roxy.shinylive 8 | host: https://github.com 9 | insightsengineering/teal.widgets: 10 | repo: insightsengineering/teal.widgets 11 | host: https://github.com 12 | insightsengineering/teal.code: 13 | repo: insightsengineering/teal.code 14 | host: https://github.com 15 | insightsengineering/teal.data: 16 | repo: insightsengineering/teal.data 17 | host: https://github.com 18 | insightsengineering/teal.slice: 19 | repo: insightsengineering/teal.slice 20 | host: https://github.com 21 | insightsengineering/teal.logger: 22 | repo: insightsengineering/teal.logger 23 | host: https://github.com 24 | insightsengineering/teal.reporter: 25 | repo: insightsengineering/teal.reporter 26 | host: https://github.com 27 | downstream_repos: 28 | insightsengineering/teal.modules.general: 29 | repo: insightsengineering/teal.modules.general 30 | host: https://github.com 31 | insightsengineering/teal.modules.clinical: 32 | repo: insightsengineering/teal.modules.clinical 33 | host: https://github.com 34 | insightsengineering/teal.osprey: 35 | repo: insightsengineering/teal.osprey 36 | host: https://github.com 37 | insightsengineering/teal.goshawk: 38 | repo: insightsengineering/teal.goshawk 39 | host: https://github.com 40 | insightsengineering/teal.modules.hermes: 41 | repo: insightsengineering/teal.modules.hermes 42 | host: https://github.com 43 | insightsengineering/teal.modules.helios: 44 | repo: insightsengineering/teal.modules.helios 45 | host: https://github.com 46 | -------------------------------------------------------------------------------- /man/module_transform_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_transform_data.R 3 | \name{module_transform_data} 4 | \alias{module_transform_data} 5 | \alias{ui_transform_teal_data} 6 | \alias{srv_transform_teal_data} 7 | \title{Module to transform \code{reactive} \code{teal_data}} 8 | \usage{ 9 | ui_transform_teal_data(id, transformators, class = "well") 10 | 11 | srv_transform_teal_data( 12 | id, 13 | data, 14 | transformators, 15 | modules = NULL, 16 | is_transform_failed = reactiveValues() 17 | ) 18 | } 19 | \arguments{ 20 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 21 | 22 | \item{transformators}{(\code{list} of \code{teal_transform_module}) that will be applied to transform module's data input. 23 | To learn more check \code{vignette("transform-input-data", package = "teal")}.} 24 | 25 | \item{class}{(character(1)) CSS class to be added in the \code{div} wrapper tag.} 26 | 27 | \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) 28 | The data which application will depend on.} 29 | 30 | \item{modules}{(\code{teal_modules} or \code{teal_module}) For \code{datanames} validation purpose} 31 | 32 | \item{is_transform_failed}{(\code{reactiveValues}) contains \code{logical} flags named after each transformator. 33 | Help to determine if any previous transformator failed, so that following transformators can be disabled 34 | and display a generic failure message.} 35 | } 36 | \value{ 37 | \code{reactive} \code{teal_data} 38 | } 39 | \description{ 40 | Module calls \code{\link[=teal_transform_module]{teal_transform_module()}} in sequence so that \verb{reactive teal_data} output 41 | from one module is handed over to the following module's input. 42 | } 43 | -------------------------------------------------------------------------------- /R/module_session_info.R: -------------------------------------------------------------------------------- 1 | #' `teal` user session info module 2 | #' 3 | #' Module to display the user session info popup and to download a lockfile. Module is included 4 | #' when running [init()] but skipped when using [`module_teal`]. Please be aware that session info 5 | #' contains R session information, so multiple module's calls will share the same information. 6 | #' 7 | #' @rdname module_session_info 8 | #' @name module_session_info 9 | #' 10 | #' @inheritParams module_teal 11 | #' 12 | #' @examplesShinylive 13 | #' library(teal) 14 | #' interactive <- function() TRUE 15 | #' {{ next_example }} 16 | #' @examples 17 | #' ui <- fluidPage( 18 | #' ui_session_info("session_info") 19 | #' ) 20 | #' 21 | #' server <- function(input, output, session) { 22 | #' srv_session_info("session_info") 23 | #' } 24 | #' 25 | #' if (interactive()) { 26 | #' shinyApp(ui, server) 27 | #' } 28 | #' 29 | #' @return `NULL` invisibly 30 | NULL 31 | 32 | #' @rdname module_session_info 33 | #' @export 34 | ui_session_info <- function(id) { 35 | ns <- NS(id) 36 | tags$div( 37 | teal.widgets::verbatim_popup_ui(ns("sessionInfo"), "Session Info", type = "link"), 38 | br(), 39 | ui_teal_lockfile(ns("lockfile")), 40 | textOutput(ns("identifier")) 41 | ) 42 | } 43 | 44 | #' @rdname module_session_info 45 | #' @export 46 | srv_session_info <- function(id) { 47 | moduleServer(id, function(input, output, session) { 48 | srv_teal_lockfile("lockfile") 49 | 50 | output$identifier <- renderText( 51 | paste0("Pid:", Sys.getpid(), " Token:", substr(session$token, 25, 32)) 52 | ) 53 | 54 | teal.widgets::verbatim_popup_srv( 55 | "sessionInfo", 56 | verbatim_content = utils::capture.output(utils::sessionInfo()), 57 | title = "SessionInfo" 58 | ) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /man/reporter_previewer_module.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/reporter_previewer_module.R 3 | \name{reporter_previewer_module} 4 | \alias{reporter_previewer_module} 5 | \title{Create a \code{teal} module for previewing a report} 6 | \usage{ 7 | reporter_previewer_module(label = "Report Previewer", server_args = list()) 8 | } 9 | \arguments{ 10 | \item{label}{(\code{character(1)}) Label shown in the navigation item for the module or module group. 11 | For \code{modules()} defaults to \code{"root"}. See \code{Details}.} 12 | 13 | \item{server_args}{(named \code{list}) Arguments will overwrite the default \code{teal.reporter} options 14 | described in the description.} 15 | } 16 | \value{ 17 | \code{teal_module} (extended with \code{teal_module_previewer} class) containing the \code{teal.reporter} previewer functionality. 18 | } 19 | \description{ 20 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} 21 | 22 | This function controls the appearance of the drop-down menu for the reporter. 23 | It is now deprecated in favor of the options: 24 | \itemize{ 25 | \item \code{teal.reporter.nav_buttons = c("preview", "download", "load", "reset")} to control which 26 | buttons will be displayed in the drop-down. 27 | \item \code{teal.reporter.rmd_output}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} 28 | \item \code{teal.reporter.rmd_yaml_args}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} 29 | \item \code{teal.reporter.global_knitr}: passed to \code{\link[teal.reporter:download_report_button]{teal.reporter::download_report_button_srv()}} 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vignettes/blueprint/module_encapsulation.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Module Encapsulation" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Module Encapsulation} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ## Introduction 12 | 13 | The `teal` framework leverages the [`shiny` module concept](https://rstudio.github.io/shiny/reference/moduleServer.html) to enable encapsulation of analytical actions in `teal` modules, while maintaining seamless communication between the modules and the application. 14 | 15 | 16 | ## Benefits 17 | 18 | By implementing the modular app technique from the `shiny` module into the creation of the `teal` module, several benefits are realized: 19 | 20 | 1. Streamlined maintenance 21 | The development of the `teal` module becomes more manageable, as it can function independently from the `teal` framework. 22 | This separation allows developers to maintain the module with ease. This approach has been successfully applied in `R` packages dedicated to `teal` module development, such as `teal.modules.general` and `teal.modules.clinical`. 23 | 24 | 1. Enhanced focus on output 25 | `teal` module developers can concentrate solely on refining parameters or encoding, and output aspects (such as data summarization and visualization) without the need to concern themselves with the intricacies of the `teal` framework. 26 | When developed correctly, the module seamlessly integrates with `teal`. 27 | 28 | 1. Facilitated collaboration 29 | `teal` module development becomes an accessible entry point for developers interested in collaborating. 30 | This approach encourages user collaboration for the improvement of `teal` modules, as developers gain a deeper understanding of the mechanics of the `teal` framework. 31 | -------------------------------------------------------------------------------- /man/check_reactive.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checkmate.R 3 | \name{check_reactive} 4 | \alias{check_reactive} 5 | \alias{test_reactive} 6 | \alias{assert_reactive} 7 | \title{Check that argument is reactive.} 8 | \usage{ 9 | check_reactive(x, null.ok = FALSE) 10 | 11 | test_reactive(x, null.ok = FALSE) 12 | 13 | assert_reactive( 14 | x, 15 | null.ok = FALSE, 16 | .var.name = checkmate::vname(x), 17 | add = NULL 18 | ) 19 | } 20 | \arguments{ 21 | \item{x}{[\code{any}]\cr 22 | Object to check.} 23 | 24 | \item{null.ok}{[\code{logical(1)}]\cr 25 | If set to \code{TRUE}, \code{x} may also be \code{NULL}. 26 | In this case only a type check of \code{x} is performed, all additional checks are disabled.} 27 | 28 | \item{.var.name}{[\code{character(1)}]\cr 29 | Name of the checked object to print in assertions. Defaults to 30 | the heuristic implemented in \code{\link[checkmate]{vname}}.} 31 | 32 | \item{add}{[\code{AssertCollection}]\cr 33 | Collection to store assertion messages. See \code{\link[checkmate]{AssertCollection}}.} 34 | } 35 | \value{ 36 | Depending on the function prefix: 37 | If the check is successful, the functions 38 | \code{assertClass}/\code{assert_class} return 39 | \code{x} invisibly, whereas 40 | \code{checkClass}/\code{check_class} and 41 | \code{testClass}/\code{test_class} return 42 | \code{TRUE}. 43 | If the check is not successful, 44 | \code{assertClass}/\code{assert_class} 45 | throws an error message, 46 | \code{testClass}/\code{test_class} 47 | returns \code{FALSE}, 48 | and \code{checkClass}/\code{check_class} 49 | return a string with the error message. 50 | The function \code{expect_class} always returns an 51 | \code{\link[testthat]{expectation}}. 52 | } 53 | \description{ 54 | Check that argument is reactive. 55 | } 56 | \keyword{internal} 57 | -------------------------------------------------------------------------------- /vignettes/blueprint/dataflow.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Data Flow" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Data Flow} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, child="_setup.Rmd"} 12 | ``` 13 | 14 | ```{r dataflow_mermaid1, echo=FALSE} 15 | shiny::pre( 16 | class = "mermaid", 17 | " 18 | %% This is a mermaid diagram, if you see this the plot failed to render. Sorry. 19 | sequenceDiagram; 20 | autonumber 21 | participant data 22 | participant filters 23 | participant filtered data 24 | participant teal module 25 | data->filters: gets 26 | filters->>filtered data: becomes 27 | filtered data->>teal module: sent to 28 | " 29 | ) 30 | ``` 31 | 32 | The sequence diagram above illustrates the different stages that data goes through within the `teal` framework, supported by the `teal.slice` package: 33 | 34 | 1. Data is created and loaded into `teal` app; 35 | - Data sets are wrapped in a `teal_data` before being passed to the app; 36 | - The [`teal_data` class](input_data.html) facilitates reproducibility; 37 | 2. Data is passed to the filter panel; 38 | - Users _(or app developers)_ can specify filters to apply; 39 | - Filters can be specified globally, for the whole app, or for specific modules; 40 | - Filtering code is appended to the data; 41 | - See the [Filter panel vignette](filter_panel.html) for details; 42 | 3. Filtered data is sent to `teal` modules for analysis; 43 | - Each module receives a `teal_data` object so analysis code applied to the data is tracked _(and can be used to reproduce the whole analysis)_; 44 | 45 | Whenever filters are added or removed, the data coming into modules is re-computed, providing the `teal` module with new filtered data to conduct the required analysis. 46 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(.srv_teal_module,default) 4 | S3method(.srv_teal_module,teal_module) 5 | S3method(.srv_teal_module,teal_modules) 6 | S3method(.ui_teal_module,default) 7 | S3method(.ui_teal_module,teal_module) 8 | S3method(.ui_teal_module,teal_modules) 9 | S3method(after,default) 10 | S3method(after,teal_module) 11 | S3method(after,teal_modules) 12 | S3method(c,teal_slices) 13 | S3method(format,teal_module) 14 | S3method(format,teal_modules) 15 | S3method(print,teal_module) 16 | S3method(print,teal_modules) 17 | S3method(within,teal_data_module) 18 | export(TealReportCard) 19 | export(add_landing_modal) 20 | export(after) 21 | export(as.teal_slices) 22 | export(build_app_title) 23 | export(disable_report) 24 | export(disable_src) 25 | export(example_module) 26 | export(init) 27 | export(make_teal_transform_server) 28 | export(modify_footer) 29 | export(modify_header) 30 | export(modify_title) 31 | export(module) 32 | export(modules) 33 | export(report_card_template) 34 | export(reporter_previewer_module) 35 | export(srv_session_info) 36 | export(srv_teal) 37 | export(srv_transform_teal_data) 38 | export(teal_data_module) 39 | export(teal_slices) 40 | export(teal_transform_module) 41 | export(ui_session_info) 42 | export(ui_teal) 43 | export(ui_transform_teal_data) 44 | export(validate_has_data) 45 | export(validate_has_elements) 46 | export(validate_has_variable) 47 | export(validate_in) 48 | export(validate_inputs) 49 | export(validate_n_levels) 50 | export(validate_no_intersection) 51 | export(validate_one_row_per_id) 52 | import(shiny) 53 | import(teal.data) 54 | import(teal.slice) 55 | importFrom(methods,as) 56 | importFrom(methods,new) 57 | importFrom(methods,setMethod) 58 | importFrom(shiny,reactiveVal) 59 | importFrom(shiny,reactiveValues) 60 | importFrom(stats,setNames) 61 | importMethodsFrom(teal.code,eval_code) 62 | -------------------------------------------------------------------------------- /man/dot-call_once_when.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_nested_tabs.R 3 | \name{.call_once_when} 4 | \alias{.call_once_when} 5 | \title{Calls expression when condition is met} 6 | \usage{ 7 | .call_once_when( 8 | eventExpr, 9 | handlerExpr, 10 | event.env = parent.frame(), 11 | handler.env = parent.frame(), 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{eventExpr}{A (quoted or unquoted) logical expression that represents the event; 17 | this can be a simple reactive value like input$click, a call to a reactive expression 18 | like dataset(), or even a complex expression inside curly braces.} 19 | 20 | \item{handlerExpr}{The expression to call whenever \code{eventExpr} is 21 | invalidated. This should be a side-effect-producing action (the return 22 | value will be ignored). It will be executed within an \code{\link[shiny:isolate]{isolate()}} 23 | scope.} 24 | 25 | \item{event.env}{The parent environment for the reactive expression. By default, 26 | this is the calling environment, the same as when defining an ordinary 27 | non-reactive expression. If \code{eventExpr} is a quosure and \code{event.quoted} is \code{TRUE}, 28 | then \code{event.env} is ignored.} 29 | 30 | \item{handler.env}{The parent environment for the reactive expression. By default, 31 | this is the calling environment, the same as when defining an ordinary 32 | non-reactive expression. If \code{handlerExpr} is a quosure and \code{handler.quoted} is \code{TRUE}, 33 | then \code{handler.env} is ignored.} 34 | 35 | \item{...}{additional arguments passed to \code{observeEvent} with the exception of \code{eventExpr} that is not allowed.} 36 | } 37 | \value{ 38 | An observer. 39 | } 40 | \description{ 41 | Function postpones \code{handlerExpr} to the moment when \code{eventExpr} (condition) returns \code{TRUE}, 42 | otherwise nothing happens. 43 | } 44 | \keyword{internal} 45 | -------------------------------------------------------------------------------- /inst/js/togglePanelItems.js: -------------------------------------------------------------------------------- 1 | // When invoked it adds the setClass and removes the removeClass from the element. 2 | function setAndRemoveClass(element, setClass, removeClass) { 3 | if (typeof element === "string") { 4 | element = document.querySelector(element); 5 | } 6 | element.classList.add(setClass); 7 | element.classList.remove(removeClass); 8 | } 9 | 10 | // When invoked it toggles the class of the element. 11 | function toggleClass(element, class1, class2) { 12 | if (typeof element === "string") { 13 | element = document.querySelector(element); 14 | } 15 | if (element.classList.contains(class1)) { 16 | setAndRemoveClass(element, class2, class1); 17 | } else { 18 | setAndRemoveClass(element, class1, class2); 19 | } 20 | } 21 | 22 | // When invoked it shows the targetSelector element. 23 | function showPanelItem(targeSelector, duration = 400, easing = "slideInTop") { 24 | $(`#${targeSelector}`).show(duration, easing); 25 | $(`#${targeSelector}`).trigger("shown"); 26 | } 27 | 28 | // When invoked it hides the targetSelector element. 29 | function hidePanelItem(targeSelector, duration = 400, easing = "slideOutLeft") { 30 | $(`#${targeSelector}`).hide(duration, easing); 31 | } 32 | 33 | // When invoked it hides/shows targetSelectors elements 34 | // and changes class of element from class1 <-> class2 35 | function togglePanelItems( 36 | element, 37 | targetSelectors, 38 | class1, 39 | class2, 40 | duration = 400, 41 | easing = "swing" 42 | ) { 43 | if (!Array.isArray(targetSelectors)) { 44 | targetSelectors = [targetSelectors]; 45 | } 46 | 47 | targetSelectors.forEach((targetSelector) => { 48 | if ($(`#${targetSelector}`).is(":visible")) { 49 | hidePanelItem(targetSelector, duration, easing); 50 | } else { 51 | showPanelItem(targetSelector, duration, easing); 52 | } 53 | }); 54 | 55 | toggleClass(element, class1, class2); 56 | } 57 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | # adapted from https://github.com/r-lib/devtools/blob/master/R/zzz.R 3 | 4 | teal_default_options <- list( 5 | teal.show_js_log = FALSE, 6 | teal.lockfile.mode = "auto", 7 | shiny.sanitize.errors = FALSE, 8 | teal.sidebar.position = "left", 9 | teal.sidebar.width = 250, 10 | teal.reporter.nav_buttons = c("preview", "download", "load", "reset"), 11 | teal.show_src = TRUE 12 | ) 13 | 14 | op <- options() 15 | toset <- !(names(teal_default_options) %in% names(op)) 16 | if (any(toset)) options(teal_default_options[toset]) 17 | 18 | # Set up the teal logger instance 19 | teal.logger::register_logger("teal") 20 | teal.logger::register_handlers("teal") 21 | 22 | invisible() 23 | } 24 | 25 | .onAttach <- function(libname, pkgname) { 26 | packageStartupMessage( 27 | "\nYou are using teal version ", 28 | # `system.file` uses the `shim` of `system.file` by `teal` 29 | # we avoid `desc` dependency here to get the version 30 | read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"] 31 | ) 32 | } 33 | 34 | # This one is here because setdiff_teal_slice should not be exported from teal.slice. 35 | setdiff_teal_slices <- getFromNamespace("setdiff_teal_slices", "teal.slice") 36 | # This one is here because it is needed by c.teal_slices but we don't want it exported from teal.slice. 37 | coalesce_r <- getFromNamespace("coalesce_r", "teal.slice") 38 | 39 | # Use non-exported function(s) from teal.code 40 | # This one is here because lang2calls should not be exported from teal.code 41 | lang2calls <- getFromNamespace("lang2calls", "teal.code") 42 | code2list <- getFromNamespace("code2list", "teal.data") 43 | 44 | # Use non-exported function(s) from teal.reporter 45 | # This one is here because .action_button_busy is an internal function that should not be exported 46 | .action_button_busy <- getFromNamespace(".action_button_busy", "teal.reporter") 47 | -------------------------------------------------------------------------------- /man/teal-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal.R 3 | \docType{package} 4 | \name{teal-package} 5 | \alias{teal} 6 | \alias{teal-package} 7 | \title{\code{teal}: Interactive exploration of clinical trials data} 8 | \description{ 9 | The \code{teal} package provides a \code{shiny} based framework for creating an 10 | interactive data analysis environment. 11 | } 12 | \details{ 13 | To learn mode about the package, visit the \href{https://insightsengineering.github.io/teal/latest-tag/}{project website} 14 | or read the \code{\link[=init]{init()}} manual page. 15 | } 16 | \seealso{ 17 | Useful links: 18 | \itemize{ 19 | \item \url{https://insightsengineering.github.io/teal/} 20 | \item \url{https://github.com/insightsengineering/teal/} 21 | \item Report bugs at \url{https://github.com/insightsengineering/teal/issues} 22 | } 23 | 24 | } 25 | \author{ 26 | \strong{Maintainer}: Dawid Kaledkowski \email{dawid.kaledkowski@roche.com} (\href{https://orcid.org/0000-0001-9533-457X}{ORCID}) 27 | 28 | Authors: 29 | \itemize{ 30 | \item Pawel Rucki \email{pawel.rucki@roche.com} 31 | \item Aleksander Chlebowski \email{aleksander.chlebowski@contractors.roche.com} (\href{https://orcid.org/0000-0001-5018-6294}{ORCID}) 32 | \item Andre Verissimo \email{andre.verissimo@roche.com} (\href{https://orcid.org/0000-0002-2212-339X}{ORCID}) 33 | \item Kartikeya Kirar \email{kartikeya.kirar@businesspartner.roche.com} 34 | \item Vedha Viyash \email{vedha.viyash@roche.com} 35 | \item Marcin Kosinski \email{marcin.kosinski.mk1@roche.com} 36 | \item Adrian Waddell \email{adrian.waddell@gene.com} 37 | \item Nikolas Burkoff 38 | \item Mahmoud Hallal 39 | \item Maciej Nasinski 40 | \item Konrad Pagacz 41 | \item Junlue Zhao 42 | \item Tadeusz Lewandowski 43 | } 44 | 45 | Other contributors: 46 | \itemize{ 47 | \item Dony Unardi \email{unardid@gene.com} [reviewer] 48 | \item Nina Qi \email{qi.ting@gene.com} [reviewer] 49 | \item Chendi Liao [reviewer] 50 | \item F. Hoffmann-La Roche AG [copyright holder, funder] 51 | \item Maximilian Mordig [contributor] 52 | } 53 | 54 | } 55 | \keyword{internal} 56 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-module_bookmark_manager.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | skip_if_too_deep(5) 4 | skip("todo: error") 5 | 6 | bookmark_manager_selector <- "button[id$='bookmark_manager-do_bookmark']" 7 | testthat::test_that("bookmark_manager_button is not rendered by default", { 8 | app <- TealAppDriver$new( 9 | init( 10 | data = simple_teal_data(), 11 | modules = example_module(label = "Example Module"), 12 | options = list() 13 | ) 14 | ) 15 | testthat::expect_null(app$get_html(bookmark_manager_selector)) 16 | app$stop() 17 | }) 18 | 19 | testthat::test_that("bookmark_manager_button is not rendered when enableBookmarking = 'url'", { 20 | app <- TealAppDriver$new( 21 | init( 22 | data = simple_teal_data(), 23 | modules = example_module(label = "Example Module"), 24 | options = list(shiny.bookmarkStore = "url") 25 | ) 26 | ) 27 | testthat::expect_null(app$get_html(bookmark_manager_selector)) 28 | app$stop() 29 | }) 30 | 31 | testthat::test_that("bookmark_manager_button is rendered when enableBookmarking = 'server'", { 32 | app <- TealAppDriver$new( 33 | init( 34 | data = simple_teal_data(), 35 | modules = example_module(label = "Example Module"), 36 | options = list(shiny.bookmarkStore = "server") 37 | ) 38 | ) 39 | testthat::expect_type(app$get_html(bookmark_manager_selector), "character") 40 | app$stop() 41 | }) 42 | 43 | testthat::test_that("bookmark_manager_button shows modal with url containing state_id when clicked", { 44 | app <- TealAppDriver$new( 45 | init( 46 | data = simple_teal_data(), 47 | modules = example_module(label = "Example Module"), 48 | options = list(shiny.bookmarkStore = "server") 49 | ) 50 | ) 51 | bookmark_button_id <- app$get_attr(bookmark_manager_selector, "id") 52 | app$click(bookmark_button_id) 53 | 54 | testthat::expect_match( 55 | rvest::html_text(app$get_html_rvest("div[id$=bookmark_modal] pre")), 56 | "_state_id_=[a-zA-Z0-9]+", # bookmark link has `_state_id_=<hex code from shiny bookmark>` 57 | ) 58 | app$stop() 59 | }) 60 | -------------------------------------------------------------------------------- /man/module_session_info.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_session_info.R 3 | \name{module_session_info} 4 | \alias{module_session_info} 5 | \alias{ui_session_info} 6 | \alias{srv_session_info} 7 | \title{\code{teal} user session info module} 8 | \usage{ 9 | ui_session_info(id) 10 | 11 | srv_session_info(id) 12 | } 13 | \arguments{ 14 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 15 | } 16 | \value{ 17 | \code{NULL} invisibly 18 | } 19 | \description{ 20 | Module to display the user session info popup and to download a lockfile. Module is included 21 | when running \code{\link[=init]{init()}} but skipped when using \code{\link{module_teal}}. Please be aware that session info 22 | contains R session information, so multiple module's calls will share the same information. 23 | } 24 | \examples{ 25 | ui <- fluidPage( 26 | ui_session_info("session_info") 27 | ) 28 | 29 | server <- function(input, output, session) { 30 | srv_session_info("session_info") 31 | } 32 | 33 | if (interactive()) { 34 | shinyApp(ui, server) 35 | } 36 | 37 | } 38 | \section{Examples in Shinylive}{ 39 | \describe{ 40 | \item{example-1}{ 41 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEracxUurmAJgAUoAczjsB06eYD6AZzg+fTQgvISUiNzA-AKCQiDC+MH4IJIE-RilGS2U1DS0hVBVSXGkiQoKi6SjAkh0Qd0r0339q4NDwhKqYtoSkgF9TQSVpdiFyUXEpbVr6nwALIVYAQXR2c2K0jL6BMF6AXSA}{Open in Shinylive} 42 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEracxUurmAJgAUoAczjsB06eYD6AZzg+fTQgvISUiNzA-AKCQiDC+MH4IJIE-RilGS2U1DS0hVBVSXGkiQoKi6SjAkh0Qd0r0339q4NDwhKqYtoSkgF9TQSVpdiFyUXEpbVr6nwALIVYAQXR2c2K0jL6BMF6AXSA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 43 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 44 | } 45 | } 46 | } 47 | 48 | -------------------------------------------------------------------------------- /man/module_data_summary.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_data_summary.R 3 | \name{module_data_summary} 4 | \alias{module_data_summary} 5 | \alias{ui_data_summary} 6 | \alias{srv_data_summary} 7 | \alias{get_filter_overview_wrapper} 8 | \alias{get_filter_overview} 9 | \alias{get_filter_overview_array} 10 | \alias{get_filter_overview_MultiAssayExperiment} 11 | \title{Data summary} 12 | \usage{ 13 | ui_data_summary(id) 14 | 15 | srv_data_summary(id, data) 16 | 17 | get_filter_overview_wrapper(teal_data) 18 | 19 | get_filter_overview(current_data, initial_data, dataname, subject_keys) 20 | 21 | get_filter_overview_array(current_data, initial_data, dataname, subject_keys) 22 | 23 | get_filter_overview_MultiAssayExperiment(current_data, initial_data, dataname) 24 | } 25 | \arguments{ 26 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 27 | 28 | \item{data}{(\code{reactive} returning \code{teal_data})} 29 | 30 | \item{current_data}{(\code{object}) current object (after filtering and transforming).} 31 | 32 | \item{initial_data}{(\code{object}) initial object.} 33 | 34 | \item{dataname}{(\code{character(1)})} 35 | 36 | \item{subject_keys}{(\code{character}) names of the columns which determine a single unique subjects} 37 | } 38 | \value{ 39 | \code{NULL}. 40 | } 41 | \description{ 42 | Module and its utils to display the number of rows and subjects in the filtered and unfiltered data. 43 | } 44 | \details{ 45 | Handling different data classes: 46 | \code{get_filter_overview()} is a pseudo S3 method which has variants for: 47 | \itemize{ 48 | \item \code{array} (\code{data.frame}, \code{DataFrame}, \code{array}, \code{Matrix} and \code{SummarizedExperiment}): Method variant 49 | can be applied to any two-dimensional objects on which \code{\link[=ncol]{ncol()}} can be used. 50 | \item \code{MultiAssayExperiment}: for which summary contains counts for \code{colData} and all \code{experiments}. 51 | \item For other data types module displays data name with warning icon and no more details. 52 | } 53 | 54 | Module includes also "Show/Hide unsupported" button to toggle rows of the summary table 55 | containing datasets where number of observations are not calculated. 56 | } 57 | \keyword{internal} 58 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug Report 3 | description: File a bug report 4 | title: "[Bug]: <title>" 5 | labels: ["bug"] 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: | 10 | Thanks for taking the time to fill out this bug report! 11 | - type: textarea 12 | id: what-happened 13 | attributes: 14 | label: What happened? 15 | description: Also tell us, what did you expect to happen? 16 | placeholder: Tell us what you see! 17 | value: "A bug happened!" 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: session-info 22 | attributes: 23 | label: sessionInfo() 24 | description: Please copy and paste your output from `sessionInfo()`. This will be automatically formatted into code, so no need for backticks. 25 | render: R 26 | - type: textarea 27 | id: logs 28 | attributes: 29 | label: Relevant log output 30 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 31 | render: R 32 | - type: checkboxes 33 | id: code-of-conduct 34 | attributes: 35 | label: Code of Conduct 36 | description: By submitting this issue, you agree to follow our [Code of Conduct.](https://insightsengineering.github.io/teal/latest-tag/CODE_OF_CONDUCT.html) 37 | options: 38 | - label: I agree to follow this project's Code of Conduct. 39 | required: true 40 | - type: checkboxes 41 | id: contributor-guidelines 42 | attributes: 43 | label: Contribution Guidelines 44 | description: By submitting this issue, you agree to follow our [Contribution Guidelines.](https://insightsengineering.github.io/teal/latest-tag/CONTRIBUTING.html) 45 | options: 46 | - label: I agree to follow this project's Contribution Guidelines. 47 | required: true 48 | - type: checkboxes 49 | id: security-policy 50 | attributes: 51 | label: Security Policy 52 | description: By submitting this issue, you agree to follow our [Security Policy.](https://github.com/insightsengineering/teal/security/policy) 53 | options: 54 | - label: I agree to follow this project's Security Policy. 55 | required: true 56 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-disable_report.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | skip_if_too_deep(5) 3 | 4 | testthat::test_that("Add to report button is not disabled by default.", { 5 | app <- TealAppDriver$new(init( 6 | data = simple_teal_data(), 7 | modules = example_module(label = "m1") 8 | )) 9 | 10 | expect_true(endsWith(app$get_attr( 11 | selector = "#teal-teal_modules-nav-m1-add_reporter_wrapper-reporter_add-add_report_card_button", 12 | attribute = "class" 13 | ), "shiny-bound-input")) 14 | 15 | app$stop() 16 | }) 17 | 18 | testthat::test_that("Report button is disabled on a module changed by disable_report()", { 19 | app <- TealAppDriver$new(init( 20 | data = simple_teal_data(), 21 | modules = example_module(label = "m1") |> disable_report() 22 | )) 23 | 24 | 25 | expect_true(endsWith(app$get_attr( 26 | selector = "#teal-teal_modules-nav-m1-add_reporter_wrapper-reporter_add-add_report_card_button", 27 | attribute = "class" 28 | ), "shiny-bound-input disabled")) 29 | 30 | app$stop() 31 | }) 32 | 33 | testthat::test_that("Report button is active on a nested module by default", { 34 | app <- TealAppDriver$new( 35 | init( 36 | data = simple_teal_data(), 37 | modules = modules( 38 | example_module(label = "m1"), 39 | example_module(label = "m2") 40 | ) 41 | ) 42 | ) 43 | 44 | expect_true(endsWith(app$get_attr( 45 | selector = "#teal-teal_modules-nav-m1-add_reporter_wrapper-reporter_add-add_report_card_button", 46 | attribute = "class" 47 | ), "shiny-bound-input")) 48 | 49 | app$stop() 50 | }) 51 | 52 | testthat::test_that("Report button is disabled on nested modules changed by disable_report()", { 53 | app <- TealAppDriver$new( 54 | init( 55 | data = simple_teal_data(), 56 | modules = modules( 57 | example_module(label = "m1"), 58 | example_module(label = "m2") 59 | ) |> disable_report() 60 | ) 61 | ) 62 | class <- app$get_attr( 63 | selector = "#teal-teal_modules-nav-m1-add_reporter_wrapper-reporter_add-add_report_card_button", 64 | attribute = "class" 65 | ) 66 | classes <- strsplit(class, split = " ", fixed = TRUE)[[1]] 67 | expect_true(all(c("shiny-bound-input", "disabled") %in% classes)) 68 | 69 | app$stop() 70 | }) 71 | -------------------------------------------------------------------------------- /.github/workflows/scheduled.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Scheduled 🕰️ 3 | 4 | on: 5 | schedule: 6 | - cron: '45 3 * * 0' 7 | workflow_dispatch: 8 | inputs: 9 | chosen-workflow: 10 | description: | 11 | Select which workflow you'd like to run 12 | required: true 13 | type: choice 14 | default: rhub 15 | options: 16 | - rhub 17 | - dependency-test 18 | - branch-cleanup 19 | - revdepcheck 20 | 21 | jobs: 22 | dependency-test: 23 | if: > 24 | github.event_name == 'schedule' || ( 25 | github.event_name == 'workflow_dispatch' && 26 | inputs.chosen-workflow == 'dependency-test' 27 | ) 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | test-strategy: ["min_cohort", "min_isolated", "release", "max"] 32 | uses: insightsengineering/r.pkg.template/.github/workflows/verdepcheck.yaml@main 33 | name: Dependency Test - ${{ matrix.test-strategy }} 🔢 34 | secrets: 35 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 36 | GCHAT_WEBHOOK: ${{ secrets.GCHAT_WEBHOOK }} 37 | with: 38 | strategy: ${{ matrix.test-strategy }} 39 | additional-env-vars: | 40 | PKG_SYSREQS_DRY_RUN=true 41 | extra-deps: | 42 | matrixStats (>= 1.5.0) 43 | branch-cleanup: 44 | if: > 45 | github.event_name == 'schedule' || ( 46 | github.event_name == 'workflow_dispatch' && 47 | inputs.chosen-workflow == 'branch-cleanup' 48 | ) 49 | name: Branch Cleanup 🧹 50 | uses: insightsengineering/r.pkg.template/.github/workflows/branch-cleanup.yaml@main 51 | secrets: 52 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 53 | revdepcheck: 54 | if: > 55 | github.event_name == 'schedule' || ( 56 | github.event_name == 'workflow_dispatch' && 57 | inputs.chosen-workflow == 'revdepcheck' 58 | ) 59 | name: revdepcheck ↩️ 60 | uses: insightsengineering/r.pkg.template/.github/workflows/revdepcheck.yaml@main 61 | 62 | rhub: 63 | if: > 64 | github.event_name == 'schedule' || ( 65 | github.event_name == 'workflow_dispatch' && 66 | inputs.chosen-workflow == 'rhub' 67 | ) 68 | name: R-hub 🌐 69 | uses: insightsengineering/r.pkg.template/.github/workflows/rhub.yaml@main 70 | 71 | -------------------------------------------------------------------------------- /man/module_filter_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_filter_data.R 3 | \name{module_filter_data} 4 | \alias{module_filter_data} 5 | \alias{ui_filter_data} 6 | \alias{srv_filter_data} 7 | \alias{.make_filtered_teal_data} 8 | \alias{.observe_active_filter_changed} 9 | \alias{.get_filter_expr} 10 | \title{Filter panel module in teal} 11 | \usage{ 12 | ui_filter_data(id) 13 | 14 | srv_filter_data(id, datasets, active_datanames, data, is_active) 15 | 16 | .make_filtered_teal_data(modules, data, datasets = NULL, datanames) 17 | 18 | .observe_active_filter_changed(datasets, is_active, active_datanames, data) 19 | 20 | .get_filter_expr(datasets, datanames) 21 | } 22 | \arguments{ 23 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 24 | 25 | \item{datasets}{(\code{reactive} returning \code{FilteredData} or \code{NULL}) 26 | When \code{datasets} is passed from the parent module (\code{srv_teal}) then \code{dataset} is a singleton 27 | which implies the filter-panel to be "global". When \code{NULL} then filter-panel is "module-specific".} 28 | 29 | \item{active_datanames}{(\code{reactive} returning \code{character}) this module's data names} 30 | 31 | \item{data}{(\code{reactive} returning \code{teal_data})} 32 | 33 | \item{modules}{(\code{teal_modules}) 34 | \code{teal_modules} object. These are the specific output modules which 35 | will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for 36 | more details.} 37 | } 38 | \value{ 39 | A \code{eventReactive} containing \code{teal_data} containing filtered objects and filter code. 40 | \code{eventReactive} triggers only if all conditions are met: 41 | \itemize{ 42 | \item tab is selected (\code{is_active}) 43 | \item when filters are changed (\code{get_filter_expr} is different than previous) 44 | } 45 | } 46 | \description{ 47 | Creates filter panel module from \code{teal_data} object and returns \code{teal_data}. It is build in a way 48 | that filter panel changes and anything what happens before (e.g. \code{\link{module_init_data}}) is triggering 49 | further reactive events only if something has changed and if the module is visible. Thanks to 50 | this special implementation all modules' data are recalculated only for those modules which are 51 | currently displayed. 52 | } 53 | \keyword{internal} 54 | -------------------------------------------------------------------------------- /man/module_teal_lockfile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_teal_lockfile.R 3 | \name{module_teal_lockfile} 4 | \alias{module_teal_lockfile} 5 | \alias{ui_teal_lockfile} 6 | \alias{srv_teal_lockfile} 7 | \alias{.teal_lockfile_process_invoke} 8 | \alias{.renv_snapshot} 9 | \alias{.is_lockfile_deps_installed} 10 | \alias{.is_disabled_lockfile_scenario} 11 | \title{Generate lockfile for application's environment reproducibility} 12 | \usage{ 13 | ui_teal_lockfile(id) 14 | 15 | srv_teal_lockfile(id) 16 | 17 | .teal_lockfile_process_invoke(lockfile_path) 18 | 19 | .renv_snapshot(lockfile_path) 20 | 21 | .is_lockfile_deps_installed() 22 | 23 | .is_disabled_lockfile_scenario() 24 | } 25 | \arguments{ 26 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 27 | 28 | \item{lockfile_path}{(\code{character}) path to the lockfile.} 29 | } 30 | \value{ 31 | \code{NULL} 32 | } 33 | \description{ 34 | Generate lockfile for application's environment reproducibility 35 | } 36 | \section{Different ways of creating lockfile}{ 37 | 38 | \code{teal} leverages \code{\link[renv:snapshot]{renv::snapshot()}}, which offers multiple methods for lockfile creation. 39 | \itemize{ 40 | \item \strong{Working directory lockfile}: \code{teal}, by default, will create an \code{implicit} type lockfile that uses 41 | \code{renv::dependencies()} to detect all R packages in the current project's working directory. 42 | \item \strong{\code{DESCRIPTION}-based lockfile}: To generate a lockfile based on a \code{DESCRIPTION} file in your working 43 | directory, set \code{renv::settings$snapshot.type("explicit")}. The naming convention for \code{type} follows 44 | \code{renv::snapshot()}. For the \code{"explicit"} type, refer to \code{renv::settings$package.dependency.fields()} for the 45 | \code{DESCRIPTION} fields included in the lockfile. 46 | \item \strong{Custom files-based lockfile}: To specify custom files as the basis for the lockfile, set 47 | \code{renv::settings$snapshot.type("custom")} and configure the \code{renv.snapshot.filter} option. 48 | } 49 | } 50 | 51 | \section{lockfile usage}{ 52 | 53 | After creating the lockfile, you can restore the application's environment using \code{renv::restore()}. 54 | } 55 | 56 | \seealso{ 57 | \code{\link[renv:snapshot]{renv::snapshot()}}, \code{\link[renv:restore]{renv::restore()}}. 58 | } 59 | \keyword{internal} 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release 🎈 3 | 4 | on: 5 | push: 6 | tags: 7 | - "v*" 8 | workflow_dispatch: 9 | 10 | jobs: 11 | docs: 12 | name: Pkgdown Docs 📚 13 | needs: release 14 | uses: insightsengineering/r.pkg.template/.github/workflows/pkgdown.yaml@main 15 | secrets: 16 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 17 | with: 18 | default-landing-page: latest-tag 19 | deps-installation-method: setup-r-dependencies 20 | validation: 21 | name: R Package Validation report 📃 22 | needs: release 23 | uses: insightsengineering/r.pkg.template/.github/workflows/validation.yaml@main 24 | secrets: 25 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 26 | with: 27 | deps-installation-method: setup-r-dependencies 28 | release: 29 | name: Create release 🎉 30 | uses: insightsengineering/r.pkg.template/.github/workflows/release.yaml@main 31 | secrets: 32 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 33 | build: 34 | name: Build package and reports 🎁 35 | needs: [release, docs] 36 | uses: insightsengineering/r.pkg.template/.github/workflows/build-check-install.yaml@main 37 | secrets: 38 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 39 | with: 40 | additional-env-vars: | 41 | _R_CHECK_CRAN_INCOMING_REMOTE_=false 42 | additional-r-cmd-check-params: --as-cran 43 | enforce-note-blocklist: true 44 | note-blocklist: | 45 | checking dependencies in R code .* NOTE 46 | checking R code for possible problems .* NOTE 47 | checking examples .* NOTE 48 | checking Rd line widths .* NOTE 49 | checking top-level files .* NOTE 50 | unit-test-report-brand: >- 51 | https://raw.githubusercontent.com/insightsengineering/hex-stickers/main/thumbs/teal.png 52 | deps-installation-method: setup-r-dependencies 53 | coverage: 54 | name: Coverage 📔 55 | needs: [release, docs] 56 | uses: insightsengineering/r.pkg.template/.github/workflows/test-coverage.yaml@main 57 | secrets: 58 | REPO_GITHUB_TOKEN: ${{ secrets.REPO_GITHUB_TOKEN }} 59 | with: 60 | additional-env-vars: | 61 | NOT_CRAN=true 62 | deps-installation-method: setup-r-dependencies 63 | wasm: 64 | name: Build WASM packages 🧑‍🏭 65 | needs: release 66 | uses: insightsengineering/r.pkg.template/.github/workflows/wasm.yaml@main 67 | -------------------------------------------------------------------------------- /man/module_init_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_init_data.R 3 | \name{module_init_data} 4 | \alias{module_init_data} 5 | \alias{ui_init_data} 6 | \alias{srv_init_data} 7 | \title{Data Module for teal} 8 | \usage{ 9 | ui_init_data(id) 10 | 11 | srv_init_data(id, data) 12 | } 13 | \arguments{ 14 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 15 | 16 | \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) 17 | The data which application will depend on.} 18 | } 19 | \value{ 20 | A \code{reactive} object that returns: 21 | Output of the \code{data}. If \code{data} fails then returned error is handled (after \code{\link[=tryCatch]{tryCatch()}}) so that 22 | rest of the application can respond to this respectively. 23 | } 24 | \description{ 25 | This module manages the \code{data} argument for \code{srv_teal}. The \code{teal} framework uses \code{\link[teal.data:teal_data]{teal.data::teal_data()}}, 26 | which can be provided in various ways: 27 | \enumerate{ 28 | \item Directly as a \code{\link[teal.data:teal_data]{teal.data::teal_data()}} object. This will automatically convert it into a \code{reactive} \code{teal_data}. 29 | \item As a \code{reactive} object that returns a \code{\link[teal.data:teal_data]{teal.data::teal_data()}} object. 30 | } 31 | } 32 | \details{ 33 | \subsection{Reactive \code{teal_data}:}{ 34 | 35 | The data in the application can be reactively updated, prompting \code{\link[=srv_teal]{srv_teal()}} to rebuild the 36 | content accordingly. There are two methods for creating interactive \code{teal_data}: 37 | \enumerate{ 38 | \item Using a \code{reactive} object provided from outside the \code{teal} application. In this scenario, 39 | reactivity is controlled by an external module, and \code{srv_teal} responds to changes. 40 | \item Using \code{\link[=teal_data_module]{teal_data_module()}}, which is embedded within the \code{teal} application, allowing data to 41 | be resubmitted by the user as needed. 42 | } 43 | 44 | Since the server of \code{\link[=teal_data_module]{teal_data_module()}} must return a \code{reactive} \code{teal_data} object, both 45 | methods (1 and 2) produce the same reactive behavior within a \code{teal} application. The distinction 46 | lies in data control: the first method involves external control, while the second method 47 | involves control from a custom module within the app. 48 | } 49 | } 50 | \keyword{internal} 51 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-show-rcode.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | 4 | skip_if_too_deep(5) 5 | 6 | testthat::test_that("e2e: Module with 'Show R Code' initializes with visible button", { 7 | app <- TealAppDriver$new( 8 | init( 9 | data = simple_teal_data(), 10 | modules = example_module(label = "Example Module") 11 | ) 12 | ) 13 | 14 | # Check if button exists. 15 | testthat::expect_identical( 16 | app$get_text(app$namespaces(TRUE)$base_id("source_code_wrapper-source_code-button")), 17 | "Show R code" 18 | ) 19 | app$stop() 20 | }) 21 | 22 | testthat::test_that("e2e: Module with 'Show R Code' has modal with two dismiss and two copy to clipboard buttons", { 23 | app <- TealAppDriver$new( 24 | init( 25 | data = simple_teal_data(), 26 | modules = example_module(label = "Example Module") 27 | ) 28 | ) 29 | 30 | app$click(selector = app$namespaces(TRUE)$base_id("source_code_wrapper-source_code-button")) 31 | 32 | # Check header and title content. 33 | testthat::expect_equal( 34 | app$get_text("#shiny-modal div.modal-header > h4"), 35 | "Show R Code" 36 | ) 37 | 38 | # There are two Dismiss buttons with similar id and the same label. 39 | buttons_text <- app$get_text("#shiny-modal button") 40 | testthat::expect_setequal(buttons_text, c("Dismiss", "Copy to Clipboard", "Dismiss", "Copy to Clipboard")) 41 | app$stop() 42 | }) 43 | 44 | testthat::test_that("e2e: Module with 'Show R Code' has code", { 45 | app <- TealAppDriver$new( 46 | init( 47 | data = simple_teal_data(), 48 | modules = example_module(label = "Example Module") 49 | ) 50 | ) 51 | 52 | app$click(selector = app$namespaces(TRUE)$base_id("source_code_wrapper-source_code-button")) 53 | 54 | # Check R code output. 55 | testthat::expect_identical( 56 | strsplit( 57 | app$get_text(app$namespaces(TRUE)$base_id("source_code_wrapper-source_code-verbatim_content")), 58 | "\n" 59 | )[[1]], 60 | c( 61 | "iris <- iris", 62 | "mtcars <- mtcars", 63 | sprintf('stopifnot(rlang::hash(iris) == "%s") # @linksto iris', rlang::hash(iris)), 64 | sprintf('stopifnot(rlang::hash(mtcars) == "%s") # @linksto mtcars', rlang::hash(mtcars)), 65 | ".raw_data <- list2env(list(iris = iris, mtcars = mtcars))", 66 | "lockEnvironment(.raw_data) # @linksto .raw_data", 67 | "object <- iris", 68 | "object" 69 | ) 70 | ) 71 | 72 | app$stop() 73 | }) 74 | -------------------------------------------------------------------------------- /vignettes/blueprint/intro.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Introduction} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | `teal` is an interactive data exploration framework based on `shiny`, designed primarily to analyze CDISC clinical trial data. 12 | 13 | A `shiny` Application created with `teal` offers users the ability to: 14 | 15 | - Import data from external sources; 16 | - Dynamically filter data for analysis; 17 | - Generate reproducible code for future analysis; 18 | - Create and download reports of analysis results _(for analysis modules that support reporting)_. 19 | 20 | Moreover, the `teal` framework provides application developers with a wide range of customizable standard analysis modules to integrate into their applications, along with a logging framework that facilitates debugging. 21 | Additionally, advanced users of the framework can develop new analysis modules and easily integrate them into any `teal` application. 22 | 23 | The `teal` framework's functionality draws heavily from the following packages: 24 | 25 | | R package | Description | 26 | |----------------------|:------------------------------------------------------------------------| 27 | |[`teal`](https://insightsengineering.github.io/teal) | `shiny`-based interactive exploration framework for analyzing data| 28 | |[`teal.widgets`](https://insightsengineering.github.io/teal.widgets) | `shiny` UI components used within `teal`| 29 | |[`teal.data`](https://insightsengineering.github.io/teal.data) | provides the data structure used in all `teal` applications| 30 | |[`teal.slice`](https://insightsengineering.github.io/teal.slice) | provides the filter panel to allow dynamic filtering of data| 31 | |[`teal.code`](https://insightsengineering.github.io/teal.code) | provides a mechanism for tracking code to reproduce an analysis| 32 | |[`teal.logger`](https://insightsengineering.github.io/teal.logger) | standardizes logging within `teal` framework| 33 | |[`teal.reporter`](https://insightsengineering.github.io/teal.reporter) | allows `teal` applications to generate reports| 34 | 35 | Although these packages are mentioned in the material, we strongly recommend visiting their vignettes to learn more about them. 36 | 37 | Learn on how to make your first `teal` application [here](../getting-started-with-teal.html)! 38 | -------------------------------------------------------------------------------- /tests/testthat/test-teal_transform_module.R: -------------------------------------------------------------------------------- 1 | testthat::describe("make_teal_transform_server produces a valid teal_transform_module", { 2 | testthat::it("expression", { 3 | label <- "output_decorator" 4 | output_decorator <- teal_transform_module( 5 | label = label, 6 | server = make_teal_transform_server( 7 | expression(data1 <- rev(data1)) 8 | ) 9 | ) 10 | 11 | shiny::testServer( 12 | app = srv_transform_teal_data, 13 | args = list( 14 | id = "test", 15 | data = reactive(teal.data::teal_data(data1 = iris, data2 = mtcars)), 16 | transformators = output_decorator 17 | ), 18 | expr = { 19 | session$flushReact() 20 | testthat::expect_identical(module_output()[["data1"]], rev(iris)) 21 | } 22 | ) 23 | }) 24 | 25 | testthat::it("quote", { 26 | label <- "output_decorator" 27 | output_decorator <- teal_transform_module( 28 | label = label, 29 | server = make_teal_transform_server( 30 | quote(data1 <- rev(data1)) 31 | ) 32 | ) 33 | 34 | shiny::testServer( 35 | app = srv_transform_teal_data, 36 | args = list( 37 | id = "test", 38 | data = reactive(teal.data::teal_data(data1 = iris, data2 = mtcars)), 39 | transformators = output_decorator 40 | ), 41 | expr = { 42 | session$flushReact() 43 | testthat::expect_identical(module_output()[["data1"]], rev(iris)) 44 | } 45 | ) 46 | }) 47 | }) 48 | 49 | testthat::test_that( 50 | "ui_transform_teal_data and srv_transform_teal_data have the same namespace for transform module", 51 | { 52 | ttm <- teal_transform_module( 53 | ui = function(id) tags$div(id = NS(id, "a_div"), "a div"), 54 | server = function(id, data) { 55 | moduleServer(id, function(input, output, session) { 56 | full_id <- session$ns("a_div") 57 | reactive(within(data(), id <- full_id, full_id = full_id)) 58 | }) 59 | } 60 | ) 61 | 62 | initial_id <- "a-path-to-an-inner-namespace" 63 | ui <- ui_transform_teal_data(initial_id, ttm) 64 | # Find element that ends in "-a_div" 65 | expected_id <- unname(unlist(ui)[grepl(".*-a_div$", unlist(ui))][[1]]) 66 | 67 | testServer( 68 | app = srv_transform_teal_data, 69 | args = list( 70 | id = initial_id, 71 | data = reactive(within(teal_data(), iris <- iris)), 72 | transformators = ttm 73 | ), 74 | expr = { 75 | session$flushReact() 76 | testthat::expect_equal(module_output()$id, expected_id) 77 | } 78 | ) 79 | } 80 | ) 81 | -------------------------------------------------------------------------------- /vignettes/blueprint/product_map.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Product Map" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Product Map} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ```{r, child="_setup.Rmd"} 12 | ``` 13 | 14 | ```{r, echo=FALSE} 15 | shiny::pre( 16 | class = "mermaid", 17 | " 18 | %% This is a mermaid diagram, if you see this the plot failed to render. Sorry. 19 | flowchart RL 20 | teal 21 | subgraph features 22 | direction LR 23 | teal.data 24 | teal.slice 25 | teal.code 26 | teal.logger 27 | teal.widgets 28 | end 29 | subgraph modules 30 | direction RL 31 | teal.modules.general 32 | teal.modules.clinical 33 | teal.osprey 34 | teal.goshawk 35 | teal.modules.hermes 36 | end 37 | teal--has-->features 38 | features--builds-->modules 39 | modules--creates-->teal 40 | subgraph modules 41 | teal.modules.general 42 | teal.modules.clinical 43 | teal.osprey 44 | teal.goshawk 45 | teal.modules.hermes 46 | end 47 | subgraph calculations 48 | direction RL 49 | tern 50 | osprey 51 | goshawk 52 | hermes 53 | end 54 | tern--supports-->teal.modules.clinical 55 | osprey--supports-->teal.osprey 56 | goshawk--supports-->teal.goshawk 57 | hermes--supports-->teal.modules.hermes 58 | style teal fill:lightblue 59 | style features fill:lightgreen 60 | style modules fill:pink 61 | " 62 | ) 63 | ``` 64 | 65 | `teal` is a modular framework that relies on a suite of related packages, as illustrated in the above diagram, to provide a wide range of functionalities. 66 | 67 | `teal`'s primary function is to create web app for analyzing clinical trial data but it **has** a multitude of features distributed across various packages. 68 | 69 | Developers can selectively leverage these packages, such as `teal.widgets`, `teal.code`, and `teal.logger`, to **build** `teal` modules for a `teal` app. 70 | This approach gives the developers the tools that speed up their work and avoid re-implementing existing logic and UI elements. 71 | 72 | The `teal` modules utilize various packages such as `tern`, `osprey`, and `goshawk` to perform calculations and analyses. 73 | These packages provide **support** to the `teal` modules by performing all computations while the modules only have to focus on how to wrap the input options and the output. 74 | 75 | Once developed, new and existing modules can be integrated into `teal` to **create** a functional `teal` app. 76 | 77 | ## Why so many packages? 78 | 79 | By breaking down `teal` features, modules, and calculations into dedicated packages, maintenance is made significantly more manageable. 80 | -------------------------------------------------------------------------------- /tests/testthat/helper-testing_depth.R: -------------------------------------------------------------------------------- 1 | #' Returns testing depth set by session option or by environmental variable. 2 | #' 3 | #' @details Looks for the session option `TESTING_DEPTH` first. 4 | #' If not set, takes the system environmental variable `TESTING_DEPTH`. 5 | #' If neither is set, then returns 3 by default. 6 | #' If the value of `TESTING_DEPTH` is not a numeric of length 1, then returns 3. 7 | #' 8 | #' @return `numeric(1)` the testing depth. 9 | #' 10 | get_testing_depth <- function() { 11 | default_depth <- 3 12 | depth <- getOption("TESTING_DEPTH", Sys.getenv("TESTING_DEPTH", default_depth)) 13 | depth <- tryCatch( 14 | as.numeric(depth), 15 | error = function(error) default_depth, 16 | warning = function(warning) default_depth 17 | ) 18 | if (length(depth) != 1) depth <- default_depth 19 | depth 20 | } 21 | 22 | #' Skipping tests in the testthat pipeline under specific scope 23 | #' @description This function should be used per each `testthat::test_that` call. 24 | #' Each of the call should specify an appropriate depth value. 25 | #' The depth value will set the appropriate scope so more/less time consuming tests could be recognized. 26 | #' The environment variable `TESTING_DEPTH` is used for changing the scope of `testthat` pipeline. 27 | #' `TESTING_DEPTH` interpretation for each possible value: 28 | #' \itemize{ 29 | #' \item{0}{no tests at all} 30 | #' \item{1}{fast - small scope - executed on every commit} 31 | #' \item{3}{medium - medium scope - daily integration pipeline} 32 | #' \item{5}{slow - all tests - daily package tests} 33 | #' } 34 | #' @param depth `numeric` the depth of the testing evaluation, 35 | #' has opposite interpretation to environment variable `TESTING_DEPTH`. 36 | #' So e.g. `0` means run it always and `5` means a heavy test which should be run rarely. 37 | #' If the `depth` argument is larger than `TESTING_DEPTH` then the test is skipped. 38 | #' @importFrom testthat skip 39 | #' @return `NULL` or invoke an error produced by `testthat::skip` 40 | #' @note By default `TESTING_DEPTH` is equal to 3 if there is no environment variable for it. 41 | #' By default `depth` argument lower or equal to 3 will not be skipped because by default `TESTING_DEPTH` 42 | #' is equal to 3. To skip <= 3 depth tests then the environment variable has to be lower than 3 respectively. 43 | skip_if_too_deep <- function(depth) { # nolintr 44 | checkmate::assert_numeric(depth, len = 1, lower = 0, upper = 5) 45 | testing_depth <- get_testing_depth() # by default 3 if there are no env variable 46 | if (testing_depth < depth) { 47 | testthat::skip(paste("testing depth", testing_depth, "is below current testing specification", depth)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # All available hooks: https://pre-commit.com/hooks.html 3 | # R specific hooks: https://github.com/lorenzwalthert/precommit 4 | default_stages: [pre-commit] 5 | default_language_version: 6 | python: python3 7 | repos: 8 | - repo: https://github.com/lorenzwalthert/precommit 9 | rev: v0.4.3.9017 10 | hooks: 11 | - id: style-files 12 | name: Style code with `styler` 13 | args: 14 | [--style_pkg=styler, --style_fun=tidyverse_style, --cache-root=styler] 15 | - id: roxygenize 16 | name: Regenerate package documentation 17 | additional_dependencies: 18 | - davidgohel/flextable # Error: package 'flextable' is not available 19 | - davidgohel/gdtools # for flextable 20 | - mirai 21 | - checkmate 22 | - cli 23 | - htmltools 24 | - jsonlite 25 | - lifecycle 26 | - logger 27 | - magrittr 28 | - methods 29 | - renv 30 | - rlang 31 | - shiny 32 | - shinyjs 33 | - stats 34 | - insightsengineering/roxy.shinylive 35 | - insightsengineering/teal.code 36 | - insightsengineering/teal.data 37 | - insightsengineering/teal.logger 38 | - insightsengineering/teal.reporter 39 | - insightsengineering/teal.slice 40 | - insightsengineering/teal.widgets 41 | - utils 42 | - shinytest2 # Necessary for documentation 43 | - shinyvalidate # Necessary for documentation 44 | - rvest # Necessary for documentation 45 | - id: spell-check 46 | name: Check spelling with `spelling` 47 | exclude: > 48 | (?x)^( 49 | .*\.[rR]| 50 | .*\.css| 51 | .*\.jpg| 52 | .*\.js| 53 | .*\.png| 54 | .*\.py| 55 | .*\.RData| 56 | .*\.Rds| 57 | .*\.rds| 58 | .*\.Rproj| 59 | .*\.sh| 60 | .*\.svg| 61 | .*\.xml| 62 | (.*/|)\_pkgdown.y[a]?ml| 63 | (.*/|)\.covrignore| 64 | (.*/|)\.gitignore| 65 | (.*/|)\.gitlab-ci\.y[a]?ml| 66 | (.*/|)\.lintr| 67 | (.*/|)\.pre-commit-.*| 68 | (.*/|)\.Rbuildignore| 69 | (.*/|)\.Renviron| 70 | (.*/|)\.Rprofile| 71 | (.*/|)CODEOWNERS| 72 | (.*/|)DESCRIPTION| 73 | (.*/|)LICENSE| 74 | (.*/|)NAMESPACE| 75 | (.*/|)staged_dependencies\.y[a]?ml| 76 | (.*/|)WORDLIST| 77 | \.github/.*\.y[a]?ml| 78 | data/.* 79 | )$ 80 | - id: no-browser-statement 81 | name: Check for browser() statement 82 | -------------------------------------------------------------------------------- /vignettes/teal-as-a-shiny-module.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Teal as a Shiny Module" 3 | author: "NEST CoreDev" 4 | output: 5 | rmarkdown::html_vignette: 6 | toc: true 7 | vignette: > 8 | %\VignetteIndexEntry{Teal as a Shiny Module} 9 | %\VignetteEngine{knitr::rmarkdown} 10 | %\VignetteEncoding{UTF-8} 11 | --- 12 | 13 | ## Introduction 14 | 15 | A `shiny` developer can embed a `teal` application into their own `shiny` app by using `shiny` module components of `teal`: `ui_teal()` and `srv_teal()`. 16 | This approach differs from using `init()` and offers greater flexibility. While `init()` includes a session info footer automatically, 17 | when using `teal` as a `shiny` module you can optionally add it manually with `ui_session_info()` and `srv_session_info()`. 18 | Using `teal` as a `shiny` module offers several advantages: 19 | 20 | - Embedding one or more `teal` applications within a larger `shiny` app 21 | - Creating `teal` applications with dynamically generated components (initial data, modules, filters) 22 | 23 | ## Example 24 | 25 | The following example demonstrates embedding `teal` as a `shiny` module within a larger `shiny` application. 26 | Users can select dataset names which are passed to the embedded `teal` component. 27 | On the server side, `srv_teal()` is called with a reactive `teal_data` object passed from the parent app's server. 28 | 29 | ```{r setup, include=FALSE} 30 | library(teal) 31 | ``` 32 | ```{r app} 33 | library(teal) 34 | 35 | data <- teal_data() |> within({ 36 | iris <- iris 37 | mtcars <- mtcars 38 | df <- data.frame(a = 1:10, b = letters[1:10]) 39 | }) 40 | 41 | mods <- modules( 42 | example_module("mod1"), 43 | example_module("mod2") 44 | ) 45 | 46 | ui_app <- fluidPage( 47 | title = "Your app with teal as a module", 48 | selectInput("datasets", "Select datasets", choices = c("iris", "mtcars", "df"), selected = "iris", multiple = TRUE), 49 | ui_teal("teal", mods), 50 | ui_session_info("session_info") 51 | ) 52 | 53 | srv_app <- function(input, output, session) { 54 | data_subset <- reactive(data[input$datasets]) 55 | srv_teal("teal", data = data_subset, modules = mods) 56 | srv_session_info("session_info") 57 | } 58 | 59 | if (interactive()) { 60 | shinyApp(ui_app, srv_app) 61 | } 62 | ``` 63 | 64 | ```{r shinylive_iframe, echo = FALSE, out.width = '150%', out.extra = 'style = "position: relative; z-index:1"', eval = requireNamespace("roxy.shinylive", quietly = TRUE) && knitr::is_html_output() && identical(Sys.getenv("IN_PKGDOWN"), "true")} 65 | code <- paste0(c( 66 | "interactive <- function() TRUE", 67 | knitr::knit_code$get("app") 68 | ), collapse = "\n") 69 | 70 | url <- roxy.shinylive::create_shinylive_url(code) 71 | knitr::include_url(url, height = "800px") 72 | ``` 73 | -------------------------------------------------------------------------------- /man/add_landing_modal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_modifiers.R 3 | \name{add_landing_modal} 4 | \alias{add_landing_modal} 5 | \title{Add a Landing Popup to \code{teal} Application} 6 | \usage{ 7 | add_landing_modal( 8 | x, 9 | title = NULL, 10 | content = NULL, 11 | footer = modalButton("Accept"), 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{(\code{teal_app}) A \code{teal_app} object created using the \code{init} function.} 17 | 18 | \item{title}{An optional title for the dialog.} 19 | 20 | \item{content}{(\code{character(1)}, \code{shiny.tag} or \code{shiny.tag.list}) with the content of the popup.} 21 | 22 | \item{footer}{UI for footer. Use \code{NULL} for no footer.} 23 | 24 | \item{...}{Additional arguments to \code{\link[shiny:modalDialog]{shiny::modalDialog()}}.} 25 | } 26 | \description{ 27 | Adds a landing popup to the \code{teal} app. This popup will be shown when the app starts. 28 | The dialog must be closed by the app user to proceed to the main application. 29 | } 30 | \examples{ 31 | app <- init( 32 | data = teal_data(IRIS = iris, MTCARS = mtcars), 33 | modules = modules(example_module()) 34 | ) |> 35 | add_landing_modal( 36 | title = "Welcome", 37 | content = "This is a landing popup.", 38 | buttons = modalButton("Accept") 39 | ) 40 | 41 | if (interactive()) { 42 | shinyApp(app$ui, app$server) 43 | } 44 | } 45 | \section{Examples in Shinylive}{ 46 | \describe{ 47 | \item{example-1}{ 48 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIFpUcxUNql2A6dIAmUUlGkBeaV2oB9Z6-YASSxAgGUPaVpGWgBnXGkAWV0AYQBBLHDPGFICFhieXHtpGCJHFWo4GIiSsoqY9jgAD1hUCp8a8rhtfggdAB8APiKoR0cfaigIRyEAc3bS7jsIBwdxUgqIvjAAdThqYngtwuWV4mEyTbBdAAtYyKq3CanZ6VQiVBVMI6KHehVSUgkKpZBbUABC-0BEDsYFSBAIcFQpC2PQcPQEtCU0nYQnIonEUm60hARRitwgrFS6HY5gAJCpaPE6TERFJGD0AL5gDkAXSAA}{Open in Shinylive} 49 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIFpUcxUNql2A6dIAmUUlGkBeaV2oB9Z6-YASSxAgGUPaVpGWgBnXGkAWV0AYQBBLHDPGFICFhieXHtpGCJHFWo4GIiSsoqY9jgAD1hUCp8a8rhtfggdAB8APiKoR0cfaigIRyEAc3bS7jsIBwdxUgqIvjAAdThqYngtwuWV4mEyTbBdAAtYyKq3CanZ6VQiVBVMI6KHehVSUgkKpZBbUABC-0BEDsYFSBAIcFQpC2PQcPQEtCU0nYQnIonEUm60hARRitwgrFS6HY5gAJCpaPE6TERFJGD0AL5gDkAXSAA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 50 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /R/teal_slices-store.R: -------------------------------------------------------------------------------- 1 | #' Store and restore `teal_slices` object 2 | #' 3 | #' Functions that write a `teal_slices` object to a file in the `JSON` format, 4 | #' and also restore the object from disk. 5 | #' 6 | #' Date and date time objects are stored in the following formats: 7 | #' 8 | #' - `Date` class is converted to the `"ISO8601"` standard (`YYYY-MM-DD`). 9 | #' - `POSIX*t` classes are converted to character by using 10 | #' `format.POSIX*t(usetz = TRUE, tz = "UTC")` (`YYYY-MM-DD HH:MM:SS UTC`, where 11 | #' `UTC` is the `Coordinated Universal Time` timezone short-code). 12 | #' 13 | #' This format is assumed during `slices_restore`. All `POSIX*t` objects in 14 | #' `selected` or `choices` fields of `teal_slice` objects are always printed in 15 | #' `UTC` timezone as well. 16 | #' 17 | #' @param tss (`teal_slices`) object to be stored. 18 | #' @param file (`character(1)`) file path where `teal_slices` object will be 19 | #' saved and restored. The file extension should be `".json"`. 20 | #' 21 | #' @return `slices_store` returns `NULL`, invisibly. 22 | #' 23 | #' @seealso [teal_slices()] 24 | #' 25 | #' @keywords internal 26 | #' 27 | slices_store <- function(tss, file) { 28 | checkmate::assert_class(tss, "teal_slices") 29 | checkmate::assert_path_for_output(file, overwrite = TRUE, extension = "json") 30 | 31 | cat(format(tss, trim_lines = FALSE), "\n", file = file) 32 | } 33 | 34 | #' @rdname slices_store 35 | #' @return `slices_restore` returns a `teal_slices` object restored from the file. 36 | #' @keywords internal 37 | slices_restore <- function(file) { 38 | checkmate::assert_file_exists(file, access = "r", extension = "json") 39 | 40 | tss_json <- jsonlite::fromJSON(file, simplifyDataFrame = FALSE) 41 | tss_json$slices <- 42 | lapply(tss_json$slices, function(slice) { 43 | for (field in c("selected", "choices")) { 44 | if (!is.null(slice[[field]])) { 45 | if (length(slice[[field]]) > 0) { 46 | date_partial_regex <- "^[0-9]{4}-[0-9]{2}-[0-9]{2}" 47 | time_stamp_regex <- paste0(date_partial_regex, "\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\sUTC$") 48 | 49 | slice[[field]] <- 50 | if (all(grepl(paste0(date_partial_regex, "$"), slice[[field]]))) { 51 | as.Date(slice[[field]]) 52 | } else if (all(grepl(time_stamp_regex, slice[[field]]))) { 53 | as.POSIXct(slice[[field]], tz = "UTC") 54 | } else { 55 | slice[[field]] 56 | } 57 | } else { 58 | slice[[field]] <- character(0) 59 | } 60 | } 61 | } 62 | slice 63 | }) 64 | 65 | tss_elements <- lapply(tss_json$slices, as.teal_slice) 66 | 67 | do.call(teal_slices, c(tss_elements, tss_json$attributes)) 68 | } 69 | -------------------------------------------------------------------------------- /man/validate_in.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_in} 4 | \alias{validate_in} 5 | \title{Validates that vector includes all expected values} 6 | \usage{ 7 | validate_in(x, choices, msg) 8 | } 9 | \arguments{ 10 | \item{x}{Vector of values to test.} 11 | 12 | \item{choices}{Vector to test against.} 13 | 14 | \item{msg}{(\code{character(1)}) Error message to display if some elements of \code{x} are not elements of \code{choices}.} 15 | } 16 | \description{ 17 | This function is a wrapper for \code{shiny::validate}. 18 | } 19 | \examples{ 20 | ui <- fluidPage( 21 | selectInput( 22 | "species", 23 | "Select species", 24 | choices = c("setosa", "versicolor", "virginica", "unknown species"), 25 | selected = "setosa", 26 | multiple = FALSE 27 | ), 28 | verbatimTextOutput("summary") 29 | ) 30 | 31 | server <- function(input, output) { 32 | output$summary <- renderPrint({ 33 | validate_in(input$species, iris$Species, "Species does not exist.") 34 | nrow(iris[iris$Species == input$species, ]) 35 | }) 36 | } 37 | if (interactive()) { 38 | shinyApp(ui, server) 39 | } 40 | 41 | } 42 | \section{Examples in Shinylive}{ 43 | \describe{ 44 | \item{example-1}{ 45 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEracxUurmAJgAUoAczjsB06QGc41OGICSEKgqpG4QHh58YJ6ofrRwnlG47hFRAMo+fqResQTxiXgpHgQAFkS0BAnSALzSBG7RcKREnlBJ0lFSjJ4VRNREjO2dtIxOQhVteB1gagDWEEQA7uExcQlRPMnhEd6+YnC2NdPeza1JRdIwKtTiqL5HAGIAggAyaSbbmyld9FDiMLo4AAPUgAeRCwVCUU8KhgMBYrA2An4EAE3kYXUsyjUGi0QkhuGkRAhIR0IBSxNIkIAJDC4QisYwKLYRPZGEJQuTth4JNxaLY-nAAPpCdj4kK03L5QkjWiealpKUJQnpJWeaS2IhVBbZYFy0gYJHc6QQRhLMXszzAWXyxVrdXVWri0iS+2EgC6KI8AF8Ud6BLQlNIxcJROIpNoySlPCUhKwnuh2OZCeiun6BGBve6gA}{Open in Shinylive} 46 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEracxUurmAJgAUoAczjsB06QGc41OGICSEKgqpG4QHh58YJ6ofrRwnlG47hFRAMo+fqResQTxiXgpHgQAFkS0BAnSALzSBG7RcKREnlBJ0lFSjJ4VRNREjO2dtIxOQhVteB1gagDWEEQA7uExcQlRPMnhEd6+YnC2NdPeza1JRdIwKtTiqL5HAGIAggAyaSbbmyld9FDiMLo4AAPUgAeRCwVCUU8KhgMBYrA2An4EAE3kYXUsyjUGi0QkhuGkRAhIR0IBSxNIkIAJDC4QisYwKLYRPZGEJQuTth4JNxaLY-nAAPpCdj4kK03L5QkjWiealpKUJQnpJWeaS2IhVBbZYFy0gYJHc6QQRhLMXszzAWXyxVrdXVWri0iS+2EgC6KI8AF8Ud6BLQlNIxcJROIpNoySlPCUhKwnuh2OZCeiun6BGBve6gA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 47 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /man/module_bookmark_manager.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_bookmark_manager.R 3 | \name{module_bookmark_manager} 4 | \alias{module_bookmark_manager} 5 | \alias{bookmark} 6 | \alias{bookmark_manager} 7 | \alias{bookmark_manager_module} 8 | \alias{ui_bookmark_panel} 9 | \alias{srv_bookmark_panel} 10 | \alias{get_bookmarking_option} 11 | \alias{need_bookmarking} 12 | \title{App state management.} 13 | \usage{ 14 | ui_bookmark_panel(id, modules) 15 | 16 | srv_bookmark_panel(id, modules) 17 | 18 | get_bookmarking_option() 19 | 20 | need_bookmarking(modules) 21 | } 22 | \arguments{ 23 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 24 | 25 | \item{modules}{(\code{teal_modules}) 26 | \code{teal_modules} object. These are the specific output modules which 27 | will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for 28 | more details.} 29 | } 30 | \value{ 31 | Invisible \code{NULL}. 32 | } 33 | \description{ 34 | \ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#experimental}{\figure{lifecycle-experimental.svg}{options: alt='[Experimental]'}}}{\strong{[Experimental]}} 35 | 36 | Capture and restore the global (app) input state. 37 | } 38 | \details{ 39 | This module introduces bookmarks into \code{teal} apps: the \code{shiny} bookmarking mechanism becomes enabled 40 | and server-side bookmarks can be created. 41 | 42 | The bookmark manager presents a button with the bookmark icon and is placed in the tab-bar. 43 | When clicked, the button creates a bookmark and opens a modal which displays the bookmark URL. 44 | 45 | \code{teal} does not guarantee that all modules (\code{teal_module} objects) are bookmarkable. 46 | Those that are, have a \code{teal_bookmarkable} attribute set to \code{TRUE}. If any modules are not bookmarkable, 47 | the bookmark manager modal displays a warning and the bookmark button displays a flag. 48 | In order to communicate that a external module is bookmarkable, the module developer 49 | should set the \code{teal_bookmarkable} attribute to \code{TRUE}. 50 | } 51 | \section{Server logic}{ 52 | 53 | A bookmark is a URL that contains the app address with a \verb{/?_state_id_=<bookmark_dir>} suffix. 54 | \verb{<bookmark_dir>} is a directory created on the server, where the state of the application is saved. 55 | Accessing the bookmark URL opens a new session of the app that starts in the previously saved state. 56 | } 57 | 58 | \section{Note}{ 59 | 60 | To enable bookmarking use either: 61 | \itemize{ 62 | \item \code{shiny} app by using \code{shinyApp(..., enableBookmarking = "server")} (not supported in \code{shinytest2}) 63 | \item set \code{options(shiny.bookmarkStore = "server")} before running the app 64 | } 65 | } 66 | 67 | \keyword{internal} 68 | -------------------------------------------------------------------------------- /R/teal_data_utils.R: -------------------------------------------------------------------------------- 1 | #' `teal_data` utils 2 | #' 3 | #' In `teal` we need to recreate the `teal_data` object due to two operations: 4 | #' - we need to append filter-data code and objects which have been evaluated in `FilteredData` and 5 | #' we want to avoid double-evaluation. 6 | #' - we need to subset `teal_data` to `datanames` used by the module, to shorten obtainable R-code 7 | #' 8 | #' Due to above recreation of `teal_data` object can't be done simply by using public 9 | #' `teal.code` and `teal.data` methods. 10 | #' 11 | #' @param data (`teal_data`) 12 | #' @param code (`character`) code to append to the object's code slot. 13 | #' @param objects (`list`) objects to append to object's environment. 14 | #' @return modified `teal_data` 15 | #' @keywords internal 16 | #' @name teal_data_utilities 17 | NULL 18 | 19 | #' @rdname teal_data_utilities 20 | .append_evaluated_code <- function(data, code, filter_states) { 21 | checkmate::assert_class(data, "teal_data") 22 | checkmate::assert_class(filter_states, "teal_slices", null.ok = TRUE) 23 | if (length(code) && !identical(code, "")) { 24 | data@code <- c(data@code, code2list(code)) 25 | teal.reporter::teal_card(data) <- c( 26 | teal.reporter::teal_card(data), 27 | "### Filter settings", 28 | teal.reporter::code_chunk( 29 | .teal_slice_to_yaml(filter_states), 30 | lang = "filters", 31 | echo = TRUE # to not hide chunk when `global_knitr$echo` is set to `FALSE` 32 | ), 33 | teal.reporter::code_chunk(code) 34 | ) 35 | methods::validObject(data) 36 | } 37 | data 38 | } 39 | 40 | #' @rdname teal_data_utilities 41 | .append_modified_data <- function(data, objects) { 42 | checkmate::assert_class(data, "teal_data") 43 | checkmate::assert_class(objects, "list") 44 | new_env <- list2env(objects, parent = .GlobalEnv) 45 | rlang::env_coalesce(new_env, as.environment(data)) 46 | data@.xData <- new_env 47 | data 48 | } 49 | 50 | #' @rdname teal_data_utilities 51 | .collapse_subsequent_chunks <- function(card) { 52 | checkmate::assert_class(card, "teal_card") 53 | init_template <- teal.reporter::teal_card() 54 | mostattributes(init_template) <- attributes(card) 55 | Reduce( 56 | function(x, this) { 57 | l <- length(x) 58 | if ( 59 | l && 60 | inherits(x[[l]], "code_chunk") && 61 | inherits(this, "code_chunk") && 62 | identical(attr(x[[l]], "params"), attr(this, "params")) 63 | ) { 64 | x[[length(x)]] <- do.call( 65 | teal.reporter::code_chunk, 66 | args = c( 67 | list(code = paste(x[[l]], this, sep = "\n")), 68 | attr(x[[l]], "params") 69 | ) 70 | ) 71 | x 72 | } else { 73 | c(x, this) 74 | } 75 | }, 76 | init = init_template, 77 | x = card 78 | ) 79 | } 80 | -------------------------------------------------------------------------------- /tests/testthat/test-reporter_previewer_module.R: -------------------------------------------------------------------------------- 1 | testthat::describe("reporter_previewer_module", { 2 | testthat::it("returns teal_module with previewer class", { 3 | module <- reporter_previewer_module(label = "test") 4 | testthat::expect_s3_class(module, "teal_module") 5 | testthat::expect_true("teal_module_previewer" %in% class(module)) 6 | testthat::expect_equal(module$label, "test") 7 | testthat::expect_equal(module$path, "test") 8 | testthat::expect_true(isTRUE(attr(module, "teal_bookmarkable"))) 9 | }) 10 | 11 | testthat::it("uses default label", { 12 | module <- reporter_previewer_module() 13 | testthat::expect_equal(module$label, "Report Previewer") 14 | testthat::expect_equal(module$path, "Report Previewer") 15 | }) 16 | 17 | testthat::it("throws error when label is not a string", { 18 | testthat::expect_error( 19 | reporter_previewer_module(label = 123), 20 | "Assertion on 'label' failed" 21 | ) 22 | }) 23 | 24 | testthat::it("throws error when server_args is not a named list", { 25 | testthat::expect_error( 26 | reporter_previewer_module(label = "test", server_args = list(1, 2)), 27 | "Assertion on 'server_args' failed" 28 | ) 29 | }) 30 | 31 | testthat::it("throws error when server_args has invalid names", { 32 | testthat::expect_error( 33 | reporter_previewer_module(label = "test", server_args = list(invalid_arg = 1)), 34 | paste0( 35 | "Assertion on 'all\\(names\\(server_args\\) %in% names\\(formals\\", 36 | "(teal\\.reporter::reporter_previewer_srv\\)\\)\\)' failed" 37 | ) 38 | ) 39 | }) 40 | 41 | testthat::it("accepts valid server_args", { 42 | testthat::expect_no_error( 43 | reporter_previewer_module( 44 | label = "test", 45 | server_args = list() 46 | ) 47 | ) 48 | }) 49 | 50 | testthat::it("creates module with server and ui functions", { 51 | module <- reporter_previewer_module(label = "test") 52 | testthat::expect_true(is.function(module$server)) 53 | testthat::expect_true(is.function(module$ui)) 54 | }) 55 | 56 | testthat::it("stores server_args", { 57 | server_args <- list(previewer_buttons = c("preview", "download")) 58 | module <- reporter_previewer_module(label = "test", server_args = server_args) 59 | testthat::expect_equal(module$server_args, server_args) 60 | }) 61 | 62 | testthat::it("processes multiple server_args", { 63 | server_args <- list( 64 | previewer_buttons = c("preview", "download"), 65 | global_knitr = list(echo = FALSE), 66 | rmd_yaml_args = list(title = "Test Report"), 67 | rmd_output = "html_document" 68 | ) 69 | 70 | previewer_module <- reporter_previewer_module( 71 | label = "test", 72 | server_args = server_args 73 | ) 74 | 75 | testthat::expect_equal(previewer_module$server_args, server_args) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /man/validate_one_row_per_id.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_one_row_per_id} 4 | \alias{validate_one_row_per_id} 5 | \title{Validate that dataset has unique rows for key variables} 6 | \usage{ 7 | validate_one_row_per_id(x, key = c("USUBJID", "STUDYID")) 8 | } 9 | \arguments{ 10 | \item{x}{(\code{data.frame})} 11 | 12 | \item{key}{(\code{character}) Vector of ID variables from \code{x} that identify unique records.} 13 | } 14 | \description{ 15 | This function is a wrapper for \code{shiny::validate}. 16 | } 17 | \examples{ 18 | iris$id <- rep(1:50, times = 3) 19 | ui <- fluidPage( 20 | selectInput( 21 | inputId = "species", 22 | label = "Select species", 23 | choices = c("setosa", "versicolor", "virginica"), 24 | selected = "setosa", 25 | multiple = TRUE 26 | ), 27 | plotOutput("plot") 28 | ) 29 | server <- function(input, output) { 30 | output$plot <- renderPlot({ 31 | iris_f <- iris[iris$Species \%in\% input$species, ] 32 | validate_one_row_per_id(iris_f, key = c("id")) 33 | 34 | hist(iris_f$Sepal.Length, breaks = 5) 35 | }) 36 | } 37 | if (interactive()) { 38 | shinyApp(ui, server) 39 | } 40 | 41 | } 42 | \section{Examples in Shinylive}{ 43 | \describe{ 44 | \item{example-1}{ 45 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIG1GtAM4ASWgBM5ixnFTsAjIgCsABlzTx8JbSALzSAMz8ECq0jsrU0XYAClAA5nDsAtLSlnDUcGIAkhCoKqQZEFlZQiWkBQ6hfGCWqPm0cJaNuJmV1FD0uSHSjQDKufmk2S0EbR143VkEABZEtATtgwQZTXCkRJZQnUNgUoyWq0TURIyHjRLmKUKrB2A8XRWVOXlicPVHObv7TrzaQwFTUcSoPKDfTGbqvbqQoikADypRqW0RpEakUiOUYJ1iqnU4i01VKfiIaNKOhA3UppBq1kxsWcEDsIkSlzKtPeVQslgA+kpYuYrMBRTZhlMZtIAKRCWXSMmkazNVrtPwAXWBEm49ig5AFJDgAsYRAA7gKWowBfZ2BKhX4ANZwVgbLb2bGRYGLKxlB1KayjVDcDAAGQoKVIiz8TDgUCdQVCnkiWQAvpE02ZhfbhKJxFJtDTupZfRBWABBdDsaJ+PEnTMCMBpzVAA}{Open in Shinylive} 46 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIG1GtAM4ASWgBM5ixnFTsAjIgCsABlzTx8JbSALzSAMz8ECq0jsrU0XYAClAA5nDsAtLSlnDUcGIAkhCoKqQZEFlZQiWkBQ6hfGCWqPm0cJaNuJmV1FD0uSHSjQDKufmk2S0EbR143VkEABZEtATtgwQZTXCkRJZQnUNgUoyWq0TURIyHjRLmKUKrB2A8XRWVOXlicPVHObv7TrzaQwFTUcSoPKDfTGbqvbqQoikADypRqW0RpEakUiOUYJ1iqnU4i01VKfiIaNKOhA3UppBq1kxsWcEDsIkSlzKtPeVQslgA+kpYuYrMBRTZhlMZtIAKRCWXSMmkazNVrtPwAXWBEm49ig5AFJDgAsYRAA7gKWowBfZ2BKhX4ANZwVgbLb2bGRYGLKxlB1KayjVDcDAAGQoKVIiz8TDgUCdQVCnkiWQAvpE02ZhfbhKJxFJtDTupZfRBWABBdDsaJ+PEnTMCMBpzVAA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 47 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 48 | } 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /man/validate_has_variable.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_has_variable} 4 | \alias{validate_has_variable} 5 | \title{Validates that dataset contains specific variable} 6 | \usage{ 7 | validate_has_variable(data, varname, msg) 8 | } 9 | \arguments{ 10 | \item{data}{(\code{data.frame})} 11 | 12 | \item{varname}{(\code{character(1)}) name of variable to check for in \code{data}} 13 | 14 | \item{msg}{(\code{character(1)}) message to display if \code{data} does not include \code{varname}} 15 | } 16 | \description{ 17 | This function is a wrapper for \code{shiny::validate}. 18 | } 19 | \examples{ 20 | data <- data.frame( 21 | one = rep("a", length.out = 20), 22 | two = rep(c("a", "b"), length.out = 20) 23 | ) 24 | ui <- fluidPage( 25 | selectInput( 26 | "var", 27 | "Select variable", 28 | choices = c("one", "two", "three", "four"), 29 | selected = "one" 30 | ), 31 | verbatimTextOutput("summary") 32 | ) 33 | 34 | server <- function(input, output) { 35 | output$summary <- renderText({ 36 | validate_has_variable(data, input$var) 37 | paste0("Selected treatment variables: ", paste(input$var, collapse = ", ")) 38 | }) 39 | } 40 | if (interactive()) { 41 | shinyApp(ui, server) 42 | } 43 | } 44 | \section{Examples in Shinylive}{ 45 | \describe{ 46 | \item{example-1}{ 47 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOkkZAXmmM4qFyh8eNLUFADmpAAWGEQqpNLeAEwADDy4rtKkAO5ECT5+7AQBUEG40kH0QWkh4VExcXkp-BDNKrQ2ytRtZgAKUGFwLhBuAM5woWIAkhCocUNubkESLKUZi2AAyuNwYtLLjLRQ9KGrwwsEkUS0BHAjeUVBHqXlYNlEz0FRvnAfYEqxjCq6TOo22YjgZjyjwgPzAGTSGSkjHolloMF0cAAHqQAPJxWakAIjFQwGAsVhVATNARjRhIjqqdTiLRCAllWKkAk6EAZDkEgAkxNJ5I6vggZhEGOx7B5IL23FodjgAH1IlARsr9odjoM7FAyqy4vz9s0FtJUOryMkAlsJuRIaRfJZ4GR5QcjqERogXmULSNyOxDaRjSwysRqNQ0GMocEqqbpABfZoJgS0JTSQPCUTiKTabkZEaRISsACC6HYbTKtKRybACYAukA}{Open in Shinylive} 48 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOkkZAXmmM4qFyh8eNLUFADmpAAWGEQqpNLeAEwADDy4rtKkAO5ECT5+7AQBUEG40kH0QWkh4VExcXkp-BDNKrQ2ytRtZgAKUGFwLhBuAM5woWIAkhCocUNubkESLKUZi2AAyuNwYtLLjLRQ9KGrwwsEkUS0BHAjeUVBHqXlYNlEz0FRvnAfYEqxjCq6TOo22YjgZjyjwgPzAGTSGSkjHolloMF0cAAHqQAPJxWakAIjFQwGAsVhVATNARjRhIjqqdTiLRCAllWKkAk6EAZDkEgAkxNJ5I6vggZhEGOx7B5IL23FodjgAH1IlARsr9odjoM7FAyqy4vz9s0FtJUOryMkAlsJuRIaRfJZ4GR5QcjqERogXmULSNyOxDaRjSwysRqNQ0GMocEqqbpABfZoJgS0JTSQPCUTiKTabkZEaRISsACC6HYbTKtKRybACYAukA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 49 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /tests/testthat/test-zzz.R: -------------------------------------------------------------------------------- 1 | testthat::describe(".onLoad: Initialised options on package load", { 2 | it("that are unset are loaded with defaults", { 3 | testthat::skip_if(getOption("testthat_interactive")) 4 | withr::with_options( 5 | list( 6 | teal.show_js_log = NULL, 7 | teal.lockfile.mode = NULL, 8 | shiny.sanitize.errors = NULL, 9 | teal.sidebar.position = NULL, 10 | teal.sidebar.width = NULL, 11 | teal.reporter.nav_buttons = NULL, 12 | teal.show_src = NULL 13 | ), 14 | { 15 | testthat::expect_no_error(.onLoad()) 16 | testthat::expect_equal(getOption("teal.show_js_log"), FALSE) 17 | testthat::expect_equal(getOption("teal.lockfile.mode"), "auto") 18 | testthat::expect_equal(getOption("shiny.sanitize.errors"), FALSE) 19 | testthat::expect_equal(getOption("teal.sidebar.position"), "left") 20 | testthat::expect_equal(getOption("teal.sidebar.width"), 250) 21 | testthat::expect_equal(getOption("teal.reporter.nav_buttons"), c("preview", "download", "load", "reset")) 22 | testthat::expect_equal(getOption("teal.show_src"), TRUE) 23 | } 24 | ) 25 | }) 26 | 27 | it("are retained and not overwritten", { 28 | testthat::skip_if(getOption("testthat_interactive")) 29 | withr::with_options( 30 | list( 31 | teal.show_js_log = TRUE, 32 | teal.lockfile.mode = "manual", 33 | shiny.sanitize.errors = TRUE, 34 | teal.sidebar.position = "right", 35 | teal.sidebar.width = 300, 36 | teal.reporter.nav_buttons = c("preview", "download"), 37 | teal.show_src = FALSE 38 | ), 39 | { 40 | testthat::expect_no_error(.onLoad()) 41 | testthat::expect_equal(getOption("teal.show_js_log"), TRUE) 42 | testthat::expect_equal(getOption("teal.lockfile.mode"), "manual") 43 | testthat::expect_equal(getOption("shiny.sanitize.errors"), TRUE) 44 | testthat::expect_equal(getOption("teal.sidebar.position"), "right") 45 | testthat::expect_equal(getOption("teal.sidebar.width"), 300) 46 | testthat::expect_equal(getOption("teal.reporter.nav_buttons"), c("preview", "download")) 47 | testthat::expect_equal(getOption("teal.show_src"), FALSE) 48 | } 49 | ) 50 | }) 51 | }) 52 | 53 | testthat::describe(".onAttach: packageStartupMessage", { 54 | it("shows version message", { 55 | testthat::skip_if(getOption("testthat_interactive")) 56 | 57 | testthat::expect_message( 58 | .onAttach(), 59 | regexp = "You are using teal version", 60 | fixed = TRUE 61 | ) 62 | }) 63 | 64 | it("includes version number in message", { 65 | testthat::skip_if(getOption("testthat_interactive")) 66 | 67 | version <- read.dcf(system.file("DESCRIPTION", package = "teal"))[, "Version"] 68 | testthat::expect_message( 69 | .onAttach(), 70 | regexp = version, 71 | fixed = TRUE 72 | ) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /man/module_teal.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_teal.R 3 | \name{module_teal} 4 | \alias{module_teal} 5 | \alias{ui_teal} 6 | \alias{srv_teal} 7 | \title{\code{teal} main module} 8 | \usage{ 9 | ui_teal(id, modules) 10 | 11 | srv_teal( 12 | id, 13 | data, 14 | modules, 15 | filter = teal_slices(), 16 | reporter = teal.reporter::Reporter$new() 17 | ) 18 | } 19 | \arguments{ 20 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 21 | 22 | \item{modules}{(\code{teal_modules}) 23 | \code{teal_modules} object. These are the specific output modules which 24 | will be displayed in the \code{teal} application. See \code{\link[=modules]{modules()}} and \code{\link[=module]{module()}} for 25 | more details.} 26 | 27 | \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) 28 | The data which application will depend on.} 29 | 30 | \item{filter}{(\code{teal_slices}) Optionally, 31 | specifies the initial filter using \code{\link[=teal_slices]{teal_slices()}}.} 32 | 33 | \item{reporter}{(\code{Reporter}) object used to store report contents. Set to \code{NULL} to globally disable reporting.} 34 | } 35 | \value{ 36 | \code{NULL} invisibly 37 | } 38 | \description{ 39 | Module to create a \code{teal} app as a Shiny Module. 40 | } 41 | \details{ 42 | This module can be used instead of \code{\link[=init]{init()}} in custom Shiny applications. Unlike \code{\link[=init]{init()}}, it doesn't 43 | automatically include \code{\link{module_session_info}}. 44 | 45 | Module is responsible for creating the main \code{shiny} app layout and initializing all the necessary 46 | components. This module establishes reactive connection between the input \code{data} and every other 47 | component in the app. Reactive change of the \code{data} passed as an argument, reloads the app and 48 | possibly keeps all input settings the same so the user can continue where one left off. 49 | \subsection{data flow in \code{teal} application}{ 50 | 51 | This module supports multiple data inputs but eventually, they are all converted to \code{reactive} 52 | returning \code{teal_data} in this module. On this \verb{reactive teal_data} object several actions are 53 | performed: 54 | \itemize{ 55 | \item data loading in \code{\link{module_init_data}} 56 | \item data filtering in \code{\link{module_filter_data}} 57 | \item data transformation in \code{\link{module_transform_data}} 58 | } 59 | } 60 | 61 | \subsection{Fallback on failure}{ 62 | 63 | \code{teal} is designed in such way that app will never crash if the error is introduced in any 64 | custom \code{shiny} module provided by app developer (e.g. \code{\link[=teal_data_module]{teal_data_module()}}, \code{\link[=teal_transform_module]{teal_transform_module()}}). 65 | If any module returns a failing object, the app will halt the evaluation and display a warning message. 66 | App user should always have a chance to fix the improper input and continue without restarting the session. 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /man/module_validate_error.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_validate_error.R 3 | \name{module_validate_error} 4 | \alias{module_validate_error} 5 | \alias{ui_validate_error} 6 | \alias{srv_validate_error} 7 | \alias{ui_check_class_teal_data} 8 | \alias{srv_check_class_teal_data} 9 | \alias{ui_check_module_datanames} 10 | \alias{srv_check_module_datanames} 11 | \title{Execute and validate \code{teal_data_module}} 12 | \usage{ 13 | ui_validate_error(id) 14 | 15 | srv_validate_error(id, data, validate_shiny_silent_error) 16 | 17 | ui_check_class_teal_data(id) 18 | 19 | srv_check_class_teal_data(id, data) 20 | 21 | ui_check_module_datanames(id) 22 | 23 | srv_check_module_datanames(id, data, modules) 24 | } 25 | \arguments{ 26 | \item{id}{(\code{character(1)}) \code{shiny} module instance id.} 27 | 28 | \item{data}{(\code{teal_data}, \code{teal_data_module}, or \code{reactive} returning \code{teal_data}) 29 | The data which application will depend on.} 30 | 31 | \item{validate_shiny_silent_error}{(\code{logical}) If \code{TRUE}, then \code{shiny.silent.error} is validated and} 32 | 33 | \item{modules}{(\code{teal_modules} or \code{teal_module}) For \code{datanames} validation purpose} 34 | } 35 | \value{ 36 | \code{reactive} \code{teal_data} 37 | } 38 | \description{ 39 | This is a low level module to handle \code{teal_data_module} execution and validation. 40 | \code{\link[=teal_transform_module]{teal_transform_module()}} inherits from \code{\link[=teal_data_module]{teal_data_module()}} so it is handled by this module too. 41 | \code{\link[=srv_teal]{srv_teal()}} accepts various \code{data} objects and eventually they are all transformed to \code{reactive} 42 | \code{\link[teal.data:teal_data]{teal.data::teal_data()}} which is a standard data class in whole \code{teal} framework. 43 | } 44 | \section{data validation}{ 45 | 46 | 47 | Executed \code{\link[=teal_data_module]{teal_data_module()}} is validated and output is validated for consistency. 48 | Output \code{data} is invalid if: 49 | \enumerate{ 50 | \item \code{\link[=teal_data_module]{teal_data_module()}} is invalid if server doesn't return \code{reactive}. \strong{Immediately crashes an app!} 51 | \item \code{reactive} throws a \code{shiny.error} - happens when module creating \code{\link[teal.data:teal_data]{teal.data::teal_data()}} fails. 52 | \item \code{reactive} returns \code{qenv.error} - happens when \code{\link[teal.data:teal_data]{teal.data::teal_data()}} evaluates a failing code. 53 | \item \code{reactive} object doesn't return \code{\link[teal.data:teal_data]{teal.data::teal_data()}}. 54 | \item \code{\link[teal.data:teal_data]{teal.data::teal_data()}} object lacks any \code{datanames} specified in the \code{modules} argument. 55 | } 56 | 57 | \code{teal} (observers in \code{srv_teal}) always waits to render an app until \code{reactive} \code{teal_data} is 58 | returned. If error 2-4 occurs, relevant error message is displayed to the app user. Once the issue is 59 | resolved, the app will continue to run. \code{teal} guarantees that errors in data don't crash the app 60 | (except error 1). 61 | } 62 | 63 | \keyword{internal} 64 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-reporter.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | 4 | testthat::test_that("e2e: reporter tab is visible when reporter is specified (default)", { 5 | skip_if_too_deep(5) 6 | app <- TealAppDriver$new( 7 | init( 8 | data = simple_teal_data(), 9 | modules = example_module(label = "Module with Reporter") 10 | ) 11 | ) 12 | 13 | testthat::expect_true(app$is_visible(selector = "#teal-reporter_menu_container")) 14 | app$stop() 15 | }) 16 | 17 | testthat::test_that("e2e: reporter tab is visible when the teal ui creation is delayed", { 18 | skip_if_too_deep(5) 19 | app <- shinytest2::AppDriver$new( 20 | shiny::shinyApp( 21 | ui = bslib::page_fluid( 22 | uiOutput("teal_as_shiny_module") 23 | ), 24 | server = function(input, output, session) { 25 | mods <- modules( 26 | example_module() 27 | ) 28 | output$teal_as_shiny_module <- renderUI({ 29 | ui_teal("teal", mods) 30 | }) 31 | srv_teal("teal", data = teal_data(iris = iris), modules = mods) 32 | } 33 | ), 34 | height = 1000, 35 | width = 1000 36 | ) 37 | 38 | # Runs the same check as `app$is_visible` to check if the selector is visible 39 | testthat::expect_true( 40 | unlist( 41 | app$get_js( 42 | sprintf( 43 | "Array.from(document.querySelectorAll('%s')).map(el => el.checkVisibility({}))", 44 | "#teal-reporter_menu_container" 45 | ) 46 | ) 47 | ) 48 | ) 49 | app$stop() 50 | }) 51 | 52 | testthat::test_that("e2e: reporter card can be customized", { 53 | skip("TODO") 54 | }) 55 | 56 | testthat::test_that("e2e: reporter tab is not created if app has no reporter", { 57 | skip_if_too_deep(5) 58 | app <- TealAppDriver$new( 59 | init( 60 | data = simple_teal_data(), 61 | modules = module(), 62 | reporter = NULL 63 | ) 64 | ) 65 | testthat::expect_null(app$get_html("#teal-reporter_menu_container")) 66 | app$stop() 67 | }) 68 | 69 | testthat::test_that("e2e: adding a report card in a module adds it in the report previewer tab", { 70 | skip_if_too_deep(5) 71 | app <- TealAppDriver$new( 72 | init( 73 | data = simple_teal_data(), 74 | modules = example_module(label = "Module with Reporter") 75 | ) 76 | ) 77 | 78 | # Add new card with label and comment 79 | app$click(app$namespaces()$base_id("add_reporter_wrapper-reporter_add-add_report_card_button")) 80 | 81 | app$set_input( 82 | app$namespaces()$base_id("add_reporter_wrapper-reporter_add-label"), 83 | "Card name" 84 | ) 85 | app$click(app$namespaces()$base_id("add_reporter_wrapper-reporter_add-add_card_ok")) 86 | 87 | # Check whether card was added 88 | app$run_js("document.querySelector('#teal-preview_report-preview_button').click();") # skipping menu hovering 89 | app$wait_for_idle() 90 | accordion_selector <- "#teal-preview_report-preview_content-reporter_cards" 91 | testthat::expect_match(app$get_text(selector = paste(accordion_selector, ".accordion-title")), "Card name") 92 | app$stop() 93 | }) 94 | -------------------------------------------------------------------------------- /vignettes/blueprint/filter_panel.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Filter Panel" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Filter Panel} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ## Introduction 12 | 13 | <style> 14 | .filter-image { 15 | float: right; 16 | max-width: 33%; 17 | max-height: 300px; 18 | transition: transform 0.3s ease, max-height 0.3s ease; 19 | transform-origin: top right; 20 | cursor: zoom-in; 21 | } 22 | 23 | .filter-image:hover { 24 | transform: scale(3); 25 | z-index: 10; 26 | } 27 | </style> 28 | 29 | 30 | <a href="images/filterpanel.png" target="_blank"> 31 | <img src="images/filterpanel.png" class="filter-image"/> 32 | </a> 33 | 34 | The `teal.slice` package provides `teal` applications with the **filter panel**, a powerful tool for exploring and analyzing data, and a key component of the `teal` framework. 35 | 36 | One of the great things about the filter panel is that it comes built-in with `teal`, requiring no programming knowledge to use. 37 | 38 | The filter panel provides a convenient way for users to subset their data, simplifying the process of exploration and comprehension. 39 | Moreover, users can activate or deactivate filter states interactively, either individually or globally, using the filter panel. 40 | 41 | 42 | <div style="clear: both"></div> 43 | 44 | ## Filter flow 45 | 46 | ```{r, child="_setup.Rmd"} 47 | ``` 48 | 49 | ```{r dataflow_mermaid1, echo=FALSE} 50 | shiny::pre( 51 | class = "mermaid", 52 | " 53 | %% This is a mermaid diagram, if you see this the plot failed to render. Sorry. 54 | sequenceDiagram 55 | autonumber 56 | data->teal.slice: processed by 57 | teal.slice->>shiny inputs: creates 58 | Note over teal.slice,shiny inputs: based on data type 59 | shiny inputs->>reactive dataset: updates 60 | reactive dataset->>teal modules: processed by 61 | " 62 | ) 63 | ``` 64 | 65 | The filter panel creates subsets of data. Subsetting is achieved by creating filter states, each of which holds a logical predicate (filter condition) that is applied to single variables. These filter conditions are composed into a single expression, a call to a particular function (e.g. `dplyr::filter`), and that expression is evaluated to create a filtered data subset. 66 | 67 | The process is entirely interactive. Filter states can be created, removed, and changed at will, however, the app developer may choose to constrain or even restrict them. 68 | 69 | When a filter state is created, the filter panel generates a _filter card_ with `shiny` inputs appropriate to the type of the variable, e.g. range selectors for numeric columns and sets of checkboxes or drop-down menus for categorical ones. 70 | 71 | As users interact with the filter cards, the subsetting complete expression is updated and filtered data is recomputed. 72 | 73 | The filtered data is passed to `teal` modules for downstream analysis. The subsetting expression is returned along with the data, ensuring an unbroken track record of the entire analysis. Signature of the data are also stored to ensure reproducible results. 74 | 75 | -------------------------------------------------------------------------------- /tests/testthat/test-validate_has_data.R: -------------------------------------------------------------------------------- 1 | data <- data.frame(x = 1:10, y = c(1:9, NA), z = c(Inf, 2:10)) 2 | 3 | testthat::test_that("validate_has_data throws no error when data has at least as many rows as min_nrow", { 4 | testthat::expect_silent(validate_has_data(data, 10)) 5 | testthat::expect_silent(validate_has_data(data, 5)) 6 | }) 7 | 8 | testthat::test_that("validate_has_data throws error when min_nrow > #rows of data", { 9 | testthat::expect_error(validate_has_data(data, 11)) 10 | }) 11 | 12 | testthat::test_that("validate_has_data accepts logical complete argument", { 13 | testthat::expect_silent(validate_has_data(data[, c("x", "z")], 10, complete = TRUE)) 14 | testthat::expect_silent(validate_has_data(data[, c("x", "z")], 10, complete = FALSE)) 15 | }) 16 | 17 | testthat::test_that("validate_has_data throws error when data has NA and complete is set to TRUE", { 18 | testthat::expect_error(validate_has_data(data[, c("x", "y")], 10, complete = TRUE)) 19 | }) 20 | 21 | testthat::test_that("validate_has_data accepts logical allow_inf argument", { 22 | testthat::expect_silent(validate_has_data(data[, c("x", "y")], 10, allow_inf = FALSE)) 23 | testthat::expect_error(validate_has_data(data[, c("x", "y")], 10, complete = TRUE, allow_inf = FALSE)) 24 | }) 25 | 26 | testthat::test_that("validate_has_data accepts throws error when data has Inf values and allow_inf is set to FALSE", { 27 | testthat::expect_error(validate_has_data(data[, c("x", "z")], 10, allow_inf = FALSE)) 28 | }) 29 | 30 | testthat::test_that("validate_has_data accepts throws error when data has Inf values and allow_inf is set to FALSE", { 31 | testthat::expect_error(validate_has_data(data[, c("x", "z")], 10, allow_inf = FALSE)) 32 | testthat::expect_error(validate_has_data(data[, c("x", "z")], 10, complete = TRUE, allow_inf = FALSE)) 33 | }) 34 | 35 | testthat::test_that("validate_has_data allow_inf argument ignores non-numeric columns", { 36 | data <- data.frame(x = 3:5, w = c("A", "B", "C"), z = c(Inf, 4, 5)) 37 | testthat::expect_silent(validate_has_data(data[, c("x", "w")], 3, allow_inf = FALSE)) 38 | testthat::expect_error(validate_has_data(data, 3, allow_inf = FALSE)) 39 | }) 40 | 41 | testthat::test_that("validate_has_data returns message when msg argument is set", { 42 | testthat::expect_error( 43 | validate_has_data(data, 11, msg = "Check data."), 44 | "Minimum number of records not met: >= 11 records required.\nCheck data." 45 | ) 46 | }) 47 | 48 | testthat::test_that("validate_has_data returns message msg argument is set and complete is set to TRUE", { 49 | testthat::expect_error( 50 | validate_has_data(data[, c("x", "y")], 11, complete = TRUE, msg = "Check data."), 51 | "Number of complete cases is less than: 11\nCheck data." 52 | ) 53 | }) 54 | 55 | testthat::test_that("validate_has_data returns throws error with non-character msg input", { 56 | testthat::expect_error( 57 | validate_has_data(data, 10, msg = 1), 58 | "Assertion on 'msg' failed: Must be of type 'string' \\(or 'NULL'\\), not 'double'" 59 | ) 60 | 61 | testthat::expect_error( 62 | validate_has_data(data, 10, msg = TRUE), 63 | "Assertion on 'msg' failed: Must be of type 'string' \\(or 'NULL'\\), not 'logical'." 64 | ) 65 | }) 66 | -------------------------------------------------------------------------------- /inst/css/custom.css: -------------------------------------------------------------------------------- 1 | /* teal custom css */ 2 | 3 | /* per tag */ 4 | 5 | body:not(.modal-open) { 6 | padding-right: 0 !important; 7 | overflow: auto !important; 8 | } 9 | 10 | /* teal_data_module modal styling */ 11 | body > div:has(~ #shiny-modal-wrapper .fade .blur_background) { 12 | transition: filter 0.3s; 13 | } 14 | 15 | body > div:has(~ #shiny-modal-wrapper .blur_background) { 16 | filter: blur(5px); 17 | } 18 | 19 | #shiny-modal.fade.in:has(.hide_background) { 20 | transition: background-color 0.3s; 21 | } 22 | 23 | #shiny-modal:has(.hide_background) { 24 | background-color: var(--bs-white); 25 | } 26 | 27 | .btn { 28 | --bs-btn-padding-x: 0.7em; 29 | --bs-btn-padding-y: 0.4em; 30 | } 31 | 32 | :root { 33 | --bs-body-font-size: 0.875rem; 34 | } 35 | 36 | .accordion-button { 37 | font-size: 0.875rem; 38 | font-weight: bold; 39 | } 40 | 41 | .teal-body .tabbable > .nav-pills { 42 | width: calc(100% - 5rem); 43 | } 44 | 45 | .teal.bookmark-popup .modal-body { 46 | padding: 0 1.5rem; 47 | } 48 | 49 | .modal-dialog:has(.teal-filter-manager-modal) { 50 | width: fit-content; 51 | } 52 | 53 | .teal-body .bslib-mb-spacing { 54 | --bslib-mb-spacer: 0; 55 | } 56 | 57 | .teal-data-module-popup .modal-dialog { 58 | --bs-modal-margin: 20px; 59 | } 60 | 61 | .teal-data-module-popup .modal-content { 62 | overflow: auto; 63 | max-height: 90vh; 64 | height: 90vh; 65 | } 66 | 67 | @media (min-width: 576px) { 68 | .teal-data-module-popup .modal-dialog { 69 | --bs-modal-width: 500px; 70 | } 71 | } 72 | 73 | @media (min-width: 992px) { 74 | .teal-data-module-popup .modal-dialog { 75 | --bs-modal-width: 90vw; 76 | } 77 | } 78 | 79 | .teal.expand-button { 80 | display: inline-flex; 81 | align-items: center; 82 | gap: 0.5rem; 83 | overflow: hidden; 84 | white-space: nowrap; 85 | max-width: 40px; 86 | transition: max-width 0.3s ease; 87 | } 88 | 89 | .teal.expand-button button { 90 | color: var(--bs-primary); 91 | background-color: transparent; 92 | border: 2px solid transparent; 93 | padding: 0.5rem; 94 | } 95 | 96 | .teal.expand-button button:disabled { 97 | color: var(--bs-dark-text-emphasis); 98 | } 99 | 100 | .teal.expand-button:hover button:disabled { 101 | border-bottom: 2px solid var(--bs-dark-bg-subtle); 102 | } 103 | 104 | .teal.expand-button .label { 105 | opacity: 0; 106 | transition: opacity 0.2s ease; 107 | } 108 | 109 | .teal.expand-button:hover { 110 | max-width: 200px; 111 | } 112 | 113 | .teal.expand-button:hover button { 114 | border-bottom: 2px solid var(--bs-primary-border-subtle); 115 | } 116 | 117 | .teal.expand-button:hover .label { 118 | opacity: 1; 119 | } 120 | 121 | .teal.primary-button { 122 | border: solid 1px var(--bs-primary); 123 | padding: 0.3em; 124 | border-radius: 5px; 125 | } 126 | 127 | .teal.primary-button.disabled { 128 | color: var(--bs-primary); 129 | pointer-events: none; 130 | opacity: 0.65; 131 | } 132 | 133 | .teal.primary-button:not(.disabled):hover { 134 | background: var(--bs-primary-bg-subtle); 135 | } 136 | 137 | [data-bs-toggle="tooltip"].disabled { 138 | cursor: not-allowed; 139 | color: var(--bs-gray); 140 | } 141 | -------------------------------------------------------------------------------- /man/validate_has_data.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_has_data} 4 | \alias{validate_has_data} 5 | \title{Validate that dataset has a minimum number of observations} 6 | \usage{ 7 | validate_has_data( 8 | x, 9 | min_nrow = NULL, 10 | complete = FALSE, 11 | allow_inf = TRUE, 12 | msg = NULL 13 | ) 14 | } 15 | \arguments{ 16 | \item{x}{(\code{data.frame})} 17 | 18 | \item{min_nrow}{(\code{numeric(1)}) Minimum allowed number of rows in \code{x}.} 19 | 20 | \item{complete}{(\code{logical(1)}) Flag specifying whether to check only complete cases. Defaults to \code{FALSE}.} 21 | 22 | \item{allow_inf}{(\code{logical(1)}) Flag specifying whether to allow infinite values. Defaults to \code{TRUE}.} 23 | 24 | \item{msg}{(\code{character(1)}) Additional message to display alongside the default message.} 25 | } 26 | \description{ 27 | This function is a wrapper for \code{shiny::validate}. 28 | } 29 | \examples{ 30 | library(teal) 31 | ui <- fluidPage( 32 | sliderInput("len", "Max Length of Sepal", 33 | min = 4.3, max = 7.9, value = 5 34 | ), 35 | plotOutput("plot") 36 | ) 37 | 38 | server <- function(input, output) { 39 | output$plot <- renderPlot({ 40 | iris_df <- iris[iris$Sepal.Length <= input$len, ] 41 | validate_has_data( 42 | iris_df, 43 | min_nrow = 10, 44 | complete = FALSE, 45 | msg = "Please adjust Max Length of Sepal" 46 | ) 47 | 48 | hist(iris_df$Sepal.Length, breaks = 5) 49 | }) 50 | } 51 | if (interactive()) { 52 | shinyApp(ui, server) 53 | } 54 | 55 | } 56 | \section{Examples in Shinylive}{ 57 | \describe{ 58 | \item{example-1}{ 59 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIF0mLDl14CVtOYqXU7AEwAKUAOZx2A6dIBnOhcRAEkIVBVSXxoKPjxpeIBZKAAPaQAZCk9SAAtpIiVpAGU4VG543D9-aRghaQBeaQAWDABmXFq0xukAdgwATk6JbhUZJoBWap4qiH9UaiJSAHkoyOj4haX4-ghdgQCRKUYHZTUNLSF1zqI1qJ0QatvSdYASLdJTxgoQxjdF6KPOY1WiMWgBAD6LiKCmkoPBwHhAVepXK1AwWQgOXysiaVyir2oFE6AF1qv4RsEoOQIbkoJCXNSoL5gTU4WCGUpZmz-HUIBCIIwiAB3HoARgADNyecQYAs4OQegAxACCGWKRmlbJgAU8PXi-zg9JkUBcACsVAFPil0pjsQUiqiKmBydJ9qzpLlwdEkVClCiytwMdk8p0mEaANYBHoTXb+AC+u3jAloRXYQnIonEUm0D2qAS9EFYKvQ7DsnUOjGOSYEYHjJKAA}{Open in Shinylive} 60 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIF0mLDl14CVtOYqXU7AEwAKUAOZx2A6dIBnOhcRAEkIVBVSXxoKPjxpeIBZKAAPaQAZCk9SAAtpIiVpAGU4VG543D9-aRghaQBeaQAWDABmXFq0xukAdgwATk6JbhUZJoBWap4qiH9UaiJSAHkoyOj4haX4-ghdgQCRKUYHZTUNLSF1zqI1qJ0QatvSdYASLdJTxgoQxjdF6KPOY1WiMWgBAD6LiKCmkoPBwHhAVepXK1AwWQgOXysiaVyir2oFE6AF1qv4RsEoOQIbkoJCXNSoL5gTU4WCGUpZmz-HUIBCIIwiAB3HoARgADNyecQYAs4OQegAxACCGWKRmlbJgAU8PXi-zg9JkUBcACsVAFPil0pjsQUiqiKmBydJ9qzpLlwdEkVClCiytwMdk8p0mEaANYBHoTXb+AC+u3jAloRXYQnIonEUm0D2qAS9EFYKvQ7DsnUOjGOSYEYHjJKAA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 61 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 62 | } 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /tests/testthat/test-module_teal_data.R: -------------------------------------------------------------------------------- 1 | testthat::test_that("ui_teal_data_module returns tagList with wrapper and validation UI via init", { 2 | data_module <- teal_data_module( 3 | ui = function(id) tags$div("test"), 4 | server = function(id) reactive(teal_data(iris = iris)) 5 | ) 6 | app <- init( 7 | data = data_module, 8 | modules = example_module(label = "Example Module") 9 | ) 10 | testthat::expect_identical(class(app$ui), "function") 11 | testthat::expect_identical(class(app$server), "function") 12 | }) 13 | 14 | testthat::test_that("srv_teal_data_module handles valid teal_data via srv_teal", { 15 | shiny::testServer( 16 | app = srv_teal, 17 | args = list( 18 | id = "test", 19 | data = teal_data_module( 20 | ui = function(id) NULL, 21 | server = function(id) { 22 | moduleServer(id, function(input, output, session) { 23 | reactive(teal_data(iris = iris)) 24 | }) 25 | } 26 | ), 27 | modules = modules(example_module()) 28 | ), 29 | expr = { 30 | session$flushReact() 31 | testthat::expect_s4_class(data_handled(), "teal_data") 32 | } 33 | ) 34 | }) 35 | 36 | testthat::test_that("srv_teal_data_module handles error in data_module via srv_teal", { 37 | shiny::testServer( 38 | app = srv_teal, 39 | args = list( 40 | id = "test", 41 | data = teal_data_module( 42 | ui = function(id) NULL, 43 | server = function(id) { 44 | moduleServer(id, function(input, output, session) { 45 | reactive(stop("test error")) 46 | }) 47 | } 48 | ), 49 | modules = modules(example_module()) 50 | ), 51 | expr = { 52 | session$flushReact() 53 | testthat::expect_s3_class(data_handled(), "error") 54 | } 55 | ) 56 | }) 57 | 58 | testthat::test_that("srv_validate_reactive_teal_data handles qenv.error via srv_teal", { 59 | shiny::testServer( 60 | app = srv_teal, 61 | args = list( 62 | id = "test", 63 | data = teal_data_module( 64 | ui = function(id) NULL, 65 | server = function(id) { 66 | moduleServer(id, function(input, output, session) { 67 | reactive(within(teal_data(), stop("qenv error"))) 68 | }) 69 | } 70 | ), 71 | modules = modules(example_module()) 72 | ), 73 | expr = { 74 | session$flushReact() 75 | testthat::expect_s3_class(data_handled(), "qenv.error") 76 | } 77 | ) 78 | }) 79 | 80 | testthat::test_that("srv_validate_reactive_teal_data handles shiny.error via srv_teal", { 81 | shiny::testServer( 82 | app = srv_teal, 83 | args = list( 84 | id = "test", 85 | data = teal_data_module( 86 | ui = function(id) NULL, 87 | server = function(id) { 88 | moduleServer(id, function(input, output, session) { 89 | reactive(structure(list(message = "test error"), class = c("error", "condition"))) 90 | }) 91 | } 92 | ), 93 | modules = modules(example_module()) 94 | ), 95 | expr = { 96 | session$flushReact() 97 | testthat::expect_s3_class(data_handled(), "error") 98 | } 99 | ) 100 | }) 101 | -------------------------------------------------------------------------------- /R/teal_data_module.R: -------------------------------------------------------------------------------- 1 | #' Data module for `teal` applications 2 | #' 3 | #' @description 4 | #' `r lifecycle::badge("experimental")` 5 | #' 6 | #' Create a `teal_data_module` object and evaluate code on it with history tracking. 7 | #' 8 | #' @details 9 | #' `teal_data_module` creates a `shiny` module to interactively supply or modify data in a `teal` application. 10 | #' The module allows for running any code (creation _and_ some modification) after the app starts or reloads. 11 | #' The body of the server function will be run in the app rather than in the global environment. 12 | #' This means it will be run every time the app starts, so use sparingly. 13 | #' 14 | #' Pass this module instead of a `teal_data` object in a call to [init()]. 15 | #' Note that the server function must always return a `teal_data` object wrapped in a reactive expression. 16 | #' 17 | #' See vignette `vignette("data-as-shiny-module", package = "teal")` for more details. 18 | #' 19 | #' @param ui (`function(id)`) 20 | #' `shiny` module UI function; must only take `id` argument 21 | #' @param server (`function(id)`) 22 | #' `shiny` module server function; must only take `id` argument; 23 | #' must return reactive expression containing `teal_data` object 24 | #' @param label (`character(1)`) Label of the module. 25 | #' @param once (`logical(1)`) 26 | #' If `TRUE`, the data module will be shown only once and will disappear after successful data loading. 27 | #' App user will no longer be able to interact with this module anymore. 28 | #' If `FALSE`, the data module can be reused multiple times. 29 | #' App user will be able to interact and change the data output from the module multiple times. 30 | #' 31 | #' @return 32 | #' `teal_data_module` returns a list of class `teal_data_module` containing two elements, `ui` and 33 | #' `server` provided via arguments. 34 | #' 35 | #' @examples 36 | #' tdm <- teal_data_module( 37 | #' ui = function(id) { 38 | #' ns <- NS(id) 39 | #' actionButton(ns("submit"), label = "Load data") 40 | #' }, 41 | #' server = function(id) { 42 | #' moduleServer(id, function(input, output, session) { 43 | #' eventReactive(input$submit, { 44 | #' data <- within( 45 | #' teal_data(), 46 | #' { 47 | #' dataset1 <- iris 48 | #' dataset2 <- mtcars 49 | #' } 50 | #' ) 51 | #' 52 | #' data 53 | #' }) 54 | #' }) 55 | #' } 56 | #' ) 57 | #' 58 | #' @name teal_data_module 59 | #' @seealso [`teal.data::teal_data-class`], [teal.code::qenv()] 60 | #' 61 | #' @export 62 | teal_data_module <- function(ui, server, label = "data module", once = TRUE) { 63 | checkmate::assert_function(ui, args = "id", nargs = 1) 64 | checkmate::assert_function(server, args = "id", nargs = 1) 65 | checkmate::assert_string(label) 66 | checkmate::assert_flag(once) 67 | structure( 68 | list( 69 | ui = ui, 70 | server = function(id) { 71 | data_out <- server(id) 72 | decorate_err_msg( 73 | assert_reactive(data_out), 74 | pre = sprintf("From: 'teal_data_module()':\nA 'teal_data_module' with \"%s\" label:", label), 75 | post = "Please make sure that this module returns a 'reactive` object containing 'teal_data' class of object." # nolint: line_length_linter. 76 | ) 77 | } 78 | ), 79 | label = label, 80 | class = "teal_data_module", 81 | once = once 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /man/make_teal_transform_server.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/teal_transform_module.R 3 | \name{make_teal_transform_server} 4 | \alias{make_teal_transform_server} 5 | \title{Make teal_transform_module's server} 6 | \usage{ 7 | make_teal_transform_server(expr) 8 | } 9 | \arguments{ 10 | \item{expr}{(\code{language}) 11 | An R call which will be evaluated within \code{\link[teal.data:teal_data]{teal.data::teal_data}} environment.} 12 | } 13 | \value{ 14 | \verb{function(id, data)} returning \code{shiny} module 15 | } 16 | \description{ 17 | A factory function to simplify creation of a \code{\link{teal_transform_module}}'s server. Specified \code{expr} 18 | is wrapped in a shiny module function and output can be passed to the \code{server} argument in 19 | \code{\link[=teal_transform_module]{teal_transform_module()}} call. Such a server function can be linked with ui and values from the 20 | inputs can be used in the expression. Object names specified in the expression will be substituted 21 | with the value of the respective input (matched by the name) - for example in 22 | \code{expression(graph <- graph + ggtitle(title))} object \code{title} will be replaced with the value of 23 | \code{input$title}. 24 | } 25 | \examples{ 26 | 27 | trim_iris <- teal_transform_module( 28 | label = "Simplified interactive transformator for iris", 29 | datanames = "iris", 30 | ui = function(id) { 31 | ns <- NS(id) 32 | numericInput(ns("n_rows"), "Subset n rows", value = 6, min = 1, max = 150, step = 1) 33 | }, 34 | server = make_teal_transform_server(expression(iris <- head(iris, n_rows))) 35 | ) 36 | 37 | app <- init( 38 | data = teal_data(iris = iris), 39 | modules = example_module(transformators = trim_iris) 40 | ) 41 | if (interactive()) { 42 | shinyApp(app$ui, app$server) 43 | } 44 | 45 | } 46 | \section{Examples in Shinylive}{ 47 | \describe{ 48 | \item{example-1}{ 49 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogNNlGtGAH1aFgM5zFXaldLMIdpUUbWYRACYq1HDsAtLS1FD0cNTSALzSfGAAypaodEq0cP7SQuSi4lLSblAeXj5QpN7K1ba0dkm4YdL+laWwcA4JSXUNeM0qtPHKahpatP46IM3hHo7SAHLJ7BP8EOGzKvAWBACSEKgqpOweoZBWjEQA7n08uIkpKvR2cKTS65c3jdIS3CoyCQAbPcYEJhgBGEFQAAeEIArAAGe52cioCFrcIAXya62kL0YUkYwxgUAA1nBXHBuK53J5vNZ8YT2HBoahGJ07JoICt7PMABZU-w8+r3CAXa52HhSgRrARoNEKXKCY7NVqkKDDZxWNVQYVdXL2O7NPyBYL6lmwdIUk1BEIlMr0yrefVuSw2Q0ygS0JTSFbCAqSEJS6TTXF2PlCVgAQXQ7HlABJBvcE4yRGtMQIwJiALpAA}{Open in Shinylive} 50 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogNNlGtGAH1aFgM5zFXaldLMIdpUUbWYRACYq1HDsAtLS1FD0cNTSALzSfGAAypaodEq0cP7SQuSi4lLSblAeXj5QpN7K1ba0dkm4YdL+laWwcA4JSXUNeM0qtPHKahpatP46IM3hHo7SAHLJ7BP8EOGzKvAWBACSEKgqpOweoZBWjEQA7n08uIkpKvR2cKTS65c3jdIS3CoyCQAbPcYEJhgBGEFQAAeEIArAAGe52cioCFrcIAXya62kL0YUkYwxgUAA1nBXHBuK53J5vNZ8YT2HBoahGJ07JoICt7PMABZU-w8+r3CAXa52HhSgRrARoNEKXKCY7NVqkKDDZxWNVQYVdXL2O7NPyBYL6lmwdIUk1BEIlMr0yrefVuSw2Q0ygS0JTSFbCAqSEJS6TTXF2PlCVgAQXQ7HlABJBvcE4yRGtMQIwJiALpAA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 51 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 52 | } 53 | } 54 | } 55 | 56 | -------------------------------------------------------------------------------- /tests/testthat/test-teal_reporter.R: -------------------------------------------------------------------------------- 1 | testthat::test_that("TealReportCard object can be initialized", { 2 | lifecycle::expect_deprecated(TealReportCard$new()) 3 | }) 4 | 5 | testthat::test_that("TealReportCard inherits from ReportCard", { 6 | testthat::expect_true(inherits(TealReportCard$new(), "ReportCard")) 7 | }) 8 | 9 | testthat::test_that("TealReportCard$new returns an object of type TealReportCard", { 10 | testthat::expect_true(inherits(TealReportCard$new(), "TealReportCard")) 11 | }) 12 | 13 | testthat::test_that("TealReportCard$get_content returns content with metadata", { 14 | card <- TealReportCard$new()$append_text("test")$append_src("test_src")$append_encodings(list(data = "test")) 15 | testthat::expect_equal(length(card$get_content()), 4) 16 | testthat::expect_equal(length(card$get_metadata()), 1) 17 | testthat::expect_identical(card$get_content()[[1]], "test") 18 | testthat::expect_identical( 19 | as.character(card$get_content()[[2]]), 20 | "test_src" 21 | ) 22 | testthat::expect_identical(card$get_content()[[4]], "\n```\ndata: test\n\n```\n") 23 | }) 24 | 25 | testthat::test_that("TealReportCard$append_src accepts a character", { 26 | card <- TealReportCard$new() 27 | testthat::expect_no_error(card$append_src("test")) 28 | }) 29 | 30 | testthat::test_that("TealReportCard$append_src returns self", { 31 | card <- TealReportCard$new() 32 | testthat::expect_identical(card$append_src("test"), card) 33 | }) 34 | 35 | testthat::test_that("TealReportCard$append_src returns title and content", { 36 | card <- TealReportCard$new() 37 | card$append_src("test") 38 | testthat::expect_identical(as.character(card$get_content()[[1]]), "test") 39 | }) 40 | 41 | testthat::test_that("TealReportCard$append_encodings accepts list of character", { 42 | card <- TealReportCard$new() 43 | testthat::expect_no_error(card$append_encodings(list(a = "test"))) 44 | }) 45 | 46 | testthat::test_that("TealReportCard$append_encodings returns self", { 47 | card <- TealReportCard$new() 48 | testthat::expect_identical(card$append_encodings(list(a = "test_encodings")), card) 49 | }) 50 | 51 | testthat::test_that("TealReportCard$append_encodings returns title and content", { 52 | card <- TealReportCard$new() 53 | card$append_encodings(list(a = "test")) 54 | testthat::expect_identical(card$get_content()[[1]], "### Selected Options") 55 | testthat::expect_identical(card$get_content()[[2]], "\n```\na: test\n\n```\n") 56 | }) 57 | 58 | testthat::test_that("TealReportCard$append_fs accepts only a teal_slices", { 59 | card <- TealReportCard$new() 60 | testthat::expect_error(card$append_fs(c(a = 1, b = 2)), 61 | regexp = "Assertion on 'fs' failed: Must inherit from class 'teal_slices', but has class 'numeric'." 62 | ) 63 | testthat::expect_no_error( 64 | card$append_fs( 65 | teal.slice::teal_slices(teal.slice::teal_slice(dataname = "a", varname = "b")) 66 | ) 67 | ) 68 | }) 69 | 70 | testthat::test_that("TealReportCard$append_fs returns self", { 71 | card <- TealReportCard$new() 72 | testthat::expect_identical( 73 | card$append_fs(teal.slice::teal_slices(teal.slice::teal_slice(dataname = "a", varname = "b"))), 74 | card 75 | ) 76 | }) 77 | 78 | testthat::test_that("TealReportCard$append_fs returns title and content", { 79 | card <- TealReportCard$new() 80 | card$append_fs(teal.slice::teal_slices(teal.slice::teal_slice(dataname = "a", varname = "b"))) 81 | testthat::expect_identical(as.character(card$get_content()[[1]]), "- Dataset name: a\n Variable name: b\n") 82 | }) 83 | -------------------------------------------------------------------------------- /man/module_filter_manager.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/module_filter_manager.R 3 | \docType{class} 4 | \encoding{UTF-8} 5 | \name{module_filter_manager} 6 | \alias{module_filter_manager} 7 | \alias{ui_filter_manager_panel} 8 | \alias{srv_filter_manager_panel} 9 | \alias{ui_filter_manager} 10 | \alias{srv_filter_manager} 11 | \alias{srv_module_filter_manager} 12 | \alias{.slicesGlobal-class} 13 | \alias{.slicesGlobal} 14 | \title{Manage multiple \code{FilteredData} objects} 15 | \usage{ 16 | ui_filter_manager_panel(id) 17 | 18 | srv_filter_manager_panel(id, slices_global) 19 | 20 | ui_filter_manager(id) 21 | 22 | srv_filter_manager(id, slices_global) 23 | 24 | srv_module_filter_manager(id, module_fd, slices_global) 25 | } 26 | \arguments{ 27 | \item{id}{(\code{character(1)}) 28 | \code{shiny} module instance id.} 29 | 30 | \item{slices_global}{(\code{reactiveVal}) 31 | containing \code{teal_slices}.} 32 | 33 | \item{module_fd}{(\code{FilteredData}) 34 | Object containing the data to be filtered in a single \code{teal} module.} 35 | } 36 | \value{ 37 | Module returns a \code{slices_global} (\code{reactiveVal}) containing a \code{teal_slices} object with mapping. 38 | } 39 | \description{ 40 | Oversee filter states across the entire application. 41 | } 42 | 43 | \section{Slices global}{ 44 | 45 | The key role in maintaining the module-specific filter states is played by the \code{.slicesGlobal} 46 | object. It is a reference class that holds the following fields: 47 | \itemize{ 48 | \item \code{all_slices} (\code{reactiveVal}) - reactive value containing all filters registered in an app. 49 | \item \code{module_slices_api} (\code{reactiveValues}) - reactive field containing references to each modules' 50 | \code{FilteredData} object methods. At this moment it is used only in \code{srv_filter_manager} to display 51 | the filter states in a table combining informations from \code{all_slices} and from 52 | \code{FilteredData$get_available_teal_slices()}. 53 | } 54 | 55 | During a session only new filters are added to \code{all_slices} unless \code{\link{module_snapshot_manager}} is 56 | used to restore previous state. Filters from \code{all_slices} can be activated or deactivated in a 57 | module which is linked (both ways) by \code{attr(, "mapping")} so that: 58 | \itemize{ 59 | \item If module's filter is added or removed in its \code{FilteredData} object, this information is passed 60 | to \code{SlicesGlobal} which updates \code{attr(, "mapping")} accordingly. 61 | \item When mapping changes in a \code{SlicesGlobal}, filters are set or removed from module's 62 | \code{FilteredData}. 63 | } 64 | } 65 | 66 | \section{Filter manager}{ 67 | 68 | Filter-manager is split into two parts: 69 | \enumerate{ 70 | \item \code{ui/srv_filter_manager_panel} - Called once for the whole app. This module observes changes in 71 | the filters in \code{slices_global} and displays them in a table utilizing information from \code{mapping}: 72 | } 73 | \itemize{ 74 | \item (\code{TRUE}) - filter is active in the module 75 | \item (\code{FALSE}) - filter is inactive in the module 76 | \item (\code{NA}) - filter is not available in the module 77 | } 78 | \enumerate{ 79 | \item \code{ui/srv_module_filter_manager} - Called once for each \code{teal_module}. Handling filter states 80 | for of single module and keeping module \code{FilteredData} consistent with \code{slices_global}, so that 81 | local filters are always reflected in the \code{slices_global} and its mapping and vice versa. 82 | } 83 | } 84 | 85 | \keyword{internal} 86 | -------------------------------------------------------------------------------- /man/validate_has_elements.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_has_elements} 4 | \alias{validate_has_elements} 5 | \title{Validates that vector has length greater than 0} 6 | \usage{ 7 | validate_has_elements(x, msg) 8 | } 9 | \arguments{ 10 | \item{x}{vector} 11 | 12 | \item{msg}{message to display} 13 | } 14 | \description{ 15 | This function is a wrapper for \code{shiny::validate}. 16 | } 17 | \examples{ 18 | data <- data.frame( 19 | id = c(1:10, 11:20, 1:10), 20 | strata = rep(c("A", "B"), each = 15) 21 | ) 22 | ui <- fluidPage( 23 | selectInput("ref1", "Select strata1 to compare", 24 | choices = c("A", "B", "C"), selected = "A" 25 | ), 26 | selectInput("ref2", "Select strata2 to compare", 27 | choices = c("A", "B", "C"), selected = "B" 28 | ), 29 | verbatimTextOutput("arm_summary") 30 | ) 31 | 32 | server <- function(input, output) { 33 | output$arm_summary <- renderText({ 34 | sample_1 <- data$id[data$strata == input$ref1] 35 | sample_2 <- data$id[data$strata == input$ref2] 36 | 37 | validate_has_elements(sample_1, "No subjects in strata1.") 38 | validate_has_elements(sample_2, "No subjects in strata2.") 39 | 40 | paste0( 41 | "Number of samples in: strata1=", length(sample_1), 42 | " comparions strata2=", length(sample_2) 43 | ) 44 | }) 45 | } 46 | if (interactive()) { 47 | shinyApp(ui, server) 48 | } 49 | } 50 | \section{Examples in Shinylive}{ 51 | \describe{ 52 | \item{example-1}{ 53 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOm0z0gLzSC7AIyI-gAMuNL+gQBMoeFBwTy4rtIAzqTMVt7SjHCo7H58YACCBWEFAEIFCdJwUAQAFpn+AKz8EK0qtDbK1B1mAApQAOZwLhBuyXDUcGIAkhCoKqQuYNlK-iXSBQDKk9OkKWmWUP7SpES+RDCoLHAlSW71RLQEcMmZ+UUb5V9gAMKVYQmUzEcE8PgKxTASQSSSBezmCyWBVWkR+O2B+1S6SgkVO52IVxudzGbl8dSeLzePg+kNKYAqeE2fwBKV2ILBTIZ0MSJKkjHolloMF0cAAHqQAPKLRHLFgwAD6yRUMBgLFYlQErQEE0YfK6qnU4i0QkRYSI0sWOhASXNpERABI5Yrlaq2F1shAzCIReL2NaSeNYKgpvKTgppHYoPaPMBI-asUdvD4TYt7at-ABde4pIMh3HhuMxuMJjJeZPzVMorMQbMSbgeSxweV1KDJeW7eBkZLsZK5pv+OkAOXOSvoACs9m8hAdsf4MBqA9I63Q7E2W22OxRSN3e1c80ORypx5P3GMSzj52AtYvrqk4MFRqTSQVB8r6CJpEQlDm969T4gZyOfwvA2KYIEGUg6h7PtQxhRc3AKC5CUYTQIDec9IhAxkwIgqDd2DJtIlaJ9iOkABfVoyIEWhv3YIRyFEcQpG0K1YTqIRWEKdB2A6QERD5SiwDIjMgA}{Open in Shinylive} 54 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOm0z0gLzSC7AIyI-gAMuNL+gQBMoeFBwTy4rtIAzqTMVt7SjHCo7H58YACCBWEFAEIFCdJwUAQAFpn+AKz8EK0qtDbK1B1mAApQAOZwLhBuyXDUcGIAkhCoKqQuYNlK-iXSBQDKk9OkKWmWUP7SpES+RDCoLHAlSW71RLQEcMmZ+UUb5V9gAMKVYQmUzEcE8PgKxTASQSSSBezmCyWBVWkR+O2B+1S6SgkVO52IVxudzGbl8dSeLzePg+kNKYAqeE2fwBKV2ILBTIZ0MSJKkjHolloMF0cAAHqQAPKLRHLFgwAD6yRUMBgLFYlQErQEE0YfK6qnU4i0QkRYSI0sWOhASXNpERABI5Yrlaq2F1shAzCIReL2NaSeNYKgpvKTgppHYoPaPMBI-asUdvD4TYt7at-ABde4pIMh3HhuMxuMJjJeZPzVMorMQbMSbgeSxweV1KDJeW7eBkZLsZK5pv+OkAOXOSvoACs9m8hAdsf4MBqA9I63Q7E2W22OxRSN3e1c80ORypx5P3GMSzj52AtYvrqk4MFRqTSQVB8r6CJpEQlDm969T4gZyOfwvA2KYIEGUg6h7PtQxhRc3AKC5CUYTQIDec9IhAxkwIgqDd2DJtIlaJ9iOkABfVoyIEWhv3YIRyFEcQpG0K1YTqIRWEKdB2A6QERD5SiwDIjMgA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 55 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 56 | } 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /R/reporter_previewer_module.R: -------------------------------------------------------------------------------- 1 | #' Create a `teal` module for previewing a report 2 | #' 3 | #' @description `r lifecycle::badge("deprecated")` 4 | #' 5 | #' This function controls the appearance of the drop-down menu for the reporter. 6 | #' It is now deprecated in favor of the options: 7 | #' - `teal.reporter.nav_buttons = c("preview", "download", "load", "reset")` to control which 8 | #' buttons will be displayed in the drop-down. 9 | #' - `teal.reporter.rmd_output`: passed to [teal.reporter::download_report_button_srv()] 10 | #' - `teal.reporter.rmd_yaml_args`: passed to [teal.reporter::download_report_button_srv()] 11 | #' - `teal.reporter.global_knitr`: passed to [teal.reporter::download_report_button_srv()] 12 | #' 13 | #' @inheritParams teal_modules 14 | #' @param server_args (named `list`) Arguments will overwrite the default `teal.reporter` options 15 | #' described in the description. 16 | #' 17 | #' @return 18 | #' `teal_module` (extended with `teal_module_previewer` class) containing the `teal.reporter` previewer functionality. 19 | #' 20 | #' @export 21 | reporter_previewer_module <- function(label = "Report Previewer", server_args = list()) { 22 | checkmate::assert_string(label) 23 | checkmate::assert_list(server_args, names = "named") 24 | checkmate::assert_true(all(names(server_args) %in% names(formals(teal.reporter::reporter_previewer_srv)))) 25 | 26 | lifecycle::deprecate_soft( 27 | when = "1.0.0", 28 | what = "reporter_previewer_module()", 29 | details = paste( 30 | "Please use `options()` to customize the reporter options:\n", 31 | "`teal.reporter.nav_buttons` to control which buttons will be displayed in the 'Report' drop-down.\n", 32 | "`teal.reporter.rmd_output` to customize the R Markdown outputs types for the report.\n", 33 | "`teal.reporter.rmd_yaml_args` to customize the widget inputs in the download report modal.\n", 34 | "`teal.reporter.global_knitr` to customize the global knitr options for the report." 35 | ) 36 | ) 37 | 38 | message("Initializing reporter_previewer_module") 39 | 40 | srv <- function(id, reporter, ...) { 41 | teal.reporter::reporter_previewer_srv(id, reporter, ...) 42 | } 43 | 44 | ui <- function(id, ...) { 45 | teal.reporter::reporter_previewer_ui(id, ...) 46 | } 47 | 48 | module <- module( 49 | label = "temporary label", 50 | server = srv, ui = ui, 51 | server_args = server_args, ui_args = list(), datanames = NULL 52 | ) 53 | # Module is created with a placeholder label and path and both are changed later. 54 | # This is to prevent another module being labeled "Report Previewer". 55 | class(module) <- c(class(module), "teal_module_previewer") 56 | module$label <- label 57 | module$path <- label 58 | attr(module, "teal_bookmarkable") <- TRUE 59 | module 60 | } 61 | 62 | #' Temporary function to handle server_args of the report_previewer_module before its hard 63 | #' deprecation. 64 | #' @param args (`list`) 65 | #' 66 | #' @keywords internal 67 | .get_reporter_options <- function(args) { 68 | opts <- list() 69 | if (length(args$previewer_buttons)) { 70 | opts <- c(opts, list(teal.reporter.nav_buttons = args$previewer_buttons)) 71 | } 72 | 73 | if (length(args$global_knitr)) { 74 | opts <- c(opts, list(teal.reporter.global_knitr = args$global_knitr)) 75 | } 76 | 77 | if (length(args$rmd_output)) { 78 | opts <- c(opts, list(teal.reporter.rmd_output = args$rmd_output)) 79 | } 80 | 81 | if (length(args$rmd_yaml_args)) { 82 | opts <- c(opts, list(teal.reporter.rmd_yaml_args = args$rmd_yaml_args)) 83 | } 84 | 85 | opts 86 | } 87 | -------------------------------------------------------------------------------- /vignettes/blueprint/input_data.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Input Data" 3 | author: "NEST CoreDev" 4 | output: rmarkdown::html_vignette 5 | vignette: > 6 | %\VignetteIndexEntry{Input Data} 7 | %\VignetteEngine{knitr::rmarkdown} 8 | %\VignetteEncoding{UTF-8} 9 | --- 10 | 11 | ## Introduction 12 | 13 | Reproducibility is paramount in the pharmaceutical industry. 14 | Accurate and consistent results are essential to ensure high-quality research and the safety of patients. 15 | By prioritizing reproducibility, researchers can validate their methods, confirm their findings, and contribute to the advancement of the field. 16 | 17 | The `teal.code` package provides the [`qenv` class](https://insightsengineering.github.io/teal.code/latest-tag/articles/qenv.html) that facilitates code reproducibility. 18 | Code is passed to a `qenv` object, where is evaluated in a specific environment. 19 | `qenv` also stores the code so that it can be retrieved on request. 20 | 21 | The `teal_data` class, which serves as the primary data interface for `teal` applications, inherits this code tracking behavior from `qenv`. 22 | 23 | ## Preparing data for a `teal` application 24 | 25 | All `teal` applications run on data provided in a `teal_data` object. 26 | Data objects are stored and modified within the environment of the `teal_data` object and all `R` code used is tracked, which allows for the code to be evaluated and executed in the `teal` application, and reproduced outside the `teal` application. 27 | This includes data loading, preprocessing, filtering, transformations, and plotting, etc. 28 | 29 | The `teal_data` object makes it easy for users to reproduce and validate the results of their analyses. 30 | 31 | ```{dot teal_data_dot_diagram, echo=FALSE} 32 | digraph G { 33 | teal_data [label = "teal_data"]; 34 | node [shape=box]; 35 | teal_modules [label = "teal modules analysis R code"]; 36 | library [label = "R library() calls"]; 37 | filter_states [label = "filter states R code"]; 38 | data [label = "data preprocessing R code"]; 39 | teal_modules -> teal_data; 40 | library -> teal_data; 41 | edge [dir="back"]; 42 | teal_data -> filter_states; 43 | teal_data -> data; 44 | } 45 | ``` 46 | 47 | Learn more about the use of `teal_data` in the [`teal.data` package vignettes](https://insightsengineering.github.io/teal.data/latest-tag/articles). 48 | 49 | ## `Show R Code` and `Reporter` 50 | 51 | In both the `teal.modules.clinical` and `teal.modules.general` packages, you'll find that most modules include a convenient `Show R Code` button. 52 | When this button is clicked, a modal window appears, revealing the `R` code responsible for generating the module's output. 53 | This functionality is achieved by inspecting the `teal_data` object and retrieving code from it. 54 | With the `Show R Code` button, users can easily copy and independently run the code to reproduce the analysis presented in the teal module. 55 | 56 | ![Show R Code](images/showrcode.png){width=60%} 57 | 58 | The Reporter feature also leverages the `teal_data` object in its operation. 59 | Much like the `Show R Code` mechanism, the code displayed in a Reporter Card is extracted from the `teal_data` object. 60 | 61 | ![Reporter](images/reporter.png){width=100%} 62 | 63 | To learn more about the `Reporter` feature, please visit the [`teal.reporter` documentation](https://insightsengineering.github.io/teal.reporter/latest-tag/index.html). 64 | 65 | Overall, `qenv` from `teal.code` and its child class, `teal_data`, are powerful tools for ensuring code reproducibility and promoting high-quality research in the `R` programming language. 66 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-modules.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | 4 | testthat::test_that("e2e: the module server logic is only triggered when the teal module becomes active", { 5 | skip_if_too_deep(5) 6 | value_export_module <- function(label = "custom module", value) { 7 | module( 8 | label = label, 9 | server = function(id, data) { 10 | moduleServer(id, function(input, output, session) { 11 | shiny::exportTestValues(value = value) 12 | }) 13 | }, 14 | ui = function(id) { 15 | ns <- NS(id) 16 | h1("Module that exports a random value for testing") 17 | } 18 | ) 19 | } 20 | 21 | app <- TealAppDriver$new( 22 | init( 23 | data = simple_teal_data(), 24 | modules = modules( 25 | value_export_module(label = "Module 1", value = 98), 26 | value_export_module(label = "Module 2", value = 99) 27 | ) 28 | ) 29 | ) 30 | 31 | expected_values <- list( 32 | `teal-teal_modules-nav-module_1-value` = 98, 33 | `teal-teal_modules-nav-module_2-value` = 99 34 | ) 35 | 36 | testthat::expect_identical(expected_values %in% app$get_values()$export, c(TRUE, FALSE)) 37 | app$navigate_teal_tab("Module 2") 38 | testthat::expect_identical(expected_values %in% app$get_values()$export, c(TRUE, TRUE)) 39 | app$stop() 40 | }) 41 | 42 | 43 | testthat::test_that("e2e: filter panel only shows the data supplied using datanames", { 44 | skip_if_too_deep(5) 45 | app <- TealAppDriver$new( 46 | init( 47 | data = simple_teal_data(), 48 | modules = modules( 49 | example_module(label = "mtcars", datanames = "mtcars") 50 | ) 51 | ) 52 | ) 53 | 54 | testthat::expect_identical( 55 | app$get_active_filter_vars(), 56 | "mtcars" 57 | ) 58 | app$stop() 59 | }) 60 | 61 | testthat::test_that("e2e: filter panel shows all the datasets when datanames is all", { 62 | skip_if_too_deep(5) 63 | app <- TealAppDriver$new( 64 | init( 65 | data = simple_teal_data(), 66 | modules = modules( 67 | example_module(label = "all", datanames = "all") 68 | ) 69 | ) 70 | ) 71 | 72 | testthat::expect_identical(app$get_active_filter_vars(), c("iris", "mtcars")) 73 | app$stop() 74 | }) 75 | 76 | 77 | testthat::test_that("e2e: nested modules layout in navigation respect order and keeps group names", { 78 | skip_if_too_deep(5) 79 | app <- TealAppDriver$new( 80 | init( 81 | data = simple_teal_data(), 82 | modules = modules( 83 | example_module(label = "Example Module"), 84 | modules( 85 | label = "Nested Modules", 86 | example_module(label = "Nested 1.1"), 87 | example_module(label = "Nested 1.2"), 88 | modules( 89 | label = "Sub Nested Modules", 90 | example_module(label = "Nested 2.1"), 91 | example_module(label = "Nested 2.2") 92 | ) 93 | ) 94 | ) 95 | ) 96 | ) 97 | app_modules <- app$get_text( 98 | selector = sprintf("ul.teal-modules-tree li %s", c("a", "span.module-group-label")) 99 | ) 100 | testthat::expect_identical( 101 | app_modules, 102 | c( 103 | "Example Module", # Depth: 1 104 | "Nested Modules", # Depth: 1 105 | "Nested 1.1", # Depth: 2 106 | "Nested 1.2", # Depth: 2 107 | "Sub Nested Modules", # Depth: 2 108 | "Nested 2.1", # Depth: 3 109 | "Nested 2.2" # Depth: 3 110 | ) 111 | ) 112 | app$stop() 113 | }) 114 | -------------------------------------------------------------------------------- /tests/testthat/test-shinytest2-decorators.R: -------------------------------------------------------------------------------- 1 | testthat::skip_if_not_installed("shinytest2") 2 | testthat::skip_if_not_installed("rvest") 3 | 4 | testthat::test_that("e2e: module with decorator UI and output is modified interactively upon changes in decorator", { 5 | skip_if_too_deep(5) 6 | 7 | interactive_decorator <- teal_transform_module( 8 | ui = function(id) { 9 | ns <- NS(id) 10 | div( 11 | textInput(ns("append_text"), "Append text", value = "random text") 12 | ) 13 | }, 14 | server = function(id, data) { 15 | moduleServer(id, function(input, output, session) { 16 | reactive({ 17 | req(data()) 18 | within(data(), 19 | { 20 | object <- paste0(object, append_text) 21 | }, 22 | append_text = input$append_text 23 | ) 24 | }) 25 | }) 26 | } 27 | ) 28 | 29 | app <- TealAppDriver$new( 30 | init( 31 | data = teal.data::teal_data(x = "Text Input"), 32 | modules = example_module(label = "Example Module", decorators = list(interactive_decorator)) 33 | ) 34 | ) 35 | 36 | app$navigate_teal_tab("Example Module") 37 | 38 | input_id <- Reduce( 39 | shiny::NS, 40 | c("decorate", "transform_1", "transform", "append_text") 41 | ) 42 | 43 | testthat::expect_true( 44 | app$is_visible(app$namespaces(TRUE)$module(input_id)) 45 | ) 46 | 47 | testthat::expect_identical( 48 | app$get_text(app$namespaces(TRUE)$module(sprintf("%s-label", input_id))), 49 | "Append text" 50 | ) 51 | 52 | testthat::expect_identical( 53 | app$get_active_module_input(input_id), 54 | "random text" 55 | ) 56 | 57 | testthat::expect_identical( 58 | app$get_active_module_output("text"), 59 | "[1] \"Text Inputrandom text\"" 60 | ) 61 | 62 | app$set_active_module_input(input_id, "new text") 63 | testthat::expect_identical( 64 | app$get_active_module_output("text"), 65 | "[1] \"Text Inputnew text\"" 66 | ) 67 | 68 | app$stop() 69 | }) 70 | 71 | testthat::test_that("e2e: module with decorator, where server fails, shows shiny error message", { 72 | skip_if_too_deep(5) 73 | failing_decorator <- teal_transform_module( 74 | ui = function(id) { 75 | ns <- NS(id) 76 | div( 77 | textInput(ns("append_text"), "Append text", value = "random text") 78 | ) 79 | }, 80 | server = function(id, data) { 81 | moduleServer(id, function(input, output, session) { 82 | reactive(stop("This is error")) 83 | }) 84 | } 85 | ) 86 | app <- TealAppDriver$new( 87 | init( 88 | data = teal.data::teal_data(iris = iris), 89 | modules = example_module(label = "Example Module", decorators = list(failing_decorator)) 90 | ) 91 | ) 92 | 93 | app$navigate_teal_tab("Example Module") 94 | 95 | input_id <- Reduce( 96 | shiny::NS, 97 | c("decorate", "transform_1", "silent_error", "message") 98 | ) 99 | 100 | testthat::expect_true(app$is_visible(app$namespaces(TRUE)$module(input_id))) 101 | 102 | app$expect_validation_error() 103 | 104 | testthat::expect_identical( 105 | strsplit(app$get_text(app$namespaces(TRUE)$module(input_id)), "\n")[[1]], 106 | c( 107 | "Shiny error when executing the `data` module.", 108 | "This is error", 109 | "Check your inputs or contact app developer if error persists." 110 | ) 111 | ) 112 | 113 | testthat::expect_setequal( 114 | app$get_active_module_output("text")$type, 115 | c("shiny.silent.error", "validation") 116 | ) 117 | 118 | app$stop() 119 | }) 120 | -------------------------------------------------------------------------------- /man/validate_no_intersection.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/validations.R 3 | \name{validate_no_intersection} 4 | \alias{validate_no_intersection} 5 | \title{Validates no intersection between two vectors} 6 | \usage{ 7 | validate_no_intersection(x, y, msg) 8 | } 9 | \arguments{ 10 | \item{x}{vector} 11 | 12 | \item{y}{vector} 13 | 14 | \item{msg}{(\code{character(1)}) message to display if \code{x} and \code{y} intersect} 15 | } 16 | \description{ 17 | This function is a wrapper for \code{shiny::validate}. 18 | } 19 | \examples{ 20 | data <- data.frame( 21 | id = c(1:10, 11:20, 1:10), 22 | strata = rep(c("A", "B", "C"), each = 10) 23 | ) 24 | 25 | ui <- fluidPage( 26 | selectInput("ref1", "Select strata1 to compare", 27 | choices = c("A", "B", "C"), 28 | selected = "A" 29 | ), 30 | selectInput("ref2", "Select strata2 to compare", 31 | choices = c("A", "B", "C"), 32 | selected = "B" 33 | ), 34 | verbatimTextOutput("summary") 35 | ) 36 | 37 | server <- function(input, output) { 38 | output$summary <- renderText({ 39 | sample_1 <- data$id[data$strata == input$ref1] 40 | sample_2 <- data$id[data$strata == input$ref2] 41 | 42 | validate_no_intersection( 43 | sample_1, sample_2, 44 | "subjects within strata1 and strata2 cannot overlap" 45 | ) 46 | paste0( 47 | "Number of subject in: reference treatment=", length(sample_1), 48 | " comparions treatment=", length(sample_2) 49 | ) 50 | }) 51 | } 52 | if (interactive()) { 53 | shinyApp(ui, server) 54 | } 55 | 56 | } 57 | \section{Examples in Shinylive}{ 58 | \describe{ 59 | \item{example-1}{ 60 | \href{https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOm0z0gLzSC7AIyI-gAMuNL+gQBMoeFBwTy4rtIAzqTMVt7SjHCo7H58YACCBWEFAEIl0gUAwgUJ0nBQBAAWmSH8EB0CKrQ2ytQ9ZgAKUADmcC4Qbslw1HBiAJIQqCqkLmDZSv6VBQDKs-OkKWmWUP7SpES+RDCoLHAlSW4tRLQEcMmZ+UU7YBV4VTAtTACSeKQOYjgnh8BWKYCSoKm4Lmi2Wq3Wm0iv32KKOqXSUEiFyuxFu90eSOezVe70+Pm+cNKf1+wMRbmmEPI0MB-wRiSRUkY9EstBgujgAA9SAB5VYrNYFZIqGAwFisOoCLoQGaMQV9VTqcRaITysJEOWrHQgJLm0jygAkSpVar62QgZhE4ql7GtlJSsFQcwA+ucFNI7FB7R5gBHHScMl4fCbVvbNv4ALpg5IB4NEsOx6Ox-GnbxJtGkVNwJSRTMQMESbgeSxwIMQIhBoTkRgzDRaMHTHMt-xhbO3XP89nsxUqegAK0OnwA7rRSM0hMcCecoO6N6ciQRt22jkRBdQ0AUwR1J3dUnBgpNJ24CgA5ZX0ETSIhKFIz+didwQIgWRViIFDvBc2SWPAZBeJUcwQKMq7sKOgZDmyj4FNcZKMJo2oQY0pDQaQsEAvBiHNMhg5BpEV7srRAC+HT0QItDfuwnYiE04hSNoVpJMka4QKwhToOwPQjiIgpMQIYD0emQA}{Open in Shinylive} 61 | \if{html}{\out{<iframe class="iframe_shinylive" src="https://shinylive.io/r/app/#code=NobwRAdghgtgpmAXGKAHVA6ASmANGAYwHsIAXOMpMAGwEsAjAJykYE8AKcqagSgB0ItMnGYFStAG5wABAB4AtNIBmAVwhjaJdj2kAVLAFUAogIEATKKShzFFqxiXN47AdOm0z0gLzSC7AIyI-gAMuNL+gQBMoeFBwTy4rtIAzqTMVt7SjHCo7H58YACCBWEFAEIl0gUAwgUJ0nBQBAAWmSH8EB0CKrQ2ytQ9ZgAKUADmcC4Qbslw1HBiAJIQqCqkLmDZSv6VBQDKs-OkKWmWUP7SpES+RDCoLHAlSW4tRLQEcMmZ+UU7YBV4VTAtTACSeKQOYjgnh8BWKYCSoKm4Lmi2Wq3Wm0iv32KKOqXSUEiFyuxFu90eSOezVe70+Pm+cNKf1+wMRbmmEPI0MB-wRiSRUkY9EstBgujgAA9SAB5VYrNYFZIqGAwFisOoCLoQGaMQV9VTqcRaITysJEOWrHQgJLm0jygAkSpVar62QgZhE4ql7GtlJSsFQcwA+ucFNI7FB7R5gBHHScMl4fCbVvbNv4ALpg5IB4NEsOx6Ox-GnbxJtGkVNwJSRTMQMESbgeSxwIMQIhBoTkRgzDRaMHTHMt-xhbO3XP89nsxUqegAK0OnwA7rRSM0hMcCecoO6N6ciQRt22jkRBdQ0AUwR1J3dUnBgpNJ24CgA5ZX0ETSIhKFIz+didwQIgWRViIFDvBc2SWPAZBeJUcwQKMq7sKOgZDmyj4FNcZKMJo2oQY0pDQaQsEAvBiHNMhg5BpEV7srRAC+HT0QItDfuwnYiE04hSNoVpJMka4QKwhToOwPQjiIgpMQIYD0emQA" style="height: 800px; width: 100vw; max-width: 1400px; border: 1px solid rgba(0,0,0,0.175); border-radius: .375rem; position: absolute; left: 50\%; margin-top: 30px; transform: translateX(-50\%); z-index: 1"></iframe>}} 62 | \if{html}{\out{<a style='height: 800px; display: block;'></a>}} 63 | } 64 | } 65 | } 66 | 67 | --------------------------------------------------------------------------------