User Tracking in Shiny Apps: Leveraging {shiny.telemetry} for Comprehensive User Distinction
Delving into the realm of <a href="https://appsilon.com/r-shiny-modules/" target="_blank" rel="noopener">Shiny app development</a>, the path to success often proves to be a complex and evolving landscape. Whether you're a seasoned developer, a data enthusiast, or a tech-savvy manager, the obstacles of developing and delivering compelling applications remain.
In this dynamic environment, solutions like <a href="https://appsilon.github.io/shiny.telemetry/" target="_blank" rel="noopener">{shiny.telemetry}</a> emerge as essential tools, offering <strong>not only advanced <a href="https://appsilon.com/shiny-telemetry-in-r-shiny-dashboards/" target="_blank" rel="noopener">event-tracking capabilities</a> but also a nuanced approach to addressing the intricacies of user engagement, adoption, and impact assessment</strong>.
Join us on this exploration of the significance of user event tracking, the integration of telemetry solutions, and how these advancements empower individuals across various roles to navigate the complexities of Shiny app development successfully.
<h3>Table of Contents</h3><ul> <li><strong><a href="#the-business-imperative">The Business Imperative</a></strong></li> <li><strong><a href="#introducing-shiny-telemetry">Introducing {shiny.telemetry}</a></strong></li> <li><strong><a href="#the-challenge">The Challenge</a></strong></li> <li><strong><a href="#addressing-the-not-logged-user-dilemma">Addressing the Not-Logged User Dilemma</a></strong></li> <li><strong><a href="#extending-telemetry-with-cookies">Extending telemetry with Cookies</a></strong></li> <li><strong><a href="#conclusion">Conclusion</a></strong></li></ul>
<h3>TL;DR:</h3><ul> <li>{shiny.telemetry} is a versatile and technology-agnostic tool essential for monitoring user engagement, assessing the success of rollouts, and understanding user behaviour.</li> <li>There are complexities involved in tracking interactions of users who are not logged in, particularly in environments utilizing technologies like Posit Connect.</li> <li>Explore using cookies - small files stored on users' browsers - to distinguish and track repeat visits by non-logged users effectively.</li> <li>{shiny.telemetry} plays a crucial role in driving data-driven decision-making and product development, and has the ability to provide comprehensive insights for creating user-centric solutions.</li></ul>
<hr />
<h2 id="the-business-imperative">The Business Imperative</h2>
The business imperative becomes evident as there is a pressing need to showcase the success of Data-Driven product rollouts to stakeholders. Tracking adoption emerges as a critical factor, emphasizing the importance of seamless communication within the team and the ability to identify early adopters.
Here are three key aspects that highlight the significance of user event tracking:
<strong>1. Strategic Decision-Making:</strong>
<ul> <li>User event tracking facilitates strategic decision-making by providing real-time insights into user behaviour.</li> <li>The ability to pinpoint early adopters and understand user challenges empowers teams to optimize product functionality proactively.</li></ul>
<strong>2. Demonstrating Impact:</strong>
<ul> <li>Through tracking user events, the focus shifts from building solutions to creating impactful tools.</li> <li>Utilizing granular data from tools like {shiny.telemetry} enables teams to showcase the tangible impact of their creations, impressing stakeholders with a results-oriented approach.</li></ul>
<strong>3. Continuous Improvement:</strong>
<ul> <li>Embracing user event tracking instils a culture of continuous improvement within organizations.</li> <li>Understanding feature usage patterns and promptly addressing user challenges allows teams to enhance their products iteratively, ensuring they evolve in tandem with the dynamic needs of users.</li></ul>
All these underscore the significance of robust user event-tracking solutions.
<h2 id="introducing-shiny-telemetry">Introducing {shiny.telemetry}</h2>
At the forefront of addressing these challenges is {shiny.telemetry}. This versatile package not only facilitates user event tracking <strong>but is also technology agnostic</strong>, making it a c<strong>omprehensive solution for organizations seeking impactful insights</strong>. The ability to <strong>showcase rollout success, monitor adoption trends, and identify potential challenges positions {shiny.telemetry} as an indispensable tool.</strong>
<img class="size-full wp-image-23009" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65e9e65daa096a8e39075b9f_f96ea9ba_telemetry-tracking.webp" alt="" width="800" height="500" /> shiny.telemetry tracking[/caption]
Here are some useful resources:
<ul> <li><a href="https://appsilon.com/shiny-telemetry-in-r-shiny-dashboards/" target="_blank" rel="noopener">A blogpost introducing {shiny.telemetry}</a></li> <li><a href="https://appsilon.github.io/shiny.telemetry/?_gl=1*1lx0er7*_ga*MTU2NzI5MDAwMS4xNjk3OTk4Mjg2*_ga_FQQZL5V93G*MTcwNDg0MzU0NC4xNC4wLjE3MDQ4NDM1NDQuNjAuMC4w" target="_blank" rel="noopener">The GitHub page of the package</a></li></ul>
<h2 id="the-challenge">The Challenge</h2>
Navigating the intricacies of user engagement in Shiny app development often poses a significant challenge: tracking interactions of users who aren't logged in, especially when leveraging technologies like Posit Connect.
The critical question emerges - how does one differentiate between non-logged users effectively? Moreover, the quest for technology agnosticism raises another concern: how can you obtain insights into the distinct users utilizing the app while remaining neutral to underlying technologies?
This challenge, if left unaddressed, can impede businesses from gaining a holistic view of user behaviour, potentially leading to misinterpretations of analytics. <strong>Fortunately, with the integration of {shiny.telemetry} and a cookies-based solution, we present a comprehensive approach to overcome these hurdles, providing a clearer understanding of user interactions regardless of their logged status.</strong>
<blockquote>Explore how R can transform your business workflows in our post: <a href="https://appsilon.com/r-programming-vs-excel-for-business-workflow/" target="_blank" rel="noopener">5 Ways R Programming and R Shiny Can Improve Your Business Workflows.</a></blockquote>
<h2 id="addressing-the-not-logged-user-dilemma">Addressing the Not-Logged User Dilemma</h2>
<h3>The Cookie-Based Solution</h3>
A significant challenge in user event tracking is distinguishing non-logged users. The cookie-based approach provides a nuanced solution. <a href="https://en.wikipedia.org/wiki/HTTP_cookie" target="_blank" rel="noopener noreferrer">Cookies</a>, small files stored on a user's browser, allow web applications to recognize repeated visits. By setting cookies for each visitor, even those not logged in, {shiny.telemetry} can distinguish repeated visits from the same browser, effectively differentiating one non-logged user from another.
<h3>Proxy Recognition</h3>
It's important to note that the cookies-based approach acts as a proxy. In scenarios where the same user accesses the application using an incognito tab or clears browser data, the cookies-based approach will consider those as coming from different users.
<h3>Minimal telemetry Setup</h3>
Let's kick off the Shiny app journey with a minimal setup to track events using telemetry. This isn't just about creating a visually appealing interface; it's about understanding user behaviour, preferences, and needs. Telemetry, in this context, becomes the silent observer, capturing invaluable data that can shape future iterations of the app. The app is designed to have a minimal setup to get us started on the path of event tracking.
<pre><code class="language-r">
library(shiny)
library(shiny.telemetry)
<br>
data_storage <- DataStorageLogFile$new("logs.txt")
telemetry <- Telemetry$new("myApp", data_storage)
shinyApp(
ui = fluidPage(
use_telemetry(),
numericInput("n", "n", 1),
plotOutput('plot')
),
server = function(input, output) {
telemetry$start_session()
output$plot <- renderPlot({ hist(runif(input$n)) })
}
)
</code></pre>
In the above code example, we first set the scene in the global environment of a shiny app by initializing simple data storage to a .txt file and a telemetry <a href="https://appsilon.com/oop-in-r-with-r6/" target="_blank" rel="noopener">R6 object</a>.
<blockquote>Explore the fundamentals of Object-Oriented Programming in R: <a href="https://appsilon.com/object-oriented-programming-in-r-part-1/" target="_blank" rel="noopener">Dive into our concise guide for an easy introduction to OOP concepts.</a></blockquote>
Next, the function call <code>use_telemetry()</code> attaches all necessary JS dependencies through the UI constructor of the shiny app.
Finally, <code>telemetry$start_session()</code> starts the session-specific tracking.
<h2 id="extending-telemetry-with-cookies">Extending telemetry With Cookies</h2>
<h3>Preparing the dependencies</h3>
Let’s set the groundwork by downloading a JavaScript Cookie library. We can easily grab it from an online CDN service:
<pre><code class="language-r">
fs::dir_create("www/")
download.file(
url = "https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js",
destfile = "www/js.cookie.js"
)
</code></pre>
We will also need the following JS functions, added as {shinyjs} extensions for simplicity.
<pre><code class="language-r">
jsCode <- "
shinyjs.bindcookie = function(params) {
var cookie = Cookies.get(params[0]);
Shiny.setInputValue('jscookie', [params[0], cookie]);
}
<br>
shinyjs.setcookie = function(params) {
Cookies.set(params[1], escape(params[0]), { expires: 0.5 });
}
"
</code></pre>
<code>shinyjs.bindcookie()</code>: Fetches the value of a specified cookie in your Shiny app. Leveraging the <code>js-cookie</code> library updates a Shiny input to keep your app seamlessly synchronized with the cookie's information.
<code>shinyjs.setcookie()</code>: Sets a cookie with a specific value and a user-friendly expiration time of 0.5 days.
<h3>The shiny.telemetry App</h3>
Regarding extending the previous minimal example with cookies, in the UI part of the app, we load the <code>{shinyjs}</code> dependencies, and also update the server function:
<pre><code class="language-r">
library(shiny)
library(shinyjs)
library(shiny.telemetry)
<br>data_storage <- DataStorageLogFile$new("logs.txt")
telemetry <- Telemetry$new("myApp", data_storage)
<br>shinyApp(
ui = fluidPage(
tags$head(tags$script(src = "js.cookie.js")),
useShinyjs(),
extendShinyjs(text = jsCode, functions = c("bindcookie", "setcookie")),
use_telemetry(),
numericInput("n", "n", 1),
plotOutput('plot'),
verbatimTextOutput('output')
),
server = function(input, output) {
telemetry$start_session(login = FALSE)
js$bindcookie("uid")
proxy_user_id <- reactive({
uid <- input$jscookie[2]
if(is.null(uid) || is.na(uid)) {
uid <- uuid::UUIDgenerate() js$setcookie(uid, "uid") } uid }) |>
bindEvent(input$jscookie, ignoreNULL = FALSE)
observe({
telemetry$log_login(username = proxy_user_id())
})
output$plot <- renderPlot({ hist(runif(input$n)) })
}
)
</code></pre>
1. <code>js$bindcookie("uid")</code>: Initialise the shiny custom binding to get access on the server side to the proxy user ID (UID) stored in the cookie.
2. <code>checkCookie <- reactive({ ... })</code>: Defines a reactive expression to manage the UID.
<ul> <li>Attempts to extract the UID from the Shiny input variable <code>jscookie</code>.</li> <li>If the UID is absent or NA, generate a new UID.</li> <li>Sets the new UID as a cookie using <code>js$setcookie</code>.</li> <li>Returns the UID for further use.</li></ul>
3. <code>observe({ telemetry$log_login(username = proxy_user_id()) })</code>: Observes UID changes and logs login events through telemetry.
<h2 id="conclusion">Conclusion</h2>
In the journey of data-driven decision-making, {shiny.telemetry} emerge as an indispensable tool. By adopting this package, organizations not only address current challenges but also lay the foundation for a future where user-focused solutions dominate.
The nuances of tracking non-logged users through a cookie-based approach further solidify the effectiveness of these tools in providing comprehensive insights. With the right tools in hand, the path to successful Data-Driven product development becomes clearer, ensuring organizations stay at the forefront of innovation.
<blockquote>Interested in diving deeper into the capabilities of shiny.telemetry? Join André Veríssimo and Wlademir Prates at Shiny Gatherings #10 for an enlightening session titled <a href="https://events.ringcentral.com/events/shiny-gathering-10" target="_blank" rel="noopener">'Open Source Spotlight: Navigating shiny.telemetry for User Tracking in Shiny Apps.' Sign up now for the event on January 30th.</a></blockquote>
<h3>You May Also Like</h3><ul> <li><a href="https://appsilon.com/reactable-extras-enhancing-shiny-applications/" target="_blank" rel="noopener">reactable.extras 0.2.0 Release: Enhanced Interactivity and Efficiency for Shiny Apps</a></li> <li><a href="https://appsilon.com/shiny-telemetry-in-r-shiny-dashboards/">{shiny.telemetry}: Enhanced User Behavior Analytics in R/Shiny Dashboards</a></li> <li><a href="https://appsilon.com/shiny-semantic-0-5-0-release/" target="_blank" rel="noopener">Announcing shiny.semantic 0.5.0: Enhanced Features for Your Shiny Applications</a></li></ul>
<strong>This article was co-authored by <a href="https://appsilon.com/author/wlademir/" target="_blank" rel="noopener">Wlademir Prates</a>.</strong>