-
Notifications
You must be signed in to change notification settings - Fork 3
Connect User Metrics #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jakubnowicki
wants to merge
40
commits into
posit-dev:main
Choose a base branch
from
Appsilon:connect-user-metrics
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Connect User Metrics #181
Changes from 24 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
aac0e77
add connect-user-metrics dir
ad72f9d
add manifest file
6820cb0
add connect-user-metrics to enxtensions.yml
627dabf
shorter readme
93a2a8a
use Posit package manager
94e6b43
add dot-files
e8baeaa
Merge remote-tracking branch 'origin/copy-user-metrics' into user-met…
6ac0883
use posit package manager
7107381
remove goal lines from config.yml
42436cf
indent manifest.json
4572c0f
Merge pull request #5 from Appsilon/copy-user-metrics
vituri 8ba0141
fix homepage link
4d0401a
remove tags; update description
d9d70f7
Merge branch 'user-metrics-prepare-manifest-files' into connect-user-…
a1861d1
update links
18ee46d
Merge pull request #6 from Appsilon/use-shortener-links
vituri 18e8d93
Add About the app section
jakubnowicki e772ebc
Merge pull request #9 from Appsilon/update-modal-text
jakubnowicki 6133051
Pre-release manifest.json update.
jakubnowicki 2a282bb
Merge pull request #10 from Appsilon/pre-release-adjustments
jakubnowicki 499ba46
copy changes from main-connect-extension
be09423
copy changes from main-connect-extension
bba6a4f
Merge remote-tracking branch 'refs/remotes/origin/pre-connect-release…
f31a566
Merge pull request #14 from Appsilon/pre-connect-release-fixes
vituri 3601137
build: upgrade dependencies
TymekAppsilon ddf8e3a
build: add rsconnect
TymekAppsilon c772ce2
refactor: address lints
TymekAppsilon 2baadba
build: remove unused dependency
TymekAppsilon 3363159
build: remove dependencies.R
TymekAppsilon d1961cf
fix: set window_title
TymekAppsilon 83a9fc9
chore: update manifest.json
TymekAppsilon 4803442
Merge remote-tracking branch 'upstream/main' into connect-user-metrics
TymekAppsilon 9c357c2
docs: update dead link
TymekAppsilon 7fb37de
chore: remove unused file
TymekAppsilon e3490d2
chore: update manifest.json
TymekAppsilon f31f418
style: update wording
TymekAppsilon 78b7868
fix: fall back to GUID when both name and title are missing
TymekAppsilon 95f7b20
fix: fall back to GUID when username is missing
TymekAppsilon 583691c
fix: handle connection issues gracefully
TymekAppsilon 33fbddf
build: downgrade to R 4.3.1
TymekAppsilon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| if (file.exists("renv")) { | ||
| source("renv/activate.R") | ||
| } else { | ||
| # The `renv` directory is automatically skipped when deploying with rsconnect. | ||
| message("No 'renv' directory found; renv won't be activated.") | ||
| } | ||
|
|
||
| # Allow absolute module imports (relative to the app root). | ||
| options(box.path = getwd()) | ||
|
|
||
| if (nzchar(system.file(package = "box.lsp"))) { | ||
| options( | ||
| languageserver.parser_hooks = list( | ||
| "box::use" = box.lsp::box_use_parser | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| options(repos = c(CRAN = "https://packagemanager.posit.co/cran/latest")) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .Renviron | ||
| .Rproj.user | ||
| .Rhistory | ||
| .DS_Store |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| linters: | ||
| linters_with_defaults( | ||
| defaults = box.linters::rhino_default_linters, | ||
| line_length_linter = line_length_linter(100) | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Only use `dependencies.R` to infer project dependencies. | ||
| * | ||
| !dependencies.R |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| .github | ||
| .lintr | ||
| .renvignore | ||
| .Renviron | ||
| .rhino | ||
| .rscignore | ||
| tests |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # How to contribute | ||
|
|
||
| This branch is a *simplified* version of Connect User Metrics, tailored to be deployed on Posit Connect Gallery. | ||
|
|
||
| For the full version of the app, please refer to [the main branch of Connect User Metrics](https://github.com/Appsilon/ConnectUserMetrics). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # Connect User Metrics Dashboard | ||
|
|
||
| Connect User Metrics makes it easy to monitor **application adoption**, | ||
| track **user engagement** and access detailed **usage analytics** | ||
| for all your Shiny applications deployed on Posit Connect. | ||
| Some key features: | ||
|
|
||
| - **Time-based analysis**: View data by day, week, or month across custom time periods | ||
| - **Flexible grouping**: Combine metrics by application, user, and date for different perspectives | ||
| - **Interactive charts**: Visualize session counts and unique users with dynamic filtering | ||
| - **Smart filtering**: Set minimum session duration and filter by specific apps or users | ||
| - **Data export**: Download raw and aggregated data as CSV files for further analysis | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| This application requires the following environment variables to be set: | ||
|
|
||
| - `CONNECT_API_KEY` | ||
| - `CONNECT_SERVER` | ||
|
|
||
| By default, Posit Connect provides values for these variables, as outlined in the [Vars (Environment Variables) Section][User Guide Vars]. | ||
| However, there are cases where you might want to set these variables manually: | ||
|
|
||
| - If you want to retrieve data for applications deployed by a different Publisher than the one | ||
| deploying the User Metrics application, set `CONNECT_API_KEY` with that Publisher's API key. | ||
| - If you want to retrieve data from a different Posit Connect instance than the one where the User | ||
| Metrics application is deployed, set `CONNECT_SERVER` with the URL of that instance. | ||
| Additionally, `CONNECT_API_KEY` must be set to authenticate on the instance specified in `CONNECT_SERVER`. | ||
|
|
||
| ## Disclaimer | ||
|
|
||
| Posit Connect usage data is most accurate for applications accessed by authenticated users. | ||
| Unauthenticated users cannot be distinguished, and will be seen in the app as "Unknown user". | ||
|
|
||
| Read more: [_Why You Should Use Posit Connect Authentication And How to Set It Up_][rsconnect-auth]. | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| ### Posit Connect does not appear to have `CONNECT_SERVER` and `CONNECT_API_KEY` set | ||
|
|
||
| Per the [Configuration appendix] in the Posit Connect Admin Guide, these variables are set by default. | ||
| However, this behavior can be overridden via [DefaultServerEnv] and [DefaultAPIKeyEnv]. | ||
|
|
||
| Check with your Posit Connect administrator if that's the case. | ||
|
|
||
| ### The API connection fails due to a timeout after deploying the User Metrics application | ||
|
|
||
| If the connection times out using the default environment variables, the issue may be that the server cannot resolve its own fully qualified domain name. | ||
|
|
||
| To fix this, go to the User Metrics [application Vars][User Guide Vars] and set `CONNECT_SERVER` to a local address, e.g. `http://localhost:3939`. | ||
|
|
||
| (Note: the scheme in the URL is required by `connectapi::connect()`.) | ||
|
|
||
| ### There is no usage data for my application | ||
|
|
||
| As with environment variables, the [Instrumentation] feature is also configurable. | ||
|
|
||
| Confirm with your Posit Connect admin that instrumentation is enabled. | ||
|
|
||
| <!-- Links --> | ||
| [User Guide Vars]: https://docs.posit.co/connect/user/content-settings/#content-vars | ||
| [rsconnect-auth]: https://go.appsilon.com/why-use-rstudio-connect-authentication-user-metrics-app | ||
| [Configuration appendix]: https://docs.posit.co/connect/admin/appendix/configuration/ | ||
| [DefaultServerEnv]: https://docs.posit.co/connect/admin/appendix/configuration/#Applications.DefaultServerEnv | ||
| [DefaultAPIKeyEnv]: https://docs.posit.co/connect/admin/appendix/configuration/#Applications.DefaultAPIKeyEnv | ||
| [Instrumentation]: https://docs.posit.co/connect/admin/appendix/configuration/#Metrics.Instrumentation |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| meta: | ||
| app_title: "Posit Connect User Metrics" | ||
| credits: | ||
| enabled: TRUE | ||
| about: | ||
| references: | ||
| homepage: | ||
| name: "Link to Appsilon" | ||
| link: "https://go.appsilon.com/appsilon-user-metrics-app" | ||
| powered_by: | ||
| rhino: | ||
| name: "Rhino" | ||
| link: "https://go.appsilon.com/github-rhino-user-metrics-app" | ||
| img_name: "rhino.png" | ||
| desc: "Rhino is an Open-Source Package developed by Appsilon to | ||
| help the R community make more professional Shiny Apps. Rhino allows you to | ||
| create Shiny apps The Appsilon Way - like a fullstack software engineer. | ||
| Apply best software engineering practices, modularize your code, | ||
| test it well, make UI beautiful, and think about user adoption | ||
| from the very beginning." | ||
| summary: "We create, maintain, and develop Shiny applications | ||
| for enterprise customers all over the world. Appsilon | ||
| provides scalability, security, and modern UI/UX with | ||
| custom R packages that native Shiny apps do not provide. | ||
| Our team is among the world's foremost experts in R Shiny | ||
| and has made a variety of Shiny innovations over the | ||
| years. Appsilon is a proud Posit Full Service | ||
| Certified Partner." | ||
| footer: | ||
| text: "Designed and developed with 💙 by" | ||
| link: | ||
| label: "Appsilon" | ||
| url: "https://go.appsilon.com/appsilon-user-metrics-app" | ||
|
|
||
| logo: "appsilon-logo.png" | ||
|
|
||
| color: | ||
| palette: | ||
| white: "#FFFFFF" | ||
| mint: "#00CDA3" | ||
| blue: "#0099F9" | ||
| yellow: "#E8C329" | ||
| purple: "#994B9D" | ||
| black: "#000000" | ||
| gray: "#15354A" | ||
| foreground: gray | ||
| background: white | ||
| primary: blue | ||
|
|
||
| typography: | ||
| fonts: | ||
| - family: Maven Pro | ||
| source: google | ||
| weight: [400, 500, 600, 700] | ||
| style: normal | ||
| - family: Roboto | ||
| source: google | ||
| weight: [400, 500, 600] | ||
| style: normal | ||
| base: | ||
| Roboto | ||
| headings: | ||
| Maven Pro |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Rhino / shinyApp entrypoint. Do not edit. | ||
| rhino::app() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| static_data.rds |
2 changes: 2 additions & 0 deletions
2
extensions/connect-user-metrics/app/constants/filter_users.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| users: | ||
| - this_user_will_not_appear_on_the_app |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Logic: application code independent from Shiny. | ||
| # https://go.appsilon.com/rhino-project-structure |
130 changes: 130 additions & 0 deletions
130
extensions/connect-user-metrics/app/logic/aggregation.R
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| box::use( | ||
| dplyr, | ||
| lubridate[floor_date], | ||
| magrittr[`%>%`], | ||
| stats[setNames], | ||
| tools[toTitleCase], | ||
| ) | ||
|
|
||
| box::use( | ||
| app/logic/utils[ | ||
| week_start_day, | ||
| week_start_id | ||
| ], | ||
| ) | ||
|
|
||
| #' Aggregate usage data based on specified levels and time period | ||
| #' @param usage Usage data frame | ||
| #' @param agg_levels Aggregation levels | ||
| #' @param date_aggregation Time period for aggregation | ||
| #' @return Aggregated data frame | ||
| aggregate_usage <- function(usage, agg_levels, date_aggregation) { | ||
| usage$day <- floor_date(usage$start_date, "day") | ||
| usage$week <- floor_date(usage$start_date, "week", | ||
| week_start = week_start_id | ||
| ) | ||
| usage$month <- floor_date(usage$start_date, "month") | ||
|
|
||
| if (date_aggregation == "day") { | ||
| usage$start_date <- usage$start_date | ||
| } else if (date_aggregation == "week") { | ||
| usage$start_date <- usage$week | ||
| } else if (date_aggregation == "month") { | ||
| usage$start_date <- usage$month | ||
| } | ||
|
|
||
| if (!is.null(agg_levels)) { | ||
| usage <- usage %>% | ||
| dplyr$group_by(dplyr$across(dplyr$all_of(agg_levels))) | ||
| } | ||
|
|
||
| user_in_agg_levels <- "user_guid" %in% agg_levels | ||
| if (user_in_agg_levels) { | ||
| usage <- usage %>% | ||
| dplyr$filter(!is.na(user_guid)) | ||
| } | ||
|
|
||
| usage %>% | ||
| dplyr$summarise( | ||
| avg_duration = mean(duration, na.rm = TRUE), | ||
| "Session count" = dplyr$n(), | ||
| "Unique users" = dplyr$n_distinct(user_guid) | ||
| ) %>% | ||
| dplyr$ungroup() | ||
| } | ||
|
|
||
| #' Add metadata to aggregated usage data | ||
| #' @param agg_usage Aggregated usage data | ||
| #' @param apps Apps data frame | ||
| #' @param users Users data frame | ||
| #' @param content_guid_present Whether content_guid is in aggregation levels | ||
| #' @param user_guid_present Whether user_guid is in aggregation levels | ||
| #' @return Aggregated usage data with metadata | ||
| add_metadata <- function(agg_usage, apps, users, content_guid_present, user_guid_present) { | ||
| if (content_guid_present) { | ||
| agg_usage <- agg_usage %>% | ||
| dplyr$left_join(apps, by = c("content_guid" = "guid")) | ||
| } | ||
| if (user_guid_present) { | ||
| agg_usage <- agg_usage %>% | ||
| dplyr$left_join(users, by = c("user_guid" = "guid")) | ||
| } | ||
| agg_usage | ||
| } | ||
|
|
||
| #' Process aggregated usage data | ||
| #' @param usage Usage data frame | ||
| #' @param agg_levels Vector of aggregation levels | ||
| #' @param date_aggregation Date aggregation level | ||
| #' @param apps Apps data frame | ||
| #' @param users Users data frame | ||
| #' @return Aggregated usage data frame | ||
| #' @export | ||
| process_agg_usage <- function(usage, agg_levels, date_aggregation, apps, users) { | ||
| content_guid_present <- "content_guid" %in% agg_levels | ||
| user_guid_present <- "user_guid" %in% agg_levels | ||
|
|
||
| # If neither content_guid nor user_guid is present, just do basic aggregation | ||
| if (!content_guid_present && !user_guid_present) { | ||
| return(aggregate_usage(usage, agg_levels, date_aggregation)) | ||
| } | ||
|
|
||
| # If no start_date in agg_levels and only one of content/user guid, | ||
| # force both to be present | ||
| if (!"start_date" %in% agg_levels && xor(content_guid_present, user_guid_present)) { | ||
| agg_levels <- c("content_guid", "user_guid") | ||
| content_guid_present <- user_guid_present <- TRUE | ||
| } | ||
|
|
||
| # Do the aggregation and add metadata | ||
| agg_usage <- aggregate_usage(usage, agg_levels, date_aggregation) | ||
| add_metadata(agg_usage, apps, users, content_guid_present, user_guid_present) | ||
| } | ||
|
|
||
| #' Format aggregated usage data for display | ||
| #' @param agg_usage Aggregated usage data frame | ||
| #' @param date_aggregation Date aggregation level ("week", "month", or "day") | ||
| #' @param format_duration Function to format duration values | ||
| #' @return Formatted data frame for display | ||
| #' @export | ||
| format_agg_usage <- function(agg_usage, date_aggregation, format_duration) { | ||
| date_col <- switch(date_aggregation, | ||
| "week" = paste(toTitleCase(week_start_day), "Date"), | ||
| "month" = "Month", | ||
| "Date" | ||
| ) | ||
|
|
||
| # Create ordered column mapping | ||
| cols <- c( | ||
| setNames("title", "Application"), | ||
| setNames("username", "Username"), | ||
| setNames("start_date", date_col), | ||
| "Session count", | ||
| "Unique users", | ||
| setNames("avg_duration", "Average session duration") | ||
| ) | ||
|
|
||
| agg_usage %>% | ||
| dplyr$mutate(avg_duration = format_duration(avg_duration)) %>% | ||
| dplyr$select(dplyr$any_of(cols)) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed there are some features not included in the
READMEthat are in theAppsilon/ConnectUsersMetricsrepo README. That totally makes sense given how the READMEs will be consumed for Gallery content.It makes me wonder if there should be a CONTRIBUTING guide here that includes some of those details so this file, for example, is a bit easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This app here is a simplified version of the origina Connect User Metrics; some of the features only make sense in the complete app, where the user is able to modify some configuration files.
I will add a CONTRIBUTE file pointing to the original branch.