Skip to content

Commit

Permalink
Merge pull request #12 from Appsilon/feat/gracefully-handle-app-list-…
Browse files Browse the repository at this point in the history
…error

Feat: Graceful Handling of First-touch API Call (App List)
  • Loading branch information
DeepanshKhurana authored Jun 12, 2024
2 parents adbadfc + 2398cc4 commit e7dd624
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 39 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ The LogAnalyzer open-source app is a simple, plug and play application developed

- `[app/logic/api_utils.R` - `get_app_list()]` Posit Connect differentiates apps with two `app_role` values: `owner` and `viewer`. You can toggle between these using the `config.yml` file. The set value is `owner`. If you want to use both together, you can simply set the value to a blank character `""`.

# FAQs

- I get `"Oops! Can't read apps from Posit Connect."` on the rightmost image?
- This may mean that the Posit Connect API's response did not send proper data.
- So far, one documented reason for this is that OAuth on Posit Connect instances may prevent the `/content` endpoint from sending app data.

# Credits

It was our collaboration with <img src="img/elkem_logo.png" alt="Elkem" width="50"/> which led to the creation of this app. The initial idea came from use-cases where we realised we wanted to track all the logs and be able to read them properly since Posit Connect was the de facto deployment environment. When we made this app, we realised there was potential in sharing this with the rest of the community and invite everyone to use it and add it. We appreciate and thank Elkem for their openness to share it with the world.
Expand Down
30 changes: 30 additions & 0 deletions app/logic/empty_state_utils.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
box::use(
shiny[
div,
img,
p,
renderUI
],
)

#' @description Function to generate an empty state UI
#' @param text Text to display in the empty state
#' @param image_path Path to the image to display in the empty state
#' @export
generate_empty_state_ui <- function(
text = "Select an application and a job to view logs",
image_path = "static/illustrations/empty_state.svg"
) {
div(
class = "empty-state-container",
p(
class = "empty-state-text",
text
),
img(
src = image_path,
class = "empty-state-image",
alt = text
)
)
}
50 changes: 27 additions & 23 deletions app/main.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ box::use(

box::use(
app/logic/api_utils[get_app_list],
app/logic/empty_state_utils[generate_empty_state_ui],
app/view/mod_app_table,
app/view/mod_header,
app/view/mod_job_list,
Expand Down Expand Up @@ -79,18 +80,12 @@ server <- function(id) {

mod_app_table$server(
"app_table",
app_list() %>%
select(
guid,
name,
r_version,
dashboard_url,
last_deployed_time
),
app_list(),
state
)

observeEvent(state$selected_app()$guid, {

if (isTruthy(state$selected_app()$guid)) {

output$job_list_pane <- renderUI({
Expand All @@ -101,12 +96,16 @@ server <- function(id) {
"job_list",
state
)

} else {

removeUI(ns("job_list_pane"))

}
}, ignoreInit = TRUE, ignoreNULL = TRUE)
}, ignoreNULL = FALSE)

observeEvent(state$selected_job()$key, {

if (isTruthy(state$selected_job()$key)) {

output$logs_pane <- renderUI({
Expand All @@ -118,22 +117,27 @@ server <- function(id) {
state
)
} else {
output$logs_pane <- renderUI({
div(
class = "empty-state-container",
p(
class = "empty-state-text",
"Select an application and a job to view logs"
),
img(
src = "static/empty_state.svg",
class = "empty-state-image",
alt = "Select an application and a job to view logs"

if (!inherits(app_list(), "data.frame")) {
empty_state <- renderUI({
generate_empty_state_ui(
text = "Oops! Can't read apps from Posit Connect.",
image_path = "static/illustrations/missing_apps.svg"
)
)
})
})
} else {
empty_state <- renderUI({
generate_empty_state_ui(
text = "Select an application and a job to view logs.",
image_path = "static/illustrations/empty_state.svg"
)
})
}

output$logs_pane <- empty_state
}
}, ignoreInit = FALSE, ignoreNULL = FALSE)

}, ignoreNULL = FALSE)

})
}
2 changes: 1 addition & 1 deletion app/static/css/app.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes
1 change: 1 addition & 0 deletions app/static/illustrations/missing_apps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
font-family: "Maven Pro", sans-serif;
}

body {
overflow: hidden;
}

.vertical-line {
border-left: 1px $grey2-border solid;
height: 80%;
Expand Down
51 changes: 36 additions & 15 deletions app/view/mod_app_table.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ box::use(
colDef,
getReactableState,
reactable,
reactableLang,
reactableOutput,
renderReactable
],
shiny[
isTruthy,
moduleServer,
NS,
reactive
Expand Down Expand Up @@ -41,23 +43,37 @@ server <- function(id, app_list, state) {

output$app_table <- renderReactable({

processed_apps <- app_list %>%
mutate(
name = paste(
if (length(app_list) > 0 && inherits(app_list, "data.frame")) {
processed_apps <- app_list %>%
select(
guid,
name,
r_version,
dashboard_url,
last_deployed_time,
sep = "_-_"
)
) %>%
select(
-c(
r_version,
dashboard_url,
last_deployed_time
) %>%
mutate(
name = paste(
name,
r_version,
dashboard_url,
last_deployed_time,
sep = "_-_"
)
) %>%
select(
-c(
r_version,
dashboard_url,
last_deployed_time
)
)
} else {
processed_apps <- data.frame(
guid = character(),
name = character()
)
}

reactable(
data = processed_apps,
Expand All @@ -75,16 +91,21 @@ server <- function(id, app_list, state) {
process_app_data(app_data)
}
)
),
language = reactableLang(
noData = "No apps found."
)
)
})

state$selected_app <- reactive({
index <- getReactableState("app_table", "selected")
list(
"guid" = app_list[index, ]$guid,
"name" = app_list[index, ]$name
)
if (isTruthy(index) && length(app_list > 0)) {
list(
"guid" = app_list[index, ]$guid,
"name" = app_list[index, ]$name
)
}
})

})
Expand Down

0 comments on commit e7dd624

Please sign in to comment.