Compare commits

...

11 Commits

Author SHA1 Message Date
Karan
07af5f91c8 chore(license): Change license from GPL-3 to MIT (#4339)
* Change license from GPL-3 to MIT

Updated the project license from GPL-3 to MIT in DESCRIPTION, LICENSE, LICENSE.md, README.md, and package.json. Added LICENSE.md with the MIT license text and updated .Rbuildignore to exclude LICENSE.md from builds.

* `npm run build` (GitHub Actions)

* Update LICENSE and add LICENSE.note

Replaced the LICENSE file content with a summary including year and copyright holder. Moved detailed third-party license information to a new LICENSE.note file.

* Remove R check log file

Deleted the ..Rcheck/00check.log file, likely to clean up generated or temporary files from the repository.
2025-12-16 17:51:22 -06:00
Barret Schloerke
fda6a9fede chore(assets): Update asset versions (#4337) 2025-12-11 11:56:42 -05:00
Barret Schloerke
d2245a2e34 Increment version number to 1.12.1.9000 2025-12-09 16:29:27 -05:00
Barret Schloerke
a12a8130b8 v1.12.1 (#4329) 2025-12-09 16:26:52 -05:00
Barret Schloerke
b436d2a96d Clarify OTel collection level usage in docs (#4335)
Co-authored-by: Carson Sievert <cpsievert1@gmail.com>
2025-12-08 15:31:57 -05:00
Barret Schloerke
05b0f270c4 fix(otel): ExtendedTask's otel enabled status set during init (#4334) 2025-12-08 14:55:59 -05:00
Barret Schloerke
f24f71e4e0 feat(otel): Add withOtelCollect() and localOtelCollect() (#4333) 2025-12-08 14:30:40 -05:00
Barret Schloerke
63a00f775f fix(otel): Duplicate otel code attribute keys using both deprecated and preferred names (#4325) 2025-12-03 16:37:20 -05:00
Barret Schloerke
5a946caf35 Skip timer tests on CRAN and fix empty vector comparison (#4327) 2025-12-03 16:29:17 -05:00
Barret Schloerke
16c016a171 Increment version number to 1.12.0.9000 2025-12-03 15:50:36 -05:00
Barret Schloerke
284af65534 Update .Rbuildignore 2025-12-03 15:50:27 -05:00
42 changed files with 2401 additions and 1708 deletions

View File

@@ -33,3 +33,5 @@
^_dev$
^.claude$
^README-npm\.md$
^CRAN-SUBMISSION$
^LICENSE\.md$

View File

@@ -1,7 +1,7 @@
Type: Package
Package: shiny
Title: Web Application Framework for R
Version: 1.12.0
Version: 1.12.1.9000
Authors@R: c(
person("Winston", "Chang", , "winston@posit.co", role = "aut",
comment = c(ORCID = "0000-0002-1576-2126")),
@@ -69,7 +69,7 @@ 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:
@@ -191,6 +191,7 @@ Collate:
'otel-reactive-update.R'
'otel-session.R'
'otel-shiny.R'
'otel-with.R'
'priorityqueue.R'
'progress.R'
'react.R'

1016
LICENSE

File diff suppressed because it is too large Load Diff

21
LICENSE.md Normal file
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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)

26
NEWS.md
View File

@@ -1,3 +1,29 @@
# shiny (development version)
# shiny 1.12.1
## New features
* `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)
## Bug fixes and minor improvements
* 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)
* `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)
* Timer tests are now skipped on CRAN. (#4327)
# shiny 1.12.0
## OpenTelemetry support

View File

@@ -520,7 +520,7 @@ bindCache.reactiveExpr <- function(x, ..., cache = "app") {
local({
impl <- attr(res, "observable", exact = TRUE)
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref)
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref, fn_name = "bindCache")
})
if (has_otel_collect("reactivity")) {

View File

@@ -240,7 +240,7 @@ bindEvent.reactiveExpr <- function(x, ..., ignoreNULL = TRUE, ignoreInit = FALSE
local({
impl <- attr(res, "observable", exact = TRUE)
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref)
impl$.otelAttrs <- append_otel_srcref_attrs(x_otel_attrs, call_srcref, fn_name = "bindEvent")
})
@@ -341,7 +341,7 @@ 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)
x$.otelAttrs <- append_otel_srcref_attrs(x$.otelAttrs, call_srcref, fn_name = "bindEvent")
if (has_otel_collect("reactivity")) {
x <- enable_otel_observe(x)

View File

@@ -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)
@@ -140,9 +162,13 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
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),
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
@@ -175,7 +201,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
private$invocation_queue$add(list(args = args, call = call))
} else {
if (has_otel_collect("reactivity")) {
if (private$is_recording_otel) {
private$otel_span <- start_otel_span(
private$otel_span_label,
attributes = private$otel_attrs
@@ -253,6 +279,7 @@ ExtendedTask <- R6Class("ExtendedTask", portable = TRUE, cloneable = FALSE,
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) {

View File

@@ -2,7 +2,7 @@
# Very similar to srcrefFromShinyCall(),
# however, this works when the function does not have a srcref attr set
otel_srcref_attributes <- function(srcref) {
otel_srcref_attributes <- function(srcref, fn_name = NULL) {
if (is.function(srcref)) {
srcref <- getSrcRefs(srcref)[[1]][[1]]
}
@@ -16,8 +16,16 @@ otel_srcref_attributes <- function(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.filepath" = attr(srcref, "srcfile")$filename,
"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]
))
@@ -41,12 +49,12 @@ get_call_srcref <- function(which_offset = 0) {
}
append_otel_srcref_attrs <- function(attrs, call_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)
srcref_attrs <- otel_srcref_attributes(call_srcref, fn_name)
if (is.null(srcref_attrs)) {
return(attrs)
}

View File

@@ -30,12 +30,7 @@ has_otel_collect <- function(collect) {
# Run expr with otel collection disabled
with_no_otel_collect <- function(expr) {
withr::with_options(
list(
shiny.otel.collect = "none"
),
expr
)
withOtelCollect("none", expr)
}

125
R/otel-with.R Normal file
View 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)
}

View File

@@ -231,7 +231,7 @@ reactiveVal <- function(value = NULL, label = NULL) {
rv <- ReactiveVal$new(value, label)
if (!is.null(call_srcref)) {
rv$.otelAttrs <- otel_srcref_attributes(call_srcref)
rv$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactiveVal")
}
ret <- structure(
@@ -646,7 +646,7 @@ reactiveValues <- function(...) {
defaultLabel = impl$.label
)
impl$.otelAttrs <- otel_srcref_attributes(call_srcref)
impl$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactiveValues")
}
impl$mset(args)
@@ -1130,7 +1130,7 @@ reactive <- function(
call_srcref <- get_call_srcref()
if (!is.null(call_srcref)) {
o$.otelAttrs <- otel_srcref_attributes(call_srcref)
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "reactive")
}
ret <- structure(
@@ -1587,7 +1587,7 @@ observe <- function(
..stacktraceon = ..stacktraceon
)
if (!is.null(call_srcref)) {
o$.otelAttrs <- otel_srcref_attributes(call_srcref)
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "observe")
}
if (has_otel_collect("reactivity")) {
@@ -2055,7 +2055,7 @@ reactive_poll_impl <- function(
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)
impl$.otelAttrs <- append_otel_srcref_attrs(impl$.otelAttrs, call_srcref, fn_name = fnName)
})
return(re)
@@ -2522,7 +2522,7 @@ observeEvent <- function(eventExpr, handlerExpr,
})
if (!is.null(call_srcref)) {
o$.otelAttrs <- otel_srcref_attributes(call_srcref)
o$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "observeEvent")
}
if (has_otel_collect("reactivity")) {
o <- enable_otel_observe(o)
@@ -2571,7 +2571,7 @@ eventReactive <- function(eventExpr, valueExpr,
if (!is.null(call_srcref)) {
impl <- attr(r, "observable", exact = TRUE)
impl$.otelAttrs <- otel_srcref_attributes(call_srcref)
impl$.otelAttrs <- otel_srcref_attributes(call_srcref, fn_name = "eventReactive")
}
if (has_otel_collect("reactivity")) {
r <- enable_otel_reactive_expr(r)
@@ -2778,7 +2778,7 @@ debounce <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
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)
er_impl$.otelAttrs <- append_otel_srcref_attrs(er_impl$.otelAttrs, call_srcref, fn_name = "debounce")
})
with_no_otel_collect({
@@ -2877,7 +2877,7 @@ throttle <- function(r, millis, priority = 100, domain = getDefaultReactiveDomai
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)
er_impl$.otelAttrs <- append_otel_srcref_attrs(er_impl$.otelAttrs, call_srcref, fn_name = "throttle")
})
er

View File

@@ -160,20 +160,30 @@ 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.
#' \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. (Includes `"reactive_update"` features).
#' * `"all"` - All Shiny OpenTelemetry tracing. Currently equivalent to `"reactivity"`.
#' * `"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 starting your Shiny app.}
#' 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)`.}
#' }
#'

View File

@@ -136,7 +136,9 @@ markRenderFunction <- function(
otelAttrs <-
otel_srcref_attributes(
attr(renderFunc, "wrappedFunc", exact = TRUE)
attr(renderFunc, "wrappedFunc", exact = TRUE),
# Can't retrieve the render function used at this point, so just use NULL
fn_name = NULL
)
ret <- structure(

View File

@@ -61,7 +61,7 @@ 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

View File

@@ -1,121 +1,129 @@
## Comments
#### 2025-12-01
#### 2025-12-08
Hi CRAN,
Test has been removed from CRAN checks.
We made changes to underlying structures that packages are not suppose to test. PRs were provided for each failing package.
Also added a couple bug fixes as found by users.
Maintainer change: From Winston Chang to Carson Sievert.
Please let me know if you need any further information.
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:
The maintainer change is correctly detected. The URL check sometimes flags a 429
error from Wikipedia, which is a temporary issue since the URL is valid when
visited manually.
0 errors | 0 warning | 1 note
```
* checking CRAN incoming feasibility ... [19s] NOTE
Maintainer: 'Carson Sievert <barret@posit.co>'
checking CRAN incoming feasibility ... [7s/70s] NOTE (1m 9.5s)
Maintainer: Carson Sievert <carson@posit.co>
New maintainer:
Carson Sievert <barret@posit.co>
Old maintainer(s):
Winston Chang <winston@posit.co>
Found the following (possibly) invalid URLs:
URL: https://en.wikipedia.org/wiki/Reactive_programming
From: README.md
Status: 429
Message: Too Many Requests
Days since last update: 5
```
## Reverse dependency fixes
The revdep checks below are failing due to changes made in https://github.com/rstudio/shiny/pull/4249 .
Unresolved PRs submitted in 2025/06:
* omicsTools - https://github.com/cheemalab/omicsTools/pull/1
* shinyGovstyle - https://github.com/dfe-analytical-services/shinyGovstyle/pull/155
* ShinyLink - https://github.com/cdc-addm/ShinyLink/pull/3
* shinySbm - https://github.com/Jo-Theo/shinySbm/pull/2
Unresolved PR submitted in 2025/10/29:
* biodosetools - PR made 2025/10/29 - https://github.com/biodosetools-team/biodosetools/pull/64
* inshiny - PR made 2025/10/29 - https://github.com/nicholasdavies/inshiny/pull/1
## Reverse dependency false positives
* SouthParkRshiny - New NOTE about installed package size. This is unrelated to any new changes in Shiny.
> ```
> * checking installed package size ... NOTE
> installed size is 8.6Mb
> sub-directories of 1Mb or more:
> data 8.0Mb
> ```
## revdepcheck results
We checked 1395 reverse dependencies (1388 from CRAN + 7 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 7 new problems
* We failed to check 21 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)
* biodosetools
checking tests ... ERROR
* inshiny
checking examples ... ERROR
checking tests ... ERROR
checking re-building of vignette outputs ... ERROR
* omicsTools
checking tests ... ERROR
* shinyGovstyle
checking tests ... ERROR
* ShinyLink
checking tests ... ERROR
* shinySbm
checking tests ... ERROR
* SouthParkRshiny
checking installed package size ... NOTE
### Failed to check
* AssumpSure
* boinet
* brms
* cheem
* ctsem
* detourr
* FAfA
* fio
* fitteR
* FossilSimShiny
* GDINA
* ggsem
* grandR
* hbsaems
* langevitour
* lavaan.shiny
* lcsm
* linkspotter
* loon.shiny
* MOsemiind
* MVN
* pandemonium
* polarisR
* RCTrep
* rstanarm
* semdrw
* shotGroups
* sphereML
* spinifex
* SurprisalAnalysis
* TestAnaAPP

View File

@@ -1,2 +1,2 @@
/*! shiny 1.12.0 | (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}

View File

@@ -1,3 +1,3 @@
/*! shiny 1.12.0 | (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

View File

@@ -1,2 +1,2 @@
/*! shiny 1.12.0 | (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}

View File

@@ -1,3 +1,3 @@
/*! shiny 1.12.0 | (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 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

View File

@@ -1,3 +1,3 @@
/*! shiny 1.12.0 | (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

View File

@@ -1,4 +1,4 @@
/*! shiny 1.12.0 | (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 __create = Object.create;
@@ -7206,7 +7206,7 @@ ${duplicateIdMsg}`;
// srcts/src/shiny/index.ts
var ShinyClass = class {
constructor() {
this.version = "1.12.0";
this.version = "1.12.1.9000";
const { inputBindings, fileInputBinding: fileInputBinding2 } = initInputBindings();
const { outputBindings } = initOutputBindings();
setFileInputBinding(fileInputBinding2);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -45,6 +45,29 @@ 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")) withAutoprint(\{ # examplesIf}
library(shiny)

View File

@@ -130,22 +130,31 @@ 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.
\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. (Includes \code{"reactive_update"} features).
\item \code{"all"} - All Shiny OpenTelemetry tracing. Currently equivalent to \code{"reactivity"}.
\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 starting your Shiny app.}
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)}.}
}
}

107
man/withOtelCollect.Rd Normal file
View 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.
}

View File

@@ -5,8 +5,8 @@
"url": "git+https://github.com/rstudio/shiny.git"
},
"name": "@posit/shiny",
"version": "1.12.0",
"license": "GPL-3.0-only",
"version": "1.12.1-alpha.9000",
"license": "MIT",
"main": "",
"browser": "",
"types": "srcts/types/extras/globalShiny.d.ts",

View File

@@ -1,47 +1,45 @@
# Revdeps
## Failed to check (28)
## Failed to check (38)
|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 |0.1.1 |1 | | |
|hbsaems |? | | | |
|langevitour |? | | | |
|lavaan.shiny |? | | | |
|lcsm |? | | | |
|linkspotter |1.3.0 |1 | | |
|linkspotter |? | | | |
|loon.shiny |? | | | |
|MOsemiind |0.1.0 |1 | | |
|MVN |6.2 |1 | | |
|MVN |? | | | |
|pandemonium |? | | | |
|polarisR |? | | | |
|Prostar |? | | | |
|RCTrep |1.2.0 |1 | | |
|recmap |? | | | |
|rstanarm |2.32.2 |1 | | |
|semdrw |? | | | |
|shotGroups |? | | | |
|sphereML |? | | | |
|spinifex |? | | | |
|StatTeacherAssistant |? | | | |
|SurprisalAnalysis |? | | | |
|TestAnaAPP |? | | | |
## New problems (7)
|package |version |error |warning |note |
|:---------------|:-------|:------|:-------|:--------|
|[biodosetools](problems.md#biodosetools)|3.7.1 |__+1__ | | |
|[inshiny](problems.md#inshiny)|0.1.0 |__+3__ | | |
|[omicsTools](problems.md#omicstools)|1.0.5 |__+1__ | | |
|[shinyGovstyle](problems.md#shinygovstyle)|0.1.0 |__+1__ | | |
|[ShinyLink](problems.md#shinylink)|0.2.2 |__+1__ | | |
|[shinySbm](problems.md#shinysbm)|0.1.5 |__+1__ | |1 |
|[SouthParkRshiny](problems.md#southparkrshiny)|1.0.0 | | |1 __+1__ |

View File

@@ -1,58 +1,42 @@
## revdepcheck results
We checked 1395 reverse dependencies (1388 from CRAN + 7 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 7 new problems
* We failed to check 21 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)
* biodosetools
checking tests ... ERROR
* inshiny
checking examples ... ERROR
checking tests ... ERROR
checking re-building of vignette outputs ... ERROR
* omicsTools
checking tests ... ERROR
* shinyGovstyle
checking tests ... ERROR
* ShinyLink
checking tests ... ERROR
* shinySbm
checking tests ... ERROR
* SouthParkRshiny
checking installed package size ... NOTE
### Failed to check
* 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)

View File

@@ -1,353 +1 @@
# biodosetools
<details>
* Version: 3.7.1
* GitHub: https://github.com/biodosetools-team/biodosetools
* Source code: https://github.com/cran/biodosetools
* Date/Publication: 2025-10-22 08:10:02 UTC
* Number of recursive dependencies: 132
Run `revdepcheck::cloud_details(, "biodosetools")` for more info
</details>
## Newly broken
* checking tests ... ERROR
```
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> library(testthat)
> library(biodosetools)
Loading required package: shiny
Loading required package: golem
>
> test_check("biodosetools")
! Problem with `glm()` -> constraint ML optimization will be used instead
...
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
- " <span class=\"action-label\">go</span>"
- "</button>"
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
[ FAIL 2 | WARN 1 | SKIP 1 | PASS 455 ]
Error:
! Test failures.
Execution halted
```
# inshiny
<details>
* Version: 0.1.0
* GitHub: https://github.com/nicholasdavies/inshiny
* Source code: https://github.com/cran/inshiny
* Date/Publication: 2025-09-09 14:00:13 UTC
* Number of recursive dependencies: 53
Run `revdepcheck::cloud_details(, "inshiny")` for more info
</details>
## Newly broken
* checking examples ... ERROR
```
Running examples in inshiny-Ex.R failed
The error most likely occurred in:
> ### Name: inline_button
> ### Title: Inline action button
> ### Aliases: inline_button
>
> ### ** Examples
>
> ui <- bslib::page_fixed(
...
+ label = shiny::span(style = "font-style:italic", "button"),
+ icon = shiny::icon("play"),
+ meaning = "Update button", accent = "success"),
+ "."
+ )
+ )
Error in check_tags(widget, shiny::tags$button(), "shiny::actionButton()") :
Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
Calls: <Anonymous> ... div -> dots_list -> inline -> inline_button -> check_tags
Execution halted
```
* checking tests ... ERROR
```
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> # This file is part of the standard setup for testthat.
> # It is recommended that you do not modify it.
> #
> # Where should you do additional test configuration?
> # Learn more about the roles of various files in:
> # * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
> # * https://testthat.r-lib.org/articles/special-files.html
...
1. ├─inshiny:::cc(...)
2. │ └─base::cat(as.character(x)) at ./helper.R:2:5
3. └─inshiny::inline_button(...)
4. └─inshiny:::check_tags(widget, shiny::tags$button(), "shiny::actionButton()")
[ FAIL 2 | WARN 0 | SKIP 9 | PASS 24 ]
Error:
! Test failures.
Execution halted
```
* checking re-building of vignette outputs ... ERROR
```
Error(s) in re-building vignettes:
--- re-building inshiny.Rmd using rmarkdown
Quitting from inshiny.Rmd:64-86 [unnamed-chunk-3]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<error/rlang_error>
Error in `check_tags()`:
! Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
---
Backtrace:
...
Error: processing vignette 'inshiny.Rmd' failed with diagnostics:
Unexpected tag structure from shiny::actionButton(). Please contact the package maintainer.
--- failed re-building inshiny.Rmd
SUMMARY: processing the following file failed:
inshiny.Rmd
Error: Vignette re-building failed.
Execution halted
```
# omicsTools
<details>
* Version: 1.0.5
* GitHub: https://github.com/YaoxiangLi/omicsTools
* Source code: https://github.com/cran/omicsTools
* Date/Publication: 2023-07-03 16:20:02 UTC
* Number of recursive dependencies: 88
Run `revdepcheck::cloud_details(, "omicsTools")` for more info
</details>
## Newly broken
* checking tests ... ERROR
```
Running spelling.R
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> # This file is part of the standard setup for testthat.
> # It is recommended that you do not modify it.
> #
> # Where should you do additional test configuration?
> # Learn more about the roles of various files in:
> # * https://r-pkgs.org/tests.html
...
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
- " <span class=\"action-label\">go</span>"
- "</button>"
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 94 ]
Error:
! Test failures.
Execution halted
```
# shinyGovstyle
<details>
* Version: 0.1.0
* GitHub: https://github.com/moj-analytical-services/shinyGovstyle
* Source code: https://github.com/cran/shinyGovstyle
* Date/Publication: 2024-09-12 14:40:02 UTC
* Number of recursive dependencies: 49
Run `revdepcheck::cloud_details(, "shinyGovstyle")` for more info
</details>
## Newly broken
* checking tests ... ERROR
```
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> library(testthat)
> library(shinyGovstyle)
>
> test_check("shinyGovstyle")
Saving _problems/test-backlink_Input-7.R
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 125 ]
...
══ Failed tests ════════════════════════════════════════════════════════════════
── Failure ('test-backlink_Input.R:4:3'): backlink works ───────────────────────
Expected `backlink_check$children[[1]][[2]]` to be identical to "Back".
Differences:
target is NULL, current is character
[ FAIL 1 | WARN 0 | SKIP 0 | PASS 125 ]
Error:
! Test failures.
Execution halted
```
# ShinyLink
<details>
* Version: 0.2.2
* GitHub: NA
* Source code: https://github.com/cran/ShinyLink
* Date/Publication: 2023-01-18 11:40:05 UTC
* Number of recursive dependencies: 128
Run `revdepcheck::cloud_details(, "ShinyLink")` for more info
</details>
## Newly broken
* checking tests ... ERROR
```
Running spelling.R
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> # This file is part of the standard setup for testthat.
> # It is recommended that you do not modify it.
> #
> # Where should you do additional test configuration?
> # Learn more about the roles of various files in:
> # * https://r-pkgs.org/tests.html
...
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
- " <span class=\"action-label\">go</span>"
- "</button>"
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 145 ]
Error:
! Test failures.
Execution halted
```
# shinySbm
<details>
* Version: 0.1.5
* GitHub: https://github.com/Jo-Theo/shinySbm
* Source code: https://github.com/cran/shinySbm
* Date/Publication: 2023-09-07 21:50:02 UTC
* Number of recursive dependencies: 134
Run `revdepcheck::cloud_details(, "shinySbm")` for more info
</details>
## Newly broken
* checking tests ... ERROR
```
Running spelling.R
Running testthat.R
Running the tests in tests/testthat.R failed.
Complete output:
> # This file is part of the standard setup for testthat.
> # It is recommended that you do not modify it.
> #
> # Where should you do additional test configuration?
> # Learn more about the roles of various files in:
> # * https://r-pkgs.org/tests.html
...
- "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">"
- " <span class=\"action-label\">go</span>"
- "</button>"
+ "<button id=\"go_filter\" type=\"button\" class=\"btn btn-default action-button\" style=\"display: none;\">go</button>"
[ FAIL 2 | WARN 0 | SKIP 1 | PASS 141 ]
Error:
! Test failures.
Execution halted
```
## In both
* checking Rd files ... NOTE
```
checkRd: (-1) FungusTreeNetwork.Rd:15-21: Lost braces in \itemize; meant \describe ?
checkRd: (-1) FungusTreeNetwork.Rd:22-28: Lost braces in \itemize; meant \describe ?
checkRd: (-1) FungusTreeNetwork.Rd:33-34: Lost braces in \itemize; meant \describe ?
checkRd: (-1) FungusTreeNetwork.Rd:33: Lost braces; missing escapes or markup?
33 | \item{tree_tree}{Results of \code{estimateSimpleSBM} for {sbm}
| ^
checkRd: (-1) FungusTreeNetwork.Rd:35-36: Lost braces in \itemize; meant \describe ?
checkRd: (-1) FungusTreeNetwork.Rd:35: Lost braces; missing escapes or markup?
35 | \item{fungus_tree}{Results of \code{estimateBipartiteSBM} for {sbm}
| ^
...
checkRd: (-1) visSbm.default.Rd:25: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:26: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:43-44: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:45: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:46: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:47: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:48: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:49: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:50: Lost braces in \itemize; meant \describe ?
checkRd: (-1) visSbm.default.Rd:51: Lost braces in \itemize; meant \describe ?
```
# SouthParkRshiny
<details>
* Version: 1.0.0
* GitHub: https://github.com/Amalan-ConStat/SouthParkRshiny
* Source code: https://github.com/cran/SouthParkRshiny
* Date/Publication: 2024-03-09 11:10:08 UTC
* Number of recursive dependencies: 112
Run `revdepcheck::cloud_details(, "SouthParkRshiny")` for more info
</details>
## Newly broken
* checking installed package size ... NOTE
```
installed size is 8.6Mb
sub-directories of 1Mb or more:
data 8.0Mb
```
## In both
* checking data for non-ASCII characters ... NOTE
```
Note: found 1562 marked UTF-8 strings
```
*Wow, no problems at all. :)*

View File

@@ -0,0 +1,31 @@
# Helper function to create a mock otel span
create_mock_otel_span <- function(name = "test_span") {
structure(
list(
name = name,
activate = function(...) NULL,
end = function(...) NULL
),
class = "otel_span"
)
}
# Helper function to create a mock tracer
create_mock_tracer <- function() {
structure(
list(
name = "mock_tracer",
is_enabled = function() TRUE,
start_span = function(name, ...) create_mock_otel_span(name)
),
class = "otel_tracer"
)
}
# Helper function to create a mock logger
create_mock_logger <- function() {
structure(
list(name = "mock_logger"),
class = "otel_logger"
)
}

View File

@@ -101,9 +101,30 @@ test_that("otel_srcref_attributes extracts attributes from srcref object", {
attrs <- otel_srcref_attributes(srcref)
# Preferred attribute names
expect_equal(attrs[["code.file.path"]], "/path/to/myfile.R")
expect_equal(attrs[["code.line.number"]], 15)
expect_equal(attrs[["code.column.number"]], 8)
expect_false("code.function.name" %in% names(attrs))
# Deprecated attribute names (for backward compatibility)
expect_equal(attrs[["code.filepath"]], "/path/to/myfile.R")
expect_equal(attrs[["code.lineno"]], 15)
expect_equal(attrs[["code.column"]], 8)
# Test with function name
attrs_with_fn <- otel_srcref_attributes(srcref, fn_name = "myFunction")
# Preferred names
expect_equal(attrs_with_fn[["code.file.path"]], "/path/to/myfile.R")
expect_equal(attrs_with_fn[["code.line.number"]], 15)
expect_equal(attrs_with_fn[["code.column.number"]], 8)
expect_equal(attrs_with_fn[["code.function.name"]], "myFunction")
# Deprecated names
expect_equal(attrs_with_fn[["code.filepath"]], "/path/to/myfile.R")
expect_equal(attrs_with_fn[["code.lineno"]], 15)
expect_equal(attrs_with_fn[["code.column"]], 8)
})
test_that("otel_srcref_attributes handles NULL srcref", {
@@ -127,9 +148,21 @@ test_that("otel_srcref_attributes extracts from function with srcref", {
{
attrs <- otel_srcref_attributes(mock_func)
expect_equal(attrs[["code.filepath"]], "function_file.R")
expect_equal(attrs[["code.lineno"]], 42)
expect_equal(attrs[["code.column"]], 12)
expect_equal(attrs[["code.file.path"]], "function_file.R")
expect_equal(attrs[["code.line.number"]], 42)
expect_equal(attrs[["code.column.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
# Test with function name
attrs_with_fn <- otel_srcref_attributes(
mock_func,
fn_name = "testFunction"
)
expect_equal(attrs_with_fn[["code.file.path"]], "function_file.R")
expect_equal(attrs_with_fn[["code.line.number"]], 42)
expect_equal(attrs_with_fn[["code.column.number"]], 12)
expect_equal(attrs_with_fn[["code.function.name"]], "testFunction")
}
)
})
@@ -186,11 +219,27 @@ test_that("otel_srcref_attributes drops NULL values", {
attrs <- otel_srcref_attributes(srcref)
# Should only contain lineno and column, not filepath
expect_equal(length(attrs), 2)
# Should only contain lineno and column (both preferred and deprecated)
expect_equal(length(attrs), 4) # 2 preferred + 2 deprecated
# Preferred names
expect_equal(attrs[["code.line.number"]], 10)
expect_equal(attrs[["code.column.number"]], 5)
expect_false("code.file.path" %in% names(attrs))
expect_false("code.function.name" %in% names(attrs))
# Deprecated names
expect_equal(attrs[["code.lineno"]], 10)
expect_equal(attrs[["code.column"]], 5)
expect_false("code.filepath" %in% names(attrs))
# Test with function name - NULL fn_name should still be dropped
attrs_with_null_fn <- otel_srcref_attributes(srcref, fn_name = NULL)
expect_equal(length(attrs_with_null_fn), 4)
expect_false("code.function.name" %in% names(attrs_with_null_fn))
# Test with function name provided
attrs_with_fn <- otel_srcref_attributes(srcref, fn_name = "testFunc")
expect_equal(length(attrs_with_fn), 5) # 4 location + 1 function name
expect_equal(attrs_with_fn[["code.function.name"]], "testFunc")
})
test_that("otel_srcref_attributes handles missing srcfile", {
@@ -202,8 +251,13 @@ test_that("otel_srcref_attributes handles missing srcfile", {
attrs <- otel_srcref_attributes(srcref)
# Should only contain lineno and column
expect_equal(length(attrs), 2)
# Should only contain lineno and column (both preferred and deprecated)
expect_equal(length(attrs), 4) # 2 preferred + 2 deprecated
# Preferred names
expect_equal(attrs[["code.line.number"]], 10)
expect_equal(attrs[["code.column.number"]], 5)
expect_false("code.file.path" %in% names(attrs))
# Deprecated names
expect_equal(attrs[["code.lineno"]], 10)
expect_equal(attrs[["code.column"]], 5)
expect_false("code.filepath" %in% names(attrs))
@@ -217,9 +271,10 @@ test_that("reactive() captures otel attributes from source reference", {
x <- get_reactive_objects()$reactive
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 4)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 4)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "reactive")
})
test_that("reactiveVal() captures otel attributes from source reference", {
@@ -228,9 +283,10 @@ test_that("reactiveVal() captures otel attributes from source reference", {
# Test the attribute extraction that would be used in reactiveVal
attrs <- attr(x, ".impl")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 5)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 5)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "reactiveVal")
})
test_that("reactiveValues() captures otel attributes from source reference", {
@@ -238,36 +294,41 @@ test_that("reactiveValues() captures otel attributes from source reference", {
attrs <- .subset2(x, "impl")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 6)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 6)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "reactiveValues")
})
test_that("observe() captures otel attributes from source reference", {
x <- get_reactive_objects()$observe
attrs <- x$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 7)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 7)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "observe")
})
test_that("otel attributes integration with render functions", {
x <- get_reactive_objects()$renderText
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 8)
expect_equal(attrs[["code.column"]], 20)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 8)
expect_equal(attrs[["code.column.number"]], 20)
# Render functions should NOT have code.function.name
expect_false("code.function.name" %in% names(attrs))
})
test_that("observeEvent() captures otel attributes from source reference", {
x <- get_reactive_objects()$observeEvent
attrs <- x$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 9)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 9)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "observeEvent")
})
test_that("otel attributes follow OpenTelemetry semantic conventions", {
@@ -282,15 +343,33 @@ test_that("otel attributes follow OpenTelemetry semantic conventions", {
attrs <- otel_srcref_attributes(srcref)
# Check that attribute names follow the convention
# Check that preferred attribute names follow the convention
expect_true("code.file.path" %in% names(attrs))
expect_true("code.line.number" %in% names(attrs))
expect_true("code.column.number" %in% names(attrs))
expect_false("code.function.name" %in% names(attrs))
# Check that deprecated names are also present
expect_true("code.filepath" %in% names(attrs))
expect_true("code.lineno" %in% names(attrs))
expect_true("code.column" %in% names(attrs))
# Check that values are of correct types
expect_true(is.character(attrs[["code.filepath"]]))
expect_true(is.numeric(attrs[["code.lineno"]]))
expect_true(is.numeric(attrs[["code.column"]]))
# Check that values are of correct types (preferred names)
expect_true(is.character(attrs[["code.file.path"]]))
expect_true(is.numeric(attrs[["code.line.number"]]))
expect_true(is.numeric(attrs[["code.column.number"]]))
# Check that deprecated names have same values
expect_equal(attrs[["code.file.path"]], attrs[["code.filepath"]])
expect_equal(attrs[["code.line.number"]], attrs[["code.lineno"]])
expect_equal(attrs[["code.column.number"]], attrs[["code.column"]])
# Test with function name
attrs_with_fn <- otel_srcref_attributes(srcref, fn_name = "myFunc")
expect_true("code.function.name" %in% names(attrs_with_fn))
expect_true(is.character(attrs_with_fn[["code.function.name"]]))
expect_equal(attrs_with_fn[["code.function.name"]], "myFunc")
})
test_that("dropNulls helper works correctly in otel_srcref_attributes", {
@@ -302,7 +381,7 @@ test_that("dropNulls helper works correctly in otel_srcref_attributes", {
)
attrs <- otel_srcref_attributes(srcref)
expect_equal(length(attrs), 3)
expect_equal(length(attrs), 6) # 3 preferred + 3 deprecated
# Test with missing filename (NULL)
srcref_no_file <- structure(
@@ -312,16 +391,17 @@ test_that("dropNulls helper works correctly in otel_srcref_attributes", {
attr(srcref_no_file, "srcfile") <- list(filename = NULL)
attrs_no_file <- otel_srcref_attributes(srcref_no_file)
expect_equal(length(attrs_no_file), 2)
expect_equal(length(attrs_no_file), 4) # 2 preferred + 2 deprecated
expect_false("code.file.path" %in% names(attrs_no_file))
expect_false("code.filepath" %in% names(attrs_no_file))
})
test_that("otel attributes are used in reactive context execution", {
# Test that otel attributes are properly passed through to spans
mock_attrs <- list(
"code.filepath" = "context_test.R",
"code.lineno" = 42L,
"code.column" = 8L
"code.file.path" = "context_test.R",
"code.line.number" = 42L,
"code.column.number" = 8L
)
# Test the context info structure used in react.R
@@ -342,9 +422,9 @@ test_that("otel attributes are combined with session attributes", {
# as happens in the reactive system
srcref_attrs <- list(
"code.filepath" = "session_test.R",
"code.lineno" = 15L,
"code.column" = 5L
"code.file.path" = "session_test.R",
"code.line.number" = 15L,
"code.column.number" = 5L
)
session_attrs <- list(
@@ -355,8 +435,8 @@ test_that("otel attributes are combined with session attributes", {
combined_attrs <- c(srcref_attrs, session_attrs)
expect_equal(length(combined_attrs), 4)
expect_equal(combined_attrs[["code.filepath"]], "session_test.R")
expect_equal(combined_attrs[["code.lineno"]], 15L)
expect_equal(combined_attrs[["code.file.path"]], "session_test.R")
expect_equal(combined_attrs[["code.line.number"]], 15L)
expect_equal(combined_attrs[["session.id"]], "test-session-123")
})
@@ -364,25 +444,28 @@ test_that("eventReactive() captures otel attributes from source reference", {
x <- get_reactive_objects()$eventReactive
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 10)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 10)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "eventReactive")
})
test_that("renderText() with bindCache() captures otel attributes", {
x <- get_reactive_objects()$renderCacheA
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
})
test_that("renderText() with bindEvent() captures otel attributes", {
x <- get_reactive_objects()$renderEventA
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
})
test_that(
@@ -391,8 +474,9 @@ test_that(
x <- get_reactive_objects()$renderCacheEventA
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
}
)
@@ -400,16 +484,18 @@ test_that("bindCache() wrapping renderText() captures otel attributes", {
x <- get_reactive_objects()$renderCacheB
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
})
test_that("bindEvent() wrapping renderText() captures otel attributes", {
x <- get_reactive_objects()$renderEventB
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
})
test_that(
@@ -418,8 +504,9 @@ test_that(
x <- get_reactive_objects()$renderCacheEventB
attrs <- attr(x, "otelAttrs")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_false("code.function.name" %in% names(attrs))
}
)
@@ -427,32 +514,36 @@ test_that("observe() with bindEvent() captures otel attributes", {
x <- get_reactive_objects()$observeEventA
attrs <- x$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
})
test_that("bindEvent() wrapping observe() captures otel attributes", {
x <- get_reactive_objects()$observeEventB
attrs <- x$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
})
test_that("reactive() with bindCache() captures otel attributes", {
x <- get_reactive_objects()$reactiveCacheA
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindCache")
})
test_that("reactive() with bindEvent() captures otel attributes", {
x <- get_reactive_objects()$reactiveEventA
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
})
test_that(
@@ -461,8 +552,9 @@ test_that(
x <- get_reactive_objects()$reactiveCacheEventA
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
}
)
@@ -470,16 +562,18 @@ test_that("bindCache() wrapping reactive() captures otel attributes", {
x <- get_reactive_objects()$reactiveCacheB
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindCache")
})
test_that("bindEvent() wrapping reactive() captures otel attributes", {
x <- get_reactive_objects()$reactiveEventB
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
})
test_that(
@@ -488,8 +582,9 @@ test_that(
x <- get_reactive_objects()$reactiveCacheEventB
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "bindEvent")
}
)
@@ -498,16 +593,18 @@ test_that("debounce() creates new reactive with otel attributes", {
x <- get_reactive_objects()$debounce
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "debounce")
})
test_that("throttle() creates new reactive with otel attributes", {
x <- get_reactive_objects()$throttle
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "throttle")
})
# Tests for ExtendedTask
@@ -518,8 +615,9 @@ test_that("ExtendedTask is created and is an R6 object", {
attrs <- .subset2(x, ".__enclos_env__")$private$otel_attrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "ExtendedTask")
})
# Tests for reactivePoll
@@ -531,8 +629,9 @@ test_that("reactivePoll() captures otel attributes from source reference", {
expect_equal(as.character(otelLabel), "reactivePoll r_poll")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "reactivePoll")
})
# Tests for reactiveFileReader
@@ -544,8 +643,9 @@ test_that("reactiveFileReader() captures otel attributes from source reference",
expect_equal(as.character(otelLabel), "reactiveFileReader r_file")
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.lineno"]], 12)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_gt(attrs[["code.line.number"]], 12)
expect_equal(attrs[["code.function.name"]], "reactiveFileReader")
})
# Tests for explicit labels
@@ -553,9 +653,10 @@ test_that("reactive() with explicit label still captures otel attributes", {
x <- get_reactive_objects()$reactiveLabeled
attrs <- attr(x, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 38)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 38)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "reactive")
# Verify label is preserved
label <- attr(x, "observable")$.label
@@ -566,9 +667,10 @@ test_that("observe() with explicit label still captures otel attributes", {
x <- get_reactive_objects()$observeLabeled
attrs <- x$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.lineno"]], 39)
expect_equal(attrs[["code.column"]], 3)
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.line.number"]], 39)
expect_equal(attrs[["code.column.number"]], 3)
expect_equal(attrs[["code.function.name"]], "observe")
# Verify label is preserved
expect_equal(x$.label, "my_observer")
@@ -583,10 +685,10 @@ test_that("reactive created inside function captures function srcref", {
r <- create_reactive()
attrs <- attr(r, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
# Line number should point to where reactive() is called inside the function
expect_true(is.numeric(attrs[["code.lineno"]]))
expect_true(is.numeric(attrs[["code.column"]]))
expect_true(is.numeric(attrs[["code.line.number"]]))
expect_true(is.numeric(attrs[["code.column.number"]]))
})
test_that("observe created inside function captures function srcref", {
@@ -597,9 +699,9 @@ test_that("observe created inside function captures function srcref", {
o <- create_observer()
attrs <- o$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.lineno"]]))
expect_true(is.numeric(attrs[["code.column"]]))
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.line.number"]]))
expect_true(is.numeric(attrs[["code.column.number"]]))
})
test_that("reactive returned from function preserves srcref", {
@@ -610,8 +712,8 @@ test_that("reactive returned from function preserves srcref", {
counter <- make_counter(42)
attrs <- attr(counter, "observable")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.lineno"]]))
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.line.number"]]))
})
test_that("reactiveVal created in function captures srcref", {
@@ -622,8 +724,8 @@ test_that("reactiveVal created in function captures srcref", {
rv <- create_val()
attrs <- attr(rv, ".impl")$.otelAttrs
expect_equal(attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.lineno"]]))
expect_equal(attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(attrs[["code.line.number"]]))
})
test_that("nested reactive expressions preserve individual srcrefs", {
@@ -633,17 +735,17 @@ test_that("nested reactive expressions preserve individual srcrefs", {
})
outer_attrs <- attr(outer_reactive, "observable")$.otelAttrs
expect_equal(outer_attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(outer_attrs[["code.lineno"]]))
expect_equal(outer_attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(outer_attrs[["code.line.number"]]))
# Get the inner reactive by executing outer
withReactiveDomain(MockShinySession$new(), {
inner_reactive <- isolate(outer_reactive())
inner_attrs <- attr(inner_reactive, "observable")$.otelAttrs
expect_equal(inner_attrs[["code.filepath"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(inner_attrs[["code.lineno"]]))
expect_equal(inner_attrs[["code.file.path"]], "test-otel-attr-srcref.R")
expect_true(is.numeric(inner_attrs[["code.line.number"]]))
# Inner should have different line number than outer
expect_false(inner_attrs[["code.lineno"]] == outer_attrs[["code.lineno"]])
expect_false(inner_attrs[["code.line.number"]] == outer_attrs[["code.line.number"]])
})
})

View File

@@ -0,0 +1,219 @@
# Tests for ExtendedTask otel behavior
ex_task_42 <- function() {
ExtendedTask$new(function() {
promises::promise_resolve(42)
})
}
test_that("ExtendedTask captures otel collection state at initialization", {
# Test that has_otel_collect is called at init, not at invoke time
withr::local_options(list(shiny.otel.collect = "reactivity"))
# Enable otel tracing
local_mocked_bindings(
otel_is_tracing_enabled = function() TRUE
)
task <- ex_task_42()
# Check that is_recording_otel is captured at init time
expect_true(task$.__enclos_env__$private$is_recording_otel)
})
test_that("ExtendedTask sets is_recording_otel to FALSE when otel disabled", {
# Enable otel tracing
local_mocked_bindings(
otel_is_tracing_enabled = function() FALSE
)
# Test with all level
withr::with_options(list(shiny.otel.collect = "all"), {
task1 <- ex_task_42()
expect_false(task1$.__enclos_env__$private$is_recording_otel)
})
# Test with reactivity level
withr::with_options(list(shiny.otel.collect = "reactivity"), {
task1 <- ex_task_42()
expect_false(task1$.__enclos_env__$private$is_recording_otel)
})
# Test with session level (should be FALSE)
withr::with_options(list(shiny.otel.collect = "session"), {
task2 <- ex_task_42()
expect_false(task2$.__enclos_env__$private$is_recording_otel)
})
# Test with none level (should be FALSE)
withr::with_options(list(shiny.otel.collect = "none"), {
task3 <- ex_task_42()
expect_false(task3$.__enclos_env__$private$is_recording_otel)
})
})
test_that("ExtendedTask sets is_recording_otel based on has_otel_collect at init", {
# Enable otel tracing
local_mocked_bindings(
otel_is_tracing_enabled = function() TRUE
)
# Test with all level
withr::with_options(list(shiny.otel.collect = "all"), {
task1 <- ex_task_42()
expect_true(task1$.__enclos_env__$private$is_recording_otel)
})
# Test with reactivity level
withr::with_options(list(shiny.otel.collect = "reactivity"), {
task1 <- ex_task_42()
expect_true(task1$.__enclos_env__$private$is_recording_otel)
})
# Test with session level (should be FALSE)
withr::with_options(list(shiny.otel.collect = "session"), {
task2 <- ex_task_42()
expect_false(task2$.__enclos_env__$private$is_recording_otel)
})
# Test with none level (should be FALSE)
withr::with_options(list(shiny.otel.collect = "none"), {
task3 <- ex_task_42()
expect_false(task3$.__enclos_env__$private$is_recording_otel)
})
})
test_that("ExtendedTask uses init-time otel setting even if option changes later", {
# Enable otel tracing
local_mocked_bindings(
otel_is_tracing_enabled = function() TRUE
)
# Test that changing the option after init doesn't affect the task
withr::with_options(list(shiny.otel.collect = "reactivity"), {
task <- ex_task_42()
})
# Capture the initial state
expect_true(task$.__enclos_env__$private$is_recording_otel)
# Change the option after initialization
withr::with_options(list(shiny.otel.collect = "none"), {
# The task should still have the init-time setting
expect_true(task$.__enclos_env__$private$is_recording_otel)
})
})
test_that("ExtendedTask respects session level otel collection", {
# Test that session level doesn't enable reactivity spans
withr::local_options(list(shiny.otel.collect = "session"))
task <- ex_task_42()
# Should not record otel at session level
expect_false(task$.__enclos_env__$private$is_recording_otel)
})
test_that("ExtendedTask respects reactive_update level otel collection", {
# Test that reactive_update level doesn't enable reactivity spans
withr::local_options(list(shiny.otel.collect = "reactive_update"))
task <- ex_task_42()
# Should not record otel at reactive_update level
expect_false(task$.__enclos_env__$private$is_recording_otel)
})
test_that("ExtendedTask creates span only when is_recording_otel is TRUE", {
# Test that span is only created when otel is enabled
withr::local_options(list(shiny.otel.collect = "reactivity"))
span_created <- FALSE
local_mocked_bindings(
start_otel_span = function(...) {
span_created <<- TRUE
create_mock_otel_span("extended_task")
},
otel_is_tracing_enabled = function() TRUE
)
with_shiny_otel_record({
withReactiveDomain(MockShinySession$new(), {
task <- ex_task_42()
# Reset the flag
span_created <- FALSE
# Invoke the task
isolate({
task$invoke()
})
})
})
# Span should have been created because is_recording_otel is TRUE
expect_true(span_created)
})
test_that("ExtendedTask does not create span when is_recording_otel is FALSE", {
# Test that span is not created when otel is disabled
withr::local_options(list(shiny.otel.collect = "none"))
span_created <- FALSE
local_mocked_bindings(
start_otel_span = function(...) {
span_created <<- TRUE
create_mock_otel_span("extended_task")
}
)
withReactiveDomain(MockShinySession$new(), {
task <- ex_task_42()
# Invoke the task
isolate({
task$invoke()
})
})
# Span should not have been created because is_recording_otel is FALSE
expect_false(span_created)
})
test_that("Multiple ExtendedTask invocations use same is_recording_otel value", {
# Enable otel tracing
withr::local_options(list(shiny.otel.collect = "reactivity"))
local_mocked_bindings(
otel_is_tracing_enabled = function() TRUE
)
withReactiveDomain(MockShinySession$new(), {
task <- ex_task_42()
# Verify is_recording_otel is TRUE at init
expect_true(task$.__enclos_env__$private$is_recording_otel)
# Change option after initialization (should not affect the task)
withr::with_options(
list(shiny.otel.collect = "none"),
{
# The task should still have the init-time setting
expect_true(task$.__enclos_env__$private$is_recording_otel)
# Verify is_recording_otel doesn't change on invocation
isolate({
task$invoke()
})
# Still should be TRUE after invoke
expect_true(task$.__enclos_env__$private$is_recording_otel)
}
)
})
})

View File

@@ -68,9 +68,12 @@ test_that("reactive bindCache labels are created", {
})
test_that("ExtendedTask otel labels are created", {
ex_task <- ExtendedTask$new(function() { promises::then(promises::promise_resolve(42), force) })
# Record everything
localOtelCollect("all")
info <- with_shiny_otel_record({
ex_task <- ExtendedTask$new(function() { promises::then(promises::promise_resolve(42), force) })
ex_task$invoke()
while(!later::loop_empty()) {
later::run_now()
@@ -79,30 +82,22 @@ test_that("ExtendedTask otel labels are created", {
trace <- info$traces[[1]]
expect_equal(
trace$name,
"ExtendedTask ex_task"
)
expect_equal(trace$name, "ExtendedTask ex_task")
# Module test
withReactiveDomain(MockShinySession$new(), {
ex2_task <- ExtendedTask$new(function() { promises::then(promises::promise_resolve(42), force) })
info <- with_shiny_otel_record({
ex2_task <- ExtendedTask$new(function() { promises::then(promises::promise_resolve(42), force) })
ex2_task$invoke()
while(!later::loop_empty()) {
later::run_now()
}
})
})
trace <- info$traces[[1]]
expect_equal(
trace$name,
"ExtendedTask mock-session:ex2_task"
)
expect_equal(trace$name, "ExtendedTask mock-session:ex2_task")
})

View File

@@ -1,14 +1,47 @@
skip_on_cran()
skip_if_not_installed("otelsdk")
expect_code_attrs <- function(trace) {
expect_code_attrs <- function(trace, expected_fn_name = NULL) {
testthat::expect_true(!is.null(trace))
testthat::expect_true(is.list(trace$attributes))
# Check preferred attribute names
testthat::expect_true(is.character(trace$attributes[["code.file.path"]]))
testthat::expect_equal(trace$attributes[["code.file.path"]], "test-otel-mock.R")
testthat::expect_true(is.numeric(trace$attributes[["code.line.number"]]))
testthat::expect_true(is.numeric(trace$attributes[["code.column.number"]]))
# Check deprecated attribute names (for backward compatibility)
testthat::expect_true(is.character(trace$attributes[["code.filepath"]]))
testthat::expect_equal(trace$attributes[["code.filepath"]], "test-otel-mock.R")
testthat::expect_true(is.numeric(trace$attributes[["code.lineno"]]))
testthat::expect_true(is.numeric(trace$attributes[["code.column"]]))
# Verify deprecated names match preferred names
testthat::expect_equal(
trace$attributes[["code.file.path"]],
trace$attributes[["code.filepath"]]
)
testthat::expect_equal(
trace$attributes[["code.line.number"]],
trace$attributes[["code.lineno"]]
)
testthat::expect_equal(
trace$attributes[["code.column.number"]],
trace$attributes[["code.column"]]
)
# Check code.function.name if expected
if (!is.null(expected_fn_name)) {
testthat::expect_true(
is.character(trace$attributes[["code.function.name"]])
)
testthat::expect_equal(
trace$attributes[["code.function.name"]],
expected_fn_name
)
}
invisible(trace)
}
MOCK_SESSION_TOKEN <- "test-session-token"
@@ -21,7 +54,7 @@ expect_session_id <- function(trace) {
invisible(trace)
}
expect_trace <- function(traces, name, pos = 1) {
expect_trace <- function(traces, name, pos = 1, expected_fn_name = NULL) {
# Filter to traces with the given name
trace_set <- traces[which(names(traces) == name)]
testthat::expect_gte(length(trace_set), pos)
@@ -30,7 +63,7 @@ expect_trace <- function(traces, name, pos = 1) {
trace <- trace_set[[pos]]
testthat::expect_true(is.list(trace))
expect_code_attrs(trace)
expect_code_attrs(trace, expected_fn_name = expected_fn_name)
expect_session_id(trace)
trace
@@ -78,9 +111,9 @@ for (bind in c("all", "reactivity")) {
session$flushReact()
})
expect_trace(traces, "observe mock-session:<anonymous>")
expect_trace(traces, "observe mock-session:my_observe")
expect_trace(traces, "observe mock-session:labeled observer")
expect_trace(traces, "observe mock-session:<anonymous>", 1, "observe")
expect_trace(traces, "observe mock-session:my_observe", 1, "observe")
expect_trace(traces, "observe mock-session:labeled observer", 1, "observe")
})
test_that(paste0("bind='", bind, "' handles reactiveVal / reactiveValues"), {
@@ -104,7 +137,7 @@ for (bind in c("all", "reactivity")) {
expect_equal(rv(), 1)
})
expect_trace(traces, "observe mock-session:<anonymous>")
expect_trace(traces, "observe mock-session:<anonymous>", 1, "observe")
# TODO-future: Add tests to see the `Set reactiveVal mock-session:rv` logs
# Requires: https://github.com/r-lib/otelsdk/issues/21
@@ -131,10 +164,16 @@ for (bind in c("all", "reactivity")) {
expect_equal(r3(), 42)
})
observe_trace <- expect_trace(traces, "observe mock-session:obs_r3")
r_trace <- expect_trace(traces, "reactive mock-session:r")
r2_trace <- expect_trace(traces, "reactive mock-session:<anonymous>")
r3_trace <- expect_trace(traces, "reactive mock-session:labeled_rv")
observe_trace <- expect_trace(
traces, "observe mock-session:obs_r3", 1, "observe"
)
r_trace <- expect_trace(traces, "reactive mock-session:r", 1, "reactive")
r2_trace <- expect_trace(
traces, "reactive mock-session:<anonymous>", 1, "reactive"
)
r3_trace <- expect_trace(
traces, "reactive mock-session:labeled_rv", 1, "reactive"
)
expect_equal(r_trace$parent, r2_trace$span_id)
expect_equal(r2_trace$parent, r3_trace$span_id)
@@ -157,7 +196,9 @@ for (bind in c("all", "reactivity")) {
expect_equal(output$txt, "Hello, world!")
})
expect_trace(traces, "output mock-session:txt")
# Outputs (render functions) should NOT have code.function.name
trace <- expect_trace(traces, "output mock-session:txt", 1, NULL)
expect_false("code.function.name" %in% names(trace$attributes))
})
test_that(paste0("bind='", bind, "' extended tasks are supported"), {
@@ -183,18 +224,28 @@ for (bind in c("all", "reactivity")) {
traces <- test_server_with_otel(session, server, bind = bind, {
session$flushReact()
while(!later::loop_empty()) {
while (!later::loop_empty()) {
later::run_now()
session$flushReact()
}
session$flushReact()
})
invoke_obs <- expect_trace(traces, "observe mock-session:invoke task")
render1_trace <- expect_trace(traces, "output mock-session:result")
ex_task_trace <- expect_trace(traces, "ExtendedTask mock-session:rand_task")
invoke_obs <- expect_trace(
traces, "observe mock-session:invoke task", 1, "observe"
)
# Render functions should NOT have code.function.name
render1_trace <- expect_trace(traces, "output mock-session:result", 1, NULL)
expect_false("code.function.name" %in% names(render1_trace$attributes))
render2_trace <- expect_trace(traces, "output mock-session:result", pos = 2)
ex_task_trace <- expect_trace(
traces, "ExtendedTask mock-session:rand_task", 1, "ExtendedTask"
)
render2_trace <- expect_trace(
traces, "output mock-session:result", pos = 2, NULL
)
expect_false("code.function.name" %in% names(render2_trace$attributes))
expect_equal(invoke_obs$span_id, ex_task_trace$parent)
})
@@ -222,9 +273,11 @@ test_that("bind = 'reactivity' traces reactive components", {
})
# Should trace reactive components (equivalent to "all")
expect_trace(traces, "observe mock-session:test_obs")
expect_trace(traces, "reactive mock-session:r")
expect_trace(traces, "output mock-session:txt")
expect_trace(traces, "observe mock-session:test_obs", 1, "observe")
expect_trace(traces, "reactive mock-session:r", 1, "reactive")
# Render functions should NOT have code.function.name
txt_trace <- expect_trace(traces, "output mock-session:txt", 1, NULL)
expect_false("code.function.name" %in% names(txt_trace$attributes))
})

View File

@@ -1,37 +1,5 @@
# Tests for otel-shiny.R functions
# Helper function to create a mock otel span
create_mock_otel_span <- function(name = "test_span") {
structure(
list(
name = name,
activate = function(...) NULL,
end = function(...) NULL
),
class = "otel_span"
)
}
# Helper function to create a mock tracer
create_mock_tracer <- function() {
structure(
list(
name = "mock_tracer",
is_enabled = function() TRUE,
start_span = function(name, ...) create_mock_otel_span(name)
),
class = "otel_tracer"
)
}
# Helper function to create a mock logger
create_mock_logger <- function() {
structure(
list(name = "mock_logger"),
class = "otel_logger"
)
}
test_that("otel_tracer_name constant is correct", {
expect_equal(otel_tracer_name, "co.posit.r-package.shiny")
})

View File

@@ -0,0 +1,316 @@
test_that("withOtelCollect sets collection level temporarily", {
# Save original option
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
# Set a baseline option
options(shiny.otel.collect = "all")
# Test that withOtelCollect temporarily changes the option
result <- withOtelCollect("none", {
getOption("shiny.otel.collect")
})
expect_equal(result, "none")
# Verify option is restored after expression
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("withOtelCollect returns value of expr", {
result <- withOtelCollect("none", {
42
})
expect_equal(result, 42)
# Test with more complex return value
result <- withOtelCollect("reactivity", {
list(a = 1, b = "test")
})
expect_equal(result, list(a = 1, b = "test"))
})
test_that("withOtelCollect validates collect level", {
expect_error(
withOtelCollect("invalid", { 1 }),
"'arg' should be one of"
)
expect_error(
withOtelCollect(123, { 1 }),
"`collect` must be a character vector"
)
expect_error(
withOtelCollect(c("all", "none"), { 1 }),
"'arg' must be of length 1"
)
})
test_that("withOtelCollect rejects session and reactive_update levels", {
expect_error(
withOtelCollect("session", { 1 }),
"'arg' should be one of"
)
expect_error(
withOtelCollect("reactive_update", { 1 }),
"'arg' should be one of"
)
})
test_that("withOtelCollect works with all valid collect levels", {
for (level in c("none", "reactivity", "all")) {
result <- withOtelCollect(level, {
getOption("shiny.otel.collect")
})
expect_equal(result, level)
}
})
test_that("withOtelCollect nests correctly", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
result <- withOtelCollect("reactivity", {
outer <- getOption("shiny.otel.collect")
inner <- withOtelCollect("none", {
getOption("shiny.otel.collect")
})
restored <- getOption("shiny.otel.collect")
list(outer = outer, inner = inner, restored = restored)
})
expect_equal(result$outer, "reactivity")
expect_equal(result$inner, "none")
expect_equal(result$restored, "reactivity")
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("withOtelCollect restores option even on error", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
expect_error(
withOtelCollect("none", {
stop("test error")
}),
"test error"
)
# Option should still be restored
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("localOtelCollect sets collection level in function scope", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
test_func <- function() {
localOtelCollect("none")
getOption("shiny.otel.collect")
}
result <- test_func()
expect_equal(result, "none")
# Option should be restored after function exits
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("localOtelCollect returns previous collect value invisibly", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
result <- withVisible(localOtelCollect("none"))
# Should return a list with the old option value
expect_type(result$value, "list")
expect_equal(result$value$shiny.otel.collect, "all")
expect_false(result$visible)
})
test_that("localOtelCollect validates collect level", {
expect_error(
localOtelCollect("invalid"),
"'arg' should be one of"
)
expect_error(
localOtelCollect(NULL),
"`collect` must be a character vector"
)
expect_error(
localOtelCollect(c("all", "none")),
"'arg' must be of length 1"
)
})
test_that("localOtelCollect rejects session and reactive_update levels", {
expect_error(
localOtelCollect("session"),
"'arg' should be one of"
)
expect_error(
localOtelCollect("reactive_update"),
"'arg' should be one of"
)
})
test_that("localOtelCollect works with all valid collect levels", {
for (level in c("none", "reactivity", "all")) {
test_func <- function() {
localOtelCollect(level)
getOption("shiny.otel.collect")
}
result <- test_func()
expect_equal(result, level)
}
})
test_that("localOtelCollect respects envir parameter", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
outer_func <- function() {
env <- environment()
inner_func <- function() {
localOtelCollect("none", envir = env)
}
inner_func()
getOption("shiny.otel.collect")
}
result <- outer_func()
expect_equal(result, "none")
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("localOtelCollect scope is limited to function", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
func1 <- function() {
localOtelCollect("reactivity")
getOption("shiny.otel.collect")
}
func2 <- function() {
localOtelCollect("none")
getOption("shiny.otel.collect")
}
result1 <- func1()
result2 <- func2()
expect_equal(result1, "reactivity")
expect_equal(result2, "none")
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("withOtelCollect and localOtelCollect work together", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
result <- withOtelCollect("reactivity", {
outer <- getOption("shiny.otel.collect")
test_func <- function() {
localOtelCollect("none")
getOption("shiny.otel.collect")
}
inner <- test_func()
restored <- getOption("shiny.otel.collect")
list(outer = outer, inner = inner, restored = restored)
})
expect_equal(result$outer, "reactivity")
expect_equal(result$inner, "none")
expect_equal(result$restored, "reactivity")
expect_equal(getOption("shiny.otel.collect"), "all")
})
test_that("withOtelCollect affects otel_collect_is_enabled", {
# This tests integration with the otel collection system
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
# With "none", nothing except "none" should be enabled
result <- withOtelCollect("none", {
list(
none = otel_collect_is_enabled("none"),
session = otel_collect_is_enabled("session"),
reactivity = otel_collect_is_enabled("reactivity")
)
})
expect_true(result$none)
expect_false(result$session)
expect_false(result$reactivity)
# With "reactivity", reactivity and below should be enabled, but not "all"
result <- withOtelCollect("reactivity", {
list(
none = otel_collect_is_enabled("none"),
session = otel_collect_is_enabled("session"),
reactive_update = otel_collect_is_enabled("reactive_update"),
reactivity = otel_collect_is_enabled("reactivity"),
all = otel_collect_is_enabled("all")
)
})
expect_true(result$none)
expect_true(result$session)
expect_true(result$reactive_update)
expect_true(result$reactivity)
expect_false(result$all)
})
test_that("localOtelCollect affects otel_collect_is_enabled", {
original <- getOption("shiny.otel.collect")
on.exit(options(shiny.otel.collect = original), add = TRUE)
options(shiny.otel.collect = "all")
test_func <- function() {
localOtelCollect("reactivity")
list(
session = otel_collect_is_enabled("session"),
reactive_update = otel_collect_is_enabled("reactive_update"),
reactivity = otel_collect_is_enabled("reactivity"),
all = otel_collect_is_enabled("all")
)
}
result <- test_func()
expect_true(result$session)
expect_true(result$reactive_update)
expect_true(result$reactivity)
expect_false(result$all)
})

View File

@@ -1,3 +1,8 @@
# Dev CRAN had some issues with comparisons between integer(0) and character(0)
# Skipping tests on CRAN as CI is enough to verify functionality
# Related: https://github.com/rstudio/shiny/issues/4326
skip_on_cran()
test_that("Scheduling works", {
ran <- FALSE
fun <- function() {
@@ -32,8 +37,13 @@ test_that("Unscheduling works", {
# Unregister
taskHandle()
expect_identical(timerCallbacks$.times, origTimes)
expect_identical(timerCallbacks$.funcs$keys(), origFuncKeys)
# Split into two sections to avoid `expect_equal(integer(0), character(0))` comparison on dev CRAN
if (length(origTimes) == 0) {
expect_equal(0, length(timerCallbacks$.times))
} else {
expect_equal(timerCallbacks$.times, origTimes)
}
expect_equal(timerCallbacks$.funcs$keys(), origFuncKeys)
})
test_that("Vectorized unscheduling works", {

View File

@@ -214,6 +214,10 @@ reference:
- runTests
- testServer
- MockShinySession
- title: OpenTelemetry
desc: Functions for OpenTelemetry tracing integration
contents:
- withOtelCollect
- title: Superseded
desc: Functions that have been `r lifecycle::badge("superseded")`
contents: