|
1 | 1 | library(shiny) |
2 | 2 | library(bslib) |
| 3 | +library(shinyjs) |
| 4 | +library(DT) |
3 | 5 | library(dplyr) |
4 | 6 | library(purrr) |
5 | 7 | library(connectapi) |
| 8 | +library(pins) |
| 9 | +library(tidyr) |
6 | 10 |
|
7 | 11 | shinyOptions( |
8 | | - cache = cachem::cache_disk("./app_cache/cache/", max_age = 60 * 60 * 12) |
| 12 | + cache = cachem::cache_disk("./app_cache/cache/", max_age = 60 * 60 * 24) |
9 | 13 | ) |
10 | 14 |
|
11 | | -ui <- page_fluid( |
| 15 | +ui <- page_fillable( |
| 16 | + useShinyjs(), |
| 17 | + |
12 | 18 | theme = bs_theme(version = 5), |
13 | 19 |
|
14 | 20 | card( |
15 | 21 | card_header("Who Deploys Most Often"), |
16 | 22 | layout_sidebar( |
17 | 23 | sidebar = sidebar( |
18 | | - title = "No Filters Yet", |
19 | | - open = FALSE |
| 24 | + title = "Filter Data", |
| 25 | + open = FALSE, |
| 26 | + actionButton("clear_cache", "Clear Cache", icon = icon("refresh")) |
20 | 27 | ), |
21 | | - card_body("Note: `n_bundles` currently uses synthetic data."), |
| 28 | + card_body(textOutput("data_note"), fill = FALSE), |
22 | 29 | card( |
23 | | - tableOutput("user_table") |
| 30 | + DTOutput("user_table") |
24 | 31 | ) |
25 | 32 | ) |
26 | 33 | ) |
27 | 34 | ) |
28 | 35 |
|
29 | 36 | server <- function(input, output, session) { |
| 37 | + # Cache invalidation button ---- |
| 38 | + cache <- cachem::cache_disk("./app_cache/cache/") |
| 39 | + observeEvent(input$clear_cache, { |
| 40 | + print("Cache cleared!") |
| 41 | + cache$reset() # Clears all cached data |
| 42 | + session$reload() # Reload the app to ensure fresh data |
| 43 | + }) |
| 44 | + |
| 45 | + # Data processing ---- |
| 46 | + |
30 | 47 | client <- connect() |
31 | 48 |
|
32 | 49 | # Load data |
33 | 50 | content <- reactive({ |
34 | | - get_content(client) |> |
35 | | - # Fake data, to be replaced with real data later. |
36 | | - mutate(n_bundles = rpois(n(), 3)) |
| 51 | + get_content(client) |
37 | 52 | }) |> bindCache("static_key") |
38 | 53 |
|
39 | | - user_table <- reactive({ |
40 | | - content() |> |
41 | | - # The `owner` column is a nested list-column. |
42 | | - # We extract the requisite metadata up to be first-class atomic vector columns. |
43 | | - mutate( |
44 | | - username = map_chr(owner, "username"), |
45 | | - user_full_name = paste(map_chr(owner, "first_name"), map_chr(owner, "last_name")) |
46 | | - ) |> |
47 | | - group_by(username) |> |
| 54 | + users <- reactive({ |
| 55 | + get_users(client) |
| 56 | + }) |> bindCache("static_key") |
| 57 | + |
| 58 | + # No need to cache, as it is only loaded when summarized_data is refreshed |
| 59 | + # (and is already cached in a pin). |
| 60 | + audit_logs <- reactive({ |
| 61 | + board <- board_connect() |
| 62 | + PIN_NAME <- paste0(board$account, "/", "connect_metrics_cache_audit_logs") |
| 63 | + pin_read(board, PIN_NAME) |
| 64 | + }) |
| 65 | + |
| 66 | + summarized_data <- reactive({ |
| 67 | + audit_log_summary <- audit_logs() |> |
| 68 | + filter(action %in% c("deploy_application", "add_application")) |> |
| 69 | + group_by(user_guid, action) |> |
48 | 70 | summarize( |
49 | | - user_full_name = first(user_full_name), |
50 | | - n_bundles = sum(n_bundles), |
51 | | - n_content_items = n(), |
52 | | - last_deploy = format(max(last_deployed_time, na.rm = TRUE), "%Y-%m-%d at %I:%M %p") |
| 71 | + n = n(), |
| 72 | + latest = max(time), |
| 73 | + .groups = "drop" |
53 | 74 | ) |> |
54 | | - arrange(desc(n_content_items)) |
| 75 | + pivot_wider(names_from = action, values_from = c(n, latest), values_fill = list(n = 0)) |
| 76 | + |
| 77 | + content_summary <- content() |> |
| 78 | + mutate(user_guid = map_chr(owner, "guid")) |> |
| 79 | + group_by(user_guid) |> |
| 80 | + summarize(active_content = n()) |
| 81 | + |
| 82 | + # Merge all the data together by user guid. |
| 83 | + users () |> |
| 84 | + mutate(full_name = paste(first_name, last_name)) |> |
| 85 | + select(user_guid = guid, username, full_name) |> |
| 86 | + full_join(audit_log_summary, by = "user_guid") |> |
| 87 | + full_join(content_summary, by = "user_guid") |> |
| 88 | + select( |
| 89 | + username, |
| 90 | + full_name, |
| 91 | + active_content, |
| 92 | + n_new_content = n_add_application, |
| 93 | + n_deploy = n_deploy_application, |
| 94 | + latest_deploy = latest_deploy_application |
| 95 | + ) |> |
| 96 | + filter(!(is.na(active_content) & is.na(n_new_content) & is.na(n_deploy))) |
| 97 | + }) |> bindCache("static_key") |
| 98 | + |
| 99 | + # Cached on the same cadence as summarized_data to avoid loading audit logs |
| 100 | + # when not required. |
| 101 | + earliest_record <- reactive({ |
| 102 | + min(audit_logs()$time) |
55 | 103 | }) |> bindCache("static_key") |
56 | 104 |
|
57 | | - output$user_table <- renderTable({user_table()}) |
| 105 | + output$data_note <- renderText( |
| 106 | + paste( |
| 107 | + "* Since", |
| 108 | + earliest_record() |
| 109 | + ) |
| 110 | + ) |
| 111 | + |
| 112 | + output$user_table <- renderDT({ |
| 113 | + print(names(summarized_data())) |
| 114 | + datatable( |
| 115 | + summarized_data(), |
| 116 | + options = list( |
| 117 | + order = list(list(3, "desc")), |
| 118 | + paging = FALSE, |
| 119 | + searching = FALSE |
| 120 | + ), |
| 121 | + filter = "top", |
| 122 | + colnames = c( |
| 123 | + "Username" = "username", |
| 124 | + "User" = "full_name", |
| 125 | + "Total Active Content" = "active_content", |
| 126 | + "Number of Newly Created Content*" = "n_new_content", |
| 127 | + "Number of Deploys*" = "n_deploy", |
| 128 | + "Time of Latest Deploy" = "latest_deploy" |
| 129 | + ) |
| 130 | + ) |> |
| 131 | + formatDate(columns = "Time of Latest Deploy", method = "toLocaleString") |
| 132 | + }) |
58 | 133 | } |
59 | 134 |
|
60 | 135 | shinyApp(ui, server) |
0 commit comments