├── 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-archived.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-maturing.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-deprecated.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-superseded.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-experimental.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-questioning.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/man/figures/lifecycle-soft-deprecated.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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]: "
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_=`
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{}}
43 | \if{html}{\out{}}
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]: "
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{}}
50 | \if{html}{\out{}}
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{}}
47 | \if{html}{\out{}}
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_=} suffix.
54 | \verb{} 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{}}
47 | \if{html}{\out{}}
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{}}
49 | \if{html}{\out{}}
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 |
28 |
29 |
30 |
31 |
32 |
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 |
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{}}
61 | \if{html}{\out{}}
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{}}
51 | \if{html}{\out{}}
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{}}
55 | \if{html}{\out{}}
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 | {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 | {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{}}
62 | \if{html}{\out{}}
63 | }
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------