Skip to content

Commit 43cfec3

Browse files
authored
who deploys most often (interactive table edition) (#32)
1 parent 5e5066d commit 43cfec3

File tree

5 files changed

+955
-24
lines changed

5 files changed

+955
-24
lines changed

extensions/who-deploys-most-often/_quarto.yml

Whitespace-only changes.
Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,135 @@
11
library(shiny)
22
library(bslib)
3+
library(shinyjs)
4+
library(DT)
35
library(dplyr)
46
library(purrr)
57
library(connectapi)
8+
library(pins)
9+
library(tidyr)
610

711
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)
913
)
1014

11-
ui <- page_fluid(
15+
ui <- page_fillable(
16+
useShinyjs(),
17+
1218
theme = bs_theme(version = 5),
1319

1420
card(
1521
card_header("Who Deploys Most Often"),
1622
layout_sidebar(
1723
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"))
2027
),
21-
card_body("Note: `n_bundles` currently uses synthetic data."),
28+
card_body(textOutput("data_note"), fill = FALSE),
2229
card(
23-
tableOutput("user_table")
30+
DTOutput("user_table")
2431
)
2532
)
2633
)
2734
)
2835

2936
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+
3047
client <- connect()
3148

3249
# Load data
3350
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)
3752
}) |> bindCache("static_key")
3853

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) |>
4870
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"
5374
) |>
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)
55103
}) |> bindCache("static_key")
56104

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+
})
58133
}
59134

60135
shinyApp(ui, server)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
name = "who-deploys-most-often"
2+
title = "Who Deploys Most Often (interactive table edition)"
3+
description = "A prototype extension showing who deploys most often."
4+
access_type = "acl"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#' ---
2+
#' title: "Script to get and pin a large segment of audit logs data"
3+
#' ---
4+
5+
library(connectapi)
6+
library(pins)
7+
8+
client <- connect()
9+
10+
audit_logs <- get_audit_logs(client, limit = 1000000, asc_order = FALSE)
11+
12+
board <- board_connect()
13+
PIN_NAME <- paste0(board$account, "/", "connect_metrics_cache_audit_logs")
14+
15+
pin_write(board, audit_logs, name = PIN_NAME)
16+
17+
# To read the data, run: `pin_read(board, PIN_NAME)`

0 commit comments

Comments
 (0)