mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-10 15:38:19 -05:00
Compare commits
49 Commits
fix-routin
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07af5f91c8 | ||
|
|
fda6a9fede | ||
|
|
d2245a2e34 | ||
|
|
a12a8130b8 | ||
|
|
b436d2a96d | ||
|
|
05b0f270c4 | ||
|
|
f24f71e4e0 | ||
|
|
63a00f775f | ||
|
|
5a946caf35 | ||
|
|
16c016a171 | ||
|
|
284af65534 | ||
|
|
b5da7868fa | ||
|
|
c8a41aa834 | ||
|
|
390f6d3b95 | ||
|
|
9a2140cd19 | ||
|
|
e3cf4fb089 | ||
|
|
472a1cdba1 | ||
|
|
b56c275364 | ||
|
|
592e825a0f | ||
|
|
50a140c580 | ||
|
|
48d255a235 | ||
|
|
a01fcc5194 | ||
|
|
b6e9e9d216 | ||
|
|
5ddb99a5b4 | ||
|
|
f981ed6363 | ||
|
|
6d6b0ea6f9 | ||
|
|
0e355ed25c | ||
|
|
80a9ff470c | ||
|
|
ead0abcd62 | ||
|
|
7dcb54bc7e | ||
|
|
ae82850e1f | ||
|
|
0610d756a8 | ||
|
|
6325067130 | ||
|
|
1a4e52dc73 | ||
|
|
08383ad8b9 | ||
|
|
ecf6bfe9a7 | ||
|
|
f7528568e5 | ||
|
|
51f653b66f | ||
|
|
460a93a5fd | ||
|
|
3ea4c8eb1d | ||
|
|
f237de559d | ||
|
|
8c7abbac44 | ||
|
|
1710316142 | ||
|
|
2d856f4f09 | ||
|
|
ab219e3408 | ||
|
|
673be3dd77 | ||
|
|
b25e6feabb | ||
|
|
e6b22d86b6 | ||
|
|
9c5196ee63 |
@@ -21,19 +21,17 @@
|
||||
^TODO-promises.md$
|
||||
^manualtests$
|
||||
^\.github$
|
||||
|
||||
^\.yarn$
|
||||
^\.vscode$
|
||||
^\.madgerc$
|
||||
^\.prettierrc\.yml$
|
||||
^jest\.config\.js$
|
||||
^package\.json$
|
||||
^tsconfig\.json$
|
||||
^yarn\.lock$
|
||||
^package-lock\.json$
|
||||
^node_modules$
|
||||
^coverage$
|
||||
^.ignore$
|
||||
^\.browserslistrc$
|
||||
^\.eslintrc\.yml$
|
||||
^\.yarnrc\.yml$
|
||||
^eslint\.config\.mjs$
|
||||
^_dev$
|
||||
^.claude$
|
||||
^README-npm\.md$
|
||||
^CRAN-SUBMISSION$
|
||||
^LICENSE\.md$
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# Browsers that we support
|
||||
last 2 versions
|
||||
not dead
|
||||
> 0.2%
|
||||
# > 1%
|
||||
Firefox ESR
|
||||
phantomjs 2.1
|
||||
IE 11 # sorry
|
||||
110
.eslintrc.yml
110
.eslintrc.yml
@@ -1,110 +0,0 @@
|
||||
root: true
|
||||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
extends:
|
||||
- 'eslint:recommended'
|
||||
- 'plugin:@typescript-eslint/recommended'
|
||||
- 'plugin:jest/recommended'
|
||||
- 'plugin:prettier/recommended'
|
||||
- 'plugin:jest-dom/recommended'
|
||||
globals:
|
||||
Atomics: readonly
|
||||
SharedArrayBuffer: readonly
|
||||
parser: '@typescript-eslint/parser'
|
||||
parserOptions:
|
||||
ecmaVersion: 2018
|
||||
sourceType: module
|
||||
project:
|
||||
- './tsconfig.json'
|
||||
ignorePatterns: # mirrors tsconfig.json's exclude
|
||||
- '**/__tests__'
|
||||
- '**/*.d.ts'
|
||||
plugins:
|
||||
- '@typescript-eslint'
|
||||
- prettier
|
||||
- jest-dom
|
||||
- unicorn
|
||||
rules:
|
||||
"@typescript-eslint/explicit-function-return-type":
|
||||
- off
|
||||
"@typescript-eslint/no-explicit-any":
|
||||
- off
|
||||
"@typescript-eslint/explicit-module-boundary-types":
|
||||
- error
|
||||
|
||||
default-case:
|
||||
- error
|
||||
linebreak-style:
|
||||
- error
|
||||
- unix
|
||||
quotes:
|
||||
- error
|
||||
- double
|
||||
- avoid-escape
|
||||
semi:
|
||||
- error
|
||||
- always
|
||||
dot-location:
|
||||
- error
|
||||
- property
|
||||
|
||||
camelcase:
|
||||
# - error
|
||||
- "off"
|
||||
|
||||
unicorn/filename-case:
|
||||
- error
|
||||
- case: camelCase
|
||||
|
||||
"@typescript-eslint/array-type":
|
||||
- error
|
||||
- default: array-simple
|
||||
readonly: array-simple
|
||||
|
||||
"@typescript-eslint/consistent-indexed-object-style":
|
||||
- error
|
||||
- index-signature
|
||||
|
||||
"@typescript-eslint/sort-type-union-intersection-members":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/consistent-type-imports":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/no-floating-promises":
|
||||
- error
|
||||
|
||||
"@typescript-eslint/naming-convention":
|
||||
- error
|
||||
|
||||
- selector: default
|
||||
format: [camelCase]
|
||||
|
||||
- selector: method
|
||||
modifiers: [private]
|
||||
format: [camelCase]
|
||||
leadingUnderscore: require
|
||||
- selector: method
|
||||
modifiers: [protected]
|
||||
format: [camelCase]
|
||||
leadingUnderscore: require
|
||||
|
||||
- selector: variable
|
||||
format: [camelCase]
|
||||
trailingUnderscore: forbid
|
||||
leadingUnderscore: forbid
|
||||
|
||||
- selector: parameter
|
||||
format: [camelCase]
|
||||
trailingUnderscore: allow
|
||||
leadingUnderscore: forbid
|
||||
|
||||
- selector: [enum, enumMember]
|
||||
format: [PascalCase]
|
||||
|
||||
- selector: typeLike
|
||||
format: [PascalCase]
|
||||
custom:
|
||||
regex: "(t|T)ype$"
|
||||
match: false
|
||||
4
.github/shiny-workflows/routine.sh
vendored
4
.github/shiny-workflows/routine.sh
vendored
@@ -5,8 +5,8 @@ echo "Updating package.json version to match DESCRIPTION Version"
|
||||
Rscript ./tools/updatePackageJsonVersion.R
|
||||
if [ -n "$(git status --porcelain package.json)" ]
|
||||
then
|
||||
echo "package.json has changed after running ./tools/updatePackageJsonVersion.R. Re-running 'yarn build'"
|
||||
yarn build
|
||||
echo "package.json has changed after running ./tools/updatePackageJsonVersion.R. Re-running 'npm run build'"
|
||||
npm run build
|
||||
git add ./inst package.json && git commit -m 'Sync package version (GitHub Actions)' || echo "No package version to commit"
|
||||
else
|
||||
echo "No package version difference detected; package.json is current."
|
||||
|
||||
10
.github/workflows/R-CMD-check.yaml
vendored
10
.github/workflows/R-CMD-check.yaml
vendored
@@ -6,9 +6,9 @@ on:
|
||||
push:
|
||||
branches: [main, rc-**]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches:
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # every monday
|
||||
- cron: "0 5 * * 1" # every monday
|
||||
|
||||
name: Package checks
|
||||
|
||||
@@ -17,7 +17,9 @@ jobs:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/website.yaml@v1
|
||||
routine:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/routine.yaml@v1
|
||||
with:
|
||||
node-version: "14.x"
|
||||
R-CMD-check:
|
||||
uses: rstudio/shiny-workflows/.github/workflows/R-CMD-check.yaml@v1
|
||||
with:
|
||||
# On R 4.2, Cairo has difficulty installing
|
||||
# Remove this line when https://github.com/s-u/Cairo/issues/52 is merged
|
||||
extra-packages: Cairo=?ignore
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -9,20 +9,16 @@
|
||||
shinyapps/
|
||||
README.html
|
||||
.*.Rnb.cached
|
||||
tools/yarn-error.log
|
||||
/_dev/
|
||||
.sass_cache_keys
|
||||
|
||||
# TypeScript / yarn
|
||||
# TypeScript
|
||||
/node_modules/
|
||||
.cache
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
.pnp.*
|
||||
coverage/
|
||||
madge.svg
|
||||
|
||||
|
||||
# GHA remotes installation
|
||||
.github/r-depends.rds
|
||||
.claude/settings.local.json
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,13 +1,12 @@
|
||||
{
|
||||
"search.exclude": {
|
||||
"**/.yarn": true,
|
||||
"**/.pnp.*": true
|
||||
},
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true,
|
||||
"[r]": {
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.formatOnSave": false,
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
|
||||
File diff suppressed because one or more lines are too long
29
.yarn/plugins/@yarnpkg/plugin-outdated.cjs
vendored
29
.yarn/plugins/@yarnpkg/plugin-outdated.cjs
vendored
File diff suppressed because one or more lines are too long
783
.yarn/releases/yarn-3.2.3.cjs
vendored
783
.yarn/releases/yarn-3.2.3.cjs
vendored
File diff suppressed because one or more lines are too long
10
.yarnrc.yml
10
.yarnrc.yml
@@ -1,10 +0,0 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
||||
spec: "https://github.com/mskelton/yarn-plugin-outdated/raw/main/bundles/@yarnpkg/plugin-outdated.js"
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: "@yarnpkg/plugin-interactive-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.2.3.cjs
|
||||
checksumBehavior: update
|
||||
192
DESCRIPTION
192
DESCRIPTION
@@ -1,122 +1,130 @@
|
||||
Package: shiny
|
||||
Type: Package
|
||||
Package: shiny
|
||||
Title: Web Application Framework for R
|
||||
Version: 1.10.0.9001
|
||||
Version: 1.12.1.9000
|
||||
Authors@R: c(
|
||||
person("Winston", "Chang", role = c("aut", "cre"), email = "winston@posit.co", comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", role = "aut", email = "joe@posit.co"),
|
||||
person("JJ", "Allaire", role = "aut", email = "jj@posit.co"),
|
||||
person("Carson", "Sievert", role = "aut", email = "carson@posit.co", comment = c(ORCID = "0000-0002-4958-2844")),
|
||||
person("Barret", "Schloerke", role = "aut", email = "barret@posit.co", comment = c(ORCID = "0000-0001-9986-114X")),
|
||||
person("Yihui", "Xie", role = "aut", email = "yihui@posit.co"),
|
||||
person("Winston", "Chang", , "winston@posit.co", role = "aut",
|
||||
comment = c(ORCID = "0000-0002-1576-2126")),
|
||||
person("Joe", "Cheng", , "joe@posit.co", role = "aut"),
|
||||
person("JJ", "Allaire", , "jj@posit.co", role = "aut"),
|
||||
person("Carson", "Sievert", , "carson@posit.co", role = c("aut", "cre"),
|
||||
comment = c(ORCID = "0000-0002-4958-2844")),
|
||||
person("Barret", "Schloerke", , "barret@posit.co", role = "aut",
|
||||
comment = c(ORCID = "0000-0001-9986-114X")),
|
||||
person("Garrick", "Aden-Buie", , "garrick@adenbuie.com", role = "aut",
|
||||
comment = c(ORCID = "0000-0002-7111-0077")),
|
||||
person("Yihui", "Xie", , "yihui@posit.co", role = "aut"),
|
||||
person("Jeff", "Allen", role = "aut"),
|
||||
person("Jonathan", "McPherson", role = "aut", email = "jonathan@posit.co"),
|
||||
person("Jonathan", "McPherson", , "jonathan@posit.co", role = "aut"),
|
||||
person("Alan", "Dipert", role = "aut"),
|
||||
person("Barbara", "Borges", role = "aut"),
|
||||
person("Posit Software, PBC", role = c("cph", "fnd")),
|
||||
person(family = "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
person(family = "jQuery contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
|
||||
person(family = "jQuery UI contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt"),
|
||||
person("Posit Software, PBC", role = c("cph", "fnd"),
|
||||
comment = c(ROR = "03wc8by49")),
|
||||
person(, "jQuery Foundation", role = "cph",
|
||||
comment = "jQuery library and jQuery UI library"),
|
||||
person(, "jQuery contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt"),
|
||||
person(, "jQuery UI contributors", role = c("ctb", "cph"),
|
||||
comment = "jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt"),
|
||||
person("Mark", "Otto", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
comment = "Bootstrap library"),
|
||||
person("Jacob", "Thornton", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Bootstrap contributors", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(family = "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
comment = "Bootstrap library"),
|
||||
person(, "Bootstrap contributors", role = "ctb",
|
||||
comment = "Bootstrap library"),
|
||||
person(, "Twitter, Inc", role = "cph",
|
||||
comment = "Bootstrap library"),
|
||||
person("Prem Nawaz", "Khan", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Victor", "Tsaran", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Dennis", "Lembree", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Srinivasu", "Chakravarthula", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Cathy", "O'Connor", role = "ctb",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person(family = "PayPal, Inc", role = "cph",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person(, "PayPal, Inc", role = "cph",
|
||||
comment = "Bootstrap accessibility plugin"),
|
||||
person("Stefan", "Petre", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Andrew", "Rowls", role = c("ctb", "cph"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
comment = "Bootstrap-datepicker library"),
|
||||
person("Brian", "Reavis", role = c("ctb", "cph"),
|
||||
comment = "selectize.js library"),
|
||||
comment = "selectize.js library"),
|
||||
person("Salmen", "Bejaoui", role = c("ctb", "cph"),
|
||||
comment = "selectize-plugin-a11y library"),
|
||||
comment = "selectize-plugin-a11y library"),
|
||||
person("Denis", "Ineshin", role = c("ctb", "cph"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
comment = "ion.rangeSlider library"),
|
||||
person("Sami", "Samhuri", role = c("ctb", "cph"),
|
||||
comment = "Javascript strftime library"),
|
||||
person(family = "SpryMedia Limited", role = c("ctb", "cph"),
|
||||
comment = "DataTables library"),
|
||||
person("John", "Fraser", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
person("John", "Gruber", role = c("ctb", "cph"),
|
||||
comment = "showdown.js library"),
|
||||
comment = "Javascript strftime library"),
|
||||
person(, "SpryMedia Limited", role = c("ctb", "cph"),
|
||||
comment = "DataTables library"),
|
||||
person("Ivan", "Sagalaev", role = c("ctb", "cph"),
|
||||
comment = "highlight.js library"),
|
||||
person(given = "R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
comment = "highlight.js library"),
|
||||
person("R Core Team", role = c("ctb", "cph"),
|
||||
comment = "tar implementation from R")
|
||||
)
|
||||
Description: Makes it incredibly easy to build interactive web
|
||||
applications with R. Automatic "reactive" binding between inputs and
|
||||
outputs and extensive prebuilt widgets make it possible to build
|
||||
beautiful, responsive, and powerful applications with minimal effort.
|
||||
License: GPL-3 | file LICENSE
|
||||
License: MIT + file LICENSE
|
||||
URL: https://shiny.posit.co/, https://github.com/rstudio/shiny
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
Depends:
|
||||
R (>= 3.0.2),
|
||||
methods
|
||||
methods,
|
||||
R (>= 3.0.2)
|
||||
Imports:
|
||||
utils,
|
||||
grDevices,
|
||||
httpuv (>= 1.5.2),
|
||||
mime (>= 0.3),
|
||||
jsonlite (>= 0.9.16),
|
||||
xtable,
|
||||
fontawesome (>= 0.4.0),
|
||||
htmltools (>= 0.5.4),
|
||||
R6 (>= 2.0),
|
||||
sourcetools,
|
||||
later (>= 1.0.0),
|
||||
promises (>= 1.3.2),
|
||||
tools,
|
||||
cli,
|
||||
rlang (>= 0.4.10),
|
||||
fastmap (>= 1.1.1),
|
||||
withr,
|
||||
commonmark (>= 1.7),
|
||||
glue (>= 1.3.2),
|
||||
bslib (>= 0.6.0),
|
||||
cachem (>= 1.1.0),
|
||||
lifecycle (>= 0.2.0)
|
||||
cli,
|
||||
commonmark (>= 2.0.0),
|
||||
fastmap (>= 1.1.1),
|
||||
fontawesome (>= 0.4.0),
|
||||
glue (>= 1.3.2),
|
||||
grDevices,
|
||||
htmltools (>= 0.5.4),
|
||||
httpuv (>= 1.5.2),
|
||||
jsonlite (>= 0.9.16),
|
||||
later (>= 1.0.0),
|
||||
lifecycle (>= 0.2.0),
|
||||
mime (>= 0.3),
|
||||
otel,
|
||||
promises (>= 1.5.0),
|
||||
R6 (>= 2.0),
|
||||
rlang (>= 0.4.10),
|
||||
sourcetools,
|
||||
tools,
|
||||
utils,
|
||||
withr,
|
||||
xtable
|
||||
Suggests:
|
||||
Cairo (>= 1.5-5),
|
||||
coro (>= 1.1.0),
|
||||
datasets,
|
||||
DT,
|
||||
Cairo (>= 1.5-5),
|
||||
testthat (>= 3.2.1),
|
||||
knitr (>= 1.6),
|
||||
markdown,
|
||||
rmarkdown,
|
||||
ggplot2,
|
||||
reactlog (>= 1.0.0),
|
||||
magrittr,
|
||||
yaml,
|
||||
mirai,
|
||||
future,
|
||||
dygraphs,
|
||||
future,
|
||||
ggplot2,
|
||||
knitr (>= 1.6),
|
||||
magrittr,
|
||||
markdown,
|
||||
mirai,
|
||||
otelsdk (>= 0.2.0),
|
||||
ragg,
|
||||
showtext,
|
||||
reactlog (>= 1.0.0),
|
||||
rmarkdown,
|
||||
sass,
|
||||
watcher
|
||||
URL: https://shiny.posit.co/,
|
||||
https://github.com/rstudio/shiny
|
||||
BugReports: https://github.com/rstudio/shiny/issues
|
||||
showtext,
|
||||
testthat (>= 3.2.1),
|
||||
watcher,
|
||||
yaml
|
||||
Config/Needs/check: shinytest2
|
||||
Config/testthat/edition: 3
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
RoxygenNote: 7.3.3
|
||||
Collate:
|
||||
'globals.R'
|
||||
'app-state.R'
|
||||
@@ -175,6 +183,15 @@ Collate:
|
||||
'modal.R'
|
||||
'modules.R'
|
||||
'notifications.R'
|
||||
'otel-attr-srcref.R'
|
||||
'otel-collect.R'
|
||||
'otel-enable.R'
|
||||
'otel-error.R'
|
||||
'otel-label.R'
|
||||
'otel-reactive-update.R'
|
||||
'otel-session.R'
|
||||
'otel-shiny.R'
|
||||
'otel-with.R'
|
||||
'priorityqueue.R'
|
||||
'progress.R'
|
||||
'react.R'
|
||||
@@ -202,6 +219,7 @@ Collate:
|
||||
'test.R'
|
||||
'update-input.R'
|
||||
'utils-lang.R'
|
||||
'utils-tags.R'
|
||||
'version_bs_date_picker.R'
|
||||
'version_ion_range_slider.R'
|
||||
'version_jquery.R'
|
||||
@@ -209,9 +227,3 @@ Collate:
|
||||
'version_selectize.R'
|
||||
'version_strftime.R'
|
||||
'viewer.R'
|
||||
RoxygenNote: 7.3.2
|
||||
Encoding: UTF-8
|
||||
Roxygen: list(markdown = TRUE)
|
||||
Config/testthat/edition: 3
|
||||
Config/Needs/check:
|
||||
shinytest2
|
||||
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2025 shiny authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
1011
LICENSE.note
Normal file
1011
LICENSE.note
Normal file
File diff suppressed because it is too large
Load Diff
@@ -165,6 +165,7 @@ export(isTruthy)
|
||||
export(isolate)
|
||||
export(key_missing)
|
||||
export(loadSupport)
|
||||
export(localOtelCollect)
|
||||
export(mainPanel)
|
||||
export(makeReactiveBinding)
|
||||
export(markRenderFunction)
|
||||
@@ -329,6 +330,7 @@ export(verticalLayout)
|
||||
export(wellPanel)
|
||||
export(withLogErrors)
|
||||
export(withMathJax)
|
||||
export(withOtelCollect)
|
||||
export(withProgress)
|
||||
export(withReactiveDomain)
|
||||
export(withTags)
|
||||
@@ -387,10 +389,13 @@ importFrom(lifecycle,is_present)
|
||||
importFrom(promises,"%...!%")
|
||||
importFrom(promises,"%...>%")
|
||||
importFrom(promises,as.promise)
|
||||
importFrom(promises,hybrid_then)
|
||||
importFrom(promises,is.promise)
|
||||
importFrom(promises,is.promising)
|
||||
importFrom(promises,promise)
|
||||
importFrom(promises,new_promise_domain)
|
||||
importFrom(promises,promise_reject)
|
||||
importFrom(promises,promise_resolve)
|
||||
importFrom(promises,with_promise_domain)
|
||||
importFrom(rlang,"%||%")
|
||||
importFrom(rlang,"fn_body<-")
|
||||
importFrom(rlang,"fn_fmls<-")
|
||||
|
||||
174
NEWS.md
174
NEWS.md
@@ -1,16 +1,144 @@
|
||||
# shiny (development version)
|
||||
|
||||
# shiny 1.12.1
|
||||
|
||||
## New features
|
||||
|
||||
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
|
||||
* `withOtelCollect()` and `localOtelCollect()` temporarily control
|
||||
OpenTelemetry collection levels during reactive expression creation,
|
||||
allowing you to enable or disable telemetry collection for specific modules
|
||||
or sections of code. (#4333)
|
||||
|
||||
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
|
||||
## Bug fixes and minor improvements
|
||||
|
||||
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
|
||||
* OpenTelemetry code attributes now include both the preferred attribute names
|
||||
(`code.file.path`, `code.line.number`, `code.column.number`) and the
|
||||
deprecated names (`code.filepath`, `code.lineno`, `code.column`) to follow
|
||||
OpenTelemetry semantic conventions while maintaining backward compatibility.
|
||||
The deprecated names will be removed in a future release after Logfire
|
||||
supports the preferred names. (#4325)
|
||||
|
||||
## Changes
|
||||
* `ExtendedTask` now captures the OpenTelemetry recording state at
|
||||
initialization time rather than at invocation time, ensuring consistent span
|
||||
recording behavior regardless of runtime configuration changes. (#4334)
|
||||
|
||||
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
|
||||
* Timer tests are now skipped on CRAN. (#4327)
|
||||
|
||||
# shiny 1.12.0
|
||||
|
||||
## OpenTelemetry support
|
||||
|
||||
* Shiny now supports [OpenTelemetry](https://opentelemetry.io/) via
|
||||
[`{otel}`](https://otel.r-lib.org/index.html). By default, if
|
||||
`otel::is_tracing_enabled()` returns `TRUE`, then `{shiny}` records all
|
||||
OpenTelemetry spans. See [`{otelsdk}`'s Collecting Telemetry
|
||||
Data](https://otelsdk.r-lib.org/reference/collecting.html) for more details
|
||||
on configuring OpenTelemetry. (#4269, #4300)
|
||||
|
||||
* Supported values for `options(shiny.otel.collect)` (or
|
||||
`Sys.getenv("SHINY_OTEL_COLLECT")`):
|
||||
* `"none"` - No Shiny OpenTelemetry tracing.
|
||||
* `"session"` - Adds session start/end spans.
|
||||
* `"reactive_update"` - Spans for any synchronous/asynchronous reactive
|
||||
update. (Includes `"session"` features).
|
||||
* `"reactivity"` - Spans for all reactive expressions. (Includes
|
||||
`"reactive_update"` features).
|
||||
* `"all"` [default] - All Shiny OpenTelemetry tracing. Currently equivalent
|
||||
to `"reactivity"`.
|
||||
|
||||
* OpenTelemetry spans are recorded for:
|
||||
* `session_start`: Wraps the calling of the `server()` function. Also
|
||||
contains HTTP request within the attributes.
|
||||
* `session_end`: Wraps the calling of the `onSessionEnded()` handlers.
|
||||
* `reactive_update`: Signals the start of when Shiny knows something is to
|
||||
be calculated. This span ends when there are no more reactive updates
|
||||
(promises or synchronous) to be calculated.
|
||||
* `reactive`, `observe`, `output`: Captures the calculation (including any
|
||||
async promise chains) of a reactive expression (`reactive()`), an observer
|
||||
(`observe()`), or an output render function (`render*()`).
|
||||
* `reactive debounce`, `reactive throttle`: Captures the calculation
|
||||
(including any async promise chains) of a `debounce()`d or `throttle()`d
|
||||
reactive expression.
|
||||
* `reactiveFileReader`, `reactivePoll`: Captures the calculation
|
||||
(including any async promise chains) of a `reactiveFileReader()` or
|
||||
`reactivePoll()`.
|
||||
* `ExtendedTask`: Captures the calculation (including any async promise
|
||||
chains) of an `ExtendedTask`.
|
||||
|
||||
* OpenTelemetry Logs are recorded for:
|
||||
* `Set reactiveVal <name>` - When a `reactiveVal()` is set
|
||||
* `Set reactiveValues <name>$<key>` - When a `reactiveValues()` element is
|
||||
set
|
||||
* Fatal or unhandled errors - When an error occurs that causes the session
|
||||
to end, or when an unhandled error occurs in a reactive context. Contains
|
||||
the error within the attributes. To unsanitize the error message being
|
||||
collected, set `options(shiny.otel.sanitize.errors = FALSE)`.
|
||||
* `Set ExtendedTask <name> <value>` - When an `ExtendedTask`'s respective
|
||||
reactive value (e.g., `status`, `value`, and `error`) is set.
|
||||
* `<ExtendedTask name> add to queue` - When an `ExtendedTask` is added to
|
||||
the task queue.
|
||||
|
||||
* All OpenTelemetry logs and spans will contain a `session.id` attribute
|
||||
containing the active session ID.
|
||||
|
||||
## New features
|
||||
|
||||
* `updateActionButton()` and `updateActionLink()` now accept values other than
|
||||
`shiny::icon()` for the `icon` argument (e.g., `fontawesome::fa()`,
|
||||
`bsicons::bs_icon()`, etc). (#4249)
|
||||
|
||||
## Bug fixes and minor improvements
|
||||
|
||||
* Showcase mode now uses server-side markdown rendering with the
|
||||
`{commonmark}` package, providing support for GitHub Flavored Markdown
|
||||
features (tables, strikethrough, autolinks, task lists). While most existing
|
||||
README.md files should continue to work as expected, some minor rendering
|
||||
differences may occur due to the change in markdown processor. (#4202,
|
||||
#4201)
|
||||
|
||||
* `debounce()`, `reactiveFileReader()`, `reactivePoll()`, `reactiveValues()`,
|
||||
and `throttle()` now attempt to retrieve the assigned name for the default
|
||||
label if the srcref is available. If a value cannot easily be produced, a
|
||||
default label is used instead. (#4269, #4300)
|
||||
|
||||
* The default label for items described below will now attempt to retrieve the
|
||||
assigned name if the srcref is available. If a value can not easily be
|
||||
produced, a default label will be used instead. This should improve the
|
||||
OpenTelemetry span labels and the reactlog experience. (#4269, #4300)
|
||||
* `reactiveValues()`, `reactivePoll()`, `reactiveFileReader()`, `debounce()`,
|
||||
`throttle()`, `observe()`
|
||||
* Combinations of `bindEvent()` and `reactive()` / `observe()`
|
||||
* Combination of `bindCache()` and `reactive()`
|
||||
|
||||
* `updateActionButton()` and `updateActionLink()` now correctly render HTML
|
||||
content passed to the `label` argument. (#4249)
|
||||
|
||||
* `updateSelectizeInput()` no longer creates multiple remove buttons when
|
||||
`options = list(plugins="remove_button")` is used. (#4275)
|
||||
|
||||
* `dateRangeInput()`/`updateDateRangeInput()` now correctly considers the time
|
||||
zones of date-time objects (POSIXct) passed to the `start`, `end`, `min` and
|
||||
`max` arguments. (thanks @ismirsehregal, #4318)
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* The return value of `actionButton()` and `actionLink()` now wraps `label`
|
||||
and `icon` in an additional HTML container element. This allows
|
||||
`updateActionButton()` and `updateActionLink()` to distinguish between the
|
||||
`label` and `icon` when making updates, and allows spacing between `label`
|
||||
and `icon` to be more easily customized via CSS.
|
||||
|
||||
# shiny 1.11.1
|
||||
|
||||
This is a patch release primarily for addressing the bugs introduced in v1.11.0.
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Fixed an issue where `InputBinding` implementations that don't pass a value to their `subscribe` callback were no longer notifying Shiny of input changes. (#4243)
|
||||
|
||||
* `updateActionButton()` and `updateActionLink()` once again handle `label` updates correctly. (#4245)
|
||||
|
||||
# shiny 1.11.0
|
||||
|
||||
## Improvements
|
||||
|
||||
@@ -21,13 +149,29 @@
|
||||
* Shows a spinner on `tableOutput()`. (#4172)
|
||||
* Places a minimum height on recalculating outputs so that the spinner is always visible. (#4172)
|
||||
|
||||
* Shiny now uses `{cli}` instead of `{crayon}` for rich log messages. (@olivroy #4170)
|
||||
* Shiny now uses `{cli}` instead of `{crayon}` for rich log messages. (thanks @olivroy, #4170)
|
||||
|
||||
* Shiny's Typescript assets are now compiled to ES2021 instead of ES5. (#4066)
|
||||
* `renderPlot()` was updated to accommodate changes in ggplot2 v4.0.0. (#4226)
|
||||
|
||||
* When adding the new tab via `insertTab()` or `bslib::nav_insert()`, the underlying JavaScript no longer renders content twice. (#4179)
|
||||
|
||||
## New features
|
||||
|
||||
* `textInput()`, `textAreaInput()`, `numericInput()` and `passwordInput()` all gain an `updateOn` option. `updateOn = "change"` is the default and previous behavior, where the input value updates immediately whenever the value changes. With `updateOn = "blur"`, the input value will update only when the text input loses focus or when the user presses Enter (or Cmd/Ctrl + Enter for `textAreaInput()`). (#4183)
|
||||
|
||||
* `textAreaInput()` gains a `autoresize` option, which automatically resizes the text area to fit its content. (#4210)
|
||||
|
||||
* The family of `update*Input()` functions can now render HTML content passed to the `label` argument (e.g., `updateInputText(label = tags$b("New label"))`). (#3996)
|
||||
|
||||
* `ExtendedTask` now catches synchronous values and errors and returns them via `$result()`. Previously, the extended task function was required to always return a promise. This change makes it easier to use `ExtendedTask` with a function that may return early or do some synchronous work before returning a promise. (#4225)
|
||||
|
||||
* `renderPlot()` was updated to accomodate changes in ggplot2 v4.0.0. (#4226)
|
||||
* The `callback` argument of Shiny.js' `InputBinding.subscribe()` method gains support for a value of `"event"`. This makes it possible for an input binding to use event priority when updating the value (i.e., send immediately and always resend, even if the value hasn't changed). (#4211)
|
||||
|
||||
## Changes
|
||||
|
||||
* Shiny no longer suspends input changes when _any_ `<input type="submit">` or `<button type="submit">` is on the page. Instead, it now only suspends when a `submitButton()` is present. If you have reason for creating a submit button from custom HTML, add a CSS class of `shiny-submit-button` to the button. (#4209)
|
||||
|
||||
* Shiny's JavaScript assets are now compiled to ES2021 instead of ES5. (#4066)
|
||||
|
||||
* Upgraded jQuery from 3.6.0 to 3.7.1. (#3969)
|
||||
|
||||
@@ -35,15 +179,13 @@
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* `runExample("08_html")` now (correctly) requests to 'shiny.min.css', eliminating a network request failure. (#4220)
|
||||
|
||||
* Fixed a bug with modals where calling `removeModal()` too quickly after `showModal()` would fail to remove the modal if the remove modal message was received while the modal was in the process of being revealed. (#4173)
|
||||
|
||||
* The Shiny Client Console (enabled with `shiny::devmode()`) no longer displays duplicate warning or error message. (#4177)
|
||||
|
||||
* Updated the JavaScript used when inserting a tab to avoid rendering dynamic UI elements twice when adding the new tab via `insertTab()` or `bslib::nav_insert()`. (#4179)
|
||||
* Synchronous errors that occur inside a `ExtendedTask` no longer stop the session. (#4225)
|
||||
|
||||
* Fixed an issue with `ExtendedTask` where synchronous errors would cause an error that would stop the current session. (#4225)
|
||||
* Calling `removeModal()` immediately after `showModal()` no longer fails to remove the modal (this would sometimes happen if the remove message was received while the modal was in the process of being revealed). (#4173)
|
||||
|
||||
* `runExample("08_html")` now (correctly) requests to 'shiny.min.css', eliminating a network request failure. (#4220)
|
||||
|
||||
* `shiny::shinyAppTemplate()` no longer errors without a call to `library(shiny)`. (#3870)
|
||||
|
||||
@@ -394,7 +536,7 @@ This release focuses on improvements in three main areas:
|
||||
|
||||
* Fixed #2951: screen readers correctly announce labels and date formats for `dateInput()` and `dateRangeInput()` widgets. (#2978)
|
||||
|
||||
* Closed #2847: `selectInput()` is reasonably accessible for screen readers even when `selectize` option is set to TRUE. To improve `selectize.js` accessibility, we have added [selectize-plugin-a11y](https://github.com/SLMNBJ/selectize-plugin-a11y) by default. (#2993)
|
||||
* Closed #2847: `selectInput()` is reasonably accessible for screen readers even when `selectize` option is set to TRUE. To improve `selectize.js` accessibility, we have added [selectize-plugin-a11y](https://github.com/SalmenBejaoui/selectize-plugin-a11y) by default. (#2993)
|
||||
|
||||
* Closed #612: Added `alt` argument to `renderPlot()` and `renderCachedPlot()` to specify descriptive texts for `plotOutput()` objects, which is essential for screen readers. By default, alt text is set to the static text, "Plot object," but even dynamic text can be made with reactive function. (#3006, thanks @trafficonese and @leonawicz for the original PR and discussion via #2494)
|
||||
|
||||
@@ -656,7 +798,7 @@ This release features plot caching, an important new tool for improving performa
|
||||
|
||||
### Minor new features and improvements
|
||||
|
||||
* Upgrade FontAwesome from 4.7.0 to 5.3.1 and made `icon` tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in [Upgrade from Version 4](https://fontawesome.com/how-to-use/on-the-web/setup/upgrading-from-version-4). Font Awesome icons can also now be used in static R Markdown documents.
|
||||
* Upgrade FontAwesome from 4.7.0 to 5.3.1 and made `icon` tags browsable, which means they will display in a web browser or RStudio viewer by default (#2186). Note that if your application or library depends on FontAwesome directly using custom CSS, you may need to make some or all of the changes recommended in [Upgrade from Version 4](https://docs.fontawesome.com/v5/web/setup/upgrade-from-v4). Font Awesome icons can also now be used in static R Markdown documents.
|
||||
|
||||
* Address #174: Added `datesdisabled` and `daysofweekdisabled` as new parameters to `dateInput()`. This resolves #174 and exposes the underlying arguments of [Bootstrap Datepicker](http://bootstrap-datepicker.readthedocs.io/en/latest/options.html#datesdisabled). `datesdisabled` expects a character vector with values in `yyyy/mm/dd` format and `daysofweekdisabled` expects an integer vector with day interger ids (Sunday=0, Saturday=6). The default value for both is `NULL`, which leaves all days selectable. Thanks, @nathancday! (#2147)
|
||||
|
||||
|
||||
@@ -478,7 +478,12 @@ bindCache.default <- function(x, ...) {
|
||||
bindCache.reactiveExpr <- function(x, ..., cache = "app") {
|
||||
check_dots_unnamed()
|
||||
|
||||
label <- exprToLabel(substitute(key), "cachedReactive")
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = exprToLabel(substitute(x), "cachedReactive")
|
||||
)
|
||||
|
||||
domain <- reactive_get_domain(x)
|
||||
|
||||
# Convert the ... to a function that returns their evaluated values.
|
||||
@@ -490,24 +495,37 @@ bindCache.reactiveExpr <- function(x, ..., cache = "app") {
|
||||
cacheHint <- rlang::hash(extractCacheHint(x))
|
||||
valueFunc <- wrapFunctionLabel(valueFunc, "cachedReactiveValueFunc", ..stacktraceon = TRUE)
|
||||
|
||||
x_classes <- class(x)
|
||||
x_otel_attrs <- attr(x, "observable", exact = TRUE)$.otelAttrs
|
||||
|
||||
# Don't hold on to the reference for x, so that it can be GC'd
|
||||
rm(x)
|
||||
# Hacky workaround for issue with `%>%` preventing GC:
|
||||
# https://github.com/tidyverse/magrittr/issues/229
|
||||
if (exists(".GenericCallEnv") && exists(".", envir = .GenericCallEnv)) {
|
||||
rm(list = ".", envir = .GenericCallEnv)
|
||||
if (exists(".GenericCallEnv") && exists(".", envir = .GenericCallEnv, inherits = FALSE)) {
|
||||
rm(list = ".", envir = .GenericCallEnv, inherits = FALSE)
|
||||
}
|
||||
|
||||
|
||||
res <- reactive(label = label, domain = domain, {
|
||||
cache <- resolve_cache_object(cache, domain)
|
||||
hybrid_chain(
|
||||
keyFunc(),
|
||||
generateCacheFun(valueFunc, cache, cacheHint, cacheReadHook = identity, cacheWriteHook = identity)
|
||||
)
|
||||
with_no_otel_collect({
|
||||
res <- reactive(label = label, domain = domain, {
|
||||
cache <- resolve_cache_object(cache, domain)
|
||||
hybrid_chain(
|
||||
keyFunc(),
|
||||
generateCacheFun(valueFunc, cache, cacheHint, cacheReadHook = identity, cacheWriteHook = identity)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
class(res) <- c("reactive.cache", class(res))
|
||||
|
||||
local({
|
||||
impl <- attr(res, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref, fn_name = "bindCache")
|
||||
})
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
res <- enable_otel_reactive_expr(res)
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
@@ -534,6 +552,7 @@ bindCache.shiny.render.function <- function(x, ..., cache = "app") {
|
||||
)
|
||||
}
|
||||
|
||||
# Passes over the otelAttrs from valueFunc to renderFunc
|
||||
renderFunc <- addAttributes(renderFunc, renderFunctionAttributes(valueFunc))
|
||||
class(renderFunc) <- c("shiny.render.function.cache", class(valueFunc))
|
||||
renderFunc
|
||||
@@ -585,7 +604,7 @@ bindCache.shiny.renderPlot <- function(x, ...,
|
||||
|
||||
observe({
|
||||
doResizeCheck()
|
||||
})
|
||||
}, label = "plot-resize")
|
||||
# TODO: Make sure this observer gets GC'd if output$foo is replaced.
|
||||
# Currently, if you reassign output$foo, the observer persists until the
|
||||
# session ends. This is generally bad programming practice and should be
|
||||
|
||||
@@ -196,31 +196,58 @@ bindEvent.reactiveExpr <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE
|
||||
valueFunc <- reactive_get_value_func(x)
|
||||
valueFunc <- wrapFunctionLabel(valueFunc, "eventReactiveValueFunc", ..stacktraceon = TRUE)
|
||||
|
||||
label <- label %||%
|
||||
sprintf('bindEvent(%s, %s)', attr(x, "observable", exact = TRUE)$.label, quos_to_label(qs))
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
if (is.null(label)) {
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = as_default_label(sprintf(
|
||||
'bindEvent(%s, %s)',
|
||||
attr(x, "observable", exact = TRUE)$.label,
|
||||
quos_to_label(qs)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
x_classes <- class(x)
|
||||
x_otel_attrs <- attr(x, "observable", exact = TRUE)$.otelAttrs
|
||||
|
||||
# Don't hold on to the reference for x, so that it can be GC'd
|
||||
rm(x)
|
||||
|
||||
initialized <- FALSE
|
||||
|
||||
res <- reactive(label = label, domain = domain, ..stacktraceon = FALSE, {
|
||||
hybrid_chain(
|
||||
eventFunc(),
|
||||
function(value) {
|
||||
if (ignoreInit && !initialized) {
|
||||
initialized <<- TRUE
|
||||
req(FALSE)
|
||||
with_no_otel_collect({
|
||||
res <- reactive(label = label, domain = domain, ..stacktraceon = FALSE, {
|
||||
hybrid_chain(
|
||||
{
|
||||
eventFunc()
|
||||
},
|
||||
function(value) {
|
||||
if (ignoreInit && !initialized) {
|
||||
initialized <<- TRUE
|
||||
req(FALSE)
|
||||
}
|
||||
|
||||
req(!ignoreNULL || !isNullEvent(value))
|
||||
|
||||
isolate(valueFunc())
|
||||
}
|
||||
|
||||
req(!ignoreNULL || !isNullEvent(value))
|
||||
|
||||
isolate(valueFunc())
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
class(res) <- c("reactive.event", class(res))
|
||||
class(res) <- c("reactive.event", x_classes)
|
||||
|
||||
local({
|
||||
impl <- attr(res, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref, fn_name = "bindEvent")
|
||||
})
|
||||
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
res <- enable_otel_reactive_expr(res)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -249,6 +276,7 @@ bindEvent.shiny.render.function <- function(x, ..., ignoreNULL = TRUE, ignoreIni
|
||||
)
|
||||
}
|
||||
|
||||
# Passes over the otelAttrs from valueFunc to renderFunc
|
||||
renderFunc <- addAttributes(renderFunc, renderFunctionAttributes(valueFunc))
|
||||
class(renderFunc) <- c("shiny.render.function.event", class(valueFunc))
|
||||
renderFunc
|
||||
@@ -269,7 +297,17 @@ bindEvent.Observer <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE,
|
||||
|
||||
# Note that because the observer will already have been logged by this point,
|
||||
# this updated label won't show up in the reactlog.
|
||||
x$.label <- label %||% sprintf('bindEvent(%s, %s)', x$.label, quos_to_label(qs))
|
||||
if (is.null(label)) {
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
x$.label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = as_default_label(
|
||||
sprintf('bindEvent(%s, %s)', x$.label, quos_to_label(qs))
|
||||
)
|
||||
)
|
||||
} else {
|
||||
x$.label <- label
|
||||
}
|
||||
|
||||
initialized <- FALSE
|
||||
|
||||
@@ -302,6 +340,13 @@ bindEvent.Observer <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE,
|
||||
)
|
||||
|
||||
class(x) <- c("Observer.event", class(x))
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
x$.otelAttrs <- append_otel_srcref_attrs(x$.otelAttrs, call_srcref, fn_name = "bindEvent")
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
x <- enable_otel_observe(x)
|
||||
}
|
||||
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,9 @@ getCallCategories <- function(calls) {
|
||||
#' @rdname stacktrace
|
||||
#' @export
|
||||
captureStackTraces <- function(expr) {
|
||||
promises::with_promise_domain(createStackTracePromiseDomain(),
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(
|
||||
createStackTracePromiseDomain(),
|
||||
expr
|
||||
)
|
||||
}
|
||||
@@ -184,7 +186,7 @@ createStackTracePromiseDomain <- function() {
|
||||
# These are actually stateless, we wouldn't have to create a new one each time
|
||||
# if we didn't want to. They're pretty cheap though.
|
||||
|
||||
d <- promises::new_promise_domain(
|
||||
d <- new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
# Subscription time
|
||||
@@ -278,7 +280,7 @@ withLogErrors <- function(expr,
|
||||
result <- captureStackTraces(expr)
|
||||
|
||||
# Handle expr being an async operation
|
||||
if (promises::is.promise(result)) {
|
||||
if (is.promise(result)) {
|
||||
result <- promises::catch(result, function(cond) {
|
||||
# Don't print shiny.silent.error (i.e. validation errors)
|
||||
if (cnd_inherits(cond, "shiny.silent.error")) {
|
||||
@@ -556,8 +558,9 @@ dropTrivialTestFrames <- function(callnames) {
|
||||
"testthat::test_local"
|
||||
)
|
||||
|
||||
firstGoodCall <- min(which(!hideable))
|
||||
toRemove <- firstGoodCall - 1L
|
||||
# Remove everything from inception to calling the test
|
||||
# It shouldn't matter how you get there, just that you're finally testing
|
||||
toRemove <- max(which(hideable))
|
||||
|
||||
c(
|
||||
rep_len(FALSE, toRemove),
|
||||
|
||||
@@ -41,6 +41,28 @@
|
||||
#' is, a function that quickly returns a promise) and allows even that very
|
||||
#' session to immediately unblock and carry on with other user interactions.
|
||||
#'
|
||||
#' @section OpenTelemetry Integration:
|
||||
#'
|
||||
#' When an `ExtendedTask` is created, if OpenTelemetry tracing is enabled for
|
||||
#' `"reactivity"` (see [withOtelCollect()]), the `ExtendedTask` will record
|
||||
#' spans for each invocation of the task. The tracing level at `invoke()` time
|
||||
#' does not affect whether spans are recorded; only the tracing level when
|
||||
#' calling `ExtendedTask$new()` matters.
|
||||
#'
|
||||
#' The OTel span will be named based on the label created from the variable the
|
||||
#' `ExtendedTask` is assigned to. If no label can be determined, the span will
|
||||
#' be named `<anonymous>`. Similar to other Shiny OpenTelemetry spans, the span
|
||||
#' will also include source reference attributes and session ID attributes.
|
||||
#'
|
||||
#' ```r
|
||||
#' withOtelCollect("all", {
|
||||
#' my_task <- ExtendedTask$new(function(...) { ... })
|
||||
#' })
|
||||
#'
|
||||
#' # Span recorded for this invocation: ExtendedTask my_task
|
||||
#' my_task$invoke(...)
|
||||
#' ```
|
||||
#'
|
||||
#' @examplesIf rlang::is_interactive() && rlang::is_installed("mirai")
|
||||
#' library(shiny)
|
||||
#' library(bslib)
|
||||
@@ -116,10 +138,37 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
#' read reactive inputs and pass them as arguments.
|
||||
initialize = function(func) {
|
||||
private$func <- func
|
||||
private$rv_status <- reactiveVal("initial")
|
||||
private$rv_value <- reactiveVal(NULL)
|
||||
private$rv_error <- reactiveVal(NULL)
|
||||
|
||||
# Do not show these private reactive values in otel spans
|
||||
with_no_otel_collect({
|
||||
private$rv_status <- reactiveVal("initial", label = "ExtendedTask$private$status")
|
||||
private$rv_value <- reactiveVal(NULL, label = "ExtendedTask$private$value")
|
||||
private$rv_error <- reactiveVal(NULL, label = "ExtendedTask$private$error")
|
||||
})
|
||||
|
||||
private$invocation_queue <- fastmap::fastqueue()
|
||||
|
||||
domain <- getDefaultReactiveDomain()
|
||||
|
||||
# Set a label for the reactive values for easier debugging
|
||||
# Go up an extra sys.call() to get the user's call to ExtendedTask$new()
|
||||
# The first sys.call() is to `initialize(...)`
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = "<anonymous>"
|
||||
)
|
||||
private$otel_span_label <- otel_span_label_extended_task(label, domain = domain)
|
||||
private$otel_log_label_add_to_queue <- otel_log_label_extended_task_add_to_queue(label, domain = domain)
|
||||
|
||||
private$otel_attrs <- c(
|
||||
otel_srcref_attributes(call_srcref, "ExtendedTask"),
|
||||
otel_session_id_attrs(domain)
|
||||
) %||% list()
|
||||
|
||||
# Capture this value at init-time, not run-time
|
||||
# This way, the span is only created if otel was enabled at time of creation... just like other spans
|
||||
private$is_recording_otel <- has_otel_collect("reactivity")
|
||||
},
|
||||
#' @description
|
||||
#' Starts executing the long-running operation. If this `ExtendedTask` is
|
||||
@@ -139,8 +188,27 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
isolate(private$rv_status()) == "running" ||
|
||||
private$invocation_queue$size() > 0
|
||||
) {
|
||||
otel_log(
|
||||
private$otel_log_add_to_queue_label,
|
||||
severity = "debug",
|
||||
attributes = c(
|
||||
private$otel_attrs,
|
||||
list(
|
||||
queue_size = private$invocation_queue$size() + 1L
|
||||
)
|
||||
)
|
||||
)
|
||||
private$invocation_queue$add(list(args = args, call = call))
|
||||
} else {
|
||||
|
||||
if (private$is_recording_otel) {
|
||||
private$otel_span <- start_otel_span(
|
||||
private$otel_span_label,
|
||||
attributes = private$otel_attrs
|
||||
)
|
||||
otel::local_active_span(private$otel_span)
|
||||
}
|
||||
|
||||
private$do_invoke(args, call = call)
|
||||
}
|
||||
invisible(NULL)
|
||||
@@ -188,7 +256,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
#' invalidation will be ignored.
|
||||
result = function() {
|
||||
switch (private$rv_status(),
|
||||
running = req(FALSE, cancelOutput="progress"),
|
||||
running = req(FALSE, cancelOutput = "progress"),
|
||||
success = if (private$rv_value()$visible) {
|
||||
private$rv_value()$value
|
||||
} else {
|
||||
@@ -208,21 +276,36 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
|
||||
rv_error = NULL,
|
||||
invocation_queue = NULL,
|
||||
|
||||
otel_span_label = NULL,
|
||||
otel_log_label_add_to_queue = NULL,
|
||||
otel_attrs = list(),
|
||||
is_recording_otel = FALSE,
|
||||
otel_span = NULL,
|
||||
|
||||
do_invoke = function(args, call = NULL) {
|
||||
private$rv_status("running")
|
||||
private$rv_value(NULL)
|
||||
private$rv_error(NULL)
|
||||
|
||||
p <- promises::promise_resolve(
|
||||
p <- promise_resolve(
|
||||
maskReactiveContext(do.call(private$func, args))
|
||||
)
|
||||
|
||||
p <- promises::then(
|
||||
p,
|
||||
onFulfilled = function(value, .visible) {
|
||||
if (is_otel_span(private$otel_span)) {
|
||||
|
||||
private$otel_span$end(status_code = "ok")
|
||||
private$otel_span <- NULL
|
||||
}
|
||||
private$on_success(list(value = value, visible = .visible))
|
||||
},
|
||||
onRejected = function(error) {
|
||||
if (is_otel_span(private$otel_span)) {
|
||||
private$otel_span$end(status_code = "error")
|
||||
private$otel_span <- NULL
|
||||
}
|
||||
private$on_error(error, call = call)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -25,3 +25,7 @@ on_load_exprs <- list()
|
||||
on_load <- function(expr) {
|
||||
on_load_exprs[[length(on_load_exprs) + 1]] <<- substitute(expr)
|
||||
}
|
||||
|
||||
on_load({
|
||||
IS_SHINY_LOCAL_PKG <- exists(".__DEVTOOLS__")
|
||||
})
|
||||
|
||||
@@ -100,7 +100,7 @@ plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
||||
createGraphicsDevicePromiseDomain <- function(which = dev.cur()) {
|
||||
force(which)
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
function(...) {
|
||||
|
||||
@@ -56,13 +56,24 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
|
||||
|
||||
value <- restoreInput(id = inputId, default = NULL)
|
||||
|
||||
tags$button(id=inputId,
|
||||
icon <- validateIcon(icon)
|
||||
|
||||
if (!is.null(icon)) {
|
||||
icon <- span(icon, class = "action-icon")
|
||||
}
|
||||
|
||||
if (!is.null(label)) {
|
||||
label <- span(label, class = "action-label")
|
||||
}
|
||||
|
||||
tags$button(
|
||||
id = inputId,
|
||||
style = css(width = validateCssUnit(width)),
|
||||
type="button",
|
||||
class="btn btn-default action-button",
|
||||
type = "button",
|
||||
class = "btn btn-default action-button",
|
||||
`data-val` = value,
|
||||
disabled = if (isTRUE(disabled)) NA else NULL,
|
||||
list(validateIcon(icon), label),
|
||||
icon, label,
|
||||
...
|
||||
)
|
||||
}
|
||||
@@ -72,30 +83,40 @@ actionButton <- function(inputId, label, icon = NULL, width = NULL,
|
||||
actionLink <- function(inputId, label, icon = NULL, ...) {
|
||||
value <- restoreInput(id = inputId, default = NULL)
|
||||
|
||||
tags$a(id=inputId,
|
||||
href="#",
|
||||
class="action-button",
|
||||
icon <- validateIcon(icon)
|
||||
|
||||
if (!is.null(icon)) {
|
||||
icon <- span(icon, class = "action-icon")
|
||||
}
|
||||
|
||||
if (!is.null(label)) {
|
||||
label <- span(label, class = "action-label")
|
||||
}
|
||||
|
||||
tags$a(
|
||||
id = inputId,
|
||||
href = "#",
|
||||
class = "action-button action-link",
|
||||
`data-val` = value,
|
||||
list(validateIcon(icon), label),
|
||||
icon, label,
|
||||
...
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
# Check that the icon parameter is valid:
|
||||
# 1) Check if the user wants to actually add an icon:
|
||||
# -- if icon=NULL, it means leave the icon unchanged
|
||||
# -- if icon=character(0), it means don't add an icon or, more usefully,
|
||||
# remove the previous icon
|
||||
# 2) If so, check that the icon has the right format (this does not check whether
|
||||
# it is a *real* icon - currently that would require a massive cross reference
|
||||
# with the "font-awesome" and the "glyphicon" libraries)
|
||||
# Throw an informative warning if icon isn't html-ish
|
||||
validateIcon <- function(icon) {
|
||||
if (is.null(icon) || identical(icon, character(0))) {
|
||||
if (length(icon) == 0) {
|
||||
return(icon)
|
||||
} else if (inherits(icon, "shiny.tag") && icon$name == "i") {
|
||||
return(icon)
|
||||
} else {
|
||||
stop("Invalid icon. Use Shiny's 'icon()' function to generate a valid icon")
|
||||
}
|
||||
if (!isTagLike(icon)) {
|
||||
rlang::warn(
|
||||
c(
|
||||
"It appears that a non-HTML value was provided to `icon`.",
|
||||
i = "Try using a `shiny::icon()` (or an equivalent) to get an icon."
|
||||
),
|
||||
class = "shiny-validate-icon"
|
||||
)
|
||||
}
|
||||
icon
|
||||
}
|
||||
|
||||
@@ -436,34 +436,36 @@ MockShinySession <- R6Class(
|
||||
if (!is.function(func))
|
||||
stop(paste("Unexpected", class(func), "output for", name))
|
||||
|
||||
obs <- observe({
|
||||
# We could just stash the promise, but we get an "unhandled promise error". This bypasses
|
||||
prom <- NULL
|
||||
tryCatch({
|
||||
v <- private$withCurrentOutput(name, func(self, name))
|
||||
if (!promises::is.promise(v)){
|
||||
# Make our sync value into a promise
|
||||
prom <- promises::promise(function(resolve, reject){ resolve(v) })
|
||||
} else {
|
||||
prom <- v
|
||||
}
|
||||
}, error=function(e){
|
||||
# Error running value()
|
||||
prom <<- promises::promise(function(resolve, reject){ reject(e) })
|
||||
})
|
||||
|
||||
private$outs[[name]]$promise <- hybrid_chain(
|
||||
prom,
|
||||
function(v){
|
||||
list(val = v, err = NULL)
|
||||
}, catch=function(e){
|
||||
if (
|
||||
!inherits(e, c("shiny.custom.error", "shiny.output.cancel", "shiny.output.progress", "shiny.silent.error"))
|
||||
) {
|
||||
self$unhandledError(e, close = FALSE)
|
||||
with_no_otel_collect({
|
||||
obs <- observe({
|
||||
# We could just stash the promise, but we get an "unhandled promise error". This bypasses
|
||||
prom <- NULL
|
||||
tryCatch({
|
||||
v <- private$withCurrentOutput(name, func(self, name))
|
||||
if (!is.promise(v)){
|
||||
# Make our sync value into a promise
|
||||
prom <- promise_resolve(v)
|
||||
} else {
|
||||
prom <- v
|
||||
}
|
||||
list(val = NULL, err = e)
|
||||
}, error=function(e){
|
||||
# Error running value()
|
||||
prom <<- promise_reject(e)
|
||||
})
|
||||
|
||||
private$outs[[name]]$promise <- hybrid_chain(
|
||||
prom,
|
||||
function(v){
|
||||
list(val = v, err = NULL)
|
||||
}, catch=function(e){
|
||||
if (
|
||||
!inherits(e, c("shiny.custom.error", "shiny.output.cancel", "shiny.output.progress", "shiny.silent.error"))
|
||||
) {
|
||||
self$unhandledError(e, close = FALSE)
|
||||
}
|
||||
list(val = NULL, err = e)
|
||||
})
|
||||
})
|
||||
})
|
||||
private$outs[[name]] <- list(obs = obs, func = func, promise = NULL)
|
||||
},
|
||||
@@ -716,7 +718,7 @@ MockShinySession <- R6Class(
|
||||
stop("Nested calls to withCurrentOutput() are not allowed.")
|
||||
}
|
||||
|
||||
promises::with_promise_domain(
|
||||
with_promise_domain(
|
||||
createVarPromiseDomain(private, "currentOutputName", name),
|
||||
expr
|
||||
)
|
||||
|
||||
65
R/otel-attr-srcref.R
Normal file
65
R/otel-attr-srcref.R
Normal file
@@ -0,0 +1,65 @@
|
||||
|
||||
|
||||
# Very similar to srcrefFromShinyCall(),
|
||||
# however, this works when the function does not have a srcref attr set
|
||||
otel_srcref_attributes <- function(srcref, fn_name = NULL) {
|
||||
if (is.function(srcref)) {
|
||||
srcref <- getSrcRefs(srcref)[[1]][[1]]
|
||||
}
|
||||
|
||||
if (is.null(srcref)) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
stopifnot(inherits(srcref, "srcref"))
|
||||
|
||||
# Semantic conventions for code: https://opentelemetry.io/docs/specs/semconv/registry/attributes/code/
|
||||
#
|
||||
# Inspiration from https://github.com/r-lib/testthat/pull/2087/files#diff-92de3306849d93d6f7e76c5aaa1b0c037e2d716f72848f8a1c70536e0c8a1564R123-R124
|
||||
filename <- attr(srcref, "srcfile")$filename
|
||||
dropNulls(list(
|
||||
"code.function.name" = fn_name,
|
||||
# Location attrs
|
||||
"code.file.path" = filename,
|
||||
"code.line.number" = srcref[1],
|
||||
"code.column.number" = srcref[2],
|
||||
# Remove these deprecated location names once Logfire supports the preferred names
|
||||
# https://github.com/pydantic/logfire/issues/1559
|
||||
"code.filepath" = filename,
|
||||
"code.lineno" = srcref[1],
|
||||
"code.column" = srcref[2]
|
||||
))
|
||||
}
|
||||
|
||||
#' Get the srcref for the call at the specified stack level
|
||||
#'
|
||||
#' If you need to go farther back in the `sys.call()` stack, supply a larger
|
||||
#' negative number to `which_offset`. The default of 0 gets the immediate
|
||||
#' caller. `-1` would get the caller's caller, and so on.
|
||||
#' @param which_offset The stack level to get the call from. Defaults to -1 (the
|
||||
#' immediate caller).
|
||||
#' @return An srcref object, or NULL if none is found.
|
||||
#' @noRd
|
||||
get_call_srcref <- function(which_offset = 0) {
|
||||
# Go back one call to account for this function itself
|
||||
call <- sys.call(which_offset - 1)
|
||||
|
||||
srcref <- attr(call, "srcref", exact = TRUE)
|
||||
srcref
|
||||
}
|
||||
|
||||
|
||||
append_otel_srcref_attrs <- function(attrs, call_srcref, fn_name) {
|
||||
if (is.null(call_srcref)) {
|
||||
return(attrs)
|
||||
}
|
||||
|
||||
srcref_attrs <- otel_srcref_attributes(call_srcref, fn_name)
|
||||
if (is.null(srcref_attrs)) {
|
||||
return(attrs)
|
||||
}
|
||||
|
||||
attrs[names(srcref_attrs)] <- srcref_attrs
|
||||
|
||||
attrs
|
||||
}
|
||||
55
R/otel-collect.R
Normal file
55
R/otel-collect.R
Normal file
@@ -0,0 +1,55 @@
|
||||
otel_collect_choices <- c(
|
||||
"none",
|
||||
"session",
|
||||
"reactive_update",
|
||||
"reactivity",
|
||||
"all"
|
||||
)
|
||||
|
||||
# Check if the collect level is sufficient
|
||||
otel_collect_is_enabled <- function(
|
||||
impl_level,
|
||||
# Listen to option and fall back to the env var
|
||||
opt_collect_level = getOption("shiny.otel.collect", Sys.getenv("SHINY_OTEL_COLLECT", "all"))
|
||||
) {
|
||||
opt_collect_level <- as_otel_collect(opt_collect_level)
|
||||
|
||||
which(opt_collect_level == otel_collect_choices) >=
|
||||
which(impl_level == otel_collect_choices)
|
||||
}
|
||||
|
||||
# Check if tracing is enabled and if the collect level is sufficient
|
||||
has_otel_collect <- function(collect) {
|
||||
# Only check pkg author input iff loaded with pkgload
|
||||
if (IS_SHINY_LOCAL_PKG) {
|
||||
stopifnot(length(collect) == 1, any(collect == otel_collect_choices))
|
||||
}
|
||||
|
||||
otel_is_tracing_enabled() && otel_collect_is_enabled(collect)
|
||||
}
|
||||
|
||||
# Run expr with otel collection disabled
|
||||
with_no_otel_collect <- function(expr) {
|
||||
withOtelCollect("none", expr)
|
||||
}
|
||||
|
||||
|
||||
## -- Helpers -----------------------------------------------------
|
||||
|
||||
# shiny.otel.collect can be:
|
||||
# "none"; To do nothing / fully opt-out
|
||||
# "session" for session/start events
|
||||
# "reactive_update" (includes "session" features) and reactive_update spans
|
||||
# "reactivity" (includes "reactive_update" features) and spans for all reactive things
|
||||
# "all" - Anything that Shiny can do. (Currently equivalent to the "reactivity" level)
|
||||
|
||||
as_otel_collect <- function(collect = "all") {
|
||||
if (!is.character(collect)) {
|
||||
stop("`collect` must be a character vector.")
|
||||
}
|
||||
|
||||
# Match to collect enum
|
||||
collect <- match.arg(collect, otel_collect_choices, several.ok = FALSE)
|
||||
|
||||
return(collect)
|
||||
}
|
||||
194
R/otel-enable.R
Normal file
194
R/otel-enable.R
Normal file
@@ -0,0 +1,194 @@
|
||||
# # Approach
|
||||
# Use flags on the reactive object to indicate whether to record OpenTelemetry spans.
|
||||
#
|
||||
# Cadence:
|
||||
# * `$.isRecordingOtel` - Whether to record OpenTelemetry spans for this reactive object
|
||||
# * `$.otelLabel` - The label to use for the OpenTelemetry span
|
||||
# * `$.otelAttrs` - Additional attributes to add to the OpenTelemetry span
|
||||
|
||||
|
||||
#' Add OpenTelemetry for reactivity to an object
|
||||
#'
|
||||
#' @description
|
||||
#'
|
||||
#' `enable_otel_*()` methods add OpenTelemetry flags for [reactive()] expressions
|
||||
#' and `render*` functions (like [renderText()], [renderTable()], ...).
|
||||
#'
|
||||
#' Wrapper to creating an active reactive OpenTelemetry span that closes when
|
||||
#' the reactive expression is done computing. Typically this is when the
|
||||
#' reactive expression finishes (synchronous) or when the returned promise is
|
||||
#' done computing (asynchronous).
|
||||
|
||||
#' @section Async with OpenTelemetry:
|
||||
#'
|
||||
#' With a reactive expression, the key and/or value expression can be
|
||||
#' _asynchronous_. In other words, they can be promises --- not regular R
|
||||
#' promises, but rather objects provided by the
|
||||
#' \href{https://rstudio.github.io/promises/}{\pkg{promises}} package, which
|
||||
#' are similar to promises in JavaScript. (See [promises::promise()] for more
|
||||
#' information.) You can also use [mirai::mirai()] or [future::future()]
|
||||
#' objects to run code in a separate process or even on a remote machine.
|
||||
#'
|
||||
#' When reactive expressions are being calculated in parallel (by having
|
||||
#' another reactive promise compute in the main process), the currently active
|
||||
#' OpenTelemetry span will be dynamically swapped out according to the
|
||||
#' currently active reactive expression. This means that as long as a promise
|
||||
#' was `then()`ed or `catch()`ed with an active OpenTelemetry span, the span
|
||||
#' will be correctly propagated to the next step (and subsequently other
|
||||
#' steps) in the promise chain.
|
||||
#'
|
||||
#' While the common case is for a reactive expression to be created
|
||||
#' synchronously, troubles arise when the reactive expression is created
|
||||
#' asynchronously. The span **must** be created before the reactive expression
|
||||
#' is executed, it **must** be active for the duration of the expression, and
|
||||
#' it **must** not be closed until the reactive expression is done executing.
|
||||
#' This is not easily achieved with a single function call, so we provide a
|
||||
#' way to create a reactive expression that is bound to an OpenTelemetry
|
||||
#' span.
|
||||
#'
|
||||
#' @section Span management and performance:
|
||||
#'
|
||||
#' Dev note - Barret 2025-10:
|
||||
#' Typically, an OpenTelemetry span (`otel_span`) will inherit from the parent
|
||||
#' span. This works well and we can think of the hierarchy as a tree. With
|
||||
#' `options("shiny.otel.collect" = <value>)`, we are able to control with a sliding
|
||||
#' dial how much of the tree we are interested in: "none", "session",
|
||||
#' "reactive_update", "reactivity", and finally "all".
|
||||
#'
|
||||
#' Leveraging this hierarchy, we can avoid creating spans that are not needed.
|
||||
#' The act of making a noop span takes on the order of 10microsec. Handling of
|
||||
#' the opspan is also in the 10s of microsecond range. We should avoid this when
|
||||
#' we **know** that we're not interested in the span. Therefore, manually
|
||||
#' handling spans should be considered for Shiny.
|
||||
#'
|
||||
#' * Q:
|
||||
#' * But what about app author who want the current span? Is there any
|
||||
#' guarantee that the current span is expected `reactive()` span?
|
||||
#' * A:
|
||||
#' * No. The current span is whatever the current span is. If the app author
|
||||
#' wants a specific span, they should create it themselves.
|
||||
#' * Proof:
|
||||
#' ```r
|
||||
#' noop <- otel::get_active_span()
|
||||
#' noop$get_context()$get_span_id()
|
||||
#' #> [1] "0000000000000000"
|
||||
#' ignore <- otelsdk::with_otel_record({
|
||||
#' a <- otel::start_local_active_span("a")
|
||||
#' a$get_context()$get_span_id() |> str()
|
||||
#' otel::with_active_span(noop, {
|
||||
#' otel::get_active_span()$get_context()$get_span_id() |> str()
|
||||
#' })
|
||||
#' })
|
||||
#' #> chr "2645e95715841e75"
|
||||
#' #> chr "2645e95715841e75"
|
||||
#' # ## It is reasonable to expect the second id to be `0000000000000000`, but it's not.
|
||||
#' ```
|
||||
#' Therefore, the app author has no guarantee that the current span is the
|
||||
#' span they're expecting. If the app author wants a specific span, they should
|
||||
#' create it themselves and let natural inheritance take over.
|
||||
#'
|
||||
#' Given this, I will imagine that app authors will set
|
||||
#' `options("shiny.otel.collect" = "reactive_update")` as their default behavior.
|
||||
#' Enough to know things are happening, but not overwhelming from **everything**
|
||||
#' that is reactive.
|
||||
#'
|
||||
#' To _light up_ a specific area, users can call `withr::with_options(list("shiny.otel.collect" = "all"), { ... })`.
|
||||
#'
|
||||
#' @param x The object to add caching to.
|
||||
#' @param ... Future parameter expansion.
|
||||
#' @noRd
|
||||
NULL
|
||||
|
||||
|
||||
enable_otel_reactive_val <- function(x) {
|
||||
|
||||
impl <- attr(x, ".impl", exact = TRUE)
|
||||
# Set flag for otel logging when setting the value
|
||||
impl$.isRecordingOtel <- TRUE
|
||||
|
||||
class(x) <- c("reactiveVal.otel", class(x))
|
||||
|
||||
x
|
||||
}
|
||||
|
||||
enable_otel_reactive_values <- function(x) {
|
||||
|
||||
impl <- .subset2(x, "impl")
|
||||
# Set flag for otel logging when setting values
|
||||
impl$.isRecordingOtel <- TRUE
|
||||
|
||||
class(x) <- c("reactivevalues.otel", class(x))
|
||||
|
||||
x
|
||||
}
|
||||
|
||||
enable_otel_reactive_expr <- function(x) {
|
||||
|
||||
domain <- reactive_get_domain(x)
|
||||
|
||||
impl <- attr(x, "observable", exact = TRUE)
|
||||
impl$.isRecordingOtel <- TRUE
|
||||
# Covers both reactive and reactive.event
|
||||
impl$.otelLabel <- otel_span_label_reactive(x, domain = impl$.domain)
|
||||
|
||||
class(x) <- c("reactiveExpr.otel", class(x))
|
||||
|
||||
x
|
||||
}
|
||||
|
||||
enable_otel_observe <- function(x) {
|
||||
x$.isRecordingOtel <- TRUE
|
||||
x$.otelLabel <- otel_span_label_observer(x, domain = x$.domain)
|
||||
|
||||
class(x) <- c("Observer.otel", class(x))
|
||||
invisible(x)
|
||||
}
|
||||
|
||||
|
||||
|
||||
enable_otel_shiny_render_function <- function(x) {
|
||||
|
||||
valueFunc <- force(x)
|
||||
otel_span_label <- NULL
|
||||
otel_span_attrs <- NULL
|
||||
|
||||
renderFunc <- function(...) {
|
||||
# Dynamically determine the span label given the current reactive domain
|
||||
if (is.null(otel_span_label)) {
|
||||
domain <- getDefaultReactiveDomain()
|
||||
otel_span_label <<-
|
||||
otel_span_label_render_function(x, domain = domain)
|
||||
otel_span_attrs <<- c(
|
||||
attr(x, "otelAttrs"),
|
||||
otel_session_id_attrs(domain)
|
||||
)
|
||||
}
|
||||
|
||||
with_otel_span(
|
||||
otel_span_label,
|
||||
{
|
||||
hybrid_then(
|
||||
valueFunc(...),
|
||||
on_failure = set_otel_exception_status_and_throw,
|
||||
# Must save the error object
|
||||
tee = FALSE
|
||||
)
|
||||
},
|
||||
attributes = otel_span_attrs
|
||||
)
|
||||
}
|
||||
|
||||
renderFunc <- addAttributes(renderFunc, renderFunctionAttributes(valueFunc))
|
||||
class(renderFunc) <- c("shiny.render.function.otel", class(valueFunc))
|
||||
renderFunc
|
||||
}
|
||||
|
||||
|
||||
# ## If we ever expose a S3 function, I'd like to add this method.
|
||||
# bindOtel.function <- function(x, ...) {
|
||||
# cli::cli_abort(paste0(
|
||||
# "Don't know how to add OpenTelemetry recording to a plain function. ",
|
||||
# "If this is a {.code render*()} function for Shiny, it may need to be updated. ",
|
||||
# "Please see {.help shiny::bindOtel} for more information."
|
||||
# ))
|
||||
# }
|
||||
56
R/otel-error.R
Normal file
56
R/otel-error.R
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
has_seen_otel_exception <- function(cnd) {
|
||||
!is.null(cnd$.shiny_otel_exception)
|
||||
}
|
||||
|
||||
mark_otel_exception_as_seen <- function(cnd) {
|
||||
cnd$.shiny_otel_exception <- TRUE
|
||||
cnd
|
||||
}
|
||||
|
||||
set_otel_exception_status_and_throw <- function(cnd) {
|
||||
cnd <- set_otel_exception_status(cnd)
|
||||
|
||||
# Rethrow the (possibly updated) error
|
||||
signalCondition(cnd)
|
||||
}
|
||||
|
||||
set_otel_exception_status <- function(cnd) {
|
||||
if (inherits(cnd, "shiny.custom.error")) {
|
||||
# No-op
|
||||
} else if (inherits(cnd, "shiny.output.cancel")) {
|
||||
# No-op
|
||||
} else if (inherits(cnd, "shiny.output.progress")) {
|
||||
# No-op
|
||||
} else if (cnd_inherits(cnd, "shiny.silent.error")) {
|
||||
# No-op
|
||||
} else {
|
||||
# Only when an unknown error occurs do we set the span status to error
|
||||
span <- otel::get_active_span()
|
||||
|
||||
# Only record the exception once at the original point of failure,
|
||||
# not every reactive expression that it passes through
|
||||
if (!has_seen_otel_exception(cnd)) {
|
||||
span$record_exception(
|
||||
# Record a sanitized error if sanitization is enabled
|
||||
get_otel_error_obj(cnd)
|
||||
)
|
||||
cnd <- mark_otel_exception_as_seen(cnd)
|
||||
}
|
||||
|
||||
# Record the error status on the span for any context touching this error
|
||||
span$set_status("error")
|
||||
}
|
||||
|
||||
cnd
|
||||
}
|
||||
|
||||
|
||||
get_otel_error_obj <- function(e) {
|
||||
# Do not expose errors to otel if sanitization is enabled
|
||||
if (getOption("shiny.otel.sanitize.errors", TRUE)) {
|
||||
sanitized_error()
|
||||
} else {
|
||||
e
|
||||
}
|
||||
}
|
||||
198
R/otel-label.R
Normal file
198
R/otel-label.R
Normal file
@@ -0,0 +1,198 @@
|
||||
# observe mymod:<anonymous>
|
||||
# observe <anonymous>
|
||||
# observe mylabel
|
||||
|
||||
# -- Reactives --------------------------------------------------------------
|
||||
|
||||
#' OpenTelemetry Label Generation Functions
|
||||
#'
|
||||
#' Functions for generating formatted labels for OpenTelemetry tracing spans
|
||||
#' in Shiny applications. These functions handle module namespacing and
|
||||
#' cache/event modifiers for different Shiny reactive constructs.
|
||||
#'
|
||||
#' @param x The object to generate a label for (reactive, observer, etc.)
|
||||
#' @param label Character string label for reactive values
|
||||
#' @param key Character string key for reactiveValues operations
|
||||
#' @param ... Additional arguments (unused)
|
||||
#' @param domain Shiny domain object containing namespace information
|
||||
#'
|
||||
#' @return Character string formatted for OpenTelemetry span labels
|
||||
#' @name otel_label
|
||||
#' @noRd
|
||||
NULL
|
||||
|
||||
otel_span_label_reactive <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"reactive",
|
||||
cache_class = "reactive.cache",
|
||||
event_class = "reactive.event"
|
||||
)
|
||||
|
||||
label <- attr(x, "observable", exact = TRUE)[[".label"]]
|
||||
otel_span_label <- otel_label_upgrade(label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
otel_span_label_render_function <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"output",
|
||||
cache_class = "shiny.render.function.cache",
|
||||
event_class = "shiny.render.function.event"
|
||||
)
|
||||
|
||||
label <- getCurrentOutputInfo(session = domain)$name %||% "<unknown>"
|
||||
otel_span_label <- otel_label_upgrade(label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
otel_span_label_observer <- function(x, ..., domain) {
|
||||
fn_name <- otel_label_with_modifiers(
|
||||
x,
|
||||
"observe",
|
||||
cache_class = NULL, # Do not match a cache class here
|
||||
event_class = "Observer.event"
|
||||
)
|
||||
|
||||
otel_span_label <- otel_label_upgrade(x$.label, domain = domain)
|
||||
|
||||
sprintf("%s %s", fn_name, otel_span_label)
|
||||
}
|
||||
|
||||
# -- Set reactive value(s) ----------------------------------------------------
|
||||
|
||||
otel_log_label_set_reactive_val <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"Set reactiveVal %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
otel_log_label_set_reactive_values <- function(label, key, ..., domain) {
|
||||
sprintf(
|
||||
"Set reactiveValues %s$%s",
|
||||
otel_label_upgrade(label, domain = domain),
|
||||
key
|
||||
)
|
||||
}
|
||||
|
||||
# -- ExtendedTask -------------------------------------------------------------
|
||||
|
||||
otel_span_label_extended_task <- function(label, suffix = NULL, ..., domain) {
|
||||
sprintf(
|
||||
"ExtendedTask %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
otel_log_label_extended_task_add_to_queue <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"ExtendedTask %s add to queue",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
# -- Debounce / Throttle -------------------------------------------------------
|
||||
|
||||
otel_label_debounce <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"debounce %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
otel_label_throttle <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"throttle %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
# ---- Reactive Poll / File Reader -----------------------------------------------
|
||||
otel_label_reactive_poll <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"reactivePoll %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
otel_label_reactive_file_reader <- function(label, ..., domain) {
|
||||
sprintf(
|
||||
"reactiveFileReader %s",
|
||||
otel_label_upgrade(label, domain = domain)
|
||||
)
|
||||
}
|
||||
|
||||
# -- Helpers --------------------------------------------------------------
|
||||
|
||||
#' Modify function name based on object class modifiers
|
||||
#'
|
||||
#' @param x Object to check class of
|
||||
#' @param fn_name Base function name
|
||||
#' @param cache_class Optional class name that indicates cache operation
|
||||
#' @param event_class Optional class name that indicates event operation
|
||||
#'
|
||||
#' @return Modified function name with "cache" or "event" suffix if applicable
|
||||
#' @noRd
|
||||
otel_label_with_modifiers <- function(
|
||||
x,
|
||||
fn_name,
|
||||
cache_class = NULL,
|
||||
event_class = NULL
|
||||
) {
|
||||
for (x_class in rev(class(x))) {
|
||||
if (!is.null(cache_class) && x_class == cache_class) {
|
||||
fn_name <- sprintf("%s cache", fn_name)
|
||||
} else if (!is.null(event_class) && x_class == event_class) {
|
||||
fn_name <- sprintf("%s event", fn_name)
|
||||
}
|
||||
}
|
||||
|
||||
fn_name
|
||||
}
|
||||
|
||||
|
||||
#' Upgrade and format OpenTelemetry labels with module namespacing
|
||||
#'
|
||||
#' Processes labels for OpenTelemetry tracing, replacing default verbose labels
|
||||
#' with cleaner alternatives and prepending module namespaces when available.
|
||||
#'
|
||||
#' @param label Character string label to upgrade
|
||||
#' @param ... Additional arguments (unused)
|
||||
#' @param domain Shiny domain object containing namespace information
|
||||
#'
|
||||
#' @return Modified label string with module prefix if applicable
|
||||
#' @noRd
|
||||
#'
|
||||
#' @details
|
||||
#' Module prefix examples:
|
||||
#' - "" -> ""
|
||||
#' - "my-nested-mod-" -> "my-nested-mod"
|
||||
otel_label_upgrade <- function(label, ..., domain) {
|
||||
# By default, `observe()` sets the label to `observe(CODE)`
|
||||
# This label is too big and inconsistent.
|
||||
# Replace it with `<anonymous>`
|
||||
# (Similar with `eventReactive()` and `observeEvent()`)
|
||||
if (is_default_label(label) && grepl("(", label, fixed = TRUE)) {
|
||||
label <- "<anonymous>"
|
||||
# label <- sprintf("<anonymous> - %s", label)
|
||||
}
|
||||
|
||||
if (is.null(domain)) {
|
||||
return(label)
|
||||
}
|
||||
|
||||
namespace <- domain$ns("")
|
||||
|
||||
if (!nzchar(namespace)) {
|
||||
return(label)
|
||||
}
|
||||
|
||||
# Remove trailing module separator
|
||||
mod_ns <- sub(sprintf("%s$", ns.sep), "", namespace)
|
||||
|
||||
# Prepend the module name to the label
|
||||
# Ex: `"mymod:x"`
|
||||
sprintf("%s:%s", mod_ns, label)
|
||||
}
|
||||
114
R/otel-reactive-update.R
Normal file
114
R/otel-reactive-update.R
Normal file
@@ -0,0 +1,114 @@
|
||||
# * `session$userData[["_otel_span_reactive_update"]]` - The active reactive update span (or `NULL`)
|
||||
|
||||
|
||||
#' Start a `reactive_update` OpenTelemetry span and store it
|
||||
#'
|
||||
#' Used when a reactive expression is updated
|
||||
#' Will only start the span iff the otel tracing is enabled
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @return Invisibly returns.
|
||||
#' @seealso `otel_span_reactive_update_teardown()`
|
||||
#' @noRd
|
||||
otel_span_reactive_update_init <- function(..., domain) {
|
||||
|
||||
if (!has_otel_collect("reactive_update")) return()
|
||||
|
||||
# Ensure cleanup is registered only once per session
|
||||
if (is.null(domain$userData[["_otel_has_reactive_cleanup"]])) {
|
||||
domain$userData[["_otel_has_reactive_cleanup"]] <- TRUE
|
||||
|
||||
# Clean up any dangling reactive spans on an unplanned exit
|
||||
domain$onSessionEnded(function() {
|
||||
otel_span_reactive_update_teardown(domain = domain)
|
||||
})
|
||||
}
|
||||
|
||||
# Safety check
|
||||
if (is_otel_span(domain$userData[["_otel_span_reactive_update"]])) {
|
||||
stop("Reactive update span already exists")
|
||||
}
|
||||
|
||||
domain$userData[["_otel_span_reactive_update"]] <-
|
||||
start_otel_span(
|
||||
"reactive_update",
|
||||
...,
|
||||
attributes = otel_session_id_attrs(domain)
|
||||
)
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
#' End a `reactive_update` OpenTelemetry span and remove it from the session
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @return Invisibly returns.
|
||||
#' @seealso `otel_span_reactive_update_init()`
|
||||
#' @noRd
|
||||
otel_span_reactive_update_teardown <- function(..., domain) {
|
||||
ospan <- domain$userData[["_otel_span_reactive_update"]]
|
||||
|
||||
if (is_otel_span(ospan)) {
|
||||
otel::end_span(ospan)
|
||||
domain$userData[["_otel_span_reactive_update"]] <- NULL
|
||||
}
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
|
||||
#' Run expr within a `reactive_update` OpenTelemetry span
|
||||
#'
|
||||
#' Used to wrap the execution of a reactive expression. Will only
|
||||
#' require/activate the span iff the otel tracing is enabled
|
||||
#' @param expr The expression to executed within the span
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @noRd
|
||||
with_otel_span_reactive_update <- function(expr, ..., domain) {
|
||||
ospan <- domain$userData[["_otel_span_reactive_update"]]
|
||||
|
||||
if (!is_otel_span(ospan)) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
# Given the reactive update span is started before and ended when exec count
|
||||
# is 0, we only need to wrap the expr in the span context
|
||||
otel::with_active_span(ospan, {force(expr)})
|
||||
}
|
||||
|
||||
|
||||
#' Run expr within `reactive_update` otel span if not already active
|
||||
#'
|
||||
#' If the reactive update otel span is not already active, run the expression
|
||||
#' within the reactive update otel span context. This ensures that nested calls
|
||||
#' to reactive expressions do not attempt to re-enter the same span.
|
||||
#'
|
||||
#' This method is used within Context `run()` and running an Output's observer
|
||||
#' implementation
|
||||
#' @param expr The expression to executed within the span
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain to associate with the span
|
||||
#' @noRd
|
||||
maybe_with_otel_span_reactive_update <- function(expr, ..., domain) {
|
||||
if (is.null(domain$userData[["_otel_reactive_update_is_active"]])) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- TRUE
|
||||
|
||||
# When the expression is done promising, clear the active flag
|
||||
hybrid_then(
|
||||
{
|
||||
with_otel_span_reactive_update(domain = domain, expr)
|
||||
},
|
||||
on_success = function(value) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- NULL
|
||||
},
|
||||
on_failure = function(e) {
|
||||
domain$userData[["_otel_reactive_update_is_active"]] <- NULL
|
||||
},
|
||||
# Return the value before the callbacks
|
||||
tee = TRUE
|
||||
)
|
||||
} else {
|
||||
expr
|
||||
}
|
||||
}
|
||||
96
R/otel-session.R
Normal file
96
R/otel-session.R
Normal file
@@ -0,0 +1,96 @@
|
||||
# Semantic conventions for session: https://opentelemetry.io/docs/specs/semconv/general/session/
|
||||
|
||||
#' Create and use session span and events
|
||||
#'
|
||||
#' If otel is disabled, the session span and events will not be created,
|
||||
#' however the expression will still be evaluated.
|
||||
#'
|
||||
#' Span: `session_start`, `session_end`
|
||||
#' @param expr Expression to evaluate within the session span
|
||||
#' @param ... Ignored
|
||||
#' @param domain The reactive domain
|
||||
#' @noRd
|
||||
otel_span_session_start <- function(expr, ..., domain) {
|
||||
|
||||
if (!has_otel_collect("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
# Wrap the server initialization
|
||||
with_otel_span(
|
||||
"session_start",
|
||||
expr,
|
||||
attributes = otel::as_attributes(c(
|
||||
otel_session_id_attrs(domain),
|
||||
otel_session_attrs(domain)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
otel_span_session_end <- function(expr, ..., domain) {
|
||||
if (!has_otel_collect("session")) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
id_attrs <- otel_session_id_attrs(domain)
|
||||
with_otel_span(
|
||||
"session_end",
|
||||
expr,
|
||||
attributes = id_attrs
|
||||
)
|
||||
}
|
||||
|
||||
# -- Helpers -------------------------------
|
||||
|
||||
|
||||
# Occurs when the websocket connection is established
|
||||
otel_session_attrs <- function(domain) {
|
||||
# TODO: Future: Posit Connect integration
|
||||
# > we are still trying to identify all of the information we want to track/expose
|
||||
#
|
||||
# * `POSIT_PRODUCT` (Fallback to RSTUDIO_PRODUCT) for host environment
|
||||
# * `CONNECT_SERVER` envvar to get the `session.address`.
|
||||
# * `CONNECT_CONTENT_GUID` for the consistent app distinguisher
|
||||
# * Maybe `CONNECT_CONTENT_JOB_KEY`?
|
||||
# * Maybe `user.id` to be their user name: https://opentelemetry.io/docs/specs/semconv/registry/attributes/user/
|
||||
attrs <- list(
|
||||
server.path =
|
||||
sub(
|
||||
"/websocket/$", "/",
|
||||
domain[["request"]][["PATH_INFO"]] %||% ""
|
||||
),
|
||||
server.address = domain[["request"]][["HTTP_HOST"]] %||% "",
|
||||
server.origin = domain[["request"]][["HTTP_ORIGIN"]] %||% "",
|
||||
## Currently, Shiny does not expose QUERY_STRING when connecting the websocket
|
||||
# so we do not provide it here.
|
||||
# QUERY_STRING = domain[["request"]][["QUERY_STRING"]] %||% "",
|
||||
server.port = domain[["request"]][["SERVER_PORT"]] %||% NA_integer_
|
||||
)
|
||||
# Safely convert SERVER_PORT to integer
|
||||
# If conversion fails, leave as-is (string or empty)
|
||||
# This avoids warnings/errors if SERVER_PORT is not a valid integer
|
||||
server_port <- suppressWarnings(as.integer(attrs$server.port))
|
||||
if (!is.na(server_port)) {
|
||||
attrs$server.port <- server_port
|
||||
}
|
||||
|
||||
attrs
|
||||
}
|
||||
|
||||
otel_session_id_attrs <- function(domain) {
|
||||
token <- domain$token
|
||||
if (is.null(token)) {
|
||||
return(list())
|
||||
}
|
||||
|
||||
list(
|
||||
# Convention for client-side with session.start and session.end events
|
||||
# https://opentelemetry.io/docs/specs/semconv/general/session/
|
||||
#
|
||||
# Since we are the server, we'll add them as an attribute to _every_ span
|
||||
# within the session as we don't know exactly when they will be called.
|
||||
# Given it's only a single attribute, the cost should be minimal, but it ties every reactive calculation together.
|
||||
session.id = token
|
||||
)
|
||||
}
|
||||
127
R/otel-shiny.R
Normal file
127
R/otel-shiny.R
Normal file
@@ -0,0 +1,127 @@
|
||||
# Used by otel to identify the tracer and logger for this package
|
||||
# https://github.com/r-lib/otel/blob/afc31bc1f4bd177870d44b051ada1d9e4e685346/R/tracer-name.R#L33-L49
|
||||
# DO NOT CHANGE THIS VALUE without understanding the implications for existing telemetry data!
|
||||
otel_tracer_name <- "co.posit.r-package.shiny"
|
||||
|
||||
init_otel <- function() {
|
||||
.globals$otel_tracer <- otel::get_tracer()
|
||||
.globals$otel_is_tracing_enabled <- otel::is_tracing_enabled(.globals$otel_tracer)
|
||||
|
||||
.globals$otel_logger <- otel::get_logger()
|
||||
# .globals$otel_is_logging_enabled <- otel::is_logging_enabled()
|
||||
}
|
||||
on_load({init_otel()})
|
||||
|
||||
#' Run expr within a Shiny OpenTelemetry recording context
|
||||
#'
|
||||
#' Reset the OpenTelemetry tracer and logger for Shiny.
|
||||
#' Used for testing purposes only.
|
||||
#' @param expr Expression to evaluate within the recording context
|
||||
#' @return The result of evaluating `otelsdk::with_otel_record(expr)` with freshly enabled Shiny otel tracer and logger
|
||||
#' @noRd
|
||||
with_shiny_otel_record <- function(expr) {
|
||||
# Only use within internal testthat tests
|
||||
stopifnot(testthat__is_testing())
|
||||
withr::defer({ init_otel() })
|
||||
|
||||
otelsdk::with_otel_record({
|
||||
init_otel()
|
||||
|
||||
force(expr)
|
||||
})
|
||||
}
|
||||
|
||||
#' Check if OpenTelemetry tracing is enabled
|
||||
#'
|
||||
#' @param tracer The OpenTelemetry tracer to check (default: Shiny otel tracer)
|
||||
#' @return `TRUE` if tracing is enabled, `FALSE` otherwise
|
||||
#' @noRd
|
||||
otel_is_tracing_enabled <- function() {
|
||||
.globals[["otel_is_tracing_enabled"]]
|
||||
}
|
||||
|
||||
#' Shiny OpenTelemetry logger
|
||||
#'
|
||||
#' Used for logging OpenTelemetry events via `otel_log()`
|
||||
#' @return An OpenTelemetry logger
|
||||
#' @noRd
|
||||
shiny_otel_logger <- function() {
|
||||
.globals[["otel_logger"]]
|
||||
}
|
||||
|
||||
|
||||
|
||||
#' Shiny OpenTelemetry tracer
|
||||
#'
|
||||
#' Used for creating OpenTelemetry spans via `with_otel_span()` and
|
||||
#' `start_otel_span()`
|
||||
#'
|
||||
#' Inspired by httr2:::get_tracer().
|
||||
#' @return An OpenTelemetry tracer
|
||||
#' @noRd
|
||||
shiny_otel_tracer <- function() {
|
||||
.globals[["otel_tracer"]]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#' Create and use a Shiny OpenTelemetry span
|
||||
#'
|
||||
#' If otel is disabled, the span will not be created,
|
||||
#' however the expression will still be evaluated.
|
||||
#' @param name Span name
|
||||
#' @param expr Expression to evaluate within the span
|
||||
#' @param ... Ignored
|
||||
#' @param attributes Optional span attributes
|
||||
#' @return The result of evaluating `expr`
|
||||
#' @noRd
|
||||
with_otel_span <- function(name, expr, ..., attributes = NULL) {
|
||||
promises::with_otel_span(name, expr, ..., attributes = attributes, tracer = shiny_otel_tracer())
|
||||
}
|
||||
|
||||
|
||||
#' Start a Shiny OpenTelemetry span
|
||||
#'
|
||||
#' @param name Span name
|
||||
#' @param ... Additional arguments passed to `otel::start_span()`
|
||||
#' @return An OpenTelemetry span
|
||||
#' @noRd
|
||||
start_otel_span <- function(name, ...) {
|
||||
otel::start_span(name, ..., tracer = shiny_otel_tracer())
|
||||
}
|
||||
|
||||
|
||||
# # TODO: Set attributes on the current active span
|
||||
# # 5. Set attributes on the current active span
|
||||
# set_otel_span_attrs(status = 200L)
|
||||
|
||||
|
||||
# -- Helpers --------------------------------------------------------------
|
||||
|
||||
|
||||
is_otel_span <- function(x) {
|
||||
inherits(x, "otel_span")
|
||||
}
|
||||
|
||||
testthat__is_testing <- function() {
|
||||
# testthat::is_testing()
|
||||
identical(Sys.getenv("TESTTHAT"), "true")
|
||||
}
|
||||
|
||||
#' Log a message using the Shiny OpenTelemetry logger
|
||||
#'
|
||||
#' @param msg The log message
|
||||
#' @param ... Additional attributes to add to the log record
|
||||
#' @param severity The log severity level (default: "info")
|
||||
#' @param logger The OpenTelemetry logger to use (default: Shiny otel logger)
|
||||
#' @return Invisibly returns.
|
||||
#' @noRd
|
||||
otel_log <- function(
|
||||
msg,
|
||||
...,
|
||||
severity = "info",
|
||||
logger = shiny_otel_logger()
|
||||
) {
|
||||
otel::log(msg, ..., severity = severity, logger = logger)
|
||||
}
|
||||
125
R/otel-with.R
Normal file
125
R/otel-with.R
Normal file
@@ -0,0 +1,125 @@
|
||||
#' Temporarily set OpenTelemetry (OTel) collection level
|
||||
#'
|
||||
#' @description
|
||||
#' Control Shiny's OTel collection level for particular reactive expression(s).
|
||||
#'
|
||||
#' `withOtelCollect()` sets the OpenTelemetry collection level for
|
||||
#' the duration of evaluating `expr`. `localOtelCollect()` sets the collection
|
||||
#' level for the remainder of the current function scope.
|
||||
#'
|
||||
#' @details
|
||||
#' Note that `"session"` and `"reactive_update"` levels are not permitted as
|
||||
#' these are runtime-specific levels that should only be set permanently via
|
||||
#' `options(shiny.otel.collect = ...)` or the `SHINY_OTEL_COLLECT` environment
|
||||
#' variable, not temporarily during reactive expression creation.
|
||||
#'
|
||||
#' @section Best practice:
|
||||
#'
|
||||
#' Best practice is to set the collection level for code that *creates* reactive
|
||||
#' expressions, not code that *runs* them. For instance:
|
||||
#'
|
||||
#' ```r
|
||||
#' # Disable telemetry for a reactive expression
|
||||
#' withOtelCollect("none", {
|
||||
#' my_reactive <- reactive({ ... })
|
||||
#' })
|
||||
#'
|
||||
#' # Disable telemetry for a render function
|
||||
#' withOtelCollect("none", {
|
||||
#' output$my_plot <- renderPlot({ ... })
|
||||
#' })
|
||||
#'
|
||||
#' #' # Disable telemetry for an observer
|
||||
#' withOtelCollect("none", {
|
||||
#' observe({ ... }))
|
||||
#' })
|
||||
#'
|
||||
#' # Disable telemetry for an entire module
|
||||
#' withOtelCollect("none", {
|
||||
#' my_result <- my_module("my_id")
|
||||
#' })
|
||||
#' # Use `my_result` as normal here
|
||||
#' ```
|
||||
#'
|
||||
#' NOTE: It's not recommended to pipe existing reactive objects into
|
||||
#' `withOtelCollect()` since they won't inherit their intended OTel settings,
|
||||
#' leading to confusion.
|
||||
#'
|
||||
#' @param collect Character string specifying the OpenTelemetry collection level.
|
||||
#' Must be one of the following:
|
||||
#'
|
||||
#' * `"none"` - No telemetry data collected
|
||||
#' * `"reactivity"` - Collect reactive execution spans (includes session and
|
||||
#' reactive update events)
|
||||
#' * `"all"` - All available telemetry (currently equivalent to `"reactivity"`)
|
||||
#' @param expr Expression to evaluate with the specified collection level
|
||||
#' (for `withOtelCollect()`).
|
||||
#' @param envir Environment where the collection level should be set
|
||||
#' (for `localOtelCollect()`). Defaults to the parent frame.
|
||||
#'
|
||||
#' @return
|
||||
#' * `withOtelCollect()` returns the value of `expr`.
|
||||
#' * `localOtelCollect()` is called for its side effect and returns the previous
|
||||
#' `collect` value invisibly.
|
||||
#'
|
||||
#' @seealso See the `shiny.otel.collect` option within [`shinyOptions`]. Setting
|
||||
#' this value will globally control OpenTelemetry collection levels.
|
||||
#'
|
||||
#' @examples
|
||||
#' \dontrun{
|
||||
#' # Temporarily disable telemetry collection
|
||||
#' withOtelCollect("none", {
|
||||
#' # Code here won't generate telemetry
|
||||
#' reactive({ input$x + 1 })
|
||||
#' })
|
||||
#'
|
||||
#' # Collect reactivity telemetry but not other events
|
||||
#' withOtelCollect("reactivity", {
|
||||
#' # Reactive execution will be traced
|
||||
#' observe({ print(input$x) })
|
||||
#' })
|
||||
#'
|
||||
#' # Use local variant in a function
|
||||
#' my_function <- function() {
|
||||
#' localOtelCollect("none")
|
||||
#' # Rest of function executes without telemetry
|
||||
#' reactive({ input$y * 2 })
|
||||
#' }
|
||||
#' }
|
||||
#'
|
||||
#' @rdname withOtelCollect
|
||||
#' @export
|
||||
withOtelCollect <- function(collect, expr) {
|
||||
collect <- as_otel_collect_with(collect)
|
||||
|
||||
withr::with_options(
|
||||
list(shiny.otel.collect = collect),
|
||||
expr
|
||||
)
|
||||
}
|
||||
|
||||
#' @rdname withOtelCollect
|
||||
#' @export
|
||||
localOtelCollect <- function(collect, envir = parent.frame()) {
|
||||
collect <- as_otel_collect_with(collect)
|
||||
|
||||
old <- withr::local_options(
|
||||
list(shiny.otel.collect = collect),
|
||||
.local_envir = envir
|
||||
)
|
||||
|
||||
invisible(old)
|
||||
}
|
||||
|
||||
# Helper function to validate collect levels for with/local functions
|
||||
# Only allows "none", "reactivity", and "all" - not "session" or "reactive_update"
|
||||
as_otel_collect_with <- function(collect) {
|
||||
if (!is.character(collect)) {
|
||||
stop("`collect` must be a character vector.")
|
||||
}
|
||||
|
||||
allowed_levels <- c("none", "reactivity", "all")
|
||||
collect <- match.arg(collect, allowed_levels, several.ok = FALSE)
|
||||
|
||||
return(collect)
|
||||
}
|
||||
80
R/react.R
80
R/react.R
@@ -16,6 +16,60 @@ processId <- local({
|
||||
}
|
||||
})
|
||||
|
||||
ctx_otel_info_obj <- function(
|
||||
isRecordingOtel = FALSE,
|
||||
otelLabel = "<unknown>",
|
||||
otelAttrs = list()
|
||||
) {
|
||||
structure(
|
||||
list(
|
||||
isRecordingOtel = isRecordingOtel,
|
||||
otelLabel = otelLabel,
|
||||
otelAttrs = otelAttrs
|
||||
),
|
||||
class = "ctx_otel_info"
|
||||
)
|
||||
}
|
||||
|
||||
with_otel_span_context <- function(otel_info, expr, domain) {
|
||||
if (!otel_is_tracing_enabled()) {
|
||||
return(force(expr))
|
||||
}
|
||||
|
||||
isRecordingOtel <- .subset2(otel_info, "isRecordingOtel")
|
||||
otelLabel <- .subset2(otel_info, "otelLabel")
|
||||
otelAttrs <- .subset2(otel_info, "otelAttrs")
|
||||
|
||||
# Always set the reactive update span as active
|
||||
# This ensures that any spans created within the reactive context
|
||||
# are at least children of the reactive update span
|
||||
maybe_with_otel_span_reactive_update(domain = domain, {
|
||||
if (isRecordingOtel) {
|
||||
with_otel_span(
|
||||
otelLabel,
|
||||
{
|
||||
# Works with both sync and async expressions
|
||||
# Needed for both observer and reactive contexts
|
||||
hybrid_then(
|
||||
expr,
|
||||
on_failure = set_otel_exception_status_and_throw,
|
||||
# Must upgrade the error object
|
||||
tee = FALSE
|
||||
)
|
||||
},
|
||||
# expr,
|
||||
attributes = otelAttrs
|
||||
)
|
||||
} else {
|
||||
force(expr)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#' @include graph.R
|
||||
Context <- R6Class(
|
||||
'Context',
|
||||
@@ -33,11 +87,14 @@ Context <- R6Class(
|
||||
.pid = NULL,
|
||||
.weak = NULL,
|
||||
|
||||
.otel_info = NULL,
|
||||
|
||||
initialize = function(
|
||||
domain, label='', type='other', prevId='',
|
||||
reactId = rLog$noReactId,
|
||||
id = .getReactiveEnvironment()$nextId(), # For dummy context
|
||||
weak = FALSE
|
||||
weak = FALSE,
|
||||
otel_info = ctx_otel_info_obj()
|
||||
) {
|
||||
id <<- id
|
||||
.label <<- label
|
||||
@@ -47,17 +104,26 @@ Context <- R6Class(
|
||||
.reactType <<- type
|
||||
.weak <<- weak
|
||||
rLog$createContext(id, label, type, prevId, domain)
|
||||
if (!is.null(otel_info)) {
|
||||
if (IS_SHINY_LOCAL_PKG) {
|
||||
stopifnot(inherits(otel_info, "ctx_otel_info"))
|
||||
}
|
||||
.otel_info <<- otel_info
|
||||
}
|
||||
},
|
||||
run = function(func) {
|
||||
"Run the provided function under this context."
|
||||
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
withReactiveDomain(.domain, {
|
||||
captureStackTraces({
|
||||
env <- .getReactiveEnvironment()
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
env$runWith(self, func)
|
||||
with_otel_span_context(.otel_info, domain = .domain, {
|
||||
captureStackTraces({
|
||||
env <- .getReactiveEnvironment()
|
||||
rLog$enter(.reactId, id, .reactType, .domain)
|
||||
on.exit(rLog$exit(.reactId, id, .reactType, .domain), add = TRUE)
|
||||
env$runWith(self, func)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -231,7 +297,7 @@ wrapForContext <- function(func, ctx) {
|
||||
}
|
||||
|
||||
reactivePromiseDomain <- function() {
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ createMockDomain <- function() {
|
||||
callbacks <- Callbacks$new()
|
||||
ended <- FALSE
|
||||
domain <- new.env(parent = emptyenv())
|
||||
domain$ns <- function(id) id
|
||||
domain$token <- "mock-domain"
|
||||
domain$onEnded <- function(callback) {
|
||||
return(callbacks$register(callback))
|
||||
}
|
||||
@@ -95,7 +97,11 @@ getDefaultReactiveDomain <- function() {
|
||||
#' @rdname domains
|
||||
#' @export
|
||||
withReactiveDomain <- function(domain, expr) {
|
||||
promises::with_promise_domain(createVarPromiseDomain(.globals, "domain", domain), expr)
|
||||
# Use `promises::` as it shows up in the stack trace
|
||||
promises::with_promise_domain(
|
||||
createVarPromiseDomain(.globals, "domain", domain),
|
||||
expr
|
||||
)
|
||||
}
|
||||
|
||||
#
|
||||
|
||||
602
R/reactives.R
602
R/reactives.R
@@ -79,19 +79,26 @@ ReactiveVal <- R6Class(
|
||||
dependents = NULL
|
||||
),
|
||||
public = list(
|
||||
.isRecordingOtel = FALSE, # Needs to be set by Shiny
|
||||
.otelLabel = NULL, # Needs to be set by Shiny
|
||||
.otelAttrs = NULL, # Needs to be set by Shiny
|
||||
|
||||
initialize = function(value, label = NULL) {
|
||||
reactId <- nextGlobalReactId()
|
||||
private$reactId <- reactId
|
||||
private$value <- value
|
||||
private$label <- label
|
||||
private$dependents <- Dependents$new(reactId = private$reactId)
|
||||
rLog$define(private$reactId, value, private$label, type = "reactiveVal", getDefaultReactiveDomain())
|
||||
|
||||
domain <- getDefaultReactiveDomain()
|
||||
rLog$define(private$reactId, value, private$label, type = "reactiveVal", domain)
|
||||
.otelLabel <<- otel_log_label_set_reactive_val(private$label, domain = domain)
|
||||
},
|
||||
get = function() {
|
||||
private$dependents$register()
|
||||
|
||||
if (private$frozen)
|
||||
reactiveStop()
|
||||
reactiveStop()
|
||||
|
||||
private$value
|
||||
},
|
||||
@@ -99,7 +106,16 @@ ReactiveVal <- R6Class(
|
||||
if (identical(private$value, value)) {
|
||||
return(invisible(FALSE))
|
||||
}
|
||||
rLog$valueChange(private$reactId, value, getDefaultReactiveDomain())
|
||||
|
||||
domain <- getDefaultReactiveDomain()
|
||||
if ((!is.null(domain)) && .isRecordingOtel) {
|
||||
otel_log(
|
||||
.otelLabel,
|
||||
severity = "info",
|
||||
attributes = c(private$.otelAttrs, otel_session_id_attrs(domain))
|
||||
)
|
||||
}
|
||||
rLog$valueChange(private$reactId, value, domain)
|
||||
private$value <- value
|
||||
private$dependents$invalidate()
|
||||
invisible(TRUE)
|
||||
@@ -205,13 +221,20 @@ ReactiveVal <- R6Class(
|
||||
#'
|
||||
#' @export
|
||||
reactiveVal <- function(value = NULL, label = NULL) {
|
||||
call_srcref <- get_call_srcref()
|
||||
if (missing(label)) {
|
||||
call <- sys.call()
|
||||
label <- rvalSrcrefToLabel(attr(call, "srcref", exact = TRUE))
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = paste0("reactiveVal", createUniqueId(4))
|
||||
)
|
||||
}
|
||||
|
||||
rv <- ReactiveVal$new(value, label)
|
||||
structure(
|
||||
if (!is.null(call_srcref)) {
|
||||
rv$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactiveVal")
|
||||
}
|
||||
|
||||
ret <- structure(
|
||||
function(x) {
|
||||
if (missing(x)) {
|
||||
rv$get()
|
||||
@@ -224,6 +247,12 @@ reactiveVal <- function(value = NULL, label = NULL) {
|
||||
label = label,
|
||||
.impl = rv
|
||||
)
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_reactive_val(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#' @rdname freezeReactiveValue
|
||||
@@ -262,8 +291,11 @@ format.reactiveVal <- function(x, ...) {
|
||||
# assigned to (e.g. for `a <- reactiveVal()`, the result should be "a"). This
|
||||
# is a fragile, error-prone operation, so we default to a random label if
|
||||
# necessary.
|
||||
rvalSrcrefToLabel <- function(srcref,
|
||||
defaultLabel = paste0("reactiveVal", createUniqueId(4))) {
|
||||
rassignSrcrefToLabel <- function(
|
||||
srcref,
|
||||
defaultLabel,
|
||||
fnName = "([a-zA-Z0-9_.]+)"
|
||||
) {
|
||||
|
||||
if (is.null(srcref))
|
||||
return(defaultLabel)
|
||||
@@ -287,7 +319,11 @@ rvalSrcrefToLabel <- function(srcref,
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], srcref[2] - 1)
|
||||
|
||||
m <- regexec("\\s*([^[:space:]]+)\\s*(<-|=)\\s*reactiveVal\\b", firstLine)
|
||||
m <- regexec(
|
||||
# Require the first assignment within the line
|
||||
paste0("^\\s*([^[:space:]]+)\\s*(<<-|<-|=)\\s*", fnName, "\\b"),
|
||||
firstLine
|
||||
)
|
||||
if (m[[1]][1] == -1) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
@@ -330,6 +366,9 @@ ReactiveValues <- R6Class(
|
||||
# object, but it does not preserve order.
|
||||
.nameOrder = character(0),
|
||||
|
||||
.isRecordingOtel = FALSE, # Needs to be set by Shiny
|
||||
.otelAttrs = NULL, # Needs to be set by Shiny
|
||||
|
||||
|
||||
initialize = function(
|
||||
dedupe = TRUE,
|
||||
@@ -406,6 +445,21 @@ ReactiveValues <- R6Class(
|
||||
return(invisible())
|
||||
}
|
||||
|
||||
if ((!is.null(domain)) && .isRecordingOtel) {
|
||||
if (
|
||||
# Any reactiveValues (other than input or clientData) are fair game
|
||||
!(.label == "input" || .label == "clientData") ||
|
||||
# Do not include updates to input or clientData unless _some_ reactivity has occured
|
||||
!is.null(domain$userData[["_otel_has_reactive_cleanup"]])
|
||||
) {
|
||||
otel_log(
|
||||
otel_log_label_set_reactive_values(.label, key, domain = domain),
|
||||
severity = "info",
|
||||
attributes = c(.otelAttrs, otel_session_id_attrs(domain))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
# If it's new, append key to the name order
|
||||
if (!key_exists) {
|
||||
.nameOrder[length(.nameOrder) + 1] <<- key
|
||||
@@ -579,10 +633,28 @@ reactiveValues <- function(...) {
|
||||
if ((length(args) > 0) && (is.null(names(args)) || any(names(args) == "")))
|
||||
rlang::abort("All arguments passed to reactiveValues() must be named.")
|
||||
|
||||
values <- .createReactiveValues(ReactiveValues$new())
|
||||
values <- .createReactiveValues(ReactiveValues$new(), withOtel = FALSE)
|
||||
|
||||
# Use .subset2() instead of [[, to avoid method dispatch
|
||||
.subset2(values, 'impl')$mset(args)
|
||||
impl <- .subset2(values, 'impl')
|
||||
|
||||
call_srcref <- get_call_srcref()
|
||||
if (!is.null(call_srcref)) {
|
||||
impl$.label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
# Pass through the random default label created in ReactiveValues$new()
|
||||
defaultLabel = impl$.label
|
||||
)
|
||||
|
||||
impl$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactiveValues")
|
||||
}
|
||||
|
||||
impl$mset(args)
|
||||
|
||||
# Add otel collection after `$mset()` so that we don't log the initial values
|
||||
# Add otel collection after `.label` so that any logging uses the correct label
|
||||
values <- maybeAddReactiveValuesOtel(values)
|
||||
|
||||
values
|
||||
}
|
||||
|
||||
@@ -597,10 +669,11 @@ checkName <- function(x) {
|
||||
# @param values A ReactiveValues object
|
||||
# @param readonly Should this object be read-only?
|
||||
# @param ns A namespace function (either `identity` or `NS(namespace)`)
|
||||
# @param withOtel Should otel collection be attempted?
|
||||
.createReactiveValues <- function(values = NULL, readonly = FALSE,
|
||||
ns = identity) {
|
||||
ns = identity, withOtel = TRUE) {
|
||||
|
||||
structure(
|
||||
ret <- structure(
|
||||
list(
|
||||
impl = values,
|
||||
readonly = readonly,
|
||||
@@ -608,6 +681,20 @@ checkName <- function(x) {
|
||||
),
|
||||
class='reactivevalues'
|
||||
)
|
||||
|
||||
if (withOtel) {
|
||||
ret <- maybeAddReactiveValuesOtel(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
maybeAddReactiveValuesOtel <- function(x) {
|
||||
if (!has_otel_collect("reactivity")) {
|
||||
return(x)
|
||||
}
|
||||
|
||||
enable_otel_reactive_values(x)
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -831,6 +918,10 @@ Observable <- R6Class(
|
||||
.mostRecentCtxId = character(0),
|
||||
.ctx = 'Context',
|
||||
|
||||
.isRecordingOtel = FALSE, # Needs to be set by Shiny
|
||||
.otelLabel = NULL, # Needs to be set by Shiny
|
||||
.otelAttrs = NULL, # Needs to be set by Shiny
|
||||
|
||||
initialize = function(func, label = deparse(substitute(func)),
|
||||
domain = getDefaultReactiveDomain(),
|
||||
..stacktraceon = TRUE) {
|
||||
@@ -885,9 +976,19 @@ Observable <- R6Class(
|
||||
simpleExprToFunction(fn_body(.origFunc), "reactive")
|
||||
},
|
||||
.updateValue = function() {
|
||||
ctx <- Context$new(.domain, .label, type = 'observable',
|
||||
prevId = .mostRecentCtxId, reactId = .reactId,
|
||||
weak = TRUE)
|
||||
ctx <- Context$new(
|
||||
.domain,
|
||||
.label,
|
||||
type = 'observable',
|
||||
prevId = .mostRecentCtxId,
|
||||
reactId = .reactId,
|
||||
weak = TRUE,
|
||||
otel_info = ctx_otel_info_obj(
|
||||
isRecordingOtel = .isRecordingOtel,
|
||||
otelLabel = .otelLabel,
|
||||
otelAttrs = c(.otelAttrs, otel_session_id_attrs(.domain))
|
||||
)
|
||||
)
|
||||
.mostRecentCtxId <<- ctx$id
|
||||
|
||||
# A Dependency object will have a weak reference to the context, which
|
||||
@@ -920,6 +1021,15 @@ Observable <- R6Class(
|
||||
},
|
||||
|
||||
error = function(cond) {
|
||||
if (.isRecordingOtel) {
|
||||
# `cond` is too early in the stack to be updated by `ctx`'s
|
||||
# `with_otel_span_context()` where it calls
|
||||
# `set_otel_exception_status_and_throw()` on eval error.
|
||||
# So we mark it as seen here.
|
||||
# When the error is re-thrown later, it won't be a _new_ error
|
||||
cond <- mark_otel_exception_as_seen(cond)
|
||||
}
|
||||
|
||||
# If an error occurs, we want to propagate the error, but we also
|
||||
# want to save a copy of it, so future callers of this reactive will
|
||||
# get the same error (i.e. the error is cached).
|
||||
@@ -1017,12 +1127,24 @@ reactive <- function(
|
||||
label <- exprToLabel(userExpr, "reactive", label)
|
||||
|
||||
o <- Observable$new(func, label, domain, ..stacktraceon = ..stacktraceon)
|
||||
structure(
|
||||
|
||||
call_srcref <- get_call_srcref()
|
||||
if (!is.null(call_srcref)) {
|
||||
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactive")
|
||||
}
|
||||
|
||||
ret <- structure(
|
||||
o$getValue,
|
||||
observable = o,
|
||||
cacheHint = list(userExpr = zap_srcref(userExpr)),
|
||||
class = c("reactiveExpr", "reactive", "function")
|
||||
)
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_reactive_expr(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
# Given the srcref to a reactive expression, attempts to figure out what the
|
||||
@@ -1030,7 +1152,7 @@ reactive <- function(
|
||||
# scans the line of code that started the reactive block and looks for something
|
||||
# that looks like assignment. If we fail, fall back to a default value (likely
|
||||
# the block of code in the body of the reactive).
|
||||
rexprSrcrefToLabel <- function(srcref, defaultLabel) {
|
||||
rexprSrcrefToLabel <- function(srcref, defaultLabel, fnName) {
|
||||
if (is.null(srcref))
|
||||
return(defaultLabel)
|
||||
|
||||
@@ -1053,7 +1175,8 @@ rexprSrcrefToLabel <- function(srcref, defaultLabel) {
|
||||
|
||||
firstLine <- substring(lines[srcref[1]], 1, srcref[2] - 1)
|
||||
|
||||
m <- regexec("(.*)(<-|=)\\s*reactive\\s*\\($", firstLine)
|
||||
# Require the assignment to be parsed from the start
|
||||
m <- regexec(paste0("^(.*)(<<-|<-|=)\\s*", fnName, "\\s*\\($"), firstLine)
|
||||
if (m[[1]][1] == -1) {
|
||||
return(defaultLabel)
|
||||
}
|
||||
@@ -1127,6 +1250,10 @@ Observer <- R6Class(
|
||||
.prevId = character(0),
|
||||
.ctx = NULL,
|
||||
|
||||
.isRecordingOtel = FALSE, # Needs to be set by Shiny
|
||||
.otelLabel = NULL, # Needs to be set by Shiny
|
||||
.otelAttrs = NULL, # Needs to be set by Shiny
|
||||
|
||||
initialize = function(observerFunc, label, suspended = FALSE, priority = 0,
|
||||
domain = getDefaultReactiveDomain(),
|
||||
autoDestroy = TRUE, ..stacktraceon = TRUE) {
|
||||
@@ -1161,7 +1288,18 @@ Observer <- R6Class(
|
||||
.createContext()$invalidate()
|
||||
},
|
||||
.createContext = function() {
|
||||
ctx <- Context$new(.domain, .label, type='observer', prevId=.prevId, reactId = .reactId)
|
||||
ctx <- Context$new(
|
||||
.domain,
|
||||
.label,
|
||||
type = 'observer',
|
||||
prevId = .prevId,
|
||||
reactId = .reactId,
|
||||
otel_info = ctx_otel_info_obj(
|
||||
isRecordingOtel = .isRecordingOtel,
|
||||
otelLabel = .otelLabel,
|
||||
otelAttrs = c(.otelAttrs, otel_session_id_attrs(.domain))
|
||||
)
|
||||
)
|
||||
.prevId <<- ctx$id
|
||||
|
||||
if (!is.null(.ctx)) {
|
||||
@@ -1430,7 +1568,14 @@ observe <- function(
|
||||
check_dots_empty()
|
||||
|
||||
func <- installExprFunction(x, "func", env, quoted)
|
||||
label <- funcToLabel(func, "observe", label)
|
||||
|
||||
call_srcref <- get_call_srcref()
|
||||
if (is.null(label)) {
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = funcToLabel(func, "observe", label)
|
||||
)
|
||||
}
|
||||
|
||||
o <- Observer$new(
|
||||
func,
|
||||
@@ -1441,6 +1586,14 @@ observe <- function(
|
||||
autoDestroy = autoDestroy,
|
||||
..stacktraceon = ..stacktraceon
|
||||
)
|
||||
if (!is.null(call_srcref)) {
|
||||
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "observe")
|
||||
}
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
o <- enable_otel_observe(o)
|
||||
}
|
||||
|
||||
invisible(o)
|
||||
}
|
||||
|
||||
@@ -1828,34 +1981,64 @@ coerceToFunc <- function(x) {
|
||||
#' }
|
||||
#' @export
|
||||
reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
reactive_poll_impl(
|
||||
fnName = "reactivePoll",
|
||||
intervalMillis = intervalMillis,
|
||||
session = session,
|
||||
checkFunc = checkFunc,
|
||||
valueFunc = valueFunc
|
||||
)
|
||||
}
|
||||
|
||||
reactive_poll_impl <- function(
|
||||
fnName,
|
||||
intervalMillis,
|
||||
session,
|
||||
checkFunc,
|
||||
valueFunc
|
||||
) {
|
||||
intervalMillis <- coerceToFunc(intervalMillis)
|
||||
|
||||
rv <- reactiveValues(cookie = isolate(checkFunc()))
|
||||
fnName <- match.arg(fnName, c("reactivePoll", "reactiveFileReader"), several.ok = FALSE)
|
||||
|
||||
call_srcref <- get_call_srcref(-1)
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = "<anonymous>",
|
||||
fnName = fnName
|
||||
)
|
||||
|
||||
re_finalized <- FALSE
|
||||
env <- environment()
|
||||
|
||||
o <- observe({
|
||||
# When no one holds a reference to the reactive returned from
|
||||
# reactivePoll, destroy and remove the observer so that it doesn't keep
|
||||
# firing and hold onto resources.
|
||||
if (re_finalized) {
|
||||
o$destroy()
|
||||
rm(o, envir = env)
|
||||
return()
|
||||
}
|
||||
with_no_otel_collect({
|
||||
cookie <- reactiveVal(
|
||||
isolate(checkFunc()),
|
||||
label = sprintf("%s %s cookie", fnName, label)
|
||||
)
|
||||
|
||||
rv$cookie <- checkFunc()
|
||||
invalidateLater(intervalMillis(), session)
|
||||
o <- observe({
|
||||
# When no one holds a reference to the reactive returned from
|
||||
# reactivePoll, destroy and remove the observer so that it doesn't keep
|
||||
# firing and hold onto resources.
|
||||
if (re_finalized) {
|
||||
o$destroy()
|
||||
rm(o, envir = env)
|
||||
return()
|
||||
}
|
||||
|
||||
cookie(checkFunc())
|
||||
invalidateLater(intervalMillis(), session)
|
||||
}, label = sprintf("%s %s cleanup", fnName, label))
|
||||
})
|
||||
|
||||
# TODO: what to use for a label?
|
||||
re <- reactive({
|
||||
rv$cookie
|
||||
re <- reactive(label = sprintf("%s %s", fnName, label), {
|
||||
# Take a dependency on the cookie, so that when it changes, this
|
||||
# reactive expression is invalidated.
|
||||
cookie()
|
||||
|
||||
valueFunc()
|
||||
|
||||
}, label = NULL)
|
||||
})
|
||||
|
||||
reg.finalizer(attr(re, "observable"), function(e) {
|
||||
re_finalized <<- TRUE
|
||||
@@ -1865,6 +2048,16 @@ reactivePoll <- function(intervalMillis, session, checkFunc, valueFunc) {
|
||||
# reference to `re` and thus prevent it from getting GC'd.
|
||||
on.exit(rm(re))
|
||||
|
||||
local({
|
||||
impl <- attr(re, "observable", exact = TRUE)
|
||||
impl$.otelLabel <-
|
||||
if (fnName == "reactivePoll")
|
||||
otel_label_reactive_poll(label, domain = impl$.domain)
|
||||
else if (fnName == "reactiveFileReader")
|
||||
otel_label_reactive_file_reader(label, domain = impl$.domain)
|
||||
impl$.otelAttrs <- append_otel_srcref_attrs(impl$.otelAttrs, call_srcref, fn_name = fnName)
|
||||
})
|
||||
|
||||
return(re)
|
||||
}
|
||||
|
||||
@@ -1928,14 +2121,16 @@ reactiveFileReader <- function(intervalMillis, session, filePath, readFunc, ...)
|
||||
filePath <- coerceToFunc(filePath)
|
||||
extraArgs <- list2(...)
|
||||
|
||||
reactivePoll(
|
||||
intervalMillis, session,
|
||||
function() {
|
||||
reactive_poll_impl(
|
||||
fnName = "reactiveFileReader",
|
||||
intervalMillis = intervalMillis,
|
||||
session = session,
|
||||
checkFunc = function() {
|
||||
path <- filePath()
|
||||
info <- file.info(path)
|
||||
return(paste(path, info$mtime, info$size))
|
||||
},
|
||||
function() {
|
||||
valueFunc = function() {
|
||||
do.call(readFunc, c(filePath(), extraArgs))
|
||||
}
|
||||
)
|
||||
@@ -2017,6 +2212,8 @@ isolate <- function(expr) {
|
||||
} else {
|
||||
reactId <- rLog$noReactId
|
||||
}
|
||||
|
||||
# Do not track otel spans for `isolate()`
|
||||
ctx <- Context$new(getDefaultReactiveDomain(), '[isolate]', type='isolate', reactId = reactId)
|
||||
on.exit(ctx$invalidate())
|
||||
# Matching ..stacktraceon../..stacktraceoff.. pair
|
||||
@@ -2295,26 +2492,41 @@ observeEvent <- function(eventExpr, handlerExpr,
|
||||
eventQ <- exprToQuo(eventExpr, event.env, event.quoted)
|
||||
handlerQ <- exprToQuo(handlerExpr, handler.env, handler.quoted)
|
||||
|
||||
label <- quoToLabel(eventQ, "observeEvent", label)
|
||||
call_srcref <- get_call_srcref()
|
||||
if (is.null(label)) {
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = quoToLabel(eventQ, "observeEvent", label)
|
||||
)
|
||||
}
|
||||
|
||||
handler <- inject(observe(
|
||||
!!handlerQ,
|
||||
label = label,
|
||||
suspended = suspended,
|
||||
priority = priority,
|
||||
domain = domain,
|
||||
autoDestroy = TRUE,
|
||||
..stacktraceon = TRUE
|
||||
))
|
||||
with_no_otel_collect({
|
||||
handler <- inject(observe(
|
||||
!!handlerQ,
|
||||
label = label,
|
||||
suspended = suspended,
|
||||
priority = priority,
|
||||
domain = domain,
|
||||
autoDestroy = TRUE,
|
||||
..stacktraceon = TRUE
|
||||
))
|
||||
|
||||
o <- inject(bindEvent(
|
||||
ignoreNULL = ignoreNULL,
|
||||
ignoreInit = ignoreInit,
|
||||
once = once,
|
||||
label = label,
|
||||
!!eventQ,
|
||||
x = handler
|
||||
))
|
||||
o <- inject(bindEvent(
|
||||
ignoreNULL = ignoreNULL,
|
||||
ignoreInit = ignoreInit,
|
||||
once = once,
|
||||
label = label,
|
||||
!!eventQ,
|
||||
x = handler
|
||||
))
|
||||
})
|
||||
|
||||
if (!is.null(call_srcref)) {
|
||||
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "observeEvent")
|
||||
}
|
||||
if (has_otel_collect("reactivity")) {
|
||||
o <- enable_otel_observe(o)
|
||||
}
|
||||
|
||||
invisible(o)
|
||||
}
|
||||
@@ -2333,15 +2545,40 @@ eventReactive <- function(eventExpr, valueExpr,
|
||||
eventQ <- exprToQuo(eventExpr, event.env, event.quoted)
|
||||
valueQ <- exprToQuo(valueExpr, value.env, value.quoted)
|
||||
|
||||
label <- quoToLabel(eventQ, "eventReactive", label)
|
||||
func <- installExprFunction(eventExpr, "func", event.env, event.quoted, wrappedWithLabel = FALSE)
|
||||
# Attach a label and a reference to the original user source for debugging
|
||||
userEventExpr <- fn_body(func)
|
||||
|
||||
invisible(inject(bindEvent(
|
||||
ignoreNULL = ignoreNULL,
|
||||
ignoreInit = ignoreInit,
|
||||
label = label,
|
||||
!!eventQ,
|
||||
x = reactive(!!valueQ, domain = domain, label = label)
|
||||
)))
|
||||
call_srcref <- get_call_srcref()
|
||||
if (is.null(label)) {
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = exprToLabel(userEventExpr, "eventReactive", label)
|
||||
)
|
||||
}
|
||||
|
||||
with_no_otel_collect({
|
||||
value_r <- inject(reactive(!!valueQ, domain = domain, label = label))
|
||||
|
||||
r <- inject(bindEvent(
|
||||
ignoreNULL = ignoreNULL,
|
||||
ignoreInit = ignoreInit,
|
||||
label = label,
|
||||
!!eventQ,
|
||||
x = value_r
|
||||
))
|
||||
})
|
||||
|
||||
if (!is.null(call_srcref)) {
|
||||
impl <- attr(r, "observable", exact = TRUE)
|
||||
impl$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "eventReactive")
|
||||
}
|
||||
if (has_otel_collect("reactivity")) {
|
||||
r <- enable_otel_reactive_expr(r)
|
||||
}
|
||||
|
||||
|
||||
return(r)
|
||||
}
|
||||
|
||||
isNullEvent <- function(value) {
|
||||
@@ -2456,71 +2693,103 @@ isNullEvent <- function(value) {
|
||||
#'
|
||||
#' @export
|
||||
debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomain()) {
|
||||
|
||||
# TODO: make a nice label for the observer(s)
|
||||
# Do not bind OpenTelemetry spans for debounce reactivity internals,
|
||||
# except for the eventReactive that is returned.
|
||||
|
||||
force(r)
|
||||
force(millis)
|
||||
|
||||
call_srcref <- get_call_srcref()
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = "<anonymous>"
|
||||
)
|
||||
|
||||
if (!is.function(millis)) {
|
||||
origMillis <- millis
|
||||
millis <- function() origMillis
|
||||
}
|
||||
|
||||
v <- reactiveValues(
|
||||
trigger = NULL,
|
||||
when = NULL # the deadline for the timer to fire; NULL if not scheduled
|
||||
)
|
||||
with_no_otel_collect({
|
||||
trigger <- reactiveVal(NULL, label = sprintf("debounce %s trigger", label))
|
||||
# the deadline for the timer to fire; NULL if not scheduled
|
||||
when <- reactiveVal(NULL, label = sprintf("debounce %s when", label))
|
||||
|
||||
# Responsible for tracking when r() changes.
|
||||
firstRun <- TRUE
|
||||
observe({
|
||||
if (firstRun) {
|
||||
# During the first run we don't want to set v$when, as this will kick off
|
||||
# the timer. We only want to do that when we see r() change.
|
||||
firstRun <<- FALSE
|
||||
# Responsible for tracking when r() changes.
|
||||
firstRun <- TRUE
|
||||
observe(
|
||||
label = sprintf("debounce %s tracker", label),
|
||||
domain = domain,
|
||||
priority = priority,
|
||||
{
|
||||
if (firstRun) {
|
||||
# During the first run we don't want to set `when`, as this will kick
|
||||
# off the timer. We only want to do that when we see `r()` change.
|
||||
firstRun <<- FALSE
|
||||
|
||||
# Ensure r() is called only after setting firstRun to FALSE since r()
|
||||
# may throw an error
|
||||
try(r(), silent = TRUE)
|
||||
return()
|
||||
}
|
||||
# This ensures r() is still tracked after firstRun
|
||||
try(r(), silent = TRUE)
|
||||
# Ensure r() is called only after setting firstRun to FALSE since r()
|
||||
# may throw an error
|
||||
try(r(), silent = TRUE)
|
||||
return()
|
||||
}
|
||||
# This ensures r() is still tracked after firstRun
|
||||
try(r(), silent = TRUE)
|
||||
|
||||
# The value (or possibly millis) changed. Start or reset the timer.
|
||||
v$when <- getDomainTimeMs(domain) + millis()
|
||||
}, label = "debounce tracker", domain = domain, priority = priority)
|
||||
# The value (or possibly millis) changed. Start or reset the timer.
|
||||
when(
|
||||
getDomainTimeMs(domain) + millis()
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
# This observer is the timer. It rests until v$when elapses, then touches
|
||||
# v$trigger.
|
||||
observe({
|
||||
if (is.null(v$when))
|
||||
return()
|
||||
# This observer is the timer. It rests until `when` elapses, then touches
|
||||
# `trigger`.
|
||||
observe(
|
||||
label = sprintf("debounce %s timer", label),
|
||||
domain = domain,
|
||||
priority = priority,
|
||||
{
|
||||
if (is.null(when()))
|
||||
return()
|
||||
|
||||
now <- getDomainTimeMs(domain)
|
||||
if (now >= v$when) {
|
||||
# Mod by 999999999 to get predictable overflow behavior
|
||||
v$trigger <- isolate(v$trigger %||% 0) %% 999999999 + 1
|
||||
v$when <- NULL
|
||||
} else {
|
||||
invalidateLater(v$when - now)
|
||||
}
|
||||
}, label = "debounce timer", domain = domain, priority = priority)
|
||||
now <- getDomainTimeMs(domain)
|
||||
if (now >= when()) {
|
||||
# Mod by 999999999 to get predictable overflow behavior
|
||||
trigger(
|
||||
isolate(trigger() %||% 0) %% 999999999 + 1
|
||||
)
|
||||
when(NULL)
|
||||
} else {
|
||||
invalidateLater(when() - now)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
# This is the actual reactive that is returned to the user. It returns the
|
||||
# value of r(), but only invalidates/updates when v$trigger is touched.
|
||||
er <- eventReactive(v$trigger, {
|
||||
r()
|
||||
}, label = "debounce result", ignoreNULL = FALSE, domain = domain)
|
||||
# value of r(), but only invalidates/updates when `trigger` is touched.
|
||||
er <- eventReactive(
|
||||
{trigger()}, {r()},
|
||||
label = sprintf("debounce %s result", label), ignoreNULL = FALSE, domain = domain
|
||||
)
|
||||
|
||||
# Force the value of er to be immediately cached upon creation. It's very hard
|
||||
# to explain why this observer is needed, but if you want to understand, try
|
||||
# commenting it out and studying the unit test failure that results.
|
||||
primer <- observe({
|
||||
primer$destroy()
|
||||
try(er(), silent = TRUE)
|
||||
}, label = "debounce primer", domain = domain, priority = priority)
|
||||
# Update the otel label
|
||||
local({
|
||||
er_impl <- attr(er, "observable", exact = TRUE)
|
||||
er_impl$.otelLabel <- otel_label_debounce(label, domain = domain)
|
||||
er_impl$.otelAttrs <- append_otel_srcref_attrs(er_impl$.otelAttrs, call_srcref, fn_name = "debounce")
|
||||
})
|
||||
|
||||
with_no_otel_collect({
|
||||
# Force the value of er to be immediately cached upon creation. It's very hard
|
||||
# to explain why this observer is needed, but if you want to understand, try
|
||||
# commenting it out and studying the unit test failure that results.
|
||||
primer <- observe({
|
||||
primer$destroy()
|
||||
try(er(), silent = TRUE)
|
||||
}, label = sprintf("debounce %s primer", label), domain = domain, priority = priority)
|
||||
})
|
||||
|
||||
er
|
||||
}
|
||||
@@ -2528,69 +2797,88 @@ debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
|
||||
#' @rdname debounce
|
||||
#' @export
|
||||
throttle <- function(r, millis, priority = 100, domain = getDefaultReactiveDomain()) {
|
||||
|
||||
# TODO: make a nice label for the observer(s)
|
||||
# Do not bind OpenTelemetry spans for throttle reactivity internals,
|
||||
# except for the eventReactive that is returned.
|
||||
|
||||
force(r)
|
||||
force(millis)
|
||||
|
||||
call_srcref <- get_call_srcref()
|
||||
label <- rassignSrcrefToLabel(
|
||||
call_srcref,
|
||||
defaultLabel = "<anonymous>"
|
||||
)
|
||||
|
||||
if (!is.function(millis)) {
|
||||
origMillis <- millis
|
||||
millis <- function() origMillis
|
||||
}
|
||||
|
||||
v <- reactiveValues(
|
||||
trigger = 0,
|
||||
lastTriggeredAt = NULL, # Last time we fired; NULL if never
|
||||
pending = FALSE # If TRUE, trigger again when timer elapses
|
||||
)
|
||||
with_no_otel_collect({
|
||||
trigger <- reactiveVal(0, label = sprintf("throttle %s trigger", label))
|
||||
# Last time we fired; NULL if never
|
||||
lastTriggeredAt <- reactiveVal(NULL, label = sprintf("throttle %s last triggered at", label))
|
||||
# If TRUE, trigger again when timer elapses
|
||||
pending <- reactiveVal(FALSE, label = sprintf("throttle %s pending", label))
|
||||
})
|
||||
|
||||
blackoutMillisLeft <- function() {
|
||||
if (is.null(v$lastTriggeredAt)) {
|
||||
if (is.null(lastTriggeredAt())) {
|
||||
0
|
||||
} else {
|
||||
max(0, v$lastTriggeredAt + millis() - getDomainTimeMs(domain))
|
||||
max(0, lastTriggeredAt() + millis() - getDomainTimeMs(domain))
|
||||
}
|
||||
}
|
||||
|
||||
trigger <- function() {
|
||||
v$lastTriggeredAt <- getDomainTimeMs(domain)
|
||||
update_trigger <- function() {
|
||||
lastTriggeredAt(getDomainTimeMs(domain))
|
||||
# Mod by 999999999 to get predictable overflow behavior
|
||||
v$trigger <- isolate(v$trigger) %% 999999999 + 1
|
||||
v$pending <- FALSE
|
||||
trigger(isolate(trigger()) %% 999999999 + 1)
|
||||
pending(FALSE)
|
||||
}
|
||||
|
||||
# Responsible for tracking when f() changes.
|
||||
observeEvent(try(r(), silent = TRUE), {
|
||||
if (v$pending) {
|
||||
# In a blackout period and someone already scheduled; do nothing
|
||||
} else if (blackoutMillisLeft() > 0) {
|
||||
# In a blackout period but this is the first change in that period; set
|
||||
# v$pending so that a trigger will be scheduled at the end of the period
|
||||
v$pending <- TRUE
|
||||
} else {
|
||||
# Not in a blackout period. Trigger, which will start a new blackout
|
||||
# period.
|
||||
trigger()
|
||||
}
|
||||
}, label = "throttle tracker", ignoreNULL = FALSE, priority = priority, domain = domain)
|
||||
with_no_otel_collect({
|
||||
# Responsible for tracking when f() changes.
|
||||
observeEvent(try(r(), silent = TRUE), {
|
||||
if (pending()) {
|
||||
# In a blackout period and someone already scheduled; do nothing
|
||||
} else if (blackoutMillisLeft() > 0) {
|
||||
# In a blackout period but this is the first change in that period; set
|
||||
# pending so that a trigger will be scheduled at the end of the period
|
||||
pending(TRUE)
|
||||
} else {
|
||||
# Not in a blackout period. Trigger, which will start a new blackout
|
||||
# period.
|
||||
update_trigger()
|
||||
}
|
||||
}, label = sprintf("throttle %s tracker", label), ignoreNULL = FALSE, priority = priority, domain = domain)
|
||||
|
||||
observe({
|
||||
if (!v$pending) {
|
||||
return()
|
||||
}
|
||||
observe({
|
||||
if (!pending()) {
|
||||
return()
|
||||
}
|
||||
|
||||
timeout <- blackoutMillisLeft()
|
||||
if (timeout > 0) {
|
||||
invalidateLater(timeout)
|
||||
} else {
|
||||
trigger()
|
||||
}
|
||||
}, priority = priority, domain = domain)
|
||||
timeout <- blackoutMillisLeft()
|
||||
if (timeout > 0) {
|
||||
invalidateLater(timeout)
|
||||
} else {
|
||||
update_trigger()
|
||||
}
|
||||
}, label = sprintf("throttle %s trigger", label), priority = priority, domain = domain)
|
||||
})
|
||||
|
||||
# This is the actual reactive that is returned to the user. It returns the
|
||||
# value of r(), but only invalidates/updates when v$trigger is touched.
|
||||
eventReactive(v$trigger, {
|
||||
# value of r(), but only invalidates/updates when trigger is touched.
|
||||
er <- eventReactive({trigger()}, {
|
||||
r()
|
||||
}, label = "throttle result", ignoreNULL = FALSE, domain = domain)
|
||||
}, label = sprintf("throttle %s result", label), ignoreNULL = FALSE, domain = domain)
|
||||
|
||||
# Update the otel label
|
||||
local({
|
||||
er_impl <- attr(er, "observable", exact = TRUE)
|
||||
er_impl$.otelLabel <- otel_label_throttle(label, domain = domain)
|
||||
er_impl$.otelAttrs <- append_otel_srcref_attrs(er_impl$.otelAttrs, call_srcref, fn_name = "throttle")
|
||||
})
|
||||
|
||||
er
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ drawPlot <- function(name, session, func, width, height, alt, pixelratio, res, .
|
||||
|
||||
hybrid_chain(
|
||||
hybrid_chain(
|
||||
promises::with_promise_domain(domain, {
|
||||
with_promise_domain(domain, {
|
||||
hybrid_chain(
|
||||
func(),
|
||||
function(value) {
|
||||
|
||||
23
R/runapp.R
23
R/runapp.R
@@ -84,13 +84,22 @@
|
||||
#' runApp(app)
|
||||
#' }
|
||||
#' @export
|
||||
runApp <- function(appDir=getwd(),
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser = getOption('shiny.launch.browser', interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase"),
|
||||
test.mode=getOption('shiny.testmode', FALSE)) {
|
||||
runApp <- function(
|
||||
appDir=getwd(),
|
||||
port=getOption('shiny.port'),
|
||||
launch.browser = getOption('shiny.launch.browser', interactive()),
|
||||
host=getOption('shiny.host', '127.0.0.1'),
|
||||
workerId="", quiet=FALSE,
|
||||
display.mode=c("auto", "normal", "showcase"),
|
||||
test.mode=getOption('shiny.testmode', FALSE)
|
||||
) {
|
||||
|
||||
# * Wrap **all** execution of the app inside the otel promise domain
|
||||
# * While this could be done at a lower level, it allows for _anything_ within
|
||||
# shiny's control to allow for the opportunity to have otel active spans be
|
||||
# reactivated upon promise domain restoration
|
||||
promises::local_otel_promise_domain()
|
||||
|
||||
on.exit({
|
||||
handlerManager$clear()
|
||||
}, add = TRUE)
|
||||
|
||||
21
R/server.R
21
R/server.R
@@ -274,15 +274,20 @@ createAppHandlers <- function(httpHandlers, serverFuncSource) {
|
||||
args <- argsForServerFunc(serverFunc, shinysession)
|
||||
|
||||
withReactiveDomain(shinysession, {
|
||||
do.call(
|
||||
# No corresponding ..stacktraceoff; the server func is pure
|
||||
# user code
|
||||
wrapFunctionLabel(appvars$server, "server",
|
||||
..stacktraceon = TRUE
|
||||
),
|
||||
args
|
||||
)
|
||||
otel_span_session_start(domain = shinysession, {
|
||||
|
||||
do.call(
|
||||
# No corresponding ..stacktraceoff; the server func is pure
|
||||
# user code
|
||||
wrapFunctionLabel(appvars$server, "server",
|
||||
..stacktraceon = TRUE
|
||||
),
|
||||
args
|
||||
)
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
},
|
||||
update = {
|
||||
|
||||
@@ -70,7 +70,7 @@ getShinyOption <- function(name, default = NULL) {
|
||||
#'
|
||||
#' You can customize the file patterns Shiny will monitor by setting the
|
||||
#' shiny.autoreload.pattern option. For example, to monitor only `ui.R`:
|
||||
#' `options(shiny.autoreload.pattern = glob2rx("ui.R"))`.
|
||||
#' `options(shiny.autoreload.pattern = glob2rx("ui.R"))`.
|
||||
#'
|
||||
#' As mentioned above, Shiny no longer polls watched files for changes.
|
||||
#' Instead, using \pkg{watcher}, Shiny is notified of file changes as they
|
||||
@@ -160,6 +160,31 @@ getShinyOption <- function(name, default = NULL) {
|
||||
# ' side devmode features. Currently the primary feature is the client-side
|
||||
# ' error console.}
|
||||
### end shiny.client_devmode
|
||||
#' \item{shiny.otel.collect (defaults to `Sys.getenv("SHINY_OTEL_COLLECT",
|
||||
#' "all")`)}{Determines how Shiny will interact with OpenTelemetry.
|
||||
#'
|
||||
#' Supported values:
|
||||
#' * `"none"` - No Shiny OpenTelemetry tracing.
|
||||
#' * `"session"` - Adds session start/end spans.
|
||||
#' * `"reactive_update"` - Spans for any synchronous/asynchronous reactive
|
||||
#' update. (Includes `"session"` features).
|
||||
#' * `"reactivity"` - Spans for all reactive expressions and logs for setting
|
||||
#' reactive vals and values. (Includes `"reactive_update"` features). This
|
||||
#' option must be set when creating any reactive objects that should record
|
||||
#' OpenTelemetry spans / logs. See [`withOtelCollect()`] and
|
||||
#' [`localOtelCollect()`] for ways to set this option locally when creating
|
||||
#' your reactive expressions.
|
||||
#' * `"all"` - All Shiny OpenTelemetry tracing. Currently equivalent to
|
||||
#' `"reactivity"`.
|
||||
#'
|
||||
#' This option is useful for debugging and profiling while in production. This
|
||||
#' option will only be useful if the `otelsdk` package is installed and
|
||||
#' `otel::is_tracing_enabled()` returns `TRUE`. Please have any OpenTelemetry
|
||||
#' environment variables set before loading any relevant R packages.
|
||||
#'
|
||||
#' To set this option locally within a specific part of your Shiny
|
||||
#' application, see [`withOtelCollect()`] and [`localOtelCollect()`].}
|
||||
#' \item{shiny.otel.sanitize.errors (defaults to `TRUE`)}{If `TRUE`, fatal and unhandled errors will be sanitized before being sent to the OpenTelemetry backend. The default value of `TRUE` is set to avoid potentially sending sensitive information to the OpenTelemetry backend. If you want the full error message and stack trace to be sent to the OpenTelemetry backend, set this option to `FALSE` or use `safeError(e)`.}
|
||||
#' }
|
||||
#'
|
||||
#'
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
#' @importFrom lifecycle deprecated is_present
|
||||
#' @importFrom grDevices dev.set dev.cur
|
||||
#' @importFrom fastmap fastmap
|
||||
#' @importFrom promises %...!%
|
||||
#' @importFrom promises %...>%
|
||||
#' @importFrom promises
|
||||
#' promise promise_resolve promise_reject is.promising
|
||||
#' as.promise
|
||||
#' %...!% %...>%
|
||||
#' as.promise is.promising is.promise
|
||||
#' promise_resolve promise_reject
|
||||
#' hybrid_then
|
||||
#' with_promise_domain new_promise_domain
|
||||
#' @importFrom rlang
|
||||
#' quo enquo enquo0 as_function get_expr get_env new_function enquos
|
||||
#' eval_tidy expr pairlist2 new_quosure enexpr as_quosure is_quosure inject
|
||||
|
||||
45
R/shiny.R
45
R/shiny.R
@@ -428,7 +428,7 @@ ShinySession <- R6Class(
|
||||
stop("Nested calls to withCurrentOutput() are not allowed.")
|
||||
}
|
||||
|
||||
promises::with_promise_domain(
|
||||
with_promise_domain(
|
||||
createVarPromiseDomain(private, "currentOutputName", name),
|
||||
expr
|
||||
)
|
||||
@@ -1056,6 +1056,19 @@ ShinySession <- R6Class(
|
||||
class(e) <- c("shiny.error.fatal", class(e))
|
||||
}
|
||||
|
||||
# For fatal errors, always log.
|
||||
# For non-fatal errors, only log if we haven't seen this error before.
|
||||
if (close || !has_seen_otel_exception(e)) {
|
||||
otel_log(
|
||||
if (close) "Fatal error" else "Unhandled error",
|
||||
severity = if (close) "fatal" else "error",
|
||||
attributes = otel::as_attributes(list(
|
||||
session.id = self$token,
|
||||
error = get_otel_error_obj(e)
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
private$unhandledErrorCallbacks$invoke(e, onError = printError)
|
||||
.globals$onUnhandledErrorCallbacks$invoke(e, onError = printError)
|
||||
|
||||
@@ -1073,7 +1086,9 @@ ShinySession <- R6Class(
|
||||
}
|
||||
# ..stacktraceon matches with the top-level ..stacktraceoff..
|
||||
withReactiveDomain(self, {
|
||||
private$closedCallbacks$invoke(onError = printError, ..stacktraceon = TRUE)
|
||||
otel_span_session_end(domain = self, {
|
||||
private$closedCallbacks$invoke(onError = printError, ..stacktraceon = TRUE)
|
||||
})
|
||||
})
|
||||
},
|
||||
isClosed = function() {
|
||||
@@ -1142,7 +1157,8 @@ ShinySession <- R6Class(
|
||||
attr(label, "srcref") <- srcref
|
||||
attr(label, "srcfile") <- srcfile
|
||||
|
||||
obs <- observe(..stacktraceon = FALSE, {
|
||||
# Do not bind this `observe()` call
|
||||
obs <- with_no_otel_collect(observe(..stacktraceon = FALSE, {
|
||||
|
||||
private$sendMessage(recalculating = list(
|
||||
name = name, status = 'recalculating'
|
||||
@@ -1154,7 +1170,9 @@ ShinySession <- R6Class(
|
||||
hybrid_chain(
|
||||
{
|
||||
private$withCurrentOutput(name, {
|
||||
shinyCallingHandlers(func())
|
||||
maybe_with_otel_span_reactive_update({
|
||||
shinyCallingHandlers(func())
|
||||
}, domain = self)
|
||||
})
|
||||
},
|
||||
catch = function(cond) {
|
||||
@@ -1179,9 +1197,7 @@ ShinySession <- R6Class(
|
||||
} else {
|
||||
if (isTRUE(getOption("show.error.messages"))) printError(cond)
|
||||
if (getOption("shiny.sanitize.errors", FALSE)) {
|
||||
cond <- simpleError(paste("An error has occurred. Check your",
|
||||
"logs or contact the app author for",
|
||||
"clarification."))
|
||||
cond <- sanitized_error()
|
||||
}
|
||||
self$unhandledError(cond, close = FALSE)
|
||||
invisible(structure(list(), class = "try-error", condition = cond))
|
||||
@@ -1245,7 +1261,7 @@ ShinySession <- R6Class(
|
||||
private$invalidatedOutputValues$set(name, value)
|
||||
}
|
||||
)
|
||||
}, suspended=private$shouldSuspend(name), label=label)
|
||||
}, suspended=private$shouldSuspend(name), label=label))
|
||||
|
||||
# If any output attributes were added to the render function attach
|
||||
# them to observer.
|
||||
@@ -2023,7 +2039,7 @@ ShinySession <- R6Class(
|
||||
ext <- paste(".", ext, sep = "")
|
||||
tmpdata <- tempfile(fileext = ext)
|
||||
return(Context$new(getDefaultReactiveDomain(), '[download]')$run(function() {
|
||||
promises::with_promise_domain(reactivePromiseDomain(), {
|
||||
with_promise_domain(reactivePromiseDomain(), {
|
||||
captureStackTraces({
|
||||
self$incrementBusyCount()
|
||||
hybrid_chain(
|
||||
@@ -2195,6 +2211,8 @@ ShinySession <- R6Class(
|
||||
if (private$busyCount == 0L) {
|
||||
rLog$asyncStart(domain = self)
|
||||
private$sendMessage(busy = "busy")
|
||||
|
||||
otel_span_reactive_update_init(domain = self)
|
||||
}
|
||||
private$busyCount <- private$busyCount + 1L
|
||||
},
|
||||
@@ -2216,6 +2234,8 @@ ShinySession <- R6Class(
|
||||
private$startCycle()
|
||||
}
|
||||
})
|
||||
|
||||
otel_span_reactive_update_teardown(domain = self)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -2723,3 +2743,10 @@ validate_session_object <- function(session, label = as.character(sys.call(sys.p
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sanitized_error <- function() {
|
||||
simpleError(paste("An error has occurred. Check your",
|
||||
"logs or contact the app author for",
|
||||
"clarification."))
|
||||
}
|
||||
|
||||
@@ -134,7 +134,14 @@ markRenderFunction <- function(
|
||||
else renderFunc(...)
|
||||
}
|
||||
|
||||
structure(
|
||||
otelAttrs <-
|
||||
otel_srcref_attributes(
|
||||
attr(renderFunc, "wrappedFunc", exact = TRUE),
|
||||
# Can't retrieve the render function used at this point, so just use NULL
|
||||
fn_name = NULL
|
||||
)
|
||||
|
||||
ret <- structure(
|
||||
wrappedRenderFunc,
|
||||
class = c("shiny.render.function", "function"),
|
||||
outputFunc = uiFunc,
|
||||
@@ -142,8 +149,15 @@ markRenderFunction <- function(
|
||||
hasExecuted = hasExecuted,
|
||||
cacheHint = cacheHint,
|
||||
cacheWriteHook = cacheWriteHook,
|
||||
cacheReadHook = cacheReadHook
|
||||
cacheReadHook = cacheReadHook,
|
||||
otelAttrs = otelAttrs
|
||||
)
|
||||
|
||||
if (has_otel_collect("reactivity")) {
|
||||
ret <- enable_otel_shiny_render_function(ret)
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
#' @export
|
||||
@@ -271,9 +285,7 @@ createRenderFunction <- function(
|
||||
# Hoist func's wrappedFunc attribute into renderFunc, so that when we pass
|
||||
# renderFunc on to markRenderFunction, it is able to find the original user
|
||||
# function.
|
||||
if (identical(cacheHint, "auto")) {
|
||||
attr(renderFunc, "wrappedFunc") <- attr(func, "wrappedFunc", exact = TRUE)
|
||||
}
|
||||
attr(renderFunc, "wrappedFunc") <- attr(func, "wrappedFunc", exact = TRUE)
|
||||
|
||||
markRenderFunction(outputFunc, renderFunc, outputArgs, cacheHint,
|
||||
cacheWriteHook, cacheReadHook)
|
||||
@@ -321,7 +333,7 @@ as.tags.shiny.render.function <- function(x, ..., inline = FALSE) {
|
||||
|
||||
# Get relevant attributes from a render function object.
|
||||
renderFunctionAttributes <- function(x) {
|
||||
attrs <- c("outputFunc", "outputArgs", "hasExecuted", "cacheHint")
|
||||
attrs <- c("outputFunc", "outputArgs", "hasExecuted", "cacheHint", "otelAttrs")
|
||||
names(attrs) <- attrs
|
||||
lapply(attrs, function(name) attr(x, name, exact = TRUE))
|
||||
}
|
||||
@@ -383,7 +395,7 @@ markOutputAttrs <- function(renderFunc, snapshotExclude = NULL,
|
||||
#' The corresponding HTML output tag should be `div` or `img` and have
|
||||
#' the CSS class name `shiny-image-output`.
|
||||
#'
|
||||
#' @seealso
|
||||
#' @seealso
|
||||
#' * For more details on how the images are generated, and how to control
|
||||
#' the output, see [plotPNG()].
|
||||
#' * Use [outputOptions()] to set general output options for an image output.
|
||||
@@ -616,7 +628,7 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
domain <- createRenderPrintPromiseDomain(width)
|
||||
hybrid_chain(
|
||||
{
|
||||
promises::with_promise_domain(domain, func())
|
||||
with_promise_domain(domain, func())
|
||||
},
|
||||
function(value) {
|
||||
res <- withVisible(value)
|
||||
@@ -645,7 +657,7 @@ renderPrint <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
createRenderPrintPromiseDomain <- function(width) {
|
||||
f <- file()
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
force(onFulfilled)
|
||||
function(...) {
|
||||
@@ -815,9 +827,9 @@ renderUI <- function(expr, env = parent.frame(), quoted = FALSE,
|
||||
#'
|
||||
#' @seealso
|
||||
#' * The download handler, like other outputs, is suspended (disabled) by
|
||||
#' default for download buttons and links that are hidden. Use
|
||||
#' [outputOptions()] to control this behavior, e.g. to set
|
||||
#' `suspendWhenHidden = FALSE` if the download is initiated by
|
||||
#' default for download buttons and links that are hidden. Use
|
||||
#' [outputOptions()] to control this behavior, e.g. to set
|
||||
#' `suspendWhenHidden = FALSE` if the download is initiated by
|
||||
#' programmatically clicking on the download button using JavaScript.
|
||||
#' @export
|
||||
downloadHandler <- function(filename, content, contentType=NULL, outputArgs=list()) {
|
||||
|
||||
16
R/showcase.R
16
R/showcase.R
@@ -33,13 +33,6 @@ showcaseHead <- function() {
|
||||
|
||||
deps <- list(
|
||||
jqueryuiDependency(),
|
||||
htmlDependency(
|
||||
"showdown",
|
||||
"0.3.1",
|
||||
src = "www/shared/showdown/compressed",
|
||||
package="shiny",
|
||||
script = "showdown.js"
|
||||
),
|
||||
htmlDependency(
|
||||
"highlight.js",
|
||||
"6.2",
|
||||
@@ -61,10 +54,11 @@ showcaseHead <- function() {
|
||||
|
||||
mdfile <- file.path.ci(getwd(), 'Readme.md')
|
||||
html <- tagList(
|
||||
if (file.exists(mdfile))
|
||||
tags$script(type="text/markdown", id="showcase-markdown-content",
|
||||
paste(readUTF8(mdfile), collapse="\n"))
|
||||
else ""
|
||||
if (file.exists(mdfile)) {
|
||||
md_content <- paste(readUTF8(mdfile), collapse="\n")
|
||||
md_html <- commonmark::markdown_html(md_content, extensions = TRUE)
|
||||
tags$template(id="showcase-markdown-content", HTML(md_html))
|
||||
} else ""
|
||||
)
|
||||
|
||||
return(attachDependencies(html, deps))
|
||||
|
||||
@@ -38,8 +38,8 @@ updateTextInput <- function(session = getDefaultReactiveDomain(), inputId, label
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
value = value,
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = value,
|
||||
placeholder = placeholder
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
@@ -116,7 +116,7 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = value
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
@@ -181,10 +181,9 @@ updateCheckboxInput <- function(session = getDefaultReactiveDomain(), inputId, l
|
||||
updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL, disabled = NULL) {
|
||||
validate_session_object(session)
|
||||
|
||||
if (!is.null(icon)) icon <- as.character(validateIcon(icon))
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
icon = icon,
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
icon = if (!is.null(icon)) processDeps(validateIcon(icon), session),
|
||||
disabled = disabled
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
@@ -192,7 +191,7 @@ updateActionButton <- function(session = getDefaultReactiveDomain(), inputId, la
|
||||
#' @rdname updateActionButton
|
||||
#' @export
|
||||
updateActionLink <- function(session = getDefaultReactiveDomain(), inputId, label = NULL, icon = NULL) {
|
||||
updateActionButton(session, inputId = inputId, label = processDeps(label, session), icon = icon)
|
||||
updateActionButton(session, inputId=inputId, label=label, icon=icon)
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +236,7 @@ updateDateInput <- function(session = getDefaultReactiveDomain(), inputId, label
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = value,
|
||||
min = min,
|
||||
max = max
|
||||
@@ -291,7 +290,7 @@ updateDateRangeInput <- function(session = getDefaultReactiveDomain(), inputId,
|
||||
max <- dateYMD(max, "max")
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = dropNulls(list(start = start, end = end)),
|
||||
min = min,
|
||||
max = max
|
||||
@@ -395,7 +394,7 @@ updateNumericInput <- function(session = getDefaultReactiveDomain(), inputId, la
|
||||
validate_session_object(session)
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
@@ -479,7 +478,7 @@ updateSliderInput <- function(session = getDefaultReactiveDomain(), inputId, lab
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
value = formatNoSci(value),
|
||||
min = formatNoSci(min),
|
||||
max = formatNoSci(max),
|
||||
@@ -511,8 +510,8 @@ updateInputOptions <- function(session, inputId, label = NULL, choices = NULL,
|
||||
}
|
||||
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
options = options,
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
options = options,
|
||||
value = selected
|
||||
))
|
||||
|
||||
@@ -668,8 +667,8 @@ updateSelectInput <- function(session = getDefaultReactiveDomain(), inputId, lab
|
||||
if (!is.null(selected)) selected <- as.character(selected)
|
||||
options <- if (!is.null(choices)) selectOptions(choices, selected, inputId, FALSE)
|
||||
message <- dropNulls(list(
|
||||
label = processDeps(label, session),
|
||||
options = options,
|
||||
label = if (!is.null(label)) processDeps(label, session),
|
||||
options = options,
|
||||
value = selected
|
||||
))
|
||||
session$sendInputMessage(inputId, message)
|
||||
|
||||
@@ -208,8 +208,10 @@ exprToLabel <- function(expr, function_name, label = NULL) {
|
||||
if (is.null(label)) {
|
||||
label <- rexprSrcrefToLabel(
|
||||
srcref[[1]],
|
||||
simpleExprToFunction(expr, function_name)
|
||||
simpleExprToFunction(expr, function_name),
|
||||
function_name
|
||||
)
|
||||
label <- as_default_label(label)
|
||||
}
|
||||
if (length(srcref) >= 2) attr(label, "srcref") <- srcref[[2]]
|
||||
attr(label, "srcfile") <- srcFileOfRef(srcref[[1]])
|
||||
@@ -229,10 +231,12 @@ funcToLabelBody <- function(func) {
|
||||
funcToLabel <- function(func, functionLabel, label = NULL) {
|
||||
if (!is.null(label)) return(label)
|
||||
|
||||
sprintf(
|
||||
'%s(%s)',
|
||||
functionLabel,
|
||||
funcToLabelBody(func)
|
||||
as_default_label(
|
||||
sprintf(
|
||||
'%s(%s)',
|
||||
functionLabel,
|
||||
funcToLabelBody(func)
|
||||
)
|
||||
)
|
||||
}
|
||||
quoToLabelBody <- function(q) {
|
||||
@@ -241,9 +245,19 @@ quoToLabelBody <- function(q) {
|
||||
quoToLabel <- function(q, functionLabel, label = NULL) {
|
||||
if (!is.null(label)) return(label)
|
||||
|
||||
sprintf(
|
||||
'%s(%s)',
|
||||
functionLabel,
|
||||
quoToLabelBody(q)
|
||||
as_default_label(
|
||||
sprintf(
|
||||
'%s(%s)',
|
||||
functionLabel,
|
||||
quoToLabelBody(q)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
as_default_label <- function(x) {
|
||||
class(x) <- c("default_label", class(x))
|
||||
x
|
||||
}
|
||||
is_default_label <- function(x) {
|
||||
inherits(x, "default_label")
|
||||
}
|
||||
|
||||
21
R/utils-tags.R
Normal file
21
R/utils-tags.R
Normal file
@@ -0,0 +1,21 @@
|
||||
# Check if `x` is a tag(), tagList(), or HTML()
|
||||
# @param strict If `FALSE`, also consider a normal list() of 'tags' to be a tag list.
|
||||
isTagLike <- function(x, strict = FALSE) {
|
||||
isTag(x) || isTagList(x, strict = strict) || isTRUE(attr(x, "html"))
|
||||
}
|
||||
|
||||
isTag <- function(x) {
|
||||
inherits(x, "shiny.tag")
|
||||
}
|
||||
|
||||
isTagList <- function(x, strict = TRUE) {
|
||||
if (strict) {
|
||||
return(inherits(x, "shiny.tag.list"))
|
||||
}
|
||||
|
||||
if (!is.list(x)) {
|
||||
return(FALSE)
|
||||
}
|
||||
|
||||
all(vapply(x, isTagLike, logical(1)))
|
||||
}
|
||||
18
R/utils.R
18
R/utils.R
@@ -797,8 +797,8 @@ cachedFuncWithFile <- function(dir, file, func, case.sensitive = FALSE) {
|
||||
last_autoreload <- 0
|
||||
|
||||
function(...) {
|
||||
fname <- if (case.sensitive) {
|
||||
file.path(dir, file)
|
||||
fname <- if (case.sensitive) {
|
||||
file.path(dir, file)
|
||||
} else {
|
||||
file.path.ci(dir, file)
|
||||
}
|
||||
@@ -1433,7 +1433,11 @@ URLencode <- function(value, reserved = FALSE) {
|
||||
dateYMD <- function(date = NULL, argName = "value") {
|
||||
if (!length(date)) return(NULL)
|
||||
tryCatch({
|
||||
res <- format(as.Date(date), "%Y-%m-%d")
|
||||
if (inherits(date, "POSIXt")) {
|
||||
res <- format(date, "%Y-%m-%d")
|
||||
} else {
|
||||
res <- format(as.Date(date), "%Y-%m-%d")
|
||||
}
|
||||
if (any(is.na(res))) stop()
|
||||
date <- res
|
||||
},
|
||||
@@ -1525,7 +1529,7 @@ promise_chain <- function(promise, ..., catch = NULL, finally = NULL,
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
promises::with_promise_domain(domain, do(), replace = replace)
|
||||
with_promise_domain(domain, do(), replace = replace)
|
||||
} else {
|
||||
do()
|
||||
}
|
||||
@@ -1542,7 +1546,7 @@ hybrid_chain <- function(expr, ..., catch = NULL, finally = NULL,
|
||||
{
|
||||
captureStackTraces({
|
||||
result <- withVisible(force(expr))
|
||||
if (promises::is.promising(result$value)) {
|
||||
if (is.promising(result$value)) {
|
||||
# Purposefully NOT including domain (nor replace), as we're already in
|
||||
# the domain at this point
|
||||
p <- promise_chain(valueWithVisible(result), ..., catch = catch, finally = finally)
|
||||
@@ -1576,7 +1580,7 @@ hybrid_chain <- function(expr, ..., catch = NULL, finally = NULL,
|
||||
}
|
||||
|
||||
if (!is.null(domain)) {
|
||||
promises::with_promise_domain(domain, do(), replace = replace)
|
||||
with_promise_domain(domain, do(), replace = replace)
|
||||
} else {
|
||||
do()
|
||||
}
|
||||
@@ -1594,7 +1598,7 @@ createVarPromiseDomain <- function(env, name, value) {
|
||||
force(name)
|
||||
force(value)
|
||||
|
||||
promises::new_promise_domain(
|
||||
new_promise_domain(
|
||||
wrapOnFulfilled = function(onFulfilled) {
|
||||
function(...) {
|
||||
orig <- env[[name]]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# Generated by tools/updateBootstrapDatepicker.R; do not edit by hand
|
||||
version_bs_date_picker <- "1.9.0"
|
||||
version_bs_date_picker <- "1.10.0"
|
||||
|
||||
14
README-npm.md
Normal file
14
README-npm.md
Normal file
@@ -0,0 +1,14 @@
|
||||
@posit/shiny
|
||||
============
|
||||
|
||||
This npm package contains TypeScript type definitions for Shiny's client-side JavaScript libraries.
|
||||
|
||||
It does not include the Shiny framework itself, though that may change in the future.
|
||||
|
||||
[Shiny](https://github.com/rstudio/shiny) is a web application framework for both R and Python, developed by Posit PBC.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @posit/shiny
|
||||
```
|
||||
@@ -61,8 +61,8 @@ We welcome contributions to the **shiny** package. Please see our [CONTRIBUTING.
|
||||
|
||||
## License
|
||||
|
||||
The shiny package as a whole is licensed under the GPLv3. See the [LICENSE](LICENSE) file for more details.
|
||||
The shiny package as a whole is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
||||
|
||||
## R version support
|
||||
|
||||
Shiny is supported on the latest release version of R, as well as the previous four minor release versions of R. For example, if the latest release R version is 4.1, then that version is supported, as well as 4.0, 3.6, 3.5, and 3.4.
|
||||
Shiny is supported on the latest release version of R, as well as the previous four minor release versions of R. For example, if the latest release R version is 4.3, then that version is supported, as well as 4.2, 4.1, 4.0, 3.6.
|
||||
|
||||
145
cran-comments.md
145
cran-comments.md
@@ -1,40 +1,129 @@
|
||||
## Comments
|
||||
|
||||
#### 2025-12-08
|
||||
|
||||
Test has been removed from CRAN checks.
|
||||
|
||||
Also added a couple bug fixes as found by users.
|
||||
|
||||
Please let me know if you need any further changes.
|
||||
|
||||
Thank you,
|
||||
Carson
|
||||
|
||||
#### 2025-12-04
|
||||
|
||||
Error:
|
||||
|
||||
```
|
||||
Check Details
|
||||
Version: 1.12.0
|
||||
Check: tests
|
||||
Result: ERROR
|
||||
Running ‘testthat.R’ [100s/394s]
|
||||
Running the tests in ‘tests/testthat.R’ failed.
|
||||
Complete output:
|
||||
> library(testthat)
|
||||
> library(shiny)
|
||||
>
|
||||
> test_check("shiny")
|
||||
Saving _problems/test-timer-35.R
|
||||
[ FAIL 1 | WARN 0 | SKIP 22 | PASS 1981 ]
|
||||
|
||||
══ Skipped tests (22) ══════════════════════════════════════════════════════════
|
||||
• File system is not case-sensitive (1): 'test-app.R:36:5'
|
||||
• I'm not sure of a great way to test this without timers. (1):
|
||||
'test-test-server.R:216:3'
|
||||
• Not testing in CI (1): 'test-devmode.R:17:3'
|
||||
• On CRAN (18): 'test-actionButton.R:59:1', 'test-busy-indication.R:1:1',
|
||||
'test-busy-indication.R:15:1', 'test-busy-indication.R:50:1',
|
||||
'test-otel-error.R:1:1', 'test-otel-mock.R:1:1', 'test-pkgdown.R:3:3',
|
||||
'test-reactivity.r:146:1', 'test-reactivity.r:1240:5',
|
||||
'test-reactivity.r:1240:5', 'test-stacks-deep.R:93:1',
|
||||
'test-stacks-deep.R:141:1', 'test-stacks.R:140:3', 'test-tabPanel.R:46:1',
|
||||
'test-tabPanel.R:66:1', 'test-tabPanel.R:73:1', 'test-tabPanel.R:83:1',
|
||||
'test-utils.R:177:3'
|
||||
• {shinytest2} is not installed (1): 'test-test-shinyAppTemplate.R:2:1'
|
||||
|
||||
══ Failed tests ════════════════════════════════════════════════════════════════
|
||||
── Failure ('test-timer.R:35:3'): Unscheduling works ───────────────────────────
|
||||
Expected `timerCallbacks$.times` to be identical to `origTimes`.
|
||||
Differences:
|
||||
`attr(actual, 'row.names')` is an integer vector ()
|
||||
`attr(expected, 'row.names')` is a character vector ()
|
||||
|
||||
|
||||
[ FAIL 1 | WARN 0 | SKIP 22 | PASS 1981 ]
|
||||
Error:
|
||||
! Test failures.
|
||||
Execution halted
|
||||
```
|
||||
|
||||
|
||||
#### 2025-12-03
|
||||
|
||||
```
|
||||
Dear maintainer,
|
||||
|
||||
Please see the problems shown on
|
||||
<https://cran.r-project.org/web/checks/check_results_shiny.html>.
|
||||
|
||||
Please correct before 2025-12-17 to safely retain your package on CRAN.
|
||||
|
||||
The CRAN Team
|
||||
```
|
||||
|
||||
## `R CMD check` results:
|
||||
|
||||
0 errors | 0 warning | 1 note
|
||||
|
||||
```
|
||||
─ checking CRAN incoming feasibility ... [7s/70s] NOTE (1m 9.5s)
|
||||
Maintainer: ‘Carson Sievert <carson@posit.co>’
|
||||
|
||||
Days since last update: 5
|
||||
```
|
||||
|
||||
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of shiny.
|
||||
We checked 1383 reverse dependencies (1376 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 2 new problems (NOTEs only)
|
||||
* We failed to check 19 packages due to installation issues
|
||||
* We saw 0 new problems
|
||||
* We failed to check 31 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
|
||||
R CMD check displayed NOTEs for two packages, unrelated to changes in shiny.
|
||||
|
||||
* HH
|
||||
checking installed package size ... NOTE
|
||||
|
||||
* PopED
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* animalEKF
|
||||
* AovBay
|
||||
* Certara.VPCResults
|
||||
* chipPCR
|
||||
* AssumpSure
|
||||
* boinet
|
||||
* brms
|
||||
* cheem
|
||||
* ctsem
|
||||
* dartR.sim
|
||||
* diveR
|
||||
* gap
|
||||
* jsmodule
|
||||
* detourr
|
||||
* FAfA
|
||||
* fio
|
||||
* fitteR
|
||||
* FossilSimShiny
|
||||
* GDINA
|
||||
* ggsem
|
||||
* grandR
|
||||
* hbsaems
|
||||
* langevitour
|
||||
* lavaan.shiny
|
||||
* lcsm
|
||||
* linkspotter
|
||||
* loon.shiny
|
||||
* robmedExtra
|
||||
* MOsemiind
|
||||
* MVN
|
||||
* pandemonium
|
||||
* polarisR
|
||||
* RCTrep
|
||||
* rstanarm
|
||||
* SensMap
|
||||
* Seurat
|
||||
* shinyTempSignal
|
||||
* Signac
|
||||
* statsr
|
||||
* semdrw
|
||||
* shotGroups
|
||||
* sphereML
|
||||
* spinifex
|
||||
* SurprisalAnalysis
|
||||
* TestAnaAPP
|
||||
* tidyvpc
|
||||
108
eslint.config.mjs
Normal file
108
eslint.config.mjs
Normal file
@@ -0,0 +1,108 @@
|
||||
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
||||
import prettier from "eslint-plugin-prettier";
|
||||
import unicorn from "eslint-plugin-unicorn";
|
||||
import globals from "globals";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import js from "@eslint/js";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
export default [{
|
||||
ignores: ["**/*.d.ts"],
|
||||
}, ...compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
), {
|
||||
plugins: {
|
||||
"@typescript-eslint": typescriptEslint,
|
||||
prettier,
|
||||
unicorn,
|
||||
},
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
Atomics: "readonly",
|
||||
SharedArrayBuffer: "readonly",
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: 2021,
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
},
|
||||
|
||||
rules: {
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
||||
"default-case": ["error"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
quotes: ["error", "double", "avoid-escape"],
|
||||
semi: ["error", "always"],
|
||||
"dot-location": ["error", "property"],
|
||||
camelcase: ["off"],
|
||||
|
||||
"unicorn/filename-case": ["error", {
|
||||
case: "camelCase",
|
||||
}],
|
||||
|
||||
"@typescript-eslint/array-type": ["error", {
|
||||
default: "array-simple",
|
||||
readonly: "array-simple",
|
||||
}],
|
||||
|
||||
"@typescript-eslint/consistent-indexed-object-style": ["error", "index-signature"],
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
|
||||
"@typescript-eslint/naming-convention": ["error", {
|
||||
selector: "default",
|
||||
format: ["camelCase"],
|
||||
}, {
|
||||
selector: "method",
|
||||
modifiers: ["private"],
|
||||
format: ["camelCase"],
|
||||
leadingUnderscore: "require",
|
||||
}, {
|
||||
selector: "method",
|
||||
modifiers: ["protected"],
|
||||
format: ["camelCase"],
|
||||
leadingUnderscore: "require",
|
||||
}, {
|
||||
selector: "variable",
|
||||
format: ["camelCase"],
|
||||
trailingUnderscore: "forbid",
|
||||
leadingUnderscore: "forbid",
|
||||
}, {
|
||||
selector: "parameter",
|
||||
format: ["camelCase"],
|
||||
trailingUnderscore: "allow",
|
||||
leadingUnderscore: "forbid",
|
||||
}, {
|
||||
selector: ["enum", "enumMember"],
|
||||
format: ["PascalCase"],
|
||||
}, {
|
||||
selector: "typeLike",
|
||||
format: ["PascalCase"],
|
||||
|
||||
custom: {
|
||||
regex: "(t|T)ype$",
|
||||
match: false,
|
||||
},
|
||||
}],
|
||||
},
|
||||
}];
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.12.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
:where([data-shiny-busy-spinners] .recalculating){position:relative}[data-shiny-busy-spinners] .recalculating{min-height:var(--shiny-spinner-size, 32px)}[data-shiny-busy-spinners] .recalculating:after{position:absolute;content:"";--_shiny-spinner-url: var(--shiny-spinner-url, url(spinners/ring.svg));--_shiny-spinner-color: var(--shiny-spinner-color, var(--bs-primary, #007bc2));--_shiny-spinner-size: var(--shiny-spinner-size, 32px);--_shiny-spinner-delay: var(--shiny-spinner-delay, 1s);background:var(--_shiny-spinner-color);width:var(--_shiny-spinner-size);height:var(--_shiny-spinner-size);inset:calc(50% - var(--_shiny-spinner-size) / 2);mask-image:var(--_shiny-spinner-url);-webkit-mask-image:var(--_shiny-spinner-url);opacity:0;animation-delay:var(--_shiny-spinner-delay);animation-name:fade-in;animation-duration:.25s;animation-fill-mode:forwards}[data-shiny-busy-spinners] .recalculating:has(>*),[data-shiny-busy-spinners] .recalculating:empty{opacity:1}[data-shiny-busy-spinners] .recalculating>*:not(.recalculating){opacity:var(--_shiny-fade-opacity);transition:opacity .25s ease var(--shiny-spinner-delay, 1s)}[data-shiny-busy-spinners] .recalculating.html-widget-output{visibility:inherit!important}[data-shiny-busy-spinners] .recalculating.html-widget-output>*{visibility:hidden}[data-shiny-busy-spinners] .recalculating.html-widget-output :after{visibility:visible}[data-shiny-busy-spinners] .recalculating.shiny-html-output:not(.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating:not(.shiny-html-output)):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(.recalculating.shiny-table-output):after{display:none}[data-shiny-busy-spinners][data-shiny-busy-pulse].shiny-busy:has(#shiny-disconnected-overlay):after{display:none}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:after{--_shiny-pulse-background: var( --shiny-pulse-background, linear-gradient( 120deg, transparent, var(--bs-indigo, #4b00c1), var(--bs-purple, #74149c), var(--bs-pink, #bf007f), transparent ) );--_shiny-pulse-height: var(--shiny-pulse-height, 3px);--_shiny-pulse-speed: var(--shiny-pulse-speed, 1.2s);position:fixed;top:0;left:0;height:var(--_shiny-pulse-height);background:var(--_shiny-pulse-background);z-index:9999;animation-name:busy-page-pulse;animation-duration:var(--_shiny-pulse-speed);animation-direction:alternate;animation-iteration-count:infinite;animation-timing-function:ease-in-out;content:""}[data-shiny-busy-pulse]:not([data-shiny-busy-spinners]).shiny-busy:has(#shiny-disconnected-overlay):after{display:none}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes busy-page-pulse{0%{left:-14%;right:97%}45%{left:0%;right:14%}55%{left:14%;right:0%}to{left:97%;right:-14%}}.shiny-spinner-output-container{--shiny-spinner-size: 0px}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.datepicker {
|
||||
border-radius: 0.25rem;
|
||||
border-radius: 3px;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid rgba(0, 0, 0, 0.15);
|
||||
border-bottom: 7px solid var(--bs-border-color-translucent);
|
||||
border-top: 0;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
@@ -41,7 +41,7 @@
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
border-bottom: 6px solid var(--bs-body-bg);
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
@@ -73,13 +73,13 @@
|
||||
.datepicker-dropdown.datepicker-orient-top:before {
|
||||
bottom: -7px;
|
||||
border-bottom: 0;
|
||||
border-top: 7px solid rgba(0, 0, 0, 0.15);
|
||||
border-top: 7px solid var(--bs-border-color-translucent);
|
||||
}
|
||||
|
||||
.datepicker-dropdown.datepicker-orient-top:after {
|
||||
bottom: -6px;
|
||||
border-bottom: 0;
|
||||
border-top: 6px solid #fff;
|
||||
border-top: 6px solid var(--bs-body-bg);
|
||||
}
|
||||
|
||||
.datepicker table {
|
||||
@@ -105,64 +105,64 @@
|
||||
}
|
||||
|
||||
.datepicker table tr td.old, .datepicker table tr td.new {
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.day:hover, .datepicker table tr td.focused {
|
||||
color: #000;
|
||||
background: #e9e9ea;
|
||||
background: #e8e9e9;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.datepicker table tr td.disabled, .datepicker table tr td.disabled:hover {
|
||||
background: none;
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted {
|
||||
color: #000;
|
||||
background-color: #d1ecf1;
|
||||
border-color: #83ccd9;
|
||||
background-color: #cdf4fa;
|
||||
border-color: #70e0f1;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted:focus, .datepicker table tr td.highlighted.focus {
|
||||
color: #000;
|
||||
background-color: #bcd4d9;
|
||||
border-color: #6299a3;
|
||||
background-color: #b9dce1;
|
||||
border-color: #54a8b5;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted:hover {
|
||||
color: #000;
|
||||
background-color: #697679;
|
||||
border-color: #73b3bf;
|
||||
background-color: #677a7d;
|
||||
border-color: #63c5d4;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted:active, .datepicker table tr td.highlighted.active {
|
||||
color: #000;
|
||||
background-color: #bcd4d9;
|
||||
border-color: #73b3bf;
|
||||
background-color: #b9dce1;
|
||||
border-color: #63c5d4;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted:active:hover, .datepicker table tr td.highlighted:active:focus, .datepicker table tr td.highlighted.focus:active, .datepicker table tr td.highlighted.active:hover, .datepicker table tr td.highlighted.active:focus, .datepicker table tr td.highlighted.active.focus {
|
||||
.datepicker table tr td.highlighted:active:hover, .datepicker table tr td.highlighted:active:focus, .datepicker table tr td.highlighted:active.focus, .datepicker table tr td.highlighted.active:hover, .datepicker table tr td.highlighted.active:focus, .datepicker table tr td.highlighted.active.focus {
|
||||
color: #000;
|
||||
background-color: #adc4c8;
|
||||
border-color: #6299a3;
|
||||
background-color: #aacbd0;
|
||||
border-color: #54a8b5;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted.disabled:hover, .datepicker table tr td.highlighted.disabled:focus, .datepicker table tr td.highlighted.disabled.focus, .datepicker table tr td.highlighted[disabled]:hover, .datepicker table tr td.highlighted[disabled]:focus, .datepicker table tr td.highlighted.focus[disabled], fieldset[disabled] .datepicker table tr td.highlighted:hover, fieldset[disabled] .datepicker table tr td.highlighted:focus, fieldset[disabled] .datepicker table tr td.highlighted.focus {
|
||||
background-color: #d1ecf1;
|
||||
border-color: #83ccd9;
|
||||
.datepicker table tr td.highlighted.disabled:hover, .datepicker table tr td.highlighted.disabled:focus, .datepicker table tr td.highlighted.disabled.focus, .datepicker table tr td.highlighted[disabled]:hover, .datepicker table tr td.highlighted[disabled]:focus, .datepicker table tr td.highlighted[disabled].focus, fieldset[disabled] .datepicker table tr td.highlighted:hover, fieldset[disabled] .datepicker table tr td.highlighted:focus, fieldset[disabled] .datepicker table tr td.highlighted.focus {
|
||||
background-color: #cdf4fa;
|
||||
border-color: #70e0f1;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted.focused {
|
||||
background: #aadce5;
|
||||
background: #9feaf5;
|
||||
}
|
||||
|
||||
.datepicker table tr td.highlighted.disabled, .datepicker table tr td.highlighted.disabled:active {
|
||||
background: #d1ecf1;
|
||||
color: #6c757d;
|
||||
background: #cdf4fa;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.today {
|
||||
@@ -189,13 +189,13 @@
|
||||
border-color: #e0a12d;
|
||||
}
|
||||
|
||||
.datepicker table tr td.today:active:hover, .datepicker table tr td.today:active:focus, .datepicker table tr td.today.focus:active, .datepicker table tr td.today.active:hover, .datepicker table tr td.today.active:focus, .datepicker table tr td.today.active.focus {
|
||||
.datepicker table tr td.today:active:hover, .datepicker table tr td.today:active:focus, .datepicker table tr td.today:active.focus, .datepicker table tr td.today.active:hover, .datepicker table tr td.today.active:focus, .datepicker table tr td.today.active.focus {
|
||||
color: #000;
|
||||
background-color: #d4b67f;
|
||||
border-color: #bf8926;
|
||||
}
|
||||
|
||||
.datepicker table tr td.today.disabled:hover, .datepicker table tr td.today.disabled:focus, .datepicker table tr td.today.disabled.focus, .datepicker table tr td.today[disabled]:hover, .datepicker table tr td.today[disabled]:focus, .datepicker table tr td.today.focus[disabled], fieldset[disabled] .datepicker table tr td.today:hover, fieldset[disabled] .datepicker table tr td.today:focus, fieldset[disabled] .datepicker table tr td.today.focus {
|
||||
.datepicker table tr td.today.disabled:hover, .datepicker table tr td.today.disabled:focus, .datepicker table tr td.today.disabled.focus, .datepicker table tr td.today[disabled]:hover, .datepicker table tr td.today[disabled]:focus, .datepicker table tr td.today[disabled].focus, fieldset[disabled] .datepicker table tr td.today:hover, fieldset[disabled] .datepicker table tr td.today:focus, fieldset[disabled] .datepicker table tr td.today.focus {
|
||||
background-color: #ffdb99;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
@@ -206,96 +206,96 @@
|
||||
|
||||
.datepicker table tr td.today.disabled, .datepicker table tr td.today.disabled:active {
|
||||
background: #ffdb99;
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range {
|
||||
color: #000;
|
||||
background-color: #e9e9ea;
|
||||
border-color: #b5b5b8;
|
||||
background-color: #e8e9e9;
|
||||
border-color: #b4b7b7;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range:focus, .datepicker table tr td.range.focus {
|
||||
color: #000;
|
||||
background-color: #d2d2d3;
|
||||
border-color: #88888a;
|
||||
background-color: #d1d2d2;
|
||||
border-color: #878989;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range:hover {
|
||||
color: #000;
|
||||
background-color: #757575;
|
||||
border-color: #9f9fa2;
|
||||
background-color: #747575;
|
||||
border-color: #9ea1a1;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range:active, .datepicker table tr td.range.active {
|
||||
color: #000;
|
||||
background-color: #d2d2d3;
|
||||
border-color: #9f9fa2;
|
||||
background-color: #d1d2d2;
|
||||
border-color: #9ea1a1;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range:active:hover, .datepicker table tr td.range:active:focus, .datepicker table tr td.range.focus:active, .datepicker table tr td.range.active:hover, .datepicker table tr td.range.active:focus, .datepicker table tr td.range.active.focus {
|
||||
.datepicker table tr td.range:active:hover, .datepicker table tr td.range:active:focus, .datepicker table tr td.range:active.focus, .datepicker table tr td.range.active:hover, .datepicker table tr td.range.active:focus, .datepicker table tr td.range.active.focus {
|
||||
color: #000;
|
||||
background-color: #c1c1c2;
|
||||
border-color: #88888a;
|
||||
background-color: #c1c1c1;
|
||||
border-color: #878989;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.disabled:hover, .datepicker table tr td.range.disabled:focus, .datepicker table tr td.range.disabled.focus, .datepicker table tr td.range[disabled]:hover, .datepicker table tr td.range[disabled]:focus, .datepicker table tr td.range.focus[disabled], fieldset[disabled] .datepicker table tr td.range:hover, fieldset[disabled] .datepicker table tr td.range:focus, fieldset[disabled] .datepicker table tr td.range.focus {
|
||||
background-color: #e9e9ea;
|
||||
border-color: #b5b5b8;
|
||||
.datepicker table tr td.range.disabled:hover, .datepicker table tr td.range.disabled:focus, .datepicker table tr td.range.disabled.focus, .datepicker table tr td.range[disabled]:hover, .datepicker table tr td.range[disabled]:focus, .datepicker table tr td.range[disabled].focus, fieldset[disabled] .datepicker table tr td.range:hover, fieldset[disabled] .datepicker table tr td.range:focus, fieldset[disabled] .datepicker table tr td.range.focus {
|
||||
background-color: #e8e9e9;
|
||||
border-color: #b4b7b7;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.focused {
|
||||
background: #cfcfd1;
|
||||
background: #ced0d0;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.disabled, .datepicker table tr td.range.disabled:active {
|
||||
background: #e9e9ea;
|
||||
color: #6c757d;
|
||||
background: #e8e9e9;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted {
|
||||
color: #000;
|
||||
background-color: #ddebee;
|
||||
border-color: #99c3cc;
|
||||
background-color: #dbeff2;
|
||||
border-color: #90ced7;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted:focus, .datepicker table tr td.range.highlighted.focus {
|
||||
color: #000;
|
||||
background-color: #c7d4d6;
|
||||
border-color: #739299;
|
||||
background-color: #c5d7da;
|
||||
border-color: #6c9aa1;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted:hover {
|
||||
color: #000;
|
||||
background-color: #6f7677;
|
||||
border-color: #87acb4;
|
||||
background-color: #6e7879;
|
||||
border-color: #7fb5bd;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted:active, .datepicker table tr td.range.highlighted.active {
|
||||
color: #000;
|
||||
background-color: #c7d4d6;
|
||||
border-color: #87acb4;
|
||||
background-color: #c5d7da;
|
||||
border-color: #7fb5bd;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted:active:hover, .datepicker table tr td.range.highlighted:active:focus, .datepicker table tr td.range.highlighted.focus:active, .datepicker table tr td.range.highlighted.active:hover, .datepicker table tr td.range.highlighted.active:focus, .datepicker table tr td.range.highlighted.active.focus {
|
||||
.datepicker table tr td.range.highlighted:active:hover, .datepicker table tr td.range.highlighted:active:focus, .datepicker table tr td.range.highlighted:active.focus, .datepicker table tr td.range.highlighted.active:hover, .datepicker table tr td.range.highlighted.active:focus, .datepicker table tr td.range.highlighted.active.focus {
|
||||
color: #000;
|
||||
background-color: #b7c3c6;
|
||||
border-color: #739299;
|
||||
background-color: #b6c6c9;
|
||||
border-color: #6c9aa1;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted.disabled:hover, .datepicker table tr td.range.highlighted.disabled:focus, .datepicker table tr td.range.highlighted.disabled.focus, .datepicker table tr td.range.highlighted[disabled]:hover, .datepicker table tr td.range.highlighted[disabled]:focus, .datepicker table tr td.range.highlighted.focus[disabled], fieldset[disabled] .datepicker table tr td.range.highlighted:hover, fieldset[disabled] .datepicker table tr td.range.highlighted:focus, fieldset[disabled] .datepicker table tr td.range.highlighted.focus {
|
||||
background-color: #ddebee;
|
||||
border-color: #99c3cc;
|
||||
.datepicker table tr td.range.highlighted.disabled:hover, .datepicker table tr td.range.highlighted.disabled:focus, .datepicker table tr td.range.highlighted.disabled.focus, .datepicker table tr td.range.highlighted[disabled]:hover, .datepicker table tr td.range.highlighted[disabled]:focus, .datepicker table tr td.range.highlighted[disabled].focus, fieldset[disabled] .datepicker table tr td.range.highlighted:hover, fieldset[disabled] .datepicker table tr td.range.highlighted:focus, fieldset[disabled] .datepicker table tr td.range.highlighted.focus {
|
||||
background-color: #dbeff2;
|
||||
border-color: #90ced7;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted.focused {
|
||||
background: #bbd7dd;
|
||||
background: #b6dee4;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.highlighted.disabled, .datepicker table tr td.range.highlighted.disabled:active {
|
||||
background: #ddebee;
|
||||
color: #6c757d;
|
||||
background: #dbeff2;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.today {
|
||||
@@ -322,92 +322,92 @@
|
||||
border-color: #d08d14;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.today:active:hover, .datepicker table tr td.range.today:active:focus, .datepicker table tr td.range.today.focus:active, .datepicker table tr td.range.today.active:hover, .datepicker table tr td.range.today.active:focus, .datepicker table tr td.range.today.active.focus {
|
||||
.datepicker table tr td.range.today:active:hover, .datepicker table tr td.range.today:active:focus, .datepicker table tr td.range.today:active.focus, .datepicker table tr td.range.today.active:hover, .datepicker table tr td.range.today.active:focus, .datepicker table tr td.range.today.active.focus {
|
||||
color: #000;
|
||||
background-color: #cba561;
|
||||
border-color: #b17811;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.today.disabled:hover, .datepicker table tr td.range.today.disabled:focus, .datepicker table tr td.range.today.disabled.focus, .datepicker table tr td.range.today[disabled]:hover, .datepicker table tr td.range.today[disabled]:focus, .datepicker table tr td.range.today.focus[disabled], fieldset[disabled] .datepicker table tr td.range.today:hover, fieldset[disabled] .datepicker table tr td.range.today:focus, fieldset[disabled] .datepicker table tr td.range.today.focus {
|
||||
.datepicker table tr td.range.today.disabled:hover, .datepicker table tr td.range.today.disabled:focus, .datepicker table tr td.range.today.disabled.focus, .datepicker table tr td.range.today[disabled]:hover, .datepicker table tr td.range.today[disabled]:focus, .datepicker table tr td.range.today[disabled].focus, fieldset[disabled] .datepicker table tr td.range.today:hover, fieldset[disabled] .datepicker table tr td.range.today:focus, fieldset[disabled] .datepicker table tr td.range.today.focus {
|
||||
background-color: #f4c775;
|
||||
border-color: #eca117;
|
||||
}
|
||||
|
||||
.datepicker table tr td.range.today.disabled, .datepicker table tr td.range.today.disabled:active {
|
||||
background: #f4c775;
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected, .datepicker table tr td.selected.highlighted {
|
||||
color: #fff;
|
||||
background-color: #898b8d;
|
||||
border-color: #6b6e71;
|
||||
color: #000;
|
||||
background-color: #878889;
|
||||
border-color: #606060;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected:focus, .datepicker table tr td.selected.focus, .datepicker table tr td.selected.highlighted:focus, .datepicker table tr td.selected.highlighted.focus {
|
||||
color: #fff;
|
||||
background-color: #959798;
|
||||
border-color: #909295;
|
||||
color: #000;
|
||||
background-color: #7a7a7b;
|
||||
border-color: #484848;
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected:hover, .datepicker table tr td.selected.highlighted:hover {
|
||||
color: #fff;
|
||||
background-color: #c4c5c6;
|
||||
border-color: #7d7f82;
|
||||
color: #000;
|
||||
background-color: #444445;
|
||||
border-color: #545454;
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected:active, .datepicker table tr td.selected.active, .datepicker table tr td.selected.highlighted:active, .datepicker table tr td.selected.highlighted.active {
|
||||
color: #fff;
|
||||
background-color: #959798;
|
||||
border-color: #7d7f82;
|
||||
color: #000;
|
||||
background-color: #7a7a7b;
|
||||
border-color: #545454;
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected:active:hover, .datepicker table tr td.selected:active:focus, .datepicker table tr td.selected.focus:active, .datepicker table tr td.selected.active:hover, .datepicker table tr td.selected.active:focus, .datepicker table tr td.selected.active.focus, .datepicker table tr td.selected.highlighted:active:hover, .datepicker table tr td.selected.highlighted:active:focus, .datepicker table tr td.selected.highlighted.focus:active, .datepicker table tr td.selected.highlighted.active:hover, .datepicker table tr td.selected.highlighted.active:focus, .datepicker table tr td.selected.highlighted.active.focus {
|
||||
color: #fff;
|
||||
background-color: #9d9fa0;
|
||||
border-color: #909295;
|
||||
.datepicker table tr td.selected:active:hover, .datepicker table tr td.selected:active:focus, .datepicker table tr td.selected:active.focus, .datepicker table tr td.selected.active:hover, .datepicker table tr td.selected.active:focus, .datepicker table tr td.selected.active.focus, .datepicker table tr td.selected.highlighted:active:hover, .datepicker table tr td.selected.highlighted:active:focus, .datepicker table tr td.selected.highlighted:active.focus, .datepicker table tr td.selected.highlighted.active:hover, .datepicker table tr td.selected.highlighted.active:focus, .datepicker table tr td.selected.highlighted.active.focus {
|
||||
color: #000;
|
||||
background-color: #707172;
|
||||
border-color: #484848;
|
||||
}
|
||||
|
||||
.datepicker table tr td.selected.disabled:hover, .datepicker table tr td.selected.disabled:focus, .datepicker table tr td.selected.disabled.focus, .datepicker table tr td.selected[disabled]:hover, .datepicker table tr td.selected[disabled]:focus, .datepicker table tr td.selected.focus[disabled], fieldset[disabled] .datepicker table tr td.selected:hover, fieldset[disabled] .datepicker table tr td.selected:focus, fieldset[disabled] .datepicker table tr td.selected.focus, .datepicker table tr td.selected.highlighted.disabled:hover, .datepicker table tr td.selected.highlighted.disabled:focus, .datepicker table tr td.selected.highlighted.disabled.focus, .datepicker table tr td.selected.highlighted[disabled]:hover, .datepicker table tr td.selected.highlighted[disabled]:focus, .datepicker table tr td.selected.highlighted.focus[disabled], fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, fieldset[disabled] .datepicker table tr td.selected.highlighted.focus {
|
||||
background-color: #898b8d;
|
||||
border-color: #6b6e71;
|
||||
.datepicker table tr td.selected.disabled:hover, .datepicker table tr td.selected.disabled:focus, .datepicker table tr td.selected.disabled.focus, .datepicker table tr td.selected[disabled]:hover, .datepicker table tr td.selected[disabled]:focus, .datepicker table tr td.selected[disabled].focus, fieldset[disabled] .datepicker table tr td.selected:hover, fieldset[disabled] .datepicker table tr td.selected:focus, fieldset[disabled] .datepicker table tr td.selected.focus, .datepicker table tr td.selected.highlighted.disabled:hover, .datepicker table tr td.selected.highlighted.disabled:focus, .datepicker table tr td.selected.highlighted.disabled.focus, .datepicker table tr td.selected.highlighted[disabled]:hover, .datepicker table tr td.selected.highlighted[disabled]:focus, .datepicker table tr td.selected.highlighted[disabled].focus, fieldset[disabled] .datepicker table tr td.selected.highlighted:hover, fieldset[disabled] .datepicker table tr td.selected.highlighted:focus, fieldset[disabled] .datepicker table tr td.selected.highlighted.focus {
|
||||
background-color: #878889;
|
||||
border-color: #606060;
|
||||
}
|
||||
|
||||
.datepicker table tr td.active, .datepicker table tr td.active.highlighted {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-color: #0277f4;
|
||||
color: #ffffff;
|
||||
background-color: #007bc2;
|
||||
border-color: #0176ba;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.datepicker table tr td.active:focus, .datepicker table tr td.active.focus, .datepicker table tr td.active.highlighted:focus, .datepicker table tr td.active.highlighted.focus {
|
||||
color: #fff;
|
||||
background-color: #1a88ff;
|
||||
border-color: #4199f7;
|
||||
color: #ffffff;
|
||||
background-color: #1a88c8;
|
||||
border-color: #4198cb;
|
||||
}
|
||||
|
||||
.datepicker table tr td.active:hover, .datepicker table tr td.active.highlighted:hover {
|
||||
color: #fff;
|
||||
background-color: #80bdff;
|
||||
border-color: #2087f5;
|
||||
color: #ffffff;
|
||||
background-color: #80bde1;
|
||||
border-color: #1f86c2;
|
||||
}
|
||||
|
||||
.datepicker table tr td.active:active, .datepicker table tr td.active.active, .datepicker table tr td.active.highlighted:active, .datepicker table tr td.active.highlighted.active {
|
||||
color: #fff;
|
||||
background-color: #1a88ff;
|
||||
border-color: #2087f5;
|
||||
color: #ffffff;
|
||||
background-color: #1a88c8;
|
||||
border-color: #1f86c2;
|
||||
}
|
||||
|
||||
.datepicker table tr td.active:active:hover, .datepicker table tr td.active:active:focus, .datepicker table tr td.active.focus:active, .datepicker table tr td.active.active:hover, .datepicker table tr td.active.active:focus, .datepicker table tr td.active.active.focus, .datepicker table tr td.active.highlighted:active:hover, .datepicker table tr td.active.highlighted:active:focus, .datepicker table tr td.active.highlighted.focus:active, .datepicker table tr td.active.highlighted.active:hover, .datepicker table tr td.active.highlighted.active:focus, .datepicker table tr td.active.highlighted.active.focus {
|
||||
color: #fff;
|
||||
background-color: #2b91ff;
|
||||
border-color: #4199f7;
|
||||
.datepicker table tr td.active:active:hover, .datepicker table tr td.active:active:focus, .datepicker table tr td.active:active.focus, .datepicker table tr td.active.active:hover, .datepicker table tr td.active.active:focus, .datepicker table tr td.active.active.focus, .datepicker table tr td.active.highlighted:active:hover, .datepicker table tr td.active.highlighted:active:focus, .datepicker table tr td.active.highlighted:active.focus, .datepicker table tr td.active.highlighted.active:hover, .datepicker table tr td.active.highlighted.active:focus, .datepicker table tr td.active.highlighted.active.focus {
|
||||
color: #ffffff;
|
||||
background-color: #2b91cc;
|
||||
border-color: #4198cb;
|
||||
}
|
||||
|
||||
.datepicker table tr td.active.disabled:hover, .datepicker table tr td.active.disabled:focus, .datepicker table tr td.active.disabled.focus, .datepicker table tr td.active[disabled]:hover, .datepicker table tr td.active[disabled]:focus, .datepicker table tr td.active.focus[disabled], fieldset[disabled] .datepicker table tr td.active:hover, fieldset[disabled] .datepicker table tr td.active:focus, fieldset[disabled] .datepicker table tr td.active.focus, .datepicker table tr td.active.highlighted.disabled:hover, .datepicker table tr td.active.highlighted.disabled:focus, .datepicker table tr td.active.highlighted.disabled.focus, .datepicker table tr td.active.highlighted[disabled]:hover, .datepicker table tr td.active.highlighted[disabled]:focus, .datepicker table tr td.active.highlighted.focus[disabled], fieldset[disabled] .datepicker table tr td.active.highlighted:hover, fieldset[disabled] .datepicker table tr td.active.highlighted:focus, fieldset[disabled] .datepicker table tr td.active.highlighted.focus {
|
||||
background-color: #007bff;
|
||||
border-color: #0277f4;
|
||||
.datepicker table tr td.active.disabled:hover, .datepicker table tr td.active.disabled:focus, .datepicker table tr td.active.disabled.focus, .datepicker table tr td.active[disabled]:hover, .datepicker table tr td.active[disabled]:focus, .datepicker table tr td.active[disabled].focus, fieldset[disabled] .datepicker table tr td.active:hover, fieldset[disabled] .datepicker table tr td.active:focus, fieldset[disabled] .datepicker table tr td.active.focus, .datepicker table tr td.active.highlighted.disabled:hover, .datepicker table tr td.active.highlighted.disabled:focus, .datepicker table tr td.active.highlighted.disabled.focus, .datepicker table tr td.active.highlighted[disabled]:hover, .datepicker table tr td.active.highlighted[disabled]:focus, .datepicker table tr td.active.highlighted[disabled].focus, fieldset[disabled] .datepicker table tr td.active.highlighted:hover, fieldset[disabled] .datepicker table tr td.active.highlighted:focus, fieldset[disabled] .datepicker table tr td.active.highlighted.focus {
|
||||
background-color: #007bc2;
|
||||
border-color: #0176ba;
|
||||
}
|
||||
|
||||
.datepicker table tr td span {
|
||||
@@ -423,53 +423,53 @@
|
||||
|
||||
.datepicker table tr td span:hover, .datepicker table tr td span.focused {
|
||||
color: #000;
|
||||
background: #e9e9ea;
|
||||
background: #e8e9e9;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.disabled, .datepicker table tr td span.disabled:hover {
|
||||
background: none;
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active, .datepicker table tr td span.active:hover, .datepicker table tr td span.active.disabled, .datepicker table tr td span.active.disabled:hover {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-color: #0277f4;
|
||||
color: #ffffff;
|
||||
background-color: #007bc2;
|
||||
border-color: #0176ba;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active:focus, .datepicker table tr td span.active.focus, .datepicker table tr td span.active:hover:focus, .datepicker table tr td span.active.focus:hover, .datepicker table tr td span.active.disabled:focus, .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active.disabled:hover:focus, .datepicker table tr td span.active.disabled.focus:hover {
|
||||
color: #fff;
|
||||
background-color: #1a88ff;
|
||||
border-color: #4199f7;
|
||||
.datepicker table tr td span.active:focus, .datepicker table tr td span.active.focus, .datepicker table tr td span.active:hover:focus, .datepicker table tr td span.active:hover.focus, .datepicker table tr td span.active.disabled:focus, .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active.disabled:hover:focus, .datepicker table tr td span.active.disabled:hover.focus {
|
||||
color: #ffffff;
|
||||
background-color: #1a88c8;
|
||||
border-color: #4198cb;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active:hover, .datepicker table tr td span.active:hover:hover, .datepicker table tr td span.active.disabled:hover, .datepicker table tr td span.active.disabled:hover:hover {
|
||||
color: #fff;
|
||||
background-color: #80bdff;
|
||||
border-color: #2087f5;
|
||||
color: #ffffff;
|
||||
background-color: #80bde1;
|
||||
border-color: #1f86c2;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active:active, .datepicker table tr td span.active.active, .datepicker table tr td span.active:hover:active, .datepicker table tr td span.active.active:hover, .datepicker table tr td span.active.disabled:active, .datepicker table tr td span.active.disabled.active, .datepicker table tr td span.active.disabled:hover:active, .datepicker table tr td span.active.disabled.active:hover {
|
||||
color: #fff;
|
||||
background-color: #1a88ff;
|
||||
border-color: #2087f5;
|
||||
.datepicker table tr td span.active:active, .datepicker table tr td span.active.active, .datepicker table tr td span.active:hover:active, .datepicker table tr td span.active:hover.active, .datepicker table tr td span.active.disabled:active, .datepicker table tr td span.active.disabled.active, .datepicker table tr td span.active.disabled:hover:active, .datepicker table tr td span.active.disabled:hover.active {
|
||||
color: #ffffff;
|
||||
background-color: #1a88c8;
|
||||
border-color: #1f86c2;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active:active:hover, .datepicker table tr td span.active:active:focus, .datepicker table tr td span.active.focus:active, .datepicker table tr td span.active.active:hover, .datepicker table tr td span.active.active:focus, .datepicker table tr td span.active.active.focus, .datepicker table tr td span.active:hover:active:hover, .datepicker table tr td span.active:hover:active:focus, .datepicker table tr td span.active.focus:hover:active, .datepicker table tr td span.active.active:hover:hover, .datepicker table tr td span.active.active:hover:focus, .datepicker table tr td span.active.active.focus:hover, .datepicker table tr td span.active.disabled:active:hover, .datepicker table tr td span.active.disabled:active:focus, .datepicker table tr td span.active.disabled.focus:active, .datepicker table tr td span.active.disabled.active:hover, .datepicker table tr td span.active.disabled.active:focus, .datepicker table tr td span.active.disabled.active.focus, .datepicker table tr td span.active.disabled:hover:active:hover, .datepicker table tr td span.active.disabled:hover:active:focus, .datepicker table tr td span.active.disabled.focus:hover:active, .datepicker table tr td span.active.disabled.active:hover:hover, .datepicker table tr td span.active.disabled.active:hover:focus, .datepicker table tr td span.active.disabled.active.focus:hover {
|
||||
color: #fff;
|
||||
background-color: #2b91ff;
|
||||
border-color: #4199f7;
|
||||
.datepicker table tr td span.active:active:hover, .datepicker table tr td span.active:active:focus, .datepicker table tr td span.active:active.focus, .datepicker table tr td span.active.active:hover, .datepicker table tr td span.active.active:focus, .datepicker table tr td span.active.active.focus, .datepicker table tr td span.active:hover:active:hover, .datepicker table tr td span.active:hover:active:focus, .datepicker table tr td span.active:hover:active.focus, .datepicker table tr td span.active:hover.active:hover, .datepicker table tr td span.active:hover.active:focus, .datepicker table tr td span.active:hover.active.focus, .datepicker table tr td span.active.disabled:active:hover, .datepicker table tr td span.active.disabled:active:focus, .datepicker table tr td span.active.disabled:active.focus, .datepicker table tr td span.active.disabled.active:hover, .datepicker table tr td span.active.disabled.active:focus, .datepicker table tr td span.active.disabled.active.focus, .datepicker table tr td span.active.disabled:hover:active:hover, .datepicker table tr td span.active.disabled:hover:active:focus, .datepicker table tr td span.active.disabled:hover:active.focus, .datepicker table tr td span.active.disabled:hover.active:hover, .datepicker table tr td span.active.disabled:hover.active:focus, .datepicker table tr td span.active.disabled:hover.active.focus {
|
||||
color: #ffffff;
|
||||
background-color: #2b91cc;
|
||||
border-color: #4198cb;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.active.disabled:hover, .datepicker table tr td span.active.disabled:focus, .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active[disabled]:hover, .datepicker table tr td span.active[disabled]:focus, .datepicker table tr td span.active.focus[disabled], fieldset[disabled] .datepicker table tr td span.active:hover, fieldset[disabled] .datepicker table tr td span.active:focus, fieldset[disabled] .datepicker table tr td span.active.focus, .datepicker table tr td span.active.disabled:hover:hover, .datepicker table tr td span.active.disabled:hover:focus, .datepicker table tr td span.active.disabled.focus:hover, .datepicker table tr td span.active[disabled]:hover:hover, .datepicker table tr td span.active[disabled]:hover:focus, .datepicker table tr td span.active.focus[disabled]:hover, fieldset[disabled] .datepicker table tr td span.active:hover:hover, fieldset[disabled] .datepicker table tr td span.active:hover:focus, fieldset[disabled] .datepicker table tr td span.active.focus:hover, .datepicker table tr td span.active.disabled.disabled:hover, .datepicker table tr td span.active.disabled.disabled:focus, .datepicker table tr td span.active.disabled.disabled.focus, .datepicker table tr td span.active.disabled[disabled]:hover, .datepicker table tr td span.active.disabled[disabled]:focus, .datepicker table tr td span.active.disabled.focus[disabled], fieldset[disabled] .datepicker table tr td span.active.disabled:hover, fieldset[disabled] .datepicker table tr td span.active.disabled:focus, fieldset[disabled] .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active.disabled.disabled:hover:hover, .datepicker table tr td span.active.disabled.disabled:hover:focus, .datepicker table tr td span.active.disabled.disabled.focus:hover, .datepicker table tr td span.active.disabled[disabled]:hover:hover, .datepicker table tr td span.active.disabled[disabled]:hover:focus, .datepicker table tr td span.active.disabled.focus[disabled]:hover, fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, fieldset[disabled] .datepicker table tr td span.active.disabled.focus:hover {
|
||||
background-color: #007bff;
|
||||
border-color: #0277f4;
|
||||
.datepicker table tr td span.active.disabled:hover, .datepicker table tr td span.active.disabled:focus, .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active[disabled]:hover, .datepicker table tr td span.active[disabled]:focus, .datepicker table tr td span.active[disabled].focus, fieldset[disabled] .datepicker table tr td span.active:hover, fieldset[disabled] .datepicker table tr td span.active:focus, fieldset[disabled] .datepicker table tr td span.active.focus, .datepicker table tr td span.active:hover.disabled:hover, .datepicker table tr td span.active:hover.disabled:focus, .datepicker table tr td span.active:hover.disabled.focus, .datepicker table tr td span.active:hover[disabled]:hover, .datepicker table tr td span.active:hover[disabled]:focus, .datepicker table tr td span.active:hover[disabled].focus, fieldset[disabled] .datepicker table tr td span.active:hover:hover, fieldset[disabled] .datepicker table tr td span.active:hover:focus, fieldset[disabled] .datepicker table tr td span.active:hover.focus, .datepicker table tr td span.active.disabled.disabled:hover, .datepicker table tr td span.active.disabled.disabled:focus, .datepicker table tr td span.active.disabled.disabled.focus, .datepicker table tr td span.active.disabled[disabled]:hover, .datepicker table tr td span.active.disabled[disabled]:focus, .datepicker table tr td span.active.disabled[disabled].focus, fieldset[disabled] .datepicker table tr td span.active.disabled:hover, fieldset[disabled] .datepicker table tr td span.active.disabled:focus, fieldset[disabled] .datepicker table tr td span.active.disabled.focus, .datepicker table tr td span.active.disabled:hover.disabled:hover, .datepicker table tr td span.active.disabled:hover.disabled:focus, .datepicker table tr td span.active.disabled:hover.disabled.focus, .datepicker table tr td span.active.disabled:hover[disabled]:hover, .datepicker table tr td span.active.disabled:hover[disabled]:focus, .datepicker table tr td span.active.disabled:hover[disabled].focus, fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover, fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus, fieldset[disabled] .datepicker table tr td span.active.disabled:hover.focus {
|
||||
background-color: #007bc2;
|
||||
border-color: #0176ba;
|
||||
}
|
||||
|
||||
.datepicker table tr td span.old, .datepicker table tr td span.new {
|
||||
color: #6c757d;
|
||||
color: #707782;
|
||||
}
|
||||
|
||||
.datepicker .datepicker-switch {
|
||||
@@ -488,7 +488,7 @@
|
||||
.datepicker .next:hover,
|
||||
.datepicker tfoot tr th:hover {
|
||||
color: #000;
|
||||
background: #e9e9ea;
|
||||
background: #e8e9e9;
|
||||
}
|
||||
|
||||
.datepicker .prev.disabled, .datepicker .next.disabled {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
/*!
|
||||
* Datepicker for Bootstrap v1.9.0 (https://github.com/uxsolutions/bootstrap-datepicker)
|
||||
* Datepicker for Bootstrap v1.10.0 (https://github.com/uxsolutions/bootstrap-datepicker)
|
||||
*
|
||||
* Licensed under the Apache License v2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* Licensed under the Apache License v2.0 (https://www.apache.org/licenses/LICENSE-2.0)
|
||||
*/
|
||||
|
||||
(function(factory){
|
||||
@@ -61,7 +61,7 @@
|
||||
replace: function(new_array){
|
||||
if (!new_array)
|
||||
return;
|
||||
if (!$.isArray(new_array))
|
||||
if (!Array.isArray(new_array))
|
||||
new_array = [new_array];
|
||||
this.clear();
|
||||
this.push.apply(this, new_array);
|
||||
@@ -103,9 +103,15 @@
|
||||
this.isInput = this.element.is('input');
|
||||
this.inputField = this.isInput ? this.element : this.element.find('input');
|
||||
this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .input-group-append, .input-group-prepend, .btn') : false;
|
||||
if (this.component && this.component.length === 0)
|
||||
if (this.component && this.component.length === 0){
|
||||
this.component = false;
|
||||
this.isInline = !this.component && this.element.is('div');
|
||||
}
|
||||
|
||||
if (this.o.isInline === null){
|
||||
this.isInline = !this.component && !this.isInput;
|
||||
} else {
|
||||
this.isInline = this.o.isInline;
|
||||
}
|
||||
|
||||
this.picker = $(DPGlobal.template);
|
||||
|
||||
@@ -176,7 +182,7 @@
|
||||
},
|
||||
|
||||
_resolveDaysOfWeek: function(daysOfWeek){
|
||||
if (!$.isArray(daysOfWeek))
|
||||
if (!Array.isArray(daysOfWeek))
|
||||
daysOfWeek = daysOfWeek.split(/[,\s]*/);
|
||||
return $.map(daysOfWeek, Number);
|
||||
},
|
||||
@@ -263,7 +269,7 @@
|
||||
o.daysOfWeekHighlighted = this._resolveDaysOfWeek(o.daysOfWeekHighlighted||[]);
|
||||
|
||||
o.datesDisabled = o.datesDisabled||[];
|
||||
if (!$.isArray(o.datesDisabled)) {
|
||||
if (!Array.isArray(o.datesDisabled)) {
|
||||
o.datesDisabled = o.datesDisabled.split(',');
|
||||
}
|
||||
o.datesDisabled = $.map(o.datesDisabled, function(d){
|
||||
@@ -570,16 +576,15 @@
|
||||
|
||||
clearDates: function(){
|
||||
this.inputField.val('');
|
||||
this.update();
|
||||
this._trigger('changeDate');
|
||||
|
||||
this.update();
|
||||
if (this.o.autoclose) {
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
setDates: function(){
|
||||
var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
var args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
this.update.apply(this, args);
|
||||
this._trigger('changeDate');
|
||||
this.setValue();
|
||||
@@ -587,7 +592,7 @@
|
||||
},
|
||||
|
||||
setUTCDates: function(){
|
||||
var args = $.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
var args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
this.setDates.apply(this, $.map(args, this._utc_to_local));
|
||||
return this;
|
||||
},
|
||||
@@ -1039,7 +1044,7 @@
|
||||
|
||||
//Check if uniqueSort exists (supported by jquery >=1.12 and >=2.2)
|
||||
//Fallback to unique function for older jquery versions
|
||||
if ($.isFunction($.uniqueSort)) {
|
||||
if (typeof $.uniqueSort === "function") {
|
||||
clsName = $.uniqueSort(clsName);
|
||||
} else {
|
||||
clsName = $.unique(clsName);
|
||||
@@ -1571,12 +1576,12 @@
|
||||
|
||||
if (new_date < this.dates[j]){
|
||||
// Date being moved earlier/left
|
||||
while (j >= 0 && new_date < this.dates[j]){
|
||||
while (j >= 0 && new_date < this.dates[j] && (this.pickers[j].element.val() || "").length > 0) {
|
||||
this.pickers[j--].setUTCDate(new_date);
|
||||
}
|
||||
} else if (new_date > this.dates[k]){
|
||||
// Date being moved later/right
|
||||
while (k < l && new_date > this.dates[k]){
|
||||
while (k < l && new_date > this.dates[k] && (this.pickers[k].element.val() || "").length > 0) {
|
||||
this.pickers[k++].setUTCDate(new_date);
|
||||
}
|
||||
}
|
||||
@@ -1690,6 +1695,7 @@
|
||||
endDate: Infinity,
|
||||
forceParse: true,
|
||||
format: 'mm/dd/yyyy',
|
||||
isInline: null,
|
||||
keepEmptyValues: false,
|
||||
keyboardNavigation: true,
|
||||
language: 'en',
|
||||
@@ -2007,7 +2013,7 @@
|
||||
|
||||
/* DATEPICKER VERSION
|
||||
* =================== */
|
||||
$.fn.datepicker.version = '1.9.0';
|
||||
$.fn.datepicker.version = '1.10.0';
|
||||
|
||||
$.fn.datepicker.deprecated = function(msg){
|
||||
var console = window.console;
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.ar-DZ.min.js
vendored
Normal file
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.ar-DZ.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(a){a.fn.datepicker.dates["ar-DZ"]={days:["الأحد","الاثنين","الثلاثاء","الأربعاء","الخميس","الجمعة","السبت","الأحد"],daysShort:["أحد","اثنين","ثلاثاء","أربعاء","خميس","جمعة","سبت","أحد"],daysMin:["ح","ن","ث","ع","خ","ج","س","ح"],months:["جانفي","فيفري","مارس","أفريل","ماي","جوان","جويليه","أوت","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],monthsShort:["جانفي","فيفري","مارس","أفريل","ماي","جوان","جويليه","أوت","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],today:"هذا اليوم",rtl:!0,monthsTitle:"أشهر",clear:"إزالة",format:"yyyy/mm/dd",weekStart:0}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.ca={days:["Diumenge","Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte"],daysShort:["Diu","Dil","Dmt","Dmc","Dij","Div","Dis"],daysMin:["dg","dl","dt","dc","dj","dv","ds"],months:["Gener","Febrer","Març","Abril","Maig","Juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],monthsShort:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],today:"Avui",monthsTitle:"Mesos",clear:"Esborrar",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates.ca={days:["diumenge","dilluns","dimarts","dimecres","dijous","divendres","dissabte"],daysShort:["dg.","dl.","dt.","dc.","dj.","dv.","ds."],daysMin:["dg","dl","dt","dc","dj","dv","ds"],months:["gener","febrer","març","abril","maig","juny","juliol","agost","setembre","octubre","novembre","desembre"],monthsShort:["gen.","febr.","març","abr.","maig","juny","jul.","ag.","set.","oct.","nov.","des."],today:"Avui",monthsTitle:"Mesos",clear:"Esborra",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],daysShort:["Son","Mon","Die","Mit","Don","Fre","Sam"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",monthsTitle:"Monate",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates.de={days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],daysShort:["So","Mo","Di","Mi","Do","Fr","Sa"],daysMin:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],monthsShort:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],today:"Heute",monthsTitle:"Monate",clear:"Löschen",weekStart:1,format:"dd.mm.yyyy"}}(jQuery);
|
||||
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.en-US.min.js
vendored
Normal file
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.en-US.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(a){a.fn.datepicker.dates["en-US"]={days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today",monthsTitle:"Months",clear:"Clear",weekStart:0,format:"m/d/yyyy"}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.fi={days:["sunnuntai","maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai"],daysShort:["sun","maa","tii","kes","tor","per","lau"],daysMin:["su","ma","ti","ke","to","pe","la"],months:["tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu"],monthsShort:["tam","hel","maa","huh","tou","kes","hei","elo","syy","lok","mar","jou"],today:"tänään",clear:"Tyhjennä",weekStart:1,format:"d.m.yyyy"}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates.fi={days:["sunnuntai","maanantai","tiistai","keskiviikko","torstai","perjantai","lauantai"],daysShort:["sun","maa","tii","kes","tor","per","lau"],daysMin:["su","ma","ti","ke","to","pe","la"],months:["tammikuu","helmikuu","maaliskuu","huhtikuu","toukokuu","kesäkuu","heinäkuu","elokuu","syyskuu","lokakuu","marraskuu","joulukuu"],monthsShort:["tammi","helmi","maalis","huhti","touko","kesä","heinä","elo","syys","loka","marras","joulu"],today:"tänään",clear:"Tyhjennä",weekStart:1,format:"d.m.yyyy"}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.id={days:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],daysShort:["Mgu","Sen","Sel","Rab","Kam","Jum","Sab"],daysMin:["Mg","Sn","Sl","Ra","Ka","Ju","Sa"],months:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ags","Sep","Okt","Nov","Des"],today:"Hari Ini",clear:"Kosongkan"}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates.id={days:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],daysShort:["Min","Sen","Sel","Rab","Kam","Jum","Sab"],daysMin:["Mg","Sn","Sl","Rb","Km","Jm","Sb"],months:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],monthsShort:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Agt","Sep","Okt","Nov","Des"],today:"Hari Ini",monthsTitle:"Bulan",clear:"Kosongkan",weekStart:0,format:"dd-mm-yyyy"}}(jQuery);
|
||||
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.mr.min.js
vendored
Normal file
1
inst/www/shared/datepicker/js/locales/bootstrap-datepicker.mr.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.mar={days:["रविवार","सोमवार","मंगळवार","बुधवार","गुरुवार","शुक्रवार","शनिवार"],daysShort:["रवि","सोम","मंगळ","बुध","गुरु","शुक्र","शनि"],daysMin:["र","सो","मं","बु","गु","शु","श"],months:["जानेवारी","फेब्रुवारी","मार्च","एप्रिल","मे","जून","जुलै","ऑगस्ट","सप्टेंबर","ऑक्टोबर","नोव्हेंबर","डिसेंबर"],monthsShort:["जाने.","फेब्रु.","मार्च","एप्रिल","मे","जून","जुलै","ऑगस्ट","सप्टें.","ऑक्टो.","नोव्हें.","डिसें."],today:"आज",monthsTitle:"महीने",clear:"हटवा",weekStart:1,format:"dd / mm / yyyy"}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates.uk={days:["Неділя","Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота"],daysShort:["Нед","Пнд","Втр","Срд","Чтв","Птн","Суб"],daysMin:["Нд","Пн","Вт","Ср","Чт","Пт","Сб"],months:["Cічень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","Вересень","Жовтень","Листопад","Грудень"],monthsShort:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","Лис","Гру"],today:"Сьогодні",clear:"Очистити",format:"dd.mm.yyyy",weekStart:1}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates.uk={days:["Неділя","Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота"],daysShort:["Нед","Пнд","Втр","Срд","Чтв","Птн","Суб"],daysMin:["Нд","Пн","Вт","Ср","Чт","Пт","Сб"],months:["Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","Вересень","Жовтень","Листопад","Грудень"],monthsShort:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","Лис","Гру"],today:"Сьогодні",clear:"Очистити",format:"dd.mm.yyyy",weekStart:1}}(jQuery);
|
||||
@@ -1 +1 @@
|
||||
!function(a){a.fn.datepicker.dates["zh-TW"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["週日","週一","週二","週三","週四","週五","週六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今天",format:"yyyy年mm月dd日",weekStart:1,clear:"清除"}}(jQuery);
|
||||
!function(a){a.fn.datepicker.dates["zh-TW"]={days:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],daysShort:["週日","週一","週二","週三","週四","週五","週六"],daysMin:["日","一","二","三","四","五","六"],months:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthsShort:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],today:"今天",monthsTitle:"月份",format:"yyyy/mm/dd",weekStart:0,titleFormat:"yyyy年mm月",clear:"清除"}}(jQuery);
|
||||
File diff suppressed because one or more lines are too long
@@ -31,7 +31,6 @@ Michael Bensoussan <mickey@seesmic.com>
|
||||
Louis-Rémi Babé <lrbabe@gmail.com>
|
||||
Robert Katić <robert.katic@gmail.com>
|
||||
Damian Janowski <damian.janowski@gmail.com>
|
||||
Anton Kovalyov <anton@kovalyov.net>
|
||||
Dušan B. Jovanovic <dbjdbj@gmail.com>
|
||||
Earle Castledine <mrspeaker@gmail.com>
|
||||
Rich Dougherty <rich@rd.gen.nz>
|
||||
@@ -69,11 +68,11 @@ temp01 <temp01irc@gmail.com>
|
||||
Colin Snover <github.com@zetafleet.com>
|
||||
Jared Grippe <jared@deadlyicon.com>
|
||||
Ryan W Tenney <ryan@10e.us>
|
||||
Alex Sexton <AlexSexton@gmail.com>
|
||||
Pinhook <contact@pinhooklabs.com>
|
||||
Ron Otten <r.j.g.otten@gmail.com>
|
||||
Jephte Clain <Jephte.Clain@univ-reunion.fr>
|
||||
Anton Matzneller <obhvsbypqghgc@gmail.com>
|
||||
Alex Sexton <AlexSexton@gmail.com>
|
||||
Dan Heberden <danheberden@gmail.com>
|
||||
Henri Wiechers <hwiechers@gmail.com>
|
||||
Russell Holbrook <russell.holbrook@patch.com>
|
||||
@@ -87,9 +86,10 @@ Sylvester Keil <sylvester@keil.or.at>
|
||||
Brandon Sterne <bsterne@mozilla.com>
|
||||
Mathias Bynens <mathias@qiwi.be>
|
||||
Lee Carpenter <elcarpie@gmail.com>
|
||||
Timmy Willison <4timmywil@gmail.com>
|
||||
Timmy Willison <timmywil@users.noreply.github.com>
|
||||
Corey Frang <gnarf37@gmail.com>
|
||||
Digitalxero <digitalxero>
|
||||
Anton Kovalyov <anton@kovalyov.net>
|
||||
David Murdoch <david@davidmurdoch.com>
|
||||
Josh Varner <josh.varner@gmail.com>
|
||||
Charles McNulty <cmcnulty@kznf.com>
|
||||
@@ -111,7 +111,7 @@ Mike Sherov <mike.sherov@gmail.com>
|
||||
Greg Hazel <ghazel@gmail.com>
|
||||
Schalk Neethling <schalk@ossreleasefeed.com>
|
||||
Denis Knauf <Denis.Knauf@gmail.com>
|
||||
Timo Tijhof <krinklemail@gmail.com>
|
||||
Timo Tijhof <krinkle@fastmail.com>
|
||||
Steen Nielsen <swinedk@gmail.com>
|
||||
Anton Ryzhov <anton@ryzhov.me>
|
||||
Shi Chuan <shichuanr@gmail.com>
|
||||
@@ -151,7 +151,6 @@ Chris Faulkner <thefaulkner@gmail.com>
|
||||
Marcel Greter <marcel.greter@ocbnet.ch>
|
||||
Elijah Manor <elijah.manor@gmail.com>
|
||||
Daniel Chatfield <chatfielddaniel@gmail.com>
|
||||
Daniel Gálvez <dgalvez@editablething.com>
|
||||
Nikita Govorov <nikita.govorov@gmail.com>
|
||||
Wesley Walser <waw325@gmail.com>
|
||||
Mike Pennisi <mike@mikepennisi.com>
|
||||
@@ -162,9 +161,7 @@ Dave Riddle <david@joyvuu.com>
|
||||
Callum Macrae <callum@lynxphp.com>
|
||||
Jonathan Sampson <jjdsampson@gmail.com>
|
||||
Benjamin Truyman <bentruyman@gmail.com>
|
||||
Jay Merrifield <fracmak@gmail.com>
|
||||
James Huston <james@jameshuston.net>
|
||||
Sai Lung Wong <sai.wong@huffingtonpost.com>
|
||||
Erick Ruiz de Chávez <erickrdch@gmail.com>
|
||||
David Bonner <dbonner@cogolabs.com>
|
||||
Allen J Schmidt Jr <cobrasoft@gmail.com>
|
||||
@@ -174,8 +171,11 @@ Ismail Khair <ismail.khair@gmail.com>
|
||||
Carl Danley <carldanley@gmail.com>
|
||||
Mike Petrovich <michael.c.petrovich@gmail.com>
|
||||
Greg Lavallee <greglavallee@wapolabs.com>
|
||||
Daniel Gálvez <dgalvez@editablething.com>
|
||||
Sai Lung Wong <sai.wong@huffingtonpost.com>
|
||||
Tom H Fuertes <TomFuertes@gmail.com>
|
||||
Roland Eckl <eckl.roland@googlemail.com>
|
||||
Jay Merrifield <fracmak@gmail.com>
|
||||
Yiming He <yiminghe@gmail.com>
|
||||
David Fox <dfoxinator@gmail.com>
|
||||
Bennett Sorbo <bsorbo@gmail.com>
|
||||
@@ -192,9 +192,9 @@ Diego Tres <diegotres@gmail.com>
|
||||
Jean Boussier <jean.boussier@gmail.com>
|
||||
Andrew Plummer <plummer.andrew@gmail.com>
|
||||
Mark Raddatz <mraddatz@gmail.com>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Isaac Z. Schlueter <i@izs.me>
|
||||
Karl Sieburg <ksieburg@yahoo.com>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Nguyen Phuc Lam <ruado1987@gmail.com>
|
||||
Dmitry Gusev <dmitry.gusev@gmail.com>
|
||||
Steven Benner <admin@stevenbenner.com>
|
||||
@@ -202,7 +202,6 @@ Li Xudong <istonelee@gmail.com>
|
||||
Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
|
||||
Renato Oliveira dos Santos <ros3@cin.ufpe.br>
|
||||
Frederic Junod <frederic.junod@camptocamp.com>
|
||||
Tom H Fuertes <tomfuertes@gmail.com>
|
||||
Mitch Foley <mitch@thefoley.net>
|
||||
ros3cin <ros3@cin.ufpe.br>
|
||||
Kyle Robinson Young <kyle@dontkry.com>
|
||||
@@ -250,7 +249,6 @@ Dan Hart <danhart@notonthehighstreet.com>
|
||||
Nazar Mokrynskyi <nazar@mokrynskyi.com>
|
||||
Benjamin Tan <demoneaux@gmail.com>
|
||||
Amit Merchant <bullredeyes@gmail.com>
|
||||
Jason Bedard <jason+github@jbedard.ca>
|
||||
Veaceslav Grimalschi <grimalschi@yandex.ru>
|
||||
Richard McDaniel <rm0026@uah.edu>
|
||||
Arthur Verschaeve <contact@arthurverschaeve.be>
|
||||
@@ -273,12 +271,12 @@ Jon Hester <jon.d.hester@gmail.com>
|
||||
Colin Frick <colin@bash.li>
|
||||
Winston Howes <winstonhowes@gmail.com>
|
||||
Alexander O'Mara <me@alexomara.com>
|
||||
Chris Rebert <github@rebertia.com>
|
||||
Bastian Buchholz <buchholz.bastian@googlemail.com>
|
||||
Mu Haibao <mhbseal@163.com>
|
||||
Calvin Metcalf <calvin.metcalf@gmail.com>
|
||||
Arthur Stolyar <nekr.fabula@gmail.com>
|
||||
Gabriel Schulhof <gabriel.schulhof@intel.com>
|
||||
Chris Rebert <github@rebertia.com>
|
||||
Gilad Peleg <giladp007@gmail.com>
|
||||
Julian Alexander Murillo <julian.alexander.murillo@gmail.com>
|
||||
Kevin Kirsche <Kev.Kirsche+GitHub@gmail.com>
|
||||
@@ -297,15 +295,14 @@ Christian Grete <webmaster@christiangrete.com>
|
||||
Tom von Clef <thomas.vonclef@gmail.com>
|
||||
Liza Ramo <liza.h.ramo@gmail.com>
|
||||
Joelle Fleurantin <joasqueeniebee@gmail.com>
|
||||
Steve Mao <maochenyan@gmail.com>
|
||||
Jon Dufresne <jon.dufresne@gmail.com>
|
||||
Jae Sung Park <alberto.park@gmail.com>
|
||||
Josh Soref <apache@soref.com>
|
||||
Saptak Sengupta <saptak013@gmail.com>
|
||||
Henry Wong <henryw4k@gmail.com>
|
||||
Jun Sun <klsforever@gmail.com>
|
||||
Martijn W. van der Lee <martijn@vanderlee.com>
|
||||
Devin Wilson <dwilson6.github@gmail.com>
|
||||
Steve Mao <maochenyan@gmail.com>
|
||||
Damian Senn <jquery@topaxi.codes>
|
||||
Zack Hall <zackhall@outlook.com>
|
||||
Vitaliy Terziev <vitaliyterziev@gmail.com>
|
||||
@@ -336,6 +333,7 @@ Jordan Beland <jordan.beland@gmail.com>
|
||||
Henry Zhu <hi@henryzoo.com>
|
||||
Nilton Cesar <niltoncms@gmail.com>
|
||||
basil.belokon <basil.belokon@gmail.com>
|
||||
Saptak Sengupta <saptak013@gmail.com>
|
||||
Andrey Meshkov <ay.meshkov@gmail.com>
|
||||
tmybr11 <tomas.perone@gmail.com>
|
||||
Luis Emilio Velasco Sanchez <emibloque@gmail.com>
|
||||
@@ -344,14 +342,34 @@ Bert Zhang <enbo@users.noreply.github.com>
|
||||
Sébastien Règne <regseb@users.noreply.github.com>
|
||||
wartmanm <3869625+wartmanm@users.noreply.github.com>
|
||||
Siddharth Dungarwal <sd5869@gmail.com>
|
||||
abnud1 <ahmad13932013@hotmail.com>
|
||||
Andrei Fangli <andrei_fangli@outlook.com>
|
||||
Marja Hölttä <marja.holtta@gmail.com>
|
||||
abnud1 <ahmad13932013@hotmail.com>
|
||||
buddh4 <mail@jharrer.de>
|
||||
Hoang <dangkyokhoang@gmail.com>
|
||||
Sean Robinson <sean.robinson@scottsdalecc.edu>
|
||||
Wonseop Kim <wonseop.kim@samsung.com>
|
||||
Pat O'Callaghan <patocallaghan@gmail.com>
|
||||
JuanMa Ruiz <ruizjuanma@gmail.com>
|
||||
Ahmed.S.ElAfifi <ahmed.s.elafifi@gmail.com>
|
||||
Sean Robinson <sean.robinson@scottsdalecc.edu>
|
||||
Christian Oliff <christianoliff@pm.me>
|
||||
Christian Wenz <christian@wenz.org>
|
||||
Jonathan <vanillajonathan@users.noreply.github.com>
|
||||
Ed Sanders <ejsanders@gmail.com>
|
||||
Pierre Grimaud <grimaud.pierre@gmail.com>
|
||||
Beatriz Rezener <beatrizrezener@users.noreply.github.com>
|
||||
Necmettin Karakaya <necmettin.karakaya@gmail.com>
|
||||
Wonhyoung Park <wh05.park@samsung.com>
|
||||
Dallas Fraser <dallas.fraser.waterloo@gmail.com>
|
||||
高灰 <www@zeroplace.cn>
|
||||
fecore1 <89127124+fecore1@users.noreply.github.com>
|
||||
ygj6 <7699524+ygj6@users.noreply.github.com>
|
||||
Bruno PIERRE <brunopierre4@yahoo.fr>
|
||||
Simon Legner <Simon.Legner@gmail.com>
|
||||
Baoshuo Ren <i@baoshuo.ren>
|
||||
Anders Kaseorg <andersk@mit.edu>
|
||||
Alex <aleksandrosansan@gmail.com>
|
||||
Gabriela Gutierrez <gabigutierrez@google.com>
|
||||
Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com>
|
||||
J.Son <x@x-wx.com>
|
||||
Liam James <liam@minimaximize.com>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.12.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{document.documentElement.classList.add("autoreload-enabled");var c=window.location.protocol==="https:"?"wss:":"ws:",s=window.location.pathname.replace(/\/?$/,"/")+"autoreload/",i=`${c}//${window.location.host}${s}`,l=document.currentScript?.dataset?.wsUrl||i;async function u(o){let e=new WebSocket(o),n=!1;return new Promise((a,r)=>{e.onopen=()=>{n=!0},e.onerror=t=>{r(t)},e.onclose=()=>{n?a(!1):r(new Error("WebSocket connection failed"))},e.onmessage=function(t){t.data==="autoreload"&&a(!0)}})}async function d(o){return new Promise(e=>setTimeout(e,o))}async function w(){for(;;){try{if(await u(l)){window.location.reload();return}}catch{console.debug("Giving up on autoreload");return}await d(1e3)}}w().catch(o=>{console.error(o)});})();
|
||||
//# sourceMappingURL=shiny-autoreload.js.map
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"version": 3,
|
||||
"sources": ["../../../srcts/extras/shiny-autoreload.ts"],
|
||||
"sourcesContent": ["/* eslint-disable unicorn/filename-case */\n\ndocument.documentElement.classList.add(\"autoreload-enabled\");\n\nconst protocol = window.location.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n// Add trailing slash to path, if necessary, before appending \"autoreload\"\nconst defaultPath =\n window.location.pathname.replace(/\\/?$/, \"/\") + \"autoreload/\";\nconst defaultUrl = `${protocol}//${window.location.host}${defaultPath}`;\n\n// By default, use the defaultUrl. But if there's a data-ws-url attribute on our\n// <script> tag, use that instead.\nconst wsUrl = document.currentScript?.dataset?.wsUrl || defaultUrl;\n\n/**\n * Connects to an autoreload URL and waits for the server to tell us what to do.\n *\n * @param url The ws:// or wss:// URL to connect to.\n * @returns true if the server requests a reload, or false if the connection was\n * successfully established but then closed without the server requesting a\n * reload\n * @throws A nondescript error if the connection fails to be established.\n */\nasync function autoreload(url: string): Promise<boolean> {\n const ws = new WebSocket(url);\n\n let success = false;\n\n return new Promise((resolve, reject) => {\n ws.onopen = () => {\n success = true;\n };\n\n ws.onerror = (err) => {\n reject(err);\n };\n\n ws.onclose = () => {\n if (!success) {\n reject(new Error(\"WebSocket connection failed\"));\n } else {\n resolve(false);\n }\n };\n\n ws.onmessage = function (event) {\n if (event.data === \"autoreload\") {\n resolve(true);\n }\n };\n });\n}\n\nasync function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function initialize() {\n while (true) {\n try {\n if (await autoreload(wsUrl)) {\n window.location.reload();\n return;\n }\n } catch (err) {\n // It's possible for the autoreload() call to throw. If it does, that\n // means we tried but failed to connect to the autoreload socket. This\n // probably means that the entire `shiny run --reload` process was\n // restarted. As of today, the autoreload websocket port number is\n // randomly chosen for each `shiny run --reload` process, so it's\n // impossible for us to recover.\n console.debug(\"Giving up on autoreload\");\n return;\n }\n // If we get here, the connection to the autoreload server was\n // successful but then got broken. Wait for a second, and then\n // try to re-establish the connection.\n await sleep(1000);\n }\n}\n\ninitialize().catch((err) => {\n console.error(err);\n});\n\nexport {};\n"],
|
||||
"mappings": ";mBAEA,SAAS,gBAAgB,UAAU,IAAI,oBAAoB,EAE3D,IAAMA,EAAW,OAAO,SAAS,WAAa,SAAW,OAAS,MAE5DC,EACJ,OAAO,SAAS,SAAS,QAAQ,OAAQ,GAAG,EAAI,cAC5CC,EAAa,GAAGF,MAAa,OAAO,SAAS,OAAOC,IAIpDE,EAAQ,SAAS,eAAe,SAAS,OAASD,EAWxD,eAAeE,EAAWC,EAA+B,CACvD,IAAMC,EAAK,IAAI,UAAUD,CAAG,EAExBE,EAAU,GAEd,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtCH,EAAG,OAAS,IAAM,CAChBC,EAAU,EACZ,EAEAD,EAAG,QAAWI,GAAQ,CACpBD,EAAOC,CAAG,CACZ,EAEAJ,EAAG,QAAU,IAAM,CACZC,EAGHC,EAAQ,EAAK,EAFbC,EAAO,IAAI,MAAM,6BAA6B,CAAC,CAInD,EAEAH,EAAG,UAAY,SAAUK,EAAO,CAC1BA,EAAM,OAAS,cACjBH,EAAQ,EAAI,CAEhB,CACF,CAAC,CACH,CAEA,eAAeI,EAAMC,EAAY,CAC/B,OAAO,IAAI,QAASL,GAAY,WAAWA,EAASK,CAAE,CAAC,CACzD,CAEA,eAAeC,GAAa,CAC1B,OAAa,CACX,GAAI,CACF,GAAI,MAAMV,EAAWD,CAAK,EAAG,CAC3B,OAAO,SAAS,OAAO,EACvB,MACF,CACF,MAAE,CAOA,QAAQ,MAAM,yBAAyB,EACvC,MACF,CAIA,MAAMS,EAAM,GAAI,CAClB,CACF,CAEAE,EAAW,EAAE,MAAOJ,GAAQ,CAC1B,QAAQ,MAAMA,CAAG,CACnB,CAAC",
|
||||
"mappings": ";mBAEA,SAAS,gBAAgB,UAAU,IAAI,oBAAoB,EAE3D,IAAMA,EAAW,OAAO,SAAS,WAAa,SAAW,OAAS,MAE5DC,EACJ,OAAO,SAAS,SAAS,QAAQ,OAAQ,GAAG,EAAI,cAC5CC,EAAa,GAAGF,CAAQ,KAAK,OAAO,SAAS,IAAI,GAAGC,CAAW,GAI/DE,EAAQ,SAAS,eAAe,SAAS,OAASD,EAWxD,eAAeE,EAAWC,EAA+B,CACvD,IAAMC,EAAK,IAAI,UAAUD,CAAG,EAExBE,EAAU,GAEd,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtCH,EAAG,OAAS,IAAM,CAChBC,EAAU,EACZ,EAEAD,EAAG,QAAWI,GAAQ,CACpBD,EAAOC,CAAG,CACZ,EAEAJ,EAAG,QAAU,IAAM,CACZC,EAGHC,EAAQ,EAAK,EAFbC,EAAO,IAAI,MAAM,6BAA6B,CAAC,CAInD,EAEAH,EAAG,UAAY,SAAUK,EAAO,CAC1BA,EAAM,OAAS,cACjBH,EAAQ,EAAI,CAEhB,CACF,CAAC,CACH,CAEA,eAAeI,EAAMC,EAAY,CAC/B,OAAO,IAAI,QAASL,GAAY,WAAWA,EAASK,CAAE,CAAC,CACzD,CAEA,eAAeC,GAAa,CAC1B,OAAa,CACX,GAAI,CACF,GAAI,MAAMV,EAAWD,CAAK,EAAG,CAC3B,OAAO,SAAS,OAAO,EACvB,MACF,CACF,MAAc,CAOZ,QAAQ,MAAM,yBAAyB,EACvC,MACF,CAIA,MAAMS,EAAM,GAAI,CAClB,CACF,CAEAE,EAAW,EAAE,MAAOJ,GAAQ,CAC1B,QAAQ,MAAMA,CAAG,CACnB,CAAC",
|
||||
"names": ["protocol", "defaultPath", "defaultUrl", "wsUrl", "autoreload", "url", "ws", "success", "resolve", "reject", "err", "event", "sleep", "ms", "initialize"]
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.12.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
#showcase-well{border-radius:0}.shiny-code{background-color:#fff;margin-bottom:0}.shiny-code code{font-family:Menlo,Consolas,Courier New,monospace}.shiny-code-container{margin-top:20px;clear:both}.shiny-code-container h3{display:inline;margin-right:15px}.showcase-header{font-size:16px;font-weight:400}.showcase-code-link{text-align:right;padding:15px}#showcase-app-container{vertical-align:top}#showcase-code-tabs{margin-right:15px}#showcase-code-tabs pre{border:none;line-height:1em}#showcase-code-tabs .nav,#showcase-code-tabs ul{margin-bottom:0}#showcase-code-tabs .tab-content{border-style:solid;border-color:#e5e5e5;border-width:0px 1px 1px 1px;overflow:auto;border-bottom-right-radius:4px;border-bottom-left-radius:4px}#showcase-app-code{width:100%}#showcase-code-position-toggle{float:right}#showcase-sxs-code{padding-top:20px;vertical-align:top}.showcase-code-license{display:block;text-align:right}#showcase-code-content pre{background-color:#fff}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
"use strict";(()=>{var f=400;function c(e,i){let t=0;if(e.nodeType===3){let n=e.nodeValue.replace(/\n/g,"").length;if(n>=i)return{element:e,offset:i};t+=n}else if(e.nodeType===1&&e.firstChild){let n=c(e.firstChild,i);if(n.element!==null)return n;t+=n.offset}return e.nextSibling?c(e.nextSibling,i-t):{element:null,offset:t}}function r(e,i,t){let n=0;for(let s=0;s<e.childNodes.length;s++){let l=e.childNodes[s];if(l.nodeType===3){let o=/\n/g,d;for(;(d=o.exec(l.nodeValue))!==null;)if(n++,n===i)return c(l,d.index+t+1)}else if(l.nodeType===1){let o=r(l,i-n,t);if(o.element!==null)return o;n+=o.offset}}return{element:null,offset:n}}function w(e,i){if(!document.createRange)return;let t=document.getElementById("srcref_"+e);if(!t){t=document.createElement("span"),t.id="srcref_"+e;let n=e,s=document.getElementById(i.replace(/\./g,"_")+"_code");if(!s)return;let l=r(s,n[0],n[4]),o=r(s,n[2],n[5]);if(l.element===null||o.element===null)return;let d=document.createRange();l.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setStartBefore(l.element.parentNode):d.setStart(l.element,l.offset),o.element.parentNode.nodeName==="SPAN"&&l.element!==o.element?d.setEndAfter(o.element.parentNode):d.setEnd(o.element,o.offset),d.surroundContents(t)}$(t).stop(!0,!0).effect("highlight",null,1600)}Shiny&&Shiny.addCustomMessageHandler("showcase-src",function(e){e.srcref&&e.srcfile&&w(e.srcref,e.srcfile)});var a=!1,m=function(e,i){let t=i?f:1,n=e?document.getElementById("showcase-sxs-code"):document.getElementById("showcase-code-inline"),s=e?document.getElementById("showcase-code-inline"):document.getElementById("showcase-sxs-code");if(document.getElementById("showcase-app-metadata")===null){let o=$("#showcase-well");e?o.fadeOut(t):o.fadeIn(t)}$(n).hide(),$(s).fadeOut(t,function(){let o=document.getElementById("showcase-code-tabs");s.removeChild(o),n.appendChild(o),e?h():document.getElementById("showcase-code-content").removeAttribute("style"),$(n).fadeIn(t),e||(document.getElementById("showcase-app-container").removeAttribute("style"),i&&$(document.body).animate({scrollTop:$(n).offset().top}));let d=document.getElementById("readme-md");d!==null&&(d.parentElement.removeChild(d),e?(s.appendChild(d),$(s).fadeIn(t)):document.getElementById("showcase-app-metadata").appendChild(d)),document.getElementById("showcase-code-position-toggle").innerHTML=e?'<i class="fa fa-level-down"></i> show below':'<i class="fa fa-level-up"></i> show with app'}),e&&$(document.body).animate({scrollTop:0},t),a=e,u(e&&i),$(window).trigger("resize")};function u(e){let t=960,n=1,s=document.getElementById("showcase-app-code").offsetWidth;s/2>960?t=s/2:s*.66>960?t=960:(t=s*.66,n=t/960);let l=document.getElementById("showcase-app-container");$(l).animate({width:t+"px",zoom:n*100+"%"},e?f:0)}var g=function(){m(!a,!0)},p=function(){document.body.offsetWidth>1350&&m(!0,!1)};function h(){document.getElementById("showcase-code-content").style.height=$(window).height()+"px"}function y(){let e=document.getElementById("showcase-markdown-content");if(e!==null){let i=e.innerText||e.innerHTML,t=window.Showdown.converter;document.getElementById("readme-md").innerHTML=new t().makeHtml(i)}}$(window).resize(function(){a&&(u(!1),h())});window.toggleCodePosition=g;$(window).on("load",p);$(window).on("load",y);window.hljs&&window.hljs.initHighlightingOnLoad();})();
|
||||
/*! shiny 1.12.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{var m=400;function c(e,s){let t=0;if(e.nodeType===3){let n=e.nodeValue?.replace(/\n/g,"").length??0;if(n>=s)return{element:e,offset:s};t+=n}else if(e.nodeType===1&&e.firstChild){let n=c(e.firstChild,s);if(n.element!==null)return n;t+=n.offset}return e.nextSibling?c(e.nextSibling,s-t):{element:null,offset:t}}function a(e,s,t){let n=0;for(let l=0;l<e.childNodes.length;l++){let i=e.childNodes[l];if(i.nodeType===3){let o=/\n/g,d;for(;(d=o.exec(i.nodeValue))!==null;)if(n++,n===s)return c(i,d.index+t+1)}else if(i.nodeType===1){let o=a(i,s-n,t);if(o.element!==null)return o;n+=o.offset}}return{element:null,offset:n}}function g(e,s){if(!document.createRange)return;let t=document.getElementById("srcref_"+e);if(!t){t=document.createElement("span"),t.id="srcref_"+e;let n=e,l=document.getElementById(s.replace(/\./g,"_")+"_code");if(!l)return;let i=a(l,n[0],n[4]),o=a(l,n[2],n[5]);if(i.element===null||o.element===null)return;let d=document.createRange();i.element.parentNode?.nodeName==="SPAN"&&i.element!==o.element?d.setStartBefore(i.element.parentNode):d.setStart(i.element,i.offset),o.element.parentNode?.nodeName==="SPAN"&&i.element!==o.element?d.setEndAfter(o.element.parentNode):d.setEnd(o.element,o.offset),d.surroundContents(t)}$(t).stop(!0,!0).effect("highlight",null,1600)}window.Shiny&&window.Shiny.addCustomMessageHandler("showcase-src",function(e){e.srcref&&e.srcfile&&g(e.srcref,e.srcfile)});var r=!1,u=function(e,s){let t=s?m:1,n=e?document.getElementById("showcase-sxs-code"):document.getElementById("showcase-code-inline"),l=e?document.getElementById("showcase-code-inline"):document.getElementById("showcase-sxs-code");if(document.getElementById("showcase-app-metadata")===null){let o=$("#showcase-well");e?o.fadeOut(t):o.fadeIn(t)}if(n===null||l===null){console.warn("Could not find the host elements for the code tabs. This is likely a bug in the showcase app.");return}$(n).hide(),$(l).fadeOut(t,function(){let o=document.getElementById("showcase-code-tabs");if(o===null){console.warn("Could not find the code tabs element. This is likely a bug in the showcase app.");return}if(l.removeChild(o),n.appendChild(o),e?p():document.getElementById("showcase-code-content")?.removeAttribute("style"),$(n).fadeIn(t),!e&&(document.getElementById("showcase-app-container")?.removeAttribute("style"),s)){let f=$(n).offset()?.top;f!==void 0&&$(document.body).animate({scrollTop:f})}let d=document.getElementById("readme-md");d!==null&&(d.parentElement?.removeChild(d),e?(l.appendChild(d),$(l).fadeIn(t)):document.getElementById("showcase-app-metadata")?.appendChild(d)),document.getElementById("showcase-code-position-toggle").innerHTML=e?'<i class="fa fa-level-down"></i> show below':'<i class="fa fa-level-up"></i> show with app'}),e&&$(document.body).animate({scrollTop:0},t),r=e,h(e&&s),$(window).trigger("resize")};function h(e){let t=960,n=1,l=document.getElementById("showcase-app-code").offsetWidth;l/2>960?t=l/2:l*.66>960?t=960:(t=l*.66,n=t/960),$("#showcase-app-container").animate({width:t+"px",zoom:n*100+"%"},e?m:0)}var w=function(){u(!r,!0)},y=function(){document.body.offsetWidth>1350&&u(!0,!1)};function p(){document.getElementById("showcase-code-content").style.height=$(window).height()+"px"}function E(){let e=document.getElementById("showcase-markdown-content");if(e!==null){let s=document.getElementById("readme-md");if(s!==null){let t=e.content.cloneNode(!0);s.appendChild(t)}}}$(window).resize(function(){r&&(h(!1),p())});window.toggleCodePosition=w;$(window).on("load",y);$(window).on("load",E);window.hljs&&window.hljs.initHighlightingOnLoad();})();
|
||||
//# sourceMappingURL=shiny-showcase.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
|
||||
/*! shiny 1.10.0.9001 | (c) 2012-2025 Posit Software, PBC. | License: GPL-3 | file LICENSE */
|
||||
/*! shiny 1.12.1.9000 | (c) 2012-2025 Posit Software, PBC. | License: MIT + file LICENSE */
|
||||
"use strict";(()=>{var t=eval;window.addEventListener("message",function(a){let e=a.data;e.code&&t(e.code)});})();
|
||||
//# sourceMappingURL=shiny-testmode.js.map
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
3
inst/www/shared/shiny.min.css
vendored
3
inst/www/shared/shiny.min.css
vendored
File diff suppressed because one or more lines are too long
110
inst/www/shared/shiny.min.js
vendored
110
inst/www/shared/shiny.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -463,6 +463,13 @@ textarea.textarea-autoresize.form-control {
|
||||
}
|
||||
}
|
||||
|
||||
// Add spacing between icon and label for actionButton()
|
||||
.action-button:not(.action-link) {
|
||||
.action-icon + .action-label {
|
||||
padding-left: 0.5ch;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overrides bootstrap-datepicker3.css styling for invalid date ranges.
|
||||
See https://github.com/rstudio/shiny/issues/2042 for details. */
|
||||
.datepicker table tbody tr td.disabled,
|
||||
@@ -473,6 +480,13 @@ textarea.textarea-autoresize.form-control {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// Selectize's remove_button plugin has a bug that can lead to multiple remove
|
||||
// buttons being displayed when options get updated. This prevents that from
|
||||
// happening (see #4274 for reprex)
|
||||
.shiny-input-select .selectize-input > .item > .remove:not(:first-child) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden tabPanels */
|
||||
.nav-hidden {
|
||||
/* override anything bootstrap sets for `.nav` */
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,34 +0,0 @@
|
||||
Copyright (c) 2007, John Fraser
|
||||
<http://www.attacklab.net/>
|
||||
All rights reserved.
|
||||
|
||||
Original Markdown copyright (c) 2004, John Gruber
|
||||
<http://daringfireball.net/>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name "Markdown" nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as
|
||||
is" and any express or implied warranties, including, but not limited
|
||||
to, the implied warranties of merchantability and fitness for a
|
||||
particular purpose are disclaimed. In no event shall the copyright owner
|
||||
or contributors be liable for any direct, indirect, incidental, special,
|
||||
exemplary, or consequential damages (including, but not limited to,
|
||||
procurement of substitute goods or services; loss of use, data, or
|
||||
profits; or business interruption) however caused and on any theory of
|
||||
liability, whether in contract, strict liability, or tort (including
|
||||
negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
testEnvironment: "jsdom",
|
||||
};
|
||||
@@ -45,8 +45,31 @@ is, a function that quickly returns a promise) and allows even that very
|
||||
session to immediately unblock and carry on with other user interactions.
|
||||
}
|
||||
|
||||
\section{OpenTelemetry Integration}{
|
||||
|
||||
|
||||
When an \code{ExtendedTask} is created, if OpenTelemetry tracing is enabled for
|
||||
\code{"reactivity"} (see \code{\link[=withOtelCollect]{withOtelCollect()}}), the \code{ExtendedTask} will record
|
||||
spans for each invocation of the task. The tracing level at \code{invoke()} time
|
||||
does not affect whether spans are recorded; only the tracing level when
|
||||
calling \code{ExtendedTask$new()} matters.
|
||||
|
||||
The OTel span will be named based on the label created from the variable the
|
||||
\code{ExtendedTask} is assigned to. If no label can be determined, the span will
|
||||
be named \verb{<anonymous>}. Similar to other Shiny OpenTelemetry spans, the span
|
||||
will also include source reference attributes and session ID attributes.
|
||||
|
||||
\if{html}{\out{<div class="sourceCode r">}}\preformatted{withOtelCollect("all", \{
|
||||
my_task <- ExtendedTask$new(function(...) \{ ... \})
|
||||
\})
|
||||
|
||||
# Span recorded for this invocation: ExtendedTask my_task
|
||||
my_task$invoke(...)
|
||||
}\if{html}{\out{</div>}}
|
||||
}
|
||||
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive() && rlang::is_installed("mirai")) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
\dontshow{if (rlang::is_interactive() && rlang::is_installed("mirai")) withAutoprint(\{ # examplesIf}
|
||||
library(shiny)
|
||||
library(bslib)
|
||||
library(mirai)
|
||||
|
||||
@@ -83,7 +83,7 @@ instead apply to the entire page, set \code{spinner_selector = 'html'} and
|
||||
\code{fade_selector = 'html'}.
|
||||
}
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
\dontshow{if (rlang::is_interactive()) withAutoprint(\{ # examplesIf}
|
||||
|
||||
library(bslib)
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ operations.
|
||||
}
|
||||
|
||||
\examples{
|
||||
\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
\dontshow{if (interactive()) withAutoprint(\{ # examplesIf}
|
||||
library(shiny)
|
||||
|
||||
ui <- fixedPage(
|
||||
|
||||
@@ -20,14 +20,15 @@ includes extensive annotated examples.
|
||||
\link{shiny-options} for documentation about global options.
|
||||
}
|
||||
\author{
|
||||
\strong{Maintainer}: Winston Chang \email{winston@posit.co} (\href{https://orcid.org/0000-0002-1576-2126}{ORCID})
|
||||
\strong{Maintainer}: Carson Sievert \email{carson@posit.co} (\href{https://orcid.org/0000-0002-4958-2844}{ORCID})
|
||||
|
||||
Authors:
|
||||
\itemize{
|
||||
\item Winston Chang \email{winston@posit.co} (\href{https://orcid.org/0000-0002-1576-2126}{ORCID})
|
||||
\item Joe Cheng \email{joe@posit.co}
|
||||
\item JJ Allaire \email{jj@posit.co}
|
||||
\item Carson Sievert \email{carson@posit.co} (\href{https://orcid.org/0000-0002-4958-2844}{ORCID})
|
||||
\item Barret Schloerke \email{barret@posit.co} (\href{https://orcid.org/0000-0001-9986-114X}{ORCID})
|
||||
\item Garrick Aden-Buie \email{garrick@adenbuie.com} (\href{https://orcid.org/0000-0002-7111-0077}{ORCID})
|
||||
\item Yihui Xie \email{yihui@posit.co}
|
||||
\item Jeff Allen
|
||||
\item Jonathan McPherson \email{jonathan@posit.co}
|
||||
@@ -37,7 +38,7 @@ Authors:
|
||||
|
||||
Other contributors:
|
||||
\itemize{
|
||||
\item Posit Software, PBC [copyright holder, funder]
|
||||
\item Posit Software, PBC (\href{https://ror.org/03wc8by49}{ROR}) [copyright holder, funder]
|
||||
\item jQuery Foundation (jQuery library and jQuery UI library) [copyright holder]
|
||||
\item jQuery contributors (jQuery library; authors listed in inst/www/shared/jquery-AUTHORS.txt) [contributor, copyright holder]
|
||||
\item jQuery UI contributors (jQuery UI library; authors listed in inst/www/shared/jqueryui/AUTHORS.txt) [contributor, copyright holder]
|
||||
@@ -58,8 +59,6 @@ Other contributors:
|
||||
\item Denis Ineshin (ion.rangeSlider library) [contributor, copyright holder]
|
||||
\item Sami Samhuri (Javascript strftime library) [contributor, copyright holder]
|
||||
\item SpryMedia Limited (DataTables library) [contributor, copyright holder]
|
||||
\item John Fraser (showdown.js library) [contributor, copyright holder]
|
||||
\item John Gruber (showdown.js library) [contributor, copyright holder]
|
||||
\item Ivan Sagalaev (highlight.js library) [contributor, copyright holder]
|
||||
\item R Core Team (tar implementation from R) [contributor, copyright holder]
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ be done 100\% correctly.}
|
||||
\code{\link[=runApp]{runApp()}} for more information.}
|
||||
\item{shiny.jquery.version (defaults to \code{3})}{The major version of jQuery to use.
|
||||
Currently only values of \code{3} or \code{1} are supported. If \code{1}, then jQuery 1.12.4 is used. If \code{3},
|
||||
then jQuery 3.6.0 is used.}
|
||||
then jQuery 3.7.1 is used.}
|
||||
\item{shiny.json.digits (defaults to \code{I(16)})}{Max number of digits to use when converting
|
||||
numbers to JSON format to send to the client web browser. Use \code{\link[=I]{I()}} to specify significant digits.
|
||||
Use \code{NA} for max precision.}
|
||||
@@ -130,6 +130,32 @@ ragg package. See \code{\link[=plotPNG]{plotPNG()}} for more information.}
|
||||
Cairo package. See \code{\link[=plotPNG]{plotPNG()}} for more information.}
|
||||
\item{shiny.devmode (defaults to \code{NULL})}{Option to enable Shiny Developer Mode. When set,
|
||||
different default \code{getOption(key)} values will be returned. See \code{\link[=devmode]{devmode()}} for more details.}
|
||||
\item{shiny.otel.collect (defaults to \code{Sys.getenv("SHINY_OTEL_COLLECT", "all")})}{Determines how Shiny will interact with OpenTelemetry.
|
||||
|
||||
Supported values:
|
||||
\itemize{
|
||||
\item \code{"none"} - No Shiny OpenTelemetry tracing.
|
||||
\item \code{"session"} - Adds session start/end spans.
|
||||
\item \code{"reactive_update"} - Spans for any synchronous/asynchronous reactive
|
||||
update. (Includes \code{"session"} features).
|
||||
\item \code{"reactivity"} - Spans for all reactive expressions and logs for setting
|
||||
reactive vals and values. (Includes \code{"reactive_update"} features). This
|
||||
option must be set when creating any reactive objects that should record
|
||||
OpenTelemetry spans / logs. See \code{\link[=withOtelCollect]{withOtelCollect()}} and
|
||||
\code{\link[=localOtelCollect]{localOtelCollect()}} for ways to set this option locally when creating
|
||||
your reactive expressions.
|
||||
\item \code{"all"} - All Shiny OpenTelemetry tracing. Currently equivalent to
|
||||
\code{"reactivity"}.
|
||||
}
|
||||
|
||||
This option is useful for debugging and profiling while in production. This
|
||||
option will only be useful if the \code{otelsdk} package is installed and
|
||||
\code{otel::is_tracing_enabled()} returns \code{TRUE}. Please have any OpenTelemetry
|
||||
environment variables set before loading any relevant R packages.
|
||||
|
||||
To set this option locally within a specific part of your Shiny
|
||||
application, see \code{\link[=withOtelCollect]{withOtelCollect()}} and \code{\link[=localOtelCollect]{localOtelCollect()}}.}
|
||||
\item{shiny.otel.sanitize.errors (defaults to \code{TRUE})}{If \code{TRUE}, fatal and unhandled errors will be sanitized before being sent to the OpenTelemetry backend. The default value of \code{TRUE} is set to avoid potentially sending sensitive information to the OpenTelemetry backend. If you want the full error message and stack trace to be sent to the OpenTelemetry backend, set this option to \code{FALSE} or use \code{safeError(e)}.}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ automatically disabled when spinner(s) are active. When both \code{spinners} and
|
||||
graying out of recalculating outputs).
|
||||
}
|
||||
\examples{
|
||||
\dontshow{if (rlang::is_interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf}
|
||||
\dontshow{if (rlang::is_interactive()) withAutoprint(\{ # examplesIf}
|
||||
|
||||
library(bslib)
|
||||
|
||||
|
||||
107
man/withOtelCollect.Rd
Normal file
107
man/withOtelCollect.Rd
Normal file
@@ -0,0 +1,107 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/otel-with.R
|
||||
\name{withOtelCollect}
|
||||
\alias{withOtelCollect}
|
||||
\alias{localOtelCollect}
|
||||
\title{Temporarily set OpenTelemetry (OTel) collection level}
|
||||
\usage{
|
||||
withOtelCollect(collect, expr)
|
||||
|
||||
localOtelCollect(collect, envir = parent.frame())
|
||||
}
|
||||
\arguments{
|
||||
\item{collect}{Character string specifying the OpenTelemetry collection level.
|
||||
Must be one of the following:
|
||||
|
||||
\if{html}{\out{<div class="sourceCode">}}\preformatted{* `"none"` - No telemetry data collected
|
||||
* `"reactivity"` - Collect reactive execution spans (includes session and
|
||||
reactive update events)
|
||||
* `"all"` - All available telemetry (currently equivalent to `"reactivity"`)
|
||||
}\if{html}{\out{</div>}}}
|
||||
|
||||
\item{expr}{Expression to evaluate with the specified collection level
|
||||
(for \code{withOtelCollect()}).}
|
||||
|
||||
\item{envir}{Environment where the collection level should be set
|
||||
(for \code{localOtelCollect()}). Defaults to the parent frame.}
|
||||
}
|
||||
\value{
|
||||
\itemize{
|
||||
\item \code{withOtelCollect()} returns the value of \code{expr}.
|
||||
\item \code{localOtelCollect()} is called for its side effect and returns the previous
|
||||
\code{collect} value invisibly.
|
||||
}
|
||||
}
|
||||
\description{
|
||||
Control Shiny's OTel collection level for particular reactive expression(s).
|
||||
|
||||
\code{withOtelCollect()} sets the OpenTelemetry collection level for
|
||||
the duration of evaluating \code{expr}. \code{localOtelCollect()} sets the collection
|
||||
level for the remainder of the current function scope.
|
||||
}
|
||||
\details{
|
||||
Note that \code{"session"} and \code{"reactive_update"} levels are not permitted as
|
||||
these are runtime-specific levels that should only be set permanently via
|
||||
\code{options(shiny.otel.collect = ...)} or the \code{SHINY_OTEL_COLLECT} environment
|
||||
variable, not temporarily during reactive expression creation.
|
||||
}
|
||||
\section{Best practice}{
|
||||
|
||||
|
||||
Best practice is to set the collection level for code that \emph{creates} reactive
|
||||
expressions, not code that \emph{runs} them. For instance:
|
||||
|
||||
\if{html}{\out{<div class="sourceCode r">}}\preformatted{# Disable telemetry for a reactive expression
|
||||
withOtelCollect("none", \{
|
||||
my_reactive <- reactive(\{ ... \})
|
||||
\})
|
||||
|
||||
# Disable telemetry for a render function
|
||||
withOtelCollect("none", \{
|
||||
output$my_plot <- renderPlot(\{ ... \})
|
||||
\})
|
||||
|
||||
#' # Disable telemetry for an observer
|
||||
withOtelCollect("none", \{
|
||||
observe(\{ ... \}))
|
||||
\})
|
||||
|
||||
# Disable telemetry for an entire module
|
||||
withOtelCollect("none", \{
|
||||
my_result <- my_module("my_id")
|
||||
\})
|
||||
# Use `my_result` as normal here
|
||||
}\if{html}{\out{</div>}}
|
||||
|
||||
NOTE: It's not recommended to pipe existing reactive objects into
|
||||
\code{withOtelCollect()} since they won't inherit their intended OTel settings,
|
||||
leading to confusion.
|
||||
}
|
||||
|
||||
\examples{
|
||||
\dontrun{
|
||||
# Temporarily disable telemetry collection
|
||||
withOtelCollect("none", {
|
||||
# Code here won't generate telemetry
|
||||
reactive({ input$x + 1 })
|
||||
})
|
||||
|
||||
# Collect reactivity telemetry but not other events
|
||||
withOtelCollect("reactivity", {
|
||||
# Reactive execution will be traced
|
||||
observe({ print(input$x) })
|
||||
})
|
||||
|
||||
# Use local variant in a function
|
||||
my_function <- function() {
|
||||
localOtelCollect("none")
|
||||
# Rest of function executes without telemetry
|
||||
reactive({ input$y * 2 })
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
\seealso{
|
||||
See the \code{shiny.otel.collect} option within \code{\link{shinyOptions}}. Setting
|
||||
this value will globally control OpenTelemetry collection levels.
|
||||
}
|
||||
6328
package-lock.json
generated
Normal file
6328
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
120
package.json
120
package.json
@@ -1,94 +1,76 @@
|
||||
{
|
||||
"private": true,
|
||||
"homepage": "https://shiny.rstudio.com",
|
||||
"repository": "github:rstudio/shiny",
|
||||
"name": "@types/rstudio-shiny",
|
||||
"version": "1.10.0-alpha.9001",
|
||||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://shiny.posit.co",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rstudio/shiny.git"
|
||||
},
|
||||
"name": "@posit/shiny",
|
||||
"version": "1.12.1-alpha.9000",
|
||||
"license": "MIT",
|
||||
"main": "",
|
||||
"browser": "",
|
||||
"types": "srcts/types/extras/globalShiny.d.ts",
|
||||
"files": [
|
||||
"DESCRIPTION",
|
||||
"LICENSE",
|
||||
"NEWS.md",
|
||||
"srcts/types/**/*.d.ts"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14",
|
||||
"yarn": ">= 1.22"
|
||||
"node": ">= 18",
|
||||
"npm": ">= 10"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/bootstrap": "3.4.0",
|
||||
"@types/bootstrap-datepicker": "0.0.14",
|
||||
"@types/datatables.net": "^1.10.19",
|
||||
"@types/bootstrap": "5.2.x",
|
||||
"@types/bootstrap-datepicker": "1.10.0",
|
||||
"@types/datatables.net": "1.10.x",
|
||||
"@types/ion-rangeslider": "2.3.0",
|
||||
"@types/jquery": "3.5.32",
|
||||
"@types/selectize": "0.12.34",
|
||||
"lit": "^3.0.0"
|
||||
"@types/selectize": "0.12.x",
|
||||
"lit": "3.2.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deanc/esbuild-plugin-postcss": "^1.0.2",
|
||||
"@selectize/selectize": "https://github.com/selectize/selectize.js.git#e3f2e0b4aa251375bc21b5fcd8ca7d374a921f08",
|
||||
"@testing-library/dom": "^7.31.0",
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/user-event": "^13.1.9",
|
||||
"@types/highlightjs": "^9.12.1",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/highlightjs": "9.x",
|
||||
"@types/jqueryui": "1.12.24",
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/showdown": "^1.9.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@typescript-eslint/parser": "^5.38.1",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"browserslist": "^4.19.1",
|
||||
"caniuse-lite": "^1.0.30001312",
|
||||
"core-js": "^3.13.0",
|
||||
"esbuild": "^0.15.10",
|
||||
"esbuild-plugin-globals": "^0.1.1",
|
||||
"esbuild-plugin-sass": "^1.0.1",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-jest": "^27.0.4",
|
||||
"eslint-plugin-jest-dom": "^4.0.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-unicorn": "^43.0.2",
|
||||
"fs-readdir-recursive": "^1.1.0",
|
||||
"jest": "^26.6.3",
|
||||
"@types/lodash": "4.x",
|
||||
"@types/node": "18.x",
|
||||
"@typescript-eslint/eslint-plugin": "8.x",
|
||||
"@typescript-eslint/parser": "8.x",
|
||||
"autoprefixer": "10.x",
|
||||
"bootstrap-datepicker": "1.10.0",
|
||||
"esbuild": ">=0.20.0",
|
||||
"esbuild-plugin-globals": "0.1.x",
|
||||
"esbuild-sass-plugin": "3.x",
|
||||
"eslint": "9.x",
|
||||
"eslint-config-prettier": "10.x",
|
||||
"eslint-plugin-prettier": "5.x",
|
||||
"eslint-plugin-unicorn": "56.x",
|
||||
"fs-readdir-recursive": "1.x",
|
||||
"jquery": "3.7.1",
|
||||
"lodash": "^4.17.21",
|
||||
"madge": "^4.0.2",
|
||||
"node-gyp": "^8.1.0",
|
||||
"phantomjs-prebuilt": "^2.1.16",
|
||||
"postcss": "^8.3.5",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-organize-imports": "^3.2.4",
|
||||
"readcontrol": "^1.0.0",
|
||||
"replace": "^1.2.1",
|
||||
"ts-jest": "^26",
|
||||
"ts-node": "^10.9.1",
|
||||
"type-coverage": "^2.22.0",
|
||||
"typescript": "^4.8.4",
|
||||
"util-inspect": "https://github.com/deecewan/browser-util-inspect#c0b4350df4378ffd743e8c36dd3898ce3992823e"
|
||||
"lodash": "4.x",
|
||||
"postcss": "8.x",
|
||||
"postcss-preset-env": "10.2.x",
|
||||
"prettier": "3.x",
|
||||
"prettier-plugin-organize-imports": "4.x",
|
||||
"readcontrol": "1.x",
|
||||
"tsx": "4.x",
|
||||
"type-coverage": "2.29.x",
|
||||
"typescript": "5.8.x"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node --eval \"1+1; // help yarn users not install the whole repo; https://github.com/yarnpkg/yarn/issues/2822#issuecomment-847360267\"",
|
||||
"watch": "yarn run build_shiny --watch",
|
||||
"build": "yarn run build_shiny && yarn run bundle_extras && yarn run bundle_external_libs",
|
||||
"build_shiny": "yarn run checks && yarn run bundle_shiny",
|
||||
"bundle_shiny": "ts-node srcts/build/shiny.ts",
|
||||
"bundle_external_libs": "ts-node srcts/build/external_libs.ts",
|
||||
"bundle_extras": "ts-node srcts/build/extras.ts",
|
||||
"test": "jest --coverage",
|
||||
"test_phantom": "echo '\n\t!! Must manually stop phantomjs test !!\n\n' && yarn bundle_shiny && phantomjs --debug=yes ../inst/www/shared/shiny.js",
|
||||
"checks": "yarn run lint && yarn run build_types && yarn run coverage && yarn run circular",
|
||||
"lint": "node --eval \"console.log('linting code...')\" && eslint --fix --ext .ts srcts/src",
|
||||
"watch": "npm run bundle_shiny -- --watch",
|
||||
"build": "npm run build_shiny && npm run bundle_extras && npm run bundle_external_libs",
|
||||
"build_shiny": "npm run checks && npm run bundle_shiny",
|
||||
"bundle_shiny": "tsx srcts/build/shiny.ts",
|
||||
"bundle_external_libs": "tsx srcts/build/external_libs.ts",
|
||||
"bundle_extras": "tsx srcts/build/extras.ts",
|
||||
"checks": "npm run lint && npm run build_types && npm run coverage && npm run circular",
|
||||
"lint": "node --eval \"console.log('linting code...')\" && eslint 'srcts/src/**/*.ts' --fix",
|
||||
"build_types": "tsc -p tsconfig.json",
|
||||
"coverage_detailed": "yarn type-check --detail",
|
||||
"coverage_detailed": "npx --yes type-check --detail",
|
||||
"coverage": "type-coverage -p tsconfig.json --at-least 90",
|
||||
"circular": "madge --circular --extensions ts srcts/src",
|
||||
"circular_image": "madge --circular --extensions ts --image madge.svg srcts/src"
|
||||
"circular": "npx --yes dpdm --transform ./srcts/src/index.ts",
|
||||
"prepack": "cp README.md README-orig.md && cp README-npm.md README.md",
|
||||
"postpack": "test -f README-orig.md && cp README-orig.md README.md && rm README-orig.md"
|
||||
},
|
||||
"prettier": {
|
||||
"plugins": [
|
||||
|
||||
@@ -1,34 +1,45 @@
|
||||
# Revdeps
|
||||
|
||||
## Failed to check (20)
|
||||
## Failed to check (38)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:------------------|:-------|:-----|:-------|:----|
|
||||
|animalEKF |1.2 |1 | | |
|
||||
|AovBay |0.1.0 |1 | | |
|
||||
|Certara.VPCResults |3.0.2 |1 | | |
|
||||
|chipPCR |1.0-2 |1 | | |
|
||||
|ctsem |3.10.1 |1 | | |
|
||||
|dartR.sim |? | | | |
|
||||
|diveR |? | | | |
|
||||
|gap |? | | | |
|
||||
|jsmodule |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|robmedExtra |0.1.1 |1 | | |
|
||||
|rstanarm |2.32.1 |1 | | |
|
||||
|SensMap |0.7 |1 | | |
|
||||
|Seurat |5.1.0 |1 | |1 |
|
||||
|shinyTempSignal |0.0.8 |1 | | |
|
||||
|Signac |1.14.0 |1 | | |
|
||||
|statsr |0.3.0 |1 | | |
|
||||
|TestAnaAPP |1.1.2 |1 | | |
|
||||
|tidyvpc |1.5.2 |1 | | |
|
||||
|visR |? | | | |
|
||||
|
||||
## New problems (2)
|
||||
|
||||
|package |version |error |warning |note |
|
||||
|:-------|:-------|:-----|:-------|:------|
|
||||
|[HH](problems.md#hh)|3.1-52 | | |__+1__ |
|
||||
|[PopED](problems.md#poped)|0.7.0 | | |__+1__ |
|
||||
|package |version |error |warning |note |
|
||||
|:--------------------|:-------|:-----|:-------|:----|
|
||||
|ADAMgui |? | | | |
|
||||
|AssumpSure |? | | | |
|
||||
|boinet |1.5.0 |1 | | |
|
||||
|brms |? | | | |
|
||||
|cheem |? | | | |
|
||||
|ctsem |3.10.4 |1 | |1 |
|
||||
|detourr |? | | | |
|
||||
|EMMAgeo |? | | | |
|
||||
|FactEff |? | | | |
|
||||
|FAfA |? | | | |
|
||||
|fio |0.1.6 |1 | | |
|
||||
|fitteR |? | | | |
|
||||
|FossilSimShiny |? | | | |
|
||||
|GDINA |? | | | |
|
||||
|ggsem |? | | | |
|
||||
|grandR |? | | | |
|
||||
|GSVA |? | | | |
|
||||
|hbsaems |? | | | |
|
||||
|langevitour |? | | | |
|
||||
|lavaan.shiny |? | | | |
|
||||
|lcsm |? | | | |
|
||||
|linkspotter |? | | | |
|
||||
|loon.shiny |? | | | |
|
||||
|MOsemiind |0.1.0 |1 | | |
|
||||
|MVN |? | | | |
|
||||
|pandemonium |? | | | |
|
||||
|polarisR |? | | | |
|
||||
|Prostar |? | | | |
|
||||
|RCTrep |1.2.0 |1 | | |
|
||||
|recmap |? | | | |
|
||||
|rstanarm |2.32.2 |1 | | |
|
||||
|semdrw |? | | | |
|
||||
|shotGroups |? | | | |
|
||||
|sphereML |? | | | |
|
||||
|spinifex |? | | | |
|
||||
|StatTeacherAssistant |? | | | |
|
||||
|SurprisalAnalysis |? | | | |
|
||||
|TestAnaAPP |? | | | |
|
||||
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
## revdepcheck results
|
||||
|
||||
We checked 1278 reverse dependencies (1277 from CRAN + 1 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
We checked 1383 reverse dependencies (1376 from CRAN + 7 from Bioconductor), comparing R CMD check results across CRAN and dev versions of this package.
|
||||
|
||||
* We saw 2 new problems
|
||||
* We failed to check 19 packages
|
||||
* We saw 0 new problems
|
||||
* We failed to check 31 packages
|
||||
|
||||
Issues with CRAN packages are summarised below.
|
||||
|
||||
### New problems
|
||||
(This reports the first line of each new failure)
|
||||
|
||||
* HH
|
||||
checking installed package size ... NOTE
|
||||
|
||||
* PopED
|
||||
checking installed package size ... NOTE
|
||||
|
||||
### Failed to check
|
||||
|
||||
* animalEKF (NA)
|
||||
* AovBay (NA)
|
||||
* Certara.VPCResults (NA)
|
||||
* chipPCR (NA)
|
||||
* ctsem (NA)
|
||||
* dartR.sim (NA)
|
||||
* diveR (NA)
|
||||
* gap (NA)
|
||||
* jsmodule (NA)
|
||||
* loon.shiny (NA)
|
||||
* robmedExtra (NA)
|
||||
* rstanarm (NA)
|
||||
* SensMap (NA)
|
||||
* Seurat (NA)
|
||||
* shinyTempSignal (NA)
|
||||
* Signac (NA)
|
||||
* statsr (NA)
|
||||
* TestAnaAPP (NA)
|
||||
* tidyvpc (NA)
|
||||
* AssumpSure (NA)
|
||||
* boinet (NA)
|
||||
* brms (NA)
|
||||
* cheem (NA)
|
||||
* ctsem (NA)
|
||||
* detourr (NA)
|
||||
* FAfA (NA)
|
||||
* fio (NA)
|
||||
* fitteR (NA)
|
||||
* FossilSimShiny (NA)
|
||||
* GDINA (NA)
|
||||
* ggsem (NA)
|
||||
* grandR (NA)
|
||||
* hbsaems (NA)
|
||||
* langevitour (NA)
|
||||
* lavaan.shiny (NA)
|
||||
* lcsm (NA)
|
||||
* linkspotter (NA)
|
||||
* loon.shiny (NA)
|
||||
* MOsemiind (NA)
|
||||
* MVN (NA)
|
||||
* pandemonium (NA)
|
||||
* polarisR (NA)
|
||||
* RCTrep (NA)
|
||||
* rstanarm (NA)
|
||||
* semdrw (NA)
|
||||
* shotGroups (NA)
|
||||
* sphereML (NA)
|
||||
* spinifex (NA)
|
||||
* SurprisalAnalysis (NA)
|
||||
* TestAnaAPP (NA)
|
||||
|
||||
@@ -1,49 +1 @@
|
||||
# HH
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 3.1-52
|
||||
* GitHub: NA
|
||||
* Source code: https://github.com/cran/HH
|
||||
* Date/Publication: 2024-02-11 00:00:02 UTC
|
||||
* Number of recursive dependencies: 165
|
||||
|
||||
Run `revdepcheck::cloud_details(, "HH")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking installed package size ... NOTE
|
||||
```
|
||||
installed size is 5.1Mb
|
||||
sub-directories of 1Mb or more:
|
||||
R 1.5Mb
|
||||
help 1.5Mb
|
||||
```
|
||||
|
||||
# PopED
|
||||
|
||||
<details>
|
||||
|
||||
* Version: 0.7.0
|
||||
* GitHub: https://github.com/andrewhooker/PopED
|
||||
* Source code: https://github.com/cran/PopED
|
||||
* Date/Publication: 2024-10-07 19:30:02 UTC
|
||||
* Number of recursive dependencies: 140
|
||||
|
||||
Run `revdepcheck::cloud_details(, "PopED")` for more info
|
||||
|
||||
</details>
|
||||
|
||||
## Newly broken
|
||||
|
||||
* checking installed package size ... NOTE
|
||||
```
|
||||
installed size is 5.5Mb
|
||||
sub-directories of 1Mb or more:
|
||||
R 1.5Mb
|
||||
doc 1.4Mb
|
||||
test 1.1Mb
|
||||
```
|
||||
|
||||
*Wow, no problems at all. :)*
|
||||
107
srcts/README.md
107
srcts/README.md
@@ -1,14 +1,20 @@
|
||||
# Using Shiny TypeScript Definitions
|
||||
|
||||
When developing TypeScript projects that use `window.Shiny`, we recommend installing the Shiny TypeScript definitions to your package. To install the latest stable definitions, call
|
||||
When developing TypeScript projects that use `window.Shiny`, we recommend installing the Shiny TypeScript definitions to your package. To install the latest stable definitions, run:
|
||||
|
||||
```bash
|
||||
yarn add https://github.com/rstudio/shiny\#v1.7.0
|
||||
npm install https://github.com/rstudio/shiny\#v1.11.0 --save-dev
|
||||
```
|
||||
|
||||
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.7.0`). If you are asked to select a version of `@types/jquery`, please select the closest matching version.
|
||||
, matching the GitHub tag to your current the Shiny CRAN release (ex: `v1.11.0`). If you are asked to select a version of `@types/jquery`, please select the closest matching version.
|
||||
|
||||
This will provide a global type definition of `Shiny`, let your IDE know that `window.Shiny` is of type `Shiny`, and declare a globally available variable `Shiny` within your project. You **should not** need to import anything. Similar to `jQuery`, it should _Just Work_<sup>TM</sup>.
|
||||
This will provide a global type definition of `window.Shiny`. In your code, you can access the Shiny object via `window.Shiny` or just `Shiny`. However, note that if you are using TypeScript, it will be OK with `window.Shiny` but it will flag uses of `Shiny` (without the `window.` prefix), because TypeScript won't know that it's a global variable. We consider it better practice to use `window.Shiny` instead of `Shiny`, but if you want TypeScript to know that `Shiny` is available as a global variable, you can add the following to a TypeScript file in your code base.
|
||||
|
||||
```ts
|
||||
declare global {
|
||||
const Shiny: ShinyClass;
|
||||
}
|
||||
```
|
||||
|
||||
When loading your compiled file, it should be loaded after `shiny.js` is loaded. If you are using an `htmlDependency()` to add your code to the page, your script will automatically be loaded after has been loaded.
|
||||
|
||||
@@ -20,31 +26,36 @@ When loading your compiled file, it should be loaded after `shiny.js` is loaded.
|
||||
All files will be described as if the working directory is the root folder of `rstudio/shiny`, not relative to this `README.md` file.
|
||||
|
||||
## First-time setup
|
||||
Shiny's TypeScript build tools use Node.js, along with [yarn](https://yarnpkg.com/) v2 to manage the JavaScript packages.
|
||||
|
||||
Shiny's TypeScript build tools use Node.js and `npm`.
|
||||
|
||||
Installation of Node.js differs across platforms, see [the official Node.js website](https://nodejs.org/) for instructions on downloading and installing. We presume that you have Node.js installed on your machine before continuing.
|
||||
|
||||
Install yarn using the [official instructions](https://yarnpkg.com/en/docs/install).
|
||||
Install npm using the [official instructions](https://docs.npmjs.com/getting-started/installing-node).
|
||||
|
||||
You can test that Node.js and yarn are installed properly by running the following commands:
|
||||
You can test that Node.js and `npm` are installed properly by running the following commands:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
yarn --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
Once both are installed, run the following in the root repo directory to install the packages :
|
||||
|
||||
```bash
|
||||
# Sitting in `rstudio/shiny` repo
|
||||
yarn install
|
||||
npm install
|
||||
```
|
||||
|
||||
### Yarn v2
|
||||
### Reproducible builds
|
||||
|
||||
This repo uses Yarn v2. If you have the latest yarn installed from brew (v1.x), Yarn will pick up on the fact that this repo is a Yarn v2 (`berry`) repo.
|
||||
To ensure that the same versions of packages are used between users, install the package lock dependencies by running:
|
||||
|
||||
For compatibility with `esbuild`, the `./node_modules` is still maintained.
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
This command will install the exact versions of packages specified in the `package-lock.json` file, ensuring that all developers are using the same versions of dependencies.
|
||||
|
||||
## Updating Node
|
||||
|
||||
@@ -61,26 +72,22 @@ npx n ls
|
||||
```
|
||||
|
||||
## Adding packages
|
||||
|
||||
If in the future you want to upgrade or add a package, run:
|
||||
|
||||
```bash
|
||||
yarn add --dev [packagename]
|
||||
npm install --save-dev [packagename]
|
||||
```
|
||||
|
||||
This will automatically add the package to the dependencies in `package.json`, and it will also update the `yarn.lock` to reflect that change. If someone other than yourself does this, simply run `yarn` to update your local packages to match the new `package.json`.
|
||||
This will automatically add the package to the dependencies in `package.json`, and it will also update the `package-lock.json` to reflect that change. If someone other than yourself does this, simply run `npm ci` to update your local packages to match the new `package.json`.
|
||||
|
||||
## Upgrading packages
|
||||
|
||||
Periodically, it's good to upgrade the packages to a recent version. There's two ways of doing this, depending on your intention:
|
||||
|
||||
1. Use `yarn up` to upgrade all dependencies to their latest version based on the version range specified in the package.json file (the `yarn.lock` file will be recreated as well. Yarn packages use [semantic versioning](https://yarnpkg.com/en/docs/dependency-versions), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Here are the most used operators (these appear before the version number):
|
||||
1. Use `npm outdated` to see outdated dependencies based on the version range specified in the `package.json` file. Npm packages use [semantic versioning](https://docs.npmjs.com/about-semantic-versioning), i.e. each version is writen with a maximum of 3 dot-separated numbers such that: `major.minor.patch`. For example in the version `3.1.4`, 3 is the major version number, 1 is the minor version number and 4 is the patch version number. Please use an `x` to denote where packages can automatically bump their version. For example, `3.1.x` will accept any patch version for `3.1`. Or `3.x` to accept any v3 minor version.
|
||||
|
||||
- `~` is for upgrades that keep the minor version the same (assuming that was specified);
|
||||
|
||||
- `^` is for upgrades that keep the major version the same (more or less -- more specifically, it allow changes that do not modify the first non-zero digit in the version, either the 3 in 3.1.4 or the 4 in 0.4.2.). This is the default operator added to the package.json when you run `yarn add [package-name]`.
|
||||
|
||||
2. Use `yarn up [package]` to upgrade a single named package to the version specified by the latest tag (potentially upgrading the package across major versions).
|
||||
|
||||
3. To see all outdated packages, run `yarn outdated`
|
||||
2. Use `npm update [package]` to upgrade a single named package to the version specified by the latest tag (potentially upgrading the package across major versions).
|
||||
|
||||
# TypeScript
|
||||
|
||||
@@ -140,25 +147,16 @@ The JavaScript community likes to build many small, effective packages that do m
|
||||
|
||||
All config files are located in the root folder to avoid opening two separate VS Code projects.
|
||||
|
||||
* `.browserslistrc`
|
||||
* Used with `browserslist` and `core-js` to determine which polyfills should be incorporated.
|
||||
* `.eslintrc.yml`
|
||||
* Used with `eslint` and `prettier` to determine how the TypeScript files should be formatted and which lint failures should cause warnings, errors, or be ignored.
|
||||
* `.madgerc`
|
||||
* Package used to determine if circular dependencies are found. `type` only imports are ignored as they are not included in the final bundle.
|
||||
* `.prettierrc.yml`
|
||||
* Used by `prettier` to know how to adjust code when a file is saved in VSCode or within `eslint`'s linting process.
|
||||
* `yarnrc.yml`
|
||||
* Notifies `yarn` to use `yarn` v2, install `./node_modules` folder for `esbuild`, and any CLI plugins.
|
||||
* `jest.config.js`
|
||||
* Used to configure [`jest` testing](https://jestjs.io/)
|
||||
* `package.json`
|
||||
* Contains useful scripts that can be run by `yarn` via `yarn run SCRIPTNAME`.
|
||||
* Contains useful scripts that can be run by `npm` via `npm run SCRIPTNAME`.
|
||||
* The scripts described below are inteded for developer use. All other scripts are means to an end.
|
||||
* `yarn run watch` - Watch `srcts/src` for changes and rebuild the JavaScript files.
|
||||
* `yarn run build` - Build `shiny.js` and `shiny.min.js` in `inst/www/shared`. Both files will have a corresponding sourcemap
|
||||
* `yarn run lint` - Fix all TypeScript lints using [`eslint`](https://eslint.org/) and [`prettier`](https://prettier.io/)
|
||||
* `yarn run test` - Run all TypeScript tests
|
||||
* `npm run watch` - Watch `srcts/src` for changes and rebuild the JavaScript files.
|
||||
* `npm run build` - Build `shiny.js` and `shiny.min.js` in `inst/www/shared`. Both files will have a corresponding sourcemap
|
||||
* `npm run lint` - Fix all TypeScript lints using [`eslint`](https://eslint.org/) and [`prettier`](https://prettier.io/)
|
||||
* `tsconfig.json` -
|
||||
* TypeScript config file
|
||||
* Notable options set:
|
||||
@@ -173,59 +171,27 @@ All config files are located in the root folder to avoid opening two separate VS
|
||||
To run all build tasks, run:
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
npm run build
|
||||
```
|
||||
|
||||
It's also useful to have `esbuild` watch for updated files and immediately re-build `shiny.js` as necessary during development. This is done with:
|
||||
|
||||
```bash
|
||||
yarn watch
|
||||
npm run watch
|
||||
```
|
||||
|
||||
Both JavaScript files will produce a sourcemap (`**.js.map`) that the browser will understand. This will help you debug Shiny's JavaScript code within the browser and point back to the original TypeScript files.
|
||||
|
||||
### Exported types
|
||||
|
||||
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `Shiny` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type definition when the Type definitions are installed by external developers.
|
||||
`./extras/globalShiny.ts` contains global declarations to define `window.Shiny`, a globally available `Shiny` variable, and a globally available `ShinyClass` type. This file is in a parallel folder to `./src` to avoid `Shiny` from being globally accessable within the source code. However, this file is the default type definition when the Type definitions are installed by external developers.
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
On push to the `main` branch or push to a Pull Request to the `main` branch, a GitHub Action will be run to make sure the bundled JavaScript code is up to date. If the source code does not compile to the exact same file, it will be committed an pushed back to the outdated branch. (This makes it so the full build tools are not necessary for small tweaks and comments. 🎉)
|
||||
|
||||
<!-- #### Auto build and browser refresh
|
||||
|
||||
An alternative to `yarn watch` is to use `entr` to trigger `grunt` when sources change. `entr` can be installed with `brew install entr` on a Mac, or on Linux using your distribution's package manager. Using this technique, it's possible to both automatically rebuild sources and reload Chrome at the same time:
|
||||
|
||||
*macOS*:
|
||||
|
||||
```bash
|
||||
find ../srcts/ | entr bash -c './node_modules/grunt/bin/grunt && osascript -e "tell application \"Google Chrome\" to reload active tab of window 1"'
|
||||
```
|
||||
|
||||
*Linux*:
|
||||
|
||||
For this to work you must first install `xdotool` using your distribution's package manager.
|
||||
|
||||
```bash
|
||||
find ../srcts/ | entr bash -c './node_modules/grunt/bin/grunt && xdotool search --onlyvisible --class Chrome windowfocus key ctrl+r'
|
||||
``` -->
|
||||
# Updating dependencies
|
||||
### `@types/jquery`
|
||||
|
||||
As of v3.5.5, `@types/jquery` produces a globally available constant of `$` and `jQuery`. This is problematic as TypeScript is there to enforce that all variables are accounted for. Declaring that these two variables exist globally removes the requirement to import `$` (or `jQuery`). This is bad for Shiny as the `$` would not be enforced to the "within package" `$`.
|
||||
|
||||
To overcome this, a patch is used to remove the globally defined `$` (and `Symbol`) variable declarations. Yarn v2 has a [`patch` protocol](https://yarnpkg.com/features/protocols#patch) that allows a local patch to be applied to the publically available `@types/jquery` package upon installation.
|
||||
|
||||
If in the future where the variables are not globally declared anymore, the patch may be removed and `@types/jquery` can be imported directly.
|
||||
|
||||
A global module plugin is used by `esbuild` to swap out a real `jquery` module to just return `window.jQuery`. This allows for tests and core code to behave the same way.
|
||||
|
||||
### `core-js`
|
||||
|
||||
To update the version of `core-js`:
|
||||
|
||||
* Check if there is a newer version available by running `yarn outdated core-js`. (If there's no output, then you have the latest version.)
|
||||
* Run `yarn add --dev core-js --exact`.
|
||||
|
||||
|
||||
### External libraries
|
||||
@@ -237,3 +203,4 @@ Shiny already has a handful of html dependencies that should NOT be bundled with
|
||||
* `bootstrap-datepicker` / `@types/bootstrap-datepicker`
|
||||
* `ion-rangeslider` / `@types/ion-rangeslider`
|
||||
* `selectize` / `@types/selectize`
|
||||
* `strftime`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user