inst/doc/otel-integration.R

## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)

## ----prerequisites------------------------------------------------------------
# # 1. Shiny >= 1.12.0 (when OTEL support was added)
# packageVersion("shiny") # Should be >= 1.12.0
# 
# # 2. OpenTelemetry packages
# install.packages("otel")
# install.packages("otelsdk")
# 
# # 3. bidux with OTEL support
# install.packages("bidux") # or development version from GitHub

## ----basic_setup--------------------------------------------------------------
# library(shiny)
# 
# # Enable OTEL collection for all events
# options(shiny.otel.collect = "all")
# 
# # Or use environment variable (set before starting R)
# # Sys.setenv(SHINY_OTEL_COLLECT = "all")
# 
# # Your Shiny app code
# ui <- fluidPage(
#   titlePanel("Sales Dashboard"),
#   sidebarLayout(
#     sidebarPanel(
#       selectInput("region", "Region:",
#                   choices = c("North", "South", "East", "West")),
#       dateRangeInput("date_range", "Date Range:")
#     ),
#     mainPanel(
#       plotOutput("sales_plot")
#     )
#   )
# )
# 
# server <- function(input, output, session) {
#   output$sales_plot <- renderPlot({
#     # Your plotting logic
#   })
# }
# 
# shinyApp(ui, server)

## ----collection_levels--------------------------------------------------------
# # For development and UX analysis - collect everything
# options(shiny.otel.collect = "all")
# 
# # For production with moderate overhead
# options(shiny.otel.collect = "reactive_update")
# 
# # Minimal production tracking
# options(shiny.otel.collect = "session")

## ----file_export--------------------------------------------------------------
# \dontrun{
# library(otel)
# library(otelsdk)
# 
# # Configure OTLP JSON file exporter
# Sys.setenv(
#   OTEL_TRACES_EXPORTER = "otlp",
#   OTEL_EXPORTER_OTLP_PROTOCOL = "http/json",
#   OTEL_EXPORTER_OTLP_ENDPOINT = "/path/to/otel_spans.json"
# )
# 
# # Enable collection
# options(shiny.otel.collect = "all")
# 
# # Run your Shiny app
# # Spans will be exported to otel_spans.json
# }

## ----json_export, echo=FALSE--------------------------------------------------
# NA

## ----sqlite_export------------------------------------------------------------
# \dontrun{
# library(otel)
# library(otelsdk)
# 
# # Configure SQLite exporter (custom implementation)
# # Note: Requires additional setup - see otel package documentation
# Sys.setenv(
#   OTEL_TRACES_EXPORTER = "sqlite",
#   OTEL_EXPORTER_SQLITE_PATH = "/path/to/otel_spans.sqlite"
# )
# 
# options(shiny.otel.collect = "all")
# }

## ----live_export--------------------------------------------------------------
# \dontrun{
# library(otel)
# library(otelsdk)
# 
# # Configure OTLP HTTP exporter to send to collector
# Sys.setenv(
#   OTEL_TRACES_EXPORTER = "otlp",
#   OTEL_EXPORTER_OTLP_PROTOCOL = "http/protobuf",
#   OTEL_EXPORTER_OTLP_ENDPOINT = "https://collector.example.com:4318",
#   OTEL_EXPORTER_OTLP_HEADERS = "Authorization=Bearer YOUR_TOKEN",
#   OTEL_RESOURCE_ATTRIBUTES = "service.name=my-shiny-app,environment=production"
# )
# 
# options(shiny.otel.collect = "all")
# }

## ----env_vars-----------------------------------------------------------------
# # Core configuration
# SHINY_OTEL_COLLECT = "all"              # Collection level
# OTEL_TRACES_EXPORTER = "otlp"           # Exporter type
# 
# # OTLP configuration
# OTEL_EXPORTER_OTLP_ENDPOINT = "https://collector:4318"  # Collector URL
# OTEL_EXPORTER_OTLP_PROTOCOL = "http/json"               # Protocol format
# OTEL_EXPORTER_OTLP_HEADERS = "Authorization=Bearer token"  # Auth headers
# 
# # Resource attributes (metadata)
# OTEL_RESOURCE_ATTRIBUTES = "service.name=my-app,environment=prod,version=1.0"
# 
# # Sampling (control data volume)
# OTEL_TRACES_SAMPLER = "traceidratio"    # Sampler type
# OTEL_TRACES_SAMPLER_ARG = "0.1"         # Sample 10% of traces

## ----bidux_workflow-----------------------------------------------------------
# library(bidux)
# library(dplyr)
# 
# # Works just like shiny.telemetry!
# issues <- bid_telemetry("otel_spans.json")
# 
# # Same friction detection
# critical_issues <- issues |>
#   filter(severity == "critical") |>
#   arrange(desc(impact_rate))
# 
# # Same BID pipeline
# interpret <- bid_interpret(
#   central_question = "How to improve user experience based on OTEL data?"
# )
# 
# notices <- bid_notices(
#   issues = critical_issues,
#   previous_stage = interpret,
#   max_issues = 3
# )
# 
# # Extract telemetry flags
# flags <- bid_flags(issues)
# flags$has_critical_issues

## ----auto_detect--------------------------------------------------------------
# # Automatically detects shiny.telemetry format
# issues_st <- bid_telemetry("telemetry.sqlite")
# 
# # Automatically detects OTLP JSON format
# issues_otel <- bid_telemetry("otel_spans.json")
# 
# # Automatically detects OTEL SQLite format
# issues_otel_db <- bid_telemetry("otel_spans.sqlite")
# 
# # Same analysis, same results, regardless of source!

## ----otel_duration------------------------------------------------------------
# # Duration calculated from span timestamps
# # duration_ms = (endTimeUnixNano - startTimeUnixNano) / 1e6
# 
# # Analyze OTEL data
# issues <- bid_telemetry("otel_spans.json")
# 
# # OTEL data provides performance context
# issues |>
#   filter(issue_type == "delayed_interaction") |>
#   select(problem, evidence)
# #> Problem: Users take a long time before making their first interaction
# #> Evidence: Median time to first input is 47 seconds

## ----complete_example---------------------------------------------------------
# \dontrun{
# # ============================================
# # STEP 1: Configure OTEL in your Shiny app
# # ============================================
# 
# library(shiny)
# library(otel)
# library(otelsdk)
# 
# # Enable OTEL with file export
# Sys.setenv(
#   OTEL_TRACES_EXPORTER = "otlp",
#   OTEL_EXPORTER_OTLP_ENDPOINT = "/tmp/shiny_otel.json"
# )
# options(shiny.otel.collect = "all")
# 
# # Your Shiny app
# ui <- fluidPage(
#   titlePanel("Sales Dashboard"),
#   sidebarLayout(
#     sidebarPanel(
#       selectInput("region", "Region:",
#                   choices = c("North", "South", "East", "West")),
#       selectInput("product", "Product:",
#                   choices = c("A", "B", "C")),
#       dateRangeInput("dates", "Date Range:")
#     ),
#     mainPanel(
#       tabsetPanel(
#         tabPanel("Overview", plotOutput("overview")),
#         tabPanel("Details", tableOutput("details")),
#         tabPanel("Settings", uiOutput("settings"))
#       )
#     )
#   )
# )
# 
# server <- function(input, output, session) {
#   output$overview <- renderPlot({
#     # Plotting logic
#   })
# 
#   output$details <- renderTable({
#     # Table logic
#   })
# 
#   output$settings <- renderUI({
#     # Settings UI
#   })
# }
# 
# # Run app and collect data
# shinyApp(ui, server)
# 
# # ============================================
# # STEP 2: Analyze OTEL data with bidux
# # ============================================
# 
# library(bidux)
# library(dplyr)
# 
# # Analyze collected OTEL spans
# issues <- bid_telemetry(
#   "/tmp/shiny_otel.json",
#   thresholds = bid_telemetry_presets("moderate")
# )
# 
# # Review identified issues
# print(issues)
# #> # BID Telemetry Issues Summary
# #> Found 5 issues from 342 sessions
# #>
# #> Critical: 1 issue
# #> High: 2 issues
# #> Medium: 2 issues
# 
# # Filter to critical issues
# critical <- issues |>
#   filter(severity == "critical")
# 
# print(critical)
# #> Issue: unused_input_product
# #> Problem: Users are not interacting with the 'product' input control
# #> Evidence: Only 12 out of 342 sessions (3.5%) interacted with 'product'
# #> Impact: 96.5% of sessions affected
# 
# # ============================================
# # STEP 3: Apply BID framework
# # ============================================
# 
# # Start BID workflow with OTEL insights
# interpret_result <- bid_interpret(
#   central_question = "Why aren't users engaging with the product filter?",
#   data_story = new_data_story(
#     hook = "96.5% of users never use the product filter",
#     context = "OTEL data from 342 sessions over 2 weeks",
#     tension = "Filter may be unnecessary or poorly positioned",
#     resolution = "Simplify interface or improve filter discoverability"
#   )
# )
# 
# # Convert OTEL issue to Notice
# notice_result <- bid_notices(
#   issues = critical,
#   previous_stage = interpret_result
# )[[1]]
# 
# # Continue through BID stages
# anticipate_result <- bid_anticipate(
#   previous_stage = notice_result,
#   bias_mitigations = list(
#     choice_overload = "Reduce number of visible filters",
#     default_effect = "Set smart defaults based on common patterns"
#   )
# )
# 
# # Use OTEL flags to inform structure
# flags <- bid_flags(issues)
# structure_result <- bid_structure(
#   previous_stage = anticipate_result,
#   telemetry_flags = flags
# )
# 
# # Validate
# validate_result <- bid_validate(
#   previous_stage = structure_result,
#   summary_panel = "Simplified dashboard with progressive disclosure",
#   next_steps = c(
#     "Remove or hide unused product filter",
#     "Re-run OTEL analysis to verify improvement",
#     "Monitor user engagement metrics"
#   )
# )
# 
# # Generate report
# bid_report(validate_result, format = "html")
# }

## ----dual_tracking------------------------------------------------------------
# \dontrun{
# library(shiny)
# library(shiny.telemetry)
# library(otel)
# 
# # Enable both systems
# telemetry <- Telemetry$new()
# options(shiny.otel.collect = "all")
# 
# ui <- fluidPage(
#   use_telemetry(),  # shiny.telemetry
#   # Your UI
# )
# 
# server <- function(input, output, session) {
#   telemetry$start_session()  # shiny.telemetry
#   # Your server logic
# }
# 
# # Analyze both sources
# issues_st <- bid_telemetry("telemetry.sqlite")
# issues_otel <- bid_telemetry("otel_spans.json")
# 
# # Compare results
# nrow(issues_st)
# nrow(issues_otel)
# }

## ----migration_phase1---------------------------------------------------------
# \dontrun{
# # Run both systems to compare
# options(shiny.otel.collect = "all")
# # Keep existing shiny.telemetry code
# 
# # Compare results weekly
# issues_st <- bid_telemetry("telemetry.sqlite")
# issues_otel <- bid_telemetry("otel_spans.json")
# 
# # Verify OTEL captures same issues
# }

## ----migration_phase2---------------------------------------------------------
# \dontrun{
# # Switch to OTEL as primary
# options(shiny.otel.collect = "all")
# # Keep shiny.telemetry as backup
# 
# # Use OTEL data for analysis
# issues <- bid_telemetry("otel_spans.json")
# }

## ----migration_phase3---------------------------------------------------------
# \dontrun{
# # Remove shiny.telemetry code
# # library(shiny.telemetry) - remove
# # use_telemetry() - remove
# # telemetry$start_session() - remove
# 
# # OTEL only
# options(shiny.otel.collect = "all")
# }

## ----troubleshoot_1-----------------------------------------------------------
# # Solution: Install OpenTelemetry packages
# install.packages("otel")
# install.packages("otelsdk")

## ----troubleshoot_2-----------------------------------------------------------
# # Check if OTEL is enabled
# getOption("shiny.otel.collect")
# #> Should return "all" or another collection level
# 
# # Verify otel is tracing
# library(otel)
# otel::is_tracing_enabled()
# #> Should return TRUE
# 
# # Enable OTEL if disabled
# options(shiny.otel.collect = "all")

## ----troubleshoot_3-----------------------------------------------------------
# # Verify file structure
# jsonlite::fromJSON("otel_spans.json", simplifyVector = FALSE) |>
#   str(max.level = 2)
# 
# # Should contain spans with startTimeUnixNano, endTimeUnixNano, etc.
# # If not, check OTLP exporter configuration

## ----troubleshoot_4-----------------------------------------------------------
# # Check exporter endpoint
# Sys.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
# 
# # Verify file path is writable
# file.access("otel_spans.json", mode = 2)
# #> Should return 0 (success)
# 
# # Check Shiny app had user interactions
# # Spans only created when actions occur

## ----troubleshoot_5-----------------------------------------------------------
# # Use sampling to reduce volume
# Sys.setenv(
#   OTEL_TRACES_SAMPLER = "traceidratio",
#   OTEL_TRACES_SAMPLER_ARG = "0.1"  # Sample 10% of traces
# )
# 
# # Or reduce collection level
# options(shiny.otel.collect = "reactive_update")  # Less than "all"

## ----troubleshoot_6-----------------------------------------------------------
# # Explicitly specify format
# issues <- bid_telemetry("otel_spans.json", format = "otlp_json")
# 
# # Or for OTEL SQLite
# issues <- bid_telemetry("otel_spans.sqlite", format = "otel_sqlite")

## ----custom_attributes--------------------------------------------------------
# \dontrun{
# library(otel)
# 
# # Add custom attributes to current span
# otel::add_span_attribute("user_role", "analyst")
# otel::add_span_attribute("dashboard_version", "2.1.0")
# 
# # These attributes are preserved in OTLP exports
# # and available for custom analysis
# }

## ----filter_attributes--------------------------------------------------------
# \dontrun{
# # Analyze OTEL data
# issues <- bid_telemetry("otel_spans.json")
# 
# # Access raw span data for custom filtering
# # (Advanced: requires understanding OTLP structure)
# raw_spans <- jsonlite::fromJSON("otel_spans.json")
# 
# # Filter spans by custom attributes before analysis
# # Then re-analyze with bidux
# }

## ----bp1----------------------------------------------------------------------
# options(shiny.otel.collect = "all")

## ----bp2----------------------------------------------------------------------
# Sys.setenv(OTEL_TRACES_SAMPLER_ARG = "0.1")  # 10% sampling

## ----bp3----------------------------------------------------------------------
# \dontrun{
# # Implement log rotation in your deployment
# # Example: daily rotation with retention
# file_pattern <- paste0("otel_spans_", Sys.Date(), ".json")
# }

## ----bp4----------------------------------------------------------------------
# \dontrun{
# file.size("otel_spans.json") / 1024 / 1024  # Size in MB
# }

## ----bp5----------------------------------------------------------------------
# # Schedule regular UX reviews
# issues <- bid_telemetry("otel_spans.json")
# if (any(issues$severity == "critical")) {
#   # Alert team
# }

## ----bp6----------------------------------------------------------------------
# # Use OTEL to identify friction points
# # Then interview users to understand root causes

Try the bidux package in your browser

Any scripts or data that you put into this service are public.

bidux documentation built on Feb. 28, 2026, 1:06 a.m.