{shiny.telemetry}: Enhanced User Behavior Analytics in R/Shiny Dashboards
Do you often find yourself developing an <a href="https://appsilon.com/r-shiny-dashboard-templates/" target="_blank" rel="noopener">R/Shiny dashboard</a> and wish you knew how your users were using the dashboard, which views are being used the most and how inputs are being modified? If so, the <a href="https://appsilon.github.io/shiny.telemetry/" target="_blank" rel="noopener">{shiny.telemetry}</a> R package might be just what you're looking for.
In this post, we'll introduce you to this powerful package and show you how it can enhance your understanding of how your dashboard is being used.
We are announcing the<strong> second release of {shiny.telemetry}</strong> with v0.2.0 that adds the “Microsoft SQL Server” database as a new storage provider, among other bug fixes.
<h3>Table of Contents</h3><ul><li><strong><a href="#introduction">What is {shiny.telemetry}?</a></strong></li><li><strong><a href="#monitoring-input">Monitoring Input and Custom Events</a></strong></li><li><strong><a href="#saving-data">Where to Save the Data?</a></strong></li><li><strong><a href="#look-at-insights">How to Look at the Insights?</a></strong></li><li><a href="#conclusion"><strong>The Impact of Shiny Telemetry</strong></a></li></ul>
<h2 id="introduction">What is {shiny.telemetry}?</h2>
{shiny.telemetry} was first released on <a href="https://cran.r-project.org/web/packages/shiny.telemetry/" target="_blank" rel="noopener">CRAN</a> in May 2023, and it adds the tools to study how users interact with an R/Shiny application.
One of our main design goals with this package is for it to be <strong>easy to use</strong> and to<strong> integrate with existing codebases</strong>. With that in mind, the minimal integration <strong>only requires 3 lines of code,</strong> and you are good to go!
<pre><code>
library(shiny)
library(shiny.telemetry)
<br>telemetry <- Telemetry$new() # 1. Initialize telemetry with default options (store to a local logfile)
<br>shinyApp(
ui = fluidPage(
use_telemetry(), # 2. Add necessary Javascript to Shiny
numericInput("n", "n", 1),
plotOutput('plot')
),
server = function(input, output) {
telemetry$start_session() # 3. Minimal setup to track events
output$plot <- renderPlot({ hist(runif(input$n)) })
}
)
</code></pre>
<strong>This will track a set of inputs and actions out-of-the-box with the <code>start_session()</code> call:</strong>
<ul><li>Length of a session.</li><li>Username detection on Posit Connect instances (customizable for other deployments).</li><li>Browser being used.</li><li>All inputs that are being changed.<ul><li>By default, the values are not stored with default configuration.</li></ul>
</li>
</ul>
These default options can be further extended to support navigation detection when using tabs or <a href="https://github.com/Appsilon/shiny.router/" target="_blank" rel="noopener">{shiny.router}</a>, or disabled to match your needs, as we see in the next section.
<video width="100%" height="auto" src="https://wordpress.appsilon.com/wp-content/uploads/2023/11/telemetry_log.webm" loop="true" autoplay="true" controls="true"></video>
<h2 id="monitoring-input">Monitoring Input and Custom Events</h2>
Listening to all events that an R/Shiny application triggers may be enough for most use cases. For those that need extra customization, {shiny.telemetry} has you covered by allowing you to<strong> track specific inputs</strong> or <strong>side-effects of the user interaction</strong> as custom events.
This can be due to an excessive verbose application that triggers too many inputs during a session, or if the raw value is not the best target to track, instead a calculated reactive value or other side effects contain more informative data.
The {shiny.telemetry} API allows you to explicitly define which inputs to track when the automated tracking of all inputs in <code>telemetry$start_session()</code> is disabled.
<pre><code>
c("input1", "input2", "input4") |>
purrr::walk(shiny.telemtry::log_input)
</code></pre>
Or at a lower level, by observing changes directly and generating a log event every time the specific input or reactive value changes.
<strong>In the snippet of code below, we are tracking a reactive value that is changed on a click to a {plotly} plot (the year selection of the <a href="https://connect.appsilon.com/olympic_history_map/" target="_blank" rel="noopener">Olympic History demo</a>).</strong>
<pre><code>
# Plotly event_data
year_selected <- reactive(event_data("plotly_click", source = "timeline")$x)
<br># Observing a reactive value
observeEvent(year_selected(), {
telemetry$log_custom_event(
"input", # simulating it as an input event. May take any other name
details = list(
id = "timeline",
value = year_selected()
)
)
})
# ...
<br></code></pre>
<h2 id="saving-data">Where to Save the Data?</h2>
{shiny.telemetry} has an extensible framework of data-storage providers, allowing you to tailor the storage solution that best suits your needs. We support 6 different data-storage providers, some are local, and some are remote. Additionally, you can also use a REST API layer that can serve as a bridge from the application with any of the storage providers.
The data can be stored locally on the deployment with a text file or SQLite database, or to a remote/local relational database.
This package currently supports <strong>SQL-family databases</strong> such as MariaDB/MySQL, PostgreSQL and Microsoft SQL server (the latter one is added in the latest release).
<img class="size-full wp-image-21669" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b0198f410a007248504073_image_2023-11-12_13-57-00.webp" alt="" width="2048" height="379" /> SQL-family Databases
We also support a <strong><a href="https://www.rplumber.io" target="_blank" rel="noopener noreferrer">Plumber</a> REST API</strong> that can, in turn, store on all storage providers using the HTTP(S) protocol to securely communicate the telemetry data. This can be an option if a relational database cannot be deployed on your environment.
<h2 id="look-at-insights">How to Look at the Insights?</h2>
{shiny.telemetry} comes with a minimal R/Shiny dashboard that allows you to look at the data that is being collected. It shows the <strong>session</strong>, <strong>navigation</strong>, <strong>input</strong> and <strong>user metrics</strong> allowing one to look at the individual details of each.
We have a <a href="https://connect.appsilon.com/shiny_telemetry-instrumented-app/" target="_blank" rel="noopener">sample dashboard</a> with a few inputs and tab navigation that uses {shiny.telemetry}.
Feel free to interact with that dashboard and then see the results on the <a href="https://connect.appsilon.com/shiny_telemetry-analytics/" target="_blank" rel="noopener">demo analytics application</a> that is shipped with {shiny.telemetry}.
<img class="size-full wp-image-21671" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b01991115f687704a3f4f3_image_2023-11-12_14-05-39.webp" alt="" width="2048" height="583" /> Dashboard
📝 Please note that the data is cleaned periodically.
This demo deployment uses local data storage, and it is fully deployed on <a href="https://posit.co/products/enterprise/connect/" target="_blank" rel="noopener noreferrer">Posit Connect</a>. You can inspect and learn from this deployment by looking at the two folders under <a href="https://github.com/Appsilon/shiny.telemetry/tree/main/inst/examples/app" target="_blank" rel="noopener">shiny.telemetry/inst/examples/app</a>. This allows for the sample dashboard to be deployed and re-deployed without losing all the data as it is stored in a different instance running the Plumber REST API.
<h2 id="conclusion">The Impact of Shiny Telemetry</h2>
Try <a href="https://rhinoverse.dev/#shiny-telemetry" target="_blank" rel="noopener">{shiny.telemetry}</a> yourself on your applications and let us know on Linkedin how you feel about it.
{shiny telemetry} is already being used on our projects and with our clients to help us better understand the users of a dashboard and improve on it to deliver the best possible user experience as per our <a href="https://shinymanifesto.com/" target="_blank" rel="noopener noreferrer">Shiny Manifesto</a>.
<blockquote>To continue reading about {shiny.telemetry} and how we integrated it with our <a href="https://demo.appsilon.com/" target="_blank" rel="noopener">demos</a>, check out the <a href="https://appsilon.com/r-shiny-telemetry-postgresql-timescale/" target="_blank" rel="noopener">fantastic blog post</a> and work by Andres Quintero that combines multiple dashboards' telemetry in a single visualization.</blockquote>
Impressed by the insights from {shiny.telemetry}? Let us show you how it can revolutionize your dashboard analytics. <a href="https://appsilon.com/#contact" target="_blank" rel="noopener">Get in touch</a>!