From 8089895900d694047bb04ace037bf2fe4656cc42 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Fri, 22 Nov 2024 16:59:58 +0530 Subject: [PATCH 01/27] feat: add theming with config.yml feat: add functions for coloring svg files feat: replace sass colors with css colors --- app/logic/empty_state_utils.R | 45 +- app/logic/general_utils.R | 30 ++ app/main.R | 84 ++-- app/static/css/app.min.css | 2 +- app/static/illustrations/empty_state.svg | 4 +- app/static/illustrations/missing_apps.svg | 2 +- app/styles/_app_table.scss | 2 +- app/styles/_colors.scss | 29 +- app/styles/_dashboard.scss | 8 +- app/styles/_header.scss | 2 +- app/styles/_job_list.scss | 2 +- app/styles/_logs.scss | 2 +- app/styles/main.scss | 2 +- config.yml | 19 + dependencies.R | 2 + renv.lock | 495 ++++++++++++---------- 16 files changed, 418 insertions(+), 312 deletions(-) diff --git a/app/logic/empty_state_utils.R b/app/logic/empty_state_utils.R index 3acbd10..e11d5f4 100644 --- a/app/logic/empty_state_utils.R +++ b/app/logic/empty_state_utils.R @@ -1,9 +1,13 @@ box::use( + base64enc[ + base64encode + ], shiny[ div, img, p, - renderUI + renderUI, + tagAppendAttributes ], ) @@ -13,7 +17,8 @@ box::use( #' @export generate_empty_state_ui <- function( text = "Select an application and a job to view logs", - image_path = "static/illustrations/empty_state.svg" + image_path = "static/illustrations/empty_state.svg", + color = NULL ) { div( class = "empty-state-container", @@ -21,10 +26,40 @@ generate_empty_state_ui <- function( class = "empty-state-text", text ), - img( - src = image_path, - class = "empty-state-image", + replace_svg_fill( + color = color, + svg_path = image_path, alt = text ) ) } + +#' Function to replace fill color in SVG +#' @param color Character. The color to replace +#' @param svg_path Character. The path to the SVG file +#' @param placeholder Character. The placeholder. Default is "PRIMARY" +#' @param alt Character. The alt text for the image +#' @return an image tag with the SVG content +replace_svg_fill <- function( + color, + svg_path = "", + placeholder = "PRIMARY", + alt = "", + class = "empty-state-image" +) { + svg_content <- readLines(svg_path) + svg_content <- paste(svg_content, collapse = "\n") + svg_content <- gsub( + placeholder, + color, + svg_content + ) + img( + class = class, + src = paste0( + "data:image/svg+xml;base64,", + base64encode(charToRaw(svg_content)) + ), + alt = alt + ) +} diff --git a/app/logic/general_utils.R b/app/logic/general_utils.R index 30e33bf..c45cbe2 100644 --- a/app/logic/general_utils.R +++ b/app/logic/general_utils.R @@ -1,3 +1,12 @@ +box::use( + config[ + get + ], + purrr[ + map_chr + ], +) + #' Function to check if a string of log text has error keywords #' #' @param text Character. The log string @@ -37,3 +46,24 @@ format_timestamp <- function( format = to ) } + +#' Generate CSS variables from config.yml +#' @param config the config file +#' @return a string of CSS variables within :root {} +#' @export +generate_css_variables <- function( + config = get("branding") +) { + css_lines <- map_chr( + names(config$colors), + function(name) { + color_value <- config$colors[[name]] + sprintf(" --%s: %s;", name, color_value) + } + ) + paste0( + ":root {\n", + paste(css_lines, collapse = "\n"), + "\n}" + ) +} diff --git a/app/main.R b/app/main.R index 4adf1dd..613b42f 100644 --- a/app/main.R +++ b/app/main.R @@ -1,24 +1,9 @@ # nolint start: box_func_import_count_linter box::use( + config[get], dplyr[select], magrittr[`%>%`], - shiny[ - div, - fluidPage, - img, - isTruthy, - moduleServer, - NS, - observeEvent, - p, - reactive, - reactiveValues, - removeUI, - renderUI, - tagList, - tags, - uiOutput - ], + shiny, shinycssloaders[withSpinner], ) # nolint end @@ -26,6 +11,7 @@ box::use( box::use( app/logic/api_utils[get_app_list], app/logic/empty_state_utils[generate_empty_state_ui], + app/logic/general_utils[generate_css_variables], app/view/mod_app_table, app/view/mod_header, app/view/mod_job_list, @@ -34,29 +20,32 @@ box::use( #' @export ui <- function(id) { - ns <- NS(id) - fluidPage( + ns <- shiny$NS(id) + shiny$fluidPage( class = "dashboard-body", + shiny$tags$head( + shiny$uiOutput(ns("dynamic_colors")) + ), mod_header$ui("header"), - div( + shiny$div( class = "dashboard-container", - div( + shiny$div( class = "app-table", mod_app_table$ui(ns("app_table")) ), - div( + shiny$div( class = "vertical-line" ), - div( + shiny$div( class = "job-list", - uiOutput(ns("job_list_pane")) + shiny$uiOutput(ns("job_list_pane")) ), - div( + shiny$div( class = "vertical-line" ), - div( + shiny$div( class = "logs", - uiOutput(ns("logs_pane")) + shiny$uiOutput(ns("logs_pane")) ) ) ) @@ -64,17 +53,24 @@ ui <- function(id) { #' @export server <- function(id) { - moduleServer(id, function(input, output, session) { + shiny$moduleServer(id, function(input, output, session) { ns <- session$ns + branding <- get("branding") + + output$dynamic_colors <- shiny$renderUI({ + css_content <- generate_css_variables(branding) + shiny$tags$style(shiny$HTML(css_content)) + }) + mod_header$server("header") - state <- reactiveValues() - state$selected_app <- reactive({}) - state$selected_job <- reactive({}) + state <- shiny$reactiveValues() + state$selected_app <- shiny$reactive({}) + state$selected_job <- shiny$reactive({}) - app_list <- reactive({ + app_list <- shiny$reactive({ get_app_list() }) @@ -84,11 +80,11 @@ server <- function(id) { state ) - observeEvent(state$selected_app()$guid, { + shiny$observeEvent(state$selected_app()$guid, { - if (isTruthy(state$selected_app()$guid)) { + if (shiny$isTruthy(state$selected_app()$guid)) { - output$job_list_pane <- renderUI({ + output$job_list_pane <- shiny$renderUI({ mod_job_list$ui(ns("job_list")) }) @@ -99,16 +95,16 @@ server <- function(id) { } else { - removeUI(ns("job_list_pane")) + shiny$removeUI(ns("job_list_pane")) } }, ignoreNULL = FALSE) - observeEvent(state$selected_job()$key, { + shiny$observeEvent(state$selected_job()$key, { - if (isTruthy(state$selected_job()$key)) { + if (shiny$isTruthy(state$selected_job()$key)) { - output$logs_pane <- renderUI({ + output$logs_pane <- shiny$renderUI({ mod_logs$ui(ns("logs")) }) @@ -119,17 +115,19 @@ server <- function(id) { } else { if (!inherits(app_list(), "data.frame")) { - empty_state <- renderUI({ + empty_state <- shiny$renderUI({ generate_empty_state_ui( text = "Oops! Can't read apps from Posit Connect.", - image_path = "static/illustrations/missing_apps.svg" + image_path = "app/static/illustrations/missing_apps.svg", + color = branding$colors$primary ) }) } else { - empty_state <- renderUI({ + empty_state <- shiny$renderUI({ generate_empty_state_ui( text = "Select an application and a job to view logs.", - image_path = "static/illustrations/empty_state.svg" + image_path = "app/static/illustrations/empty_state.svg", + color = branding$colors$primary ) }) } diff --git a/app/static/css/app.min.css b/app/static/css/app.min.css index 1060d16..79ee1dc 100644 --- a/app/static/css/app.min.css +++ b/app/static/css/app.min.css @@ -1 +1 @@ -@import"https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:#a50e0e}.green-text{color:#3a5a40}.yellow-text{color:#a58e0e}.red-highlight{background-color:rgba(252,232,230,.3137254902)}.green-highlight{background-color:rgba(224,240,223,.3137254902)}.yellow-highlight{background-color:rgba(240,235,187,.3137254902)}.red-text{color:#a50e0e}.green-text{color:#3a5a40}.yellow-text{color:#a58e0e}.red-highlight{background-color:rgba(252,232,230,.3137254902)}.green-highlight{background-color:rgba(224,240,223,.3137254902)}.yellow-highlight{background-color:rgba(240,235,187,.3137254902)}.logs .rt-td-inner{padding:0 !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:gray;margin-bottom:40px}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:.75em}.logs-container .logs-download{position:absolute;z-index:2;right:0;margin:10px;background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:#fff;color:#333;height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:rgba(0,0,0,0)}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:rgba(0,0,0,.062745098)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:#fff;width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:gray;font-size:.75em}.red-text{color:#a50e0e}.green-text{color:#3a5a40}.yellow-text{color:#a58e0e}.red-highlight{background-color:rgba(252,232,230,.3137254902)}.green-highlight{background-color:rgba(224,240,223,.3137254902)}.yellow-highlight{background-color:rgba(240,235,187,.3137254902)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:.75em;color:gray}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:#0099f9;color:#fff;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro",sans-serif}body{overflow:hidden}.vertical-line{border-left:1px #eee solid;height:80%;align-self:center} +@import"https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.logs .rt-td-inner{padding:0 !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:var(--grey-text);margin-bottom:40px}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:.75em}.logs-container .logs-download{position:absolute;z-index:2;right:0;margin:10px;background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:var(--white);color:var(--black-text);height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:rgba(0,0,0,0)}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:var(--selected-row)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:var(--white);width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:var(--grey-text);font-size:.75em}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:.75em;color:var(--grey-text)}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:var(--primary);color:#fff;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro",sans-serif}body{overflow:hidden}.vertical-line{border-left:1px var(--grey2-border) solid;height:80%;align-self:center} diff --git a/app/static/illustrations/empty_state.svg b/app/static/illustrations/empty_state.svg index f64d1ee..722a490 100644 --- a/app/static/illustrations/empty_state.svg +++ b/app/static/illustrations/empty_state.svg @@ -7,7 +7,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/app/static/illustrations/missing_apps.svg b/app/static/illustrations/missing_apps.svg index df10112..65e230a 100644 --- a/app/static/illustrations/missing_apps.svg +++ b/app/static/illustrations/missing_apps.svg @@ -1 +1 @@ -server down \ No newline at end of file +server down diff --git a/app/styles/_app_table.scss b/app/styles/_app_table.scss index 1904be5..676d8d1 100644 --- a/app/styles/_app_table.scss +++ b/app/styles/_app_table.scss @@ -17,7 +17,7 @@ display: flex; flex-direction: column; gap: 5px; - color: $grey-text; + color: var(--grey-text); font-size: 0.75em; } } diff --git a/app/styles/_colors.scss b/app/styles/_colors.scss index 9c7f1cb..0be7e97 100644 --- a/app/styles/_colors.scss +++ b/app/styles/_colors.scss @@ -1,40 +1,23 @@ -$red: #a50e0e; -$red-highlight: #fce8e650; -$green: #3a5a40; -$green-highlight: #e0f0df50; -$yellow: #a58e0e; -$yellow-highlight: #f0ebbb50; -$grey1: #eaeaea; -$grey1-border: #d8d8d8; -$grey2: #f8f8f8; -$grey2-border: #eee; -$black: black; -$white: white; -$grey-text: grey; -$black-text: #333; -$selected-row: #00000010; -$appsilon-blue: #0099f9; - .red-text { - color: $red; + color: var(--red); } .green-text { - color: $green; + color: var(--green); } .yellow-text { - color: $yellow; + color: var(--yellow); } .red-highlight { - background-color: $red-highlight; + background-color: var(--red-highlight); } .green-highlight { - background-color: $green-highlight; + background-color: var(--green-highlight); } .yellow-highlight { - background-color: $yellow-highlight; + background-color: var(--yellow-highlight); } diff --git a/app/styles/_dashboard.scss b/app/styles/_dashboard.scss index c99af6a..69a4270 100644 --- a/app/styles/_dashboard.scss +++ b/app/styles/_dashboard.scss @@ -3,8 +3,8 @@ } .content-wrapper { - background: $white; - color: $black-text; + background: var(--white); + color: var(--black-text); height: 90vh; } @@ -40,7 +40,7 @@ } .rt-tr-selected { - background: $selected-row; + background: var(--selected-row); } .app-table { @@ -56,7 +56,7 @@ } .logs { - background: $white; + background: var(--white); width: 55%; height: 100%; overflow-y: auto; diff --git a/app/styles/_header.scss b/app/styles/_header.scss index b959d4e..15e5459 100644 --- a/app/styles/_header.scss +++ b/app/styles/_header.scss @@ -30,7 +30,7 @@ .right { .cta-button { - background: $appsilon-blue; + background: var(--primary); color: white; padding: 10px; border-radius: 10px; diff --git a/app/styles/_job_list.scss b/app/styles/_job_list.scss index 1ca3cef..63ed958 100644 --- a/app/styles/_job_list.scss +++ b/app/styles/_job_list.scss @@ -5,6 +5,6 @@ .job-start-time, .job-end-time { font-size: 0.75em; - color: $grey-text; + color: var(--grey-text); } } diff --git a/app/styles/_logs.scss b/app/styles/_logs.scss index 5f4535e..e0d9d50 100644 --- a/app/styles/_logs.scss +++ b/app/styles/_logs.scss @@ -16,7 +16,7 @@ } .empty-state-text { - color: $grey-text; + color: var(--grey-text); margin-bottom: 40px; } } diff --git a/app/styles/main.scss b/app/styles/main.scss index 17d49ff..d2497f6 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -15,7 +15,7 @@ body { } .vertical-line { - border-left: 1px $grey2-border solid; + border-left: 1px var(--grey2-border) solid; height: 80%; align-self: center; } diff --git a/config.yml b/config.yml index 8e5f085..e0d219e 100644 --- a/config.yml +++ b/config.yml @@ -2,4 +2,23 @@ default: rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO") rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA) app_role: "owner" + branding: + colors: + red: "#a50e0e"# + red-highlight: "#fce8e650" + green: "#3a5a40" + green-highlight: "#e0f0df50" + yellow: "#a58e0e" + yellow-highlight: "#f0ebbb50" + grey1: "#eaeaea" + grey1-border: "#d8d8d8" + grey2: "#f8f8f8" + grey2-border: "#eee" + black: black + white: white + grey-text: grey + black-text: "#333" + selected-row: "#00000010" + primary: "red" + diff --git a/dependencies.R b/dependencies.R index 289f81d..03ed7ac 100644 --- a/dependencies.R +++ b/dependencies.R @@ -6,3 +6,5 @@ library(reactable) library(rhino) library(rsconnect) library(shinycssloaders) +library(treesitter) +library(treesitter.r) diff --git a/renv.lock b/renv.lock index 2c0c389..a7b9d2a 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.3.2", + "Version": "4.4.1", "Repositories": [ { "Name": "CRAN", @@ -9,6 +9,17 @@ ] }, "Packages": { + "PKI": { + "Package": "PKI", + "Version": "0.1-14", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "base64enc" + ], + "Hash": "f5b9c6b2f62f1fa3dd53fd1ddccbb241" + }, "R.cache": { "Package": "R.cache", "Version": "0.16.0", @@ -37,22 +48,22 @@ }, "R.oo": { "Package": "R.oo", - "Version": "1.25.0", + "Version": "1.27.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R.methodsS3", "methods", "utils" ], - "Hash": "a0900a114f4f0194cf4aa8cd4a700681" + "Hash": "6ac79ff194202248cf946fe3a5d6d498" }, "R.utils": { "Package": "R.utils", - "Version": "2.12.2", + "Version": "2.12.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R.methodsS3", @@ -61,7 +72,7 @@ "tools", "utils" ], - "Hash": "325f01db13da12c04d8f6e7be36ff514" + "Hash": "3dc2829b790254bfba21e60965787651" }, "R6": { "Package": "R6", @@ -75,34 +86,34 @@ }, "Rcpp": { "Package": "Rcpp", - "Version": "1.0.11", + "Version": "1.0.13-1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "methods", "utils" ], - "Hash": "ae6cbbe1492f4de79c45fce06f967ce8" + "Hash": "6b868847b365672d6c1677b1608da9ed" }, "askpass": { "Package": "askpass", - "Version": "1.2.0", + "Version": "1.2.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "sys" ], - "Hash": "cad6cf7f1d5f6e906700b9d3e718c796" + "Hash": "c39f4155b3ceb1a9a2799d700fbd4b6a" }, "backports": { "Package": "backports", - "Version": "1.4.1", + "Version": "1.5.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "c39fbec8a30d23e721980b8afb31984c" + "Hash": "e1e1b9d75c37401117b636b7ae50827a" }, "base64enc": { "Package": "base64enc", @@ -116,93 +127,132 @@ }, "box": { "Package": "box", - "Version": "1.1.3", + "Version": "1.2.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "tools" ], - "Hash": "ce8187a260e8e3abc2294284badc3b76" + "Hash": "d94049c1d9446b0abb413fde9e82a505" + }, + "box.linters": { + "Package": "box.linters", + "Version": "0.10.5", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "cli", + "fs", + "glue", + "lintr", + "purrr", + "rlang", + "stringr", + "withr", + "xfun", + "xml2", + "xmlparsedata" + ], + "Hash": "8e26a8f1052518f7b69c967117f918be" + }, + "box.lsp": { + "Package": "box.lsp", + "Version": "0.1.3", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "box", + "cli", + "fs", + "rlang" + ], + "Hash": "c39c37f080928f9fd464eb582120d698" }, "brio": { "Package": "brio", - "Version": "1.1.3", + "Version": "1.1.5", "Source": "Repository", - "Repository": "CRAN", - "Hash": "976cf154dfb043c012d87cddd8bca363" + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "c1ee497a6d999947c2c224ae46799b1a" }, "bslib": { "Package": "bslib", - "Version": "0.5.1", + "Version": "0.8.0", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "base64enc", "cachem", + "fastmap", "grDevices", "htmltools", "jquerylib", "jsonlite", + "lifecycle", "memoise", "mime", "rlang", "sass" ], - "Hash": "283015ddfbb9d7bf15ea9f0b5698f0d9" + "Hash": "b299c6741ca9746fb227debcb0f9fb6c" }, "cachem": { "Package": "cachem", - "Version": "1.0.8", + "Version": "1.1.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "fastmap", "rlang" ], - "Hash": "c35768291560ce302c0a6589f92e837d" + "Hash": "cd9a672193789068eb5a2aad65a0dedf" }, "callr": { "Package": "callr", - "Version": "3.7.3", + "Version": "3.7.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", "processx", "utils" ], - "Hash": "9b2191ede20fa29828139b9900922e51" + "Hash": "d7e13f49c19103ece9e58ad2d83a7354" }, "cli": { "Package": "cli", - "Version": "3.6.1", + "Version": "3.6.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "89e6d8219950eac806ae0c489052048a" + "Hash": "b21916dd77a27642b447374a5d30ecf3" }, "codetools": { "Package": "codetools", - "Version": "0.2-19", + "Version": "0.2-20", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R" ], - "Hash": "c089a619a7fae175d149d89164f8c7d8" + "Hash": "61e097f35917d342622f21cdc79c256e" }, "commonmark": { "Package": "commonmark", - "Version": "1.9.0", + "Version": "1.9.2", "Source": "Repository", - "Repository": "CRAN", - "Hash": "d691c61bff84bd63c383874d2d0c3307" + "Repository": "RSPM", + "Hash": "14eb0596f987c71535d07c3aff814742" }, "config": { "Package": "config", @@ -216,25 +266,25 @@ }, "crayon": { "Package": "crayon", - "Version": "1.5.2", + "Version": "1.5.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "grDevices", "methods", "utils" ], - "Hash": "e8a1e41acf02548751f45c718d55aa6a" + "Hash": "859d96e65ef198fd43e82b9628d593ef" }, "curl": { "Package": "curl", - "Version": "5.2.0", + "Version": "6.0.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "ce88d13c0b10fe88a37d9c59dba2d7f9" + "Hash": "e8ba62486230951fcd2b881c5be23f96" }, "cyclocomp": { "Package": "cyclocomp", @@ -252,17 +302,16 @@ }, "desc": { "Package": "desc", - "Version": "1.4.2", + "Version": "1.4.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", "cli", - "rprojroot", "utils" ], - "Hash": "6b9602c7ebbe87101a9c8edb6e8b6d21" + "Hash": "99b79fcbd6c4d1ce087f5c5c758b384f" }, "diffobj": { "Package": "diffobj", @@ -281,14 +330,14 @@ }, "digest": { "Package": "digest", - "Version": "0.6.33", + "Version": "0.6.37", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "b18a9cf3c003977b0cc49d5e76ebe48d" + "Hash": "33698c4b3127fc9f506654607fb73676" }, "dplyr": { "Package": "dplyr", @@ -313,69 +362,57 @@ ], "Hash": "fedd9d00c2944ff00a0e2696ccf048ec" }, - "ellipsis": { - "Package": "ellipsis", - "Version": "0.3.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R", - "rlang" - ], - "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077" - }, "evaluate": { "Package": "evaluate", - "Version": "0.23", + "Version": "1.0.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ - "R", - "methods" + "R" ], - "Hash": "daf4a1246be12c1fa8c7705a0935c1a0" + "Hash": "3fd29944b231036ad67c3edb32e02201" }, "fansi": { "Package": "fansi", - "Version": "1.0.5", + "Version": "1.0.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "grDevices", "utils" ], - "Hash": "3e8583a60163b4bc1a80016e63b9959e" + "Hash": "962174cf2aeb5b9eea581522286a911f" }, "fastmap": { "Package": "fastmap", - "Version": "1.1.1", + "Version": "1.2.0", "Source": "Repository", - "Repository": "CRAN", - "Hash": "f7736a18de97dea803bde0a2daaafb27" + "Repository": "RSPM", + "Hash": "aa5e1cd11c2d15497494c5292d7ffcc8" }, "fontawesome": { "Package": "fontawesome", - "Version": "0.5.2", + "Version": "0.5.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "htmltools", "rlang" ], - "Hash": "c2efdd5f0bcd1ea861c2d4e2a883a67d" + "Hash": "bd1297f9b5b1fc1372d19e2c4cd82215" }, "fs": { "Package": "fs", - "Version": "1.6.3", + "Version": "1.6.5", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "methods" ], - "Hash": "47b5f30c720c23999b913a1a635cf0bb" + "Hash": "7f48af39fa27711ea5fbd183b399920d" }, "generics": { "Package": "generics", @@ -390,42 +427,41 @@ }, "glue": { "Package": "glue", - "Version": "1.6.2", + "Version": "1.8.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "methods" ], - "Hash": "4f2596dfb05dac67b9dc558e5c6fba2e" + "Hash": "5899f1eaa825580172bb56c08266f37c" }, "highr": { "Package": "highr", - "Version": "0.10", + "Version": "0.11", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "xfun" ], - "Hash": "06230136b2d2b9ba5805e1963fa6e890" + "Hash": "d65ba49117ca223614f71b60d85b8ab7" }, "htmltools": { "Package": "htmltools", - "Version": "0.5.7", + "Version": "0.5.8.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "R", "base64enc", "digest", - "ellipsis", "fastmap", "grDevices", "rlang", "utils" ], - "Hash": "2d7b3857980e0e0d0a1fd6f11928ab0f" + "Hash": "81d371a9cc60640e74e4ab6ac46dcedc" }, "htmlwidgets": { "Package": "htmlwidgets", @@ -444,9 +480,9 @@ }, "httpuv": { "Package": "httpuv", - "Version": "1.6.12", + "Version": "1.6.15", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", @@ -455,11 +491,11 @@ "promises", "utils" ], - "Hash": "c992f75861325961c29a188b45e549f7" + "Hash": "d55aa087c47a63ead0f6fc10f8fa1ee0" }, "httr2": { "Package": "httr2", - "Version": "1.0.0", + "Version": "1.0.6", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -476,7 +512,7 @@ "vctrs", "withr" ], - "Hash": "e2b30f1fc039a0bab047dd52bb20ef71" + "Hash": "3ef5d07ec78803475a94367d71b40c41" }, "jquerylib": { "Package": "jquerylib", @@ -490,19 +526,19 @@ }, "jsonlite": { "Package": "jsonlite", - "Version": "1.8.7", + "Version": "1.8.9", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "methods" ], - "Hash": "266a20443ca13c65688b2116d5220f76" + "Hash": "4e993b65c2c3ffbffce7bb3e2c6f832b" }, "knitr": { "Package": "knitr", - "Version": "1.45", + "Version": "1.49", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "evaluate", @@ -512,18 +548,18 @@ "xfun", "yaml" ], - "Hash": "1ec462871063897135c1bcbe0fc8f07d" + "Hash": "9fcb189926d93c636dea94fbe4f44480" }, "later": { "Package": "later", - "Version": "1.3.1", + "Version": "1.3.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "Rcpp", "rlang" ], - "Hash": "40401c9cf2bc2259dfe83311c9384710" + "Hash": "a3e051d405326b8b0012377434c62b37" }, "lazyeval": { "Package": "lazyeval", @@ -537,22 +573,22 @@ }, "lifecycle": { "Package": "lifecycle", - "Version": "1.0.3", + "Version": "1.0.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", "glue", "rlang" ], - "Hash": "001cecbeac1cff9301bdc3775ee46a86" + "Hash": "b8552d117e1b808b09a832f589b79035" }, "lintr": { "Package": "lintr", - "Version": "3.1.0", + "Version": "3.1.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "backports", @@ -567,17 +603,18 @@ "xml2", "xmlparsedata" ], - "Hash": "2b4b803af6017e93b67ddaf0eacba918" + "Hash": "08cff46381a242d44c0d8dd0aabd9f71" }, "logger": { "Package": "logger", - "Version": "0.2.2", + "Version": "0.4.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ + "R", "utils" ], - "Hash": "c269b06beb2bbadb0d058c0e6fa4ec3d" + "Hash": "f25d781d5bc7757e08cf38c741a5ad1c" }, "magrittr": { "Package": "magrittr", @@ -612,13 +649,13 @@ }, "openssl": { "Package": "openssl", - "Version": "2.1.1", + "Version": "2.2.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "askpass" ], - "Hash": "2a0dc8c6adfb6f032e4d4af82d258ab5" + "Hash": "d413e0fef796c9401a4419485f709ca1" }, "packrat": { "Package": "packrat", @@ -651,21 +688,18 @@ }, "pkgbuild": { "Package": "pkgbuild", - "Version": "1.4.2", + "Version": "1.4.5", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", "callr", "cli", - "crayon", "desc", - "prettyunits", - "processx", - "rprojroot" + "processx" ], - "Hash": "beb25b32a957a22a5c301a9e441190b3" + "Hash": "30eaaab94db72652e72e3475c1b55278" }, "pkgconfig": { "Package": "pkgconfig", @@ -679,24 +713,25 @@ }, "pkgload": { "Package": "pkgload", - "Version": "1.3.3", + "Version": "1.4.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", - "crayon", "desc", "fs", "glue", + "lifecycle", "methods", "pkgbuild", + "processx", "rlang", "rprojroot", "utils", "withr" ], - "Hash": "903d68319ae9923fb2e2ee7fa8230b91" + "Hash": "2ec30ffbeec83da57655b850cf2d3e0e" }, "praise": { "Package": "praise", @@ -705,34 +740,24 @@ "Repository": "CRAN", "Hash": "a555924add98c99d2f411e37e7d25e9f" }, - "prettyunits": { - "Package": "prettyunits", - "Version": "1.2.0", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "R" - ], - "Hash": "6b01fc98b1e86c4f705ce9dcfd2f57c7" - }, "processx": { "Package": "processx", - "Version": "3.8.2", + "Version": "3.8.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", "ps", "utils" ], - "Hash": "3efbd8ac1be0296a46c55387aeace0f3" + "Hash": "0c90a7d71988856bad2a2a45dd871bb9" }, "promises": { "Package": "promises", - "Version": "1.2.1", + "Version": "1.3.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R6", "Rcpp", @@ -742,18 +767,18 @@ "rlang", "stats" ], - "Hash": "0d8a15c9d000970ada1ab21405387dee" + "Hash": "434cd5388a3979e74be5c219bcd6e77d" }, "ps": { "Package": "ps", - "Version": "1.7.5", + "Version": "1.8.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "709d852d33178db54b17c722e5b1e594" + "Hash": "b4404b1de13758dea1c0484ad0d48563" }, "purrr": { "Package": "purrr", @@ -782,13 +807,13 @@ }, "reactR": { "Package": "reactR", - "Version": "0.5.0", + "Version": "0.6.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ "htmltools" ], - "Hash": "c9014fd1a435b2d790dd506589cb24e5" + "Hash": "b8e3d93f508045812f47136c7c44c251" }, "reactable": { "Package": "reactable", @@ -805,21 +830,11 @@ ], "Hash": "6069eb2a6597963eae0605c1875ff14c" }, - "rematch2": { - "Package": "rematch2", - "Version": "2.1.2", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "tibble" - ], - "Hash": "76c9e04c712a05848ae7a23d2f170a40" - }, "remotes": { "Package": "remotes", - "Version": "2.4.2.1", + "Version": "2.5.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "methods", @@ -827,7 +842,7 @@ "tools", "utils" ], - "Hash": "63d15047eb239f95160112bcadc4fcb9" + "Hash": "3ee025083e66f18db6cf27b56e23e141" }, "renv": { "Package": "renv", @@ -851,12 +866,14 @@ }, "rhino": { "Package": "rhino", - "Version": "1.7.0", + "Version": "1.10.1", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "box", + "box.linters", + "box.lsp", "cli", "config", "fs", @@ -872,27 +889,26 @@ "testthat", "utils", "withr", - "xml2", "yaml" ], - "Hash": "59ee79b26dd590b08dd1a3111d093832" + "Hash": "cd58cf8f362b8cc8c82aea1de9468218" }, "rlang": { "Package": "rlang", - "Version": "1.1.1", + "Version": "1.1.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "a85c767b55f0bf9b7ad16c6d7baee5bb" + "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" }, "rmarkdown": { "Package": "rmarkdown", - "Version": "2.25", + "Version": "2.29", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "bslib", @@ -903,31 +919,31 @@ "jsonlite", "knitr", "methods", - "stringr", "tinytex", "tools", "utils", "xfun", "yaml" ], - "Hash": "d65e35823c817f09f4de424fcdfa812a" + "Hash": "df99277f63d01c34e95e3d2f06a79736" }, "rprojroot": { "Package": "rprojroot", - "Version": "2.0.3", + "Version": "2.0.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "1de7ab598047a87bba48434ba35d497d" + "Hash": "4c8415e0ec1e29f3f4f6fc108bef0144" }, "rsconnect": { "Package": "rsconnect", - "Version": "1.2.1", + "Version": "1.3.3", "Source": "Repository", "Repository": "RSPM", "Requirements": [ + "PKI", "R", "cli", "curl", @@ -942,18 +958,18 @@ "tools", "yaml" ], - "Hash": "94bb3a2125b01b13dd2e4a784c2a9639" + "Hash": "d466c98fdce812325feb4ad406c6ca4b" }, "rstudioapi": { "Package": "rstudioapi", - "Version": "0.15.0", + "Version": "0.17.1", "Source": "Repository", - "Repository": "CRAN", - "Hash": "5564500e25cffad9e22244ced1379887" + "Repository": "RSPM", + "Hash": "5f90cd73946d706cfe26024294236113" }, "sass": { "Package": "sass", - "Version": "0.4.7", + "Version": "0.4.9", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -963,11 +979,11 @@ "rappdirs", "rlang" ], - "Hash": "6bd4d33b50ff927191ec9acbf52fd056" + "Hash": "d53dbfddf695303ea4ad66f86e99b95d" }, "shiny": { "Package": "shiny", - "Version": "1.7.5.1", + "Version": "1.9.1", "Source": "Repository", "Repository": "CRAN", "Requirements": [ @@ -977,7 +993,6 @@ "cachem", "commonmark", "crayon", - "ellipsis", "fastmap", "fontawesome", "glue", @@ -997,21 +1012,22 @@ "withr", "xtable" ], - "Hash": "5ec01cc255f2138fc2f0dc74d2b1a1a1" + "Hash": "6a293995a66e12c48d13aa1f957d09c7" }, "shinycssloaders": { "Package": "shinycssloaders", - "Version": "1.0.0", + "Version": "1.1.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "digest", "glue", "grDevices", + "htmltools", "shiny" ], - "Hash": "f39bb3c44a9b496723ec7e86f9a771d8" + "Hash": "2b45a467a30d6a88a1892a738c0900cf" }, "sourcetools": { "Package": "sourcetools", @@ -1025,16 +1041,16 @@ }, "stringi": { "Package": "stringi", - "Version": "1.8.3", + "Version": "1.8.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "stats", "tools", "utils" ], - "Hash": "058aebddea264f4c99401515182e656a" + "Hash": "39e1144fd75428983dc3f63aa53dfa91" }, "stringr": { "Package": "stringr", @@ -1055,9 +1071,9 @@ }, "styler": { "Package": "styler", - "Version": "1.10.2", + "Version": "1.10.3", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R.cache", @@ -1070,20 +1086,20 @@ "vctrs", "withr" ], - "Hash": "d61238fd44fc63c8adf4565efe8eb682" + "Hash": "93a2b1beac2437bdcc4724f8bf867e2c" }, "sys": { "Package": "sys", - "Version": "3.4.2", + "Version": "3.4.3", "Source": "Repository", - "Repository": "CRAN", - "Hash": "3a1be13d68d47a8cd0bfd74739ca1555" + "Repository": "RSPM", + "Hash": "de342ebfebdbf40477d0758d05426646" }, "testthat": { "Package": "testthat", - "Version": "3.2.0", + "Version": "3.2.1.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", @@ -1092,7 +1108,6 @@ "cli", "desc", "digest", - "ellipsis", "evaluate", "jsonlite", "lifecycle", @@ -1107,7 +1122,7 @@ "waldo", "withr" ], - "Hash": "877508719fcb8c9525eccdadf07a5102" + "Hash": "3f6e7e5e2220856ff865e4834766bf2b" }, "tibble": { "Package": "tibble", @@ -1130,9 +1145,9 @@ }, "tidyselect": { "Package": "tidyselect", - "Version": "1.2.0", + "Version": "1.2.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -1142,17 +1157,41 @@ "vctrs", "withr" ], - "Hash": "79540e5fcd9e0435af547d885f184fd5" + "Hash": "829f27b9c4919c16b593794a6344d6c0" }, "tinytex": { "Package": "tinytex", - "Version": "0.49", + "Version": "0.54", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "xfun" ], - "Hash": "5ac22900ae0f386e54f1c307eca7d843" + "Hash": "3ec7e3ddcacc2d34a9046941222bf94d" + }, + "treesitter": { + "Package": "treesitter", + "Version": "0.1.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "R6", + "cli", + "rlang", + "vctrs" + ], + "Hash": "22a579e4ac2ddd021df1791da7fd080f" + }, + "treesitter.r": { + "Package": "treesitter.r", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R" + ], + "Hash": "067c058dbd496c4cd696449870b11d52" }, "utf8": { "Package": "utf8", @@ -1166,9 +1205,9 @@ }, "vctrs": { "Package": "vctrs", - "Version": "0.6.4", + "Version": "0.6.5", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -1176,60 +1215,60 @@ "lifecycle", "rlang" ], - "Hash": "266c1ca411266ba8f365fcc726444b87" + "Hash": "c03fa420630029418f7e6da3667aac4a" }, "waldo": { "Package": "waldo", - "Version": "0.5.2", + "Version": "0.6.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", "diffobj", - "fansi", "glue", "methods", - "rematch2", - "rlang", - "tibble" + "rlang" ], - "Hash": "c7d3fd6d29ab077cbac8f0e2751449e6" + "Hash": "52f574062a7b66e56926988c3fbdb3b7" }, "withr": { "Package": "withr", - "Version": "2.5.2", + "Version": "3.0.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "grDevices", - "graphics", - "stats" + "graphics" ], - "Hash": "4b25e70111b7d644322e9513f403a272" + "Hash": "cc2d62c76458d425210d1eb1478b30b4" }, "xfun": { "Package": "xfun", - "Version": "0.41", + "Version": "0.49", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ + "R", + "grDevices", "stats", "tools" ], - "Hash": "460a5e0fe46a80ef87424ad216028014" + "Hash": "8687398773806cfff9401a2feca96298" }, "xml2": { "Package": "xml2", - "Version": "1.3.5", + "Version": "1.3.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", - "methods" + "cli", + "methods", + "rlang" ], - "Hash": "6c40e5cfcc6aefd88110666e18c31f40" + "Hash": "1d0336142f4cd25d8d23cd3ba7a8fb61" }, "xmlparsedata": { "Package": "xmlparsedata", @@ -1255,10 +1294,10 @@ }, "yaml": { "Package": "yaml", - "Version": "2.3.7", + "Version": "2.3.10", "Source": "Repository", - "Repository": "CRAN", - "Hash": "0d0056cc5383fbc240ccd0cb584bf436" + "Repository": "RSPM", + "Hash": "51dab85c6c98e50a18d7551e9d49f76c" } } } From ac42cd18462da40c062cb6796a5f900ff9e28098 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Fri, 22 Nov 2024 17:00:59 +0530 Subject: [PATCH 02/27] chore: add onClick = "select" to both app list and job list tables --- app/view/mod_app_table.R | 1 + app/view/mod_job_list.R | 1 + 2 files changed, 2 insertions(+) diff --git a/app/view/mod_app_table.R b/app/view/mod_app_table.R index 52c6e0c..859a4f1 100644 --- a/app/view/mod_app_table.R +++ b/app/view/mod_app_table.R @@ -80,6 +80,7 @@ server <- function(id, app_list, state) { searchable = TRUE, borderless = TRUE, pagination = FALSE, + onClick = "select", selection = "single", columns = list( guid = colDef( diff --git a/app/view/mod_job_list.R b/app/view/mod_job_list.R index c4a6fbf..feadeee 100644 --- a/app/view/mod_job_list.R +++ b/app/view/mod_job_list.R @@ -58,6 +58,7 @@ server <- function(id, state) { selection = "single", borderless = TRUE, pagination = FALSE, + onClick = "select", columns = list( job = colDef( cell = function(job_data) { From df18d7ef02f2eee2ee314495c3856b526de6d0d2 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Fri, 22 Nov 2024 17:03:36 +0530 Subject: [PATCH 03/27] chore: revert config color to Appsilon Blue --- config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.yml b/config.yml index e0d219e..f47b412 100644 --- a/config.yml +++ b/config.yml @@ -19,6 +19,6 @@ default: grey-text: grey black-text: "#333" selected-row: "#00000010" - primary: "red" + primary: "#0099f9" From 2250cfab651f53b0e90c5185d3398668a6a6e74f Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Fri, 22 Nov 2024 17:09:09 +0530 Subject: [PATCH 04/27] docs: update README with new FAQs about branding --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index aaea915..5a9fa1c 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ The LogAnalyzer open-source app is a simple, plug and play application developed - 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. +- How do I rebrand the application? + - You can edit the branding in the `config.yml` file. You'll find the `colors` key which will build the CSS. +- How do I recolor the SVGs? + - This requires some creativity. We recommend replacing the primary color hex which you can find in the `.svg` file as `fill="#hexcde"` to `PRIMARY`. + - We use this as a default value in the function that replaces it but you are welcome to use another value and modify the function. # Credits From b33ef11b77b88b5bfe47ef02f0373f6266386115 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Fri, 22 Nov 2024 17:17:26 +0530 Subject: [PATCH 05/27] feat: add handling for logo in config.yml theming --- app/static/{appsilon-logo.png => logo.png} | Bin app/view/mod_header.R | 16 ++++++++++++---- config.yml | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) rename app/static/{appsilon-logo.png => logo.png} (100%) diff --git a/app/static/appsilon-logo.png b/app/static/logo.png similarity index 100% rename from app/static/appsilon-logo.png rename to app/static/logo.png diff --git a/app/view/mod_header.R b/app/view/mod_header.R index e94e1d0..e6a860c 100644 --- a/app/view/mod_header.R +++ b/app/view/mod_header.R @@ -1,5 +1,9 @@ box::use( + config[ + get + ], shiny[ + a, actionLink, div, h2, @@ -12,14 +16,18 @@ box::use( #' @export ui <- function(id) { ns <- NS(id) + branding <- get("branding") div( class = "header", div( class = "left header-section", - img( - src = "static/appsilon-logo.png", - alt = "Appsilon logo", - href = "https://demo.appsilon.com" + a( + img( + src = branding$logo$src, + alt = branding$logo$alt + ), + href = branding$logo$href, + target = "_blank" ), div( class = "vertical-line" diff --git a/config.yml b/config.yml index f47b412..4a6c758 100644 --- a/config.yml +++ b/config.yml @@ -3,8 +3,12 @@ default: rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA) app_role: "owner" branding: + logo: + src: "static/logo.png" + alt: "Appsilon logo" + href: "https://demo.appsilon.com" colors: - red: "#a50e0e"# + red: "#a50e0e" red-highlight: "#fce8e650" green: "#3a5a40" green-highlight: "#e0f0df50" From 5543096827e6eeb57ff72dd4052c46ef11f920e6 Mon Sep 17 00:00:00 2001 From: Tymoteusz Makowski Date: Wed, 12 Mar 2025 09:20:13 -0400 Subject: [PATCH 06/27] refactor: untangle main.R server --- app/main.R | 91 ++++++++++++++++-------------------------------------- 1 file changed, 27 insertions(+), 64 deletions(-) diff --git a/app/main.R b/app/main.R index 613b42f..df78c34 100644 --- a/app/main.R +++ b/app/main.R @@ -1,12 +1,7 @@ -# nolint start: box_func_import_count_linter box::use( config[get], - dplyr[select], - magrittr[`%>%`], shiny, - shinycssloaders[withSpinner], ) -# nolint end box::use( app/logic/api_utils[get_app_list], @@ -57,6 +52,7 @@ server <- function(id) { ns <- session$ns + app_list <- get_app_list() branding <- get("branding") output$dynamic_colors <- shiny$renderUI({ @@ -66,76 +62,43 @@ server <- function(id) { mod_header$server("header") - state <- shiny$reactiveValues() - state$selected_app <- shiny$reactive({}) - state$selected_job <- shiny$reactive({}) + selected_app_ <- mod_app_table$server("app_table", app_list)$selected_app_ - app_list <- shiny$reactive({ - get_app_list() - }) - - mod_app_table$server( - "app_table", - app_list(), - state - ) + selected_job_ <- mod_job_list$server("job_list", selected_app_)$selected_job_ - shiny$observeEvent(state$selected_app()$guid, { + mod_logs$server("logs", selected_app_, selected_job_) - if (shiny$isTruthy(state$selected_app()$guid)) { + output$job_list_pane <- shiny$renderUI({ + if (!shiny$isTruthy(selected_app_()$guid)) { + return(NULL) + } - output$job_list_pane <- shiny$renderUI({ - mod_job_list$ui(ns("job_list")) - }) + mod_job_list$ui(ns("job_list")) + }) - mod_job_list$server( - "job_list", - state + output$logs_pane <- shiny$renderUI({ + if (!is.data.frame(app_list) || nrow(app_list) == 0) { + return( + generate_empty_state_ui( + text = "Oops! Can't read apps from Posit Connect.", + image_path = "app/static/illustrations/missing_apps.svg", + color = branding$colors$primary + ) ) - - } else { - - shiny$removeUI(ns("job_list_pane")) - } - }, ignoreNULL = FALSE) - - shiny$observeEvent(state$selected_job()$key, { - if (shiny$isTruthy(state$selected_job()$key)) { - - output$logs_pane <- shiny$renderUI({ - mod_logs$ui(ns("logs")) - }) - - mod_logs$server( - "logs", - state + if (!shiny$isTruthy(selected_job_()$key)) { + return( + generate_empty_state_ui( + text = "Select an application and a job to view logs.", + image_path = "app/static/illustrations/empty_state.svg", + color = branding$colors$primary + ) ) - } else { - - if (!inherits(app_list(), "data.frame")) { - empty_state <- shiny$renderUI({ - generate_empty_state_ui( - text = "Oops! Can't read apps from Posit Connect.", - image_path = "app/static/illustrations/missing_apps.svg", - color = branding$colors$primary - ) - }) - } else { - empty_state <- shiny$renderUI({ - generate_empty_state_ui( - text = "Select an application and a job to view logs.", - image_path = "app/static/illustrations/empty_state.svg", - color = branding$colors$primary - ) - }) - } - - output$logs_pane <- empty_state } - }, ignoreNULL = FALSE) + mod_logs$ui(ns("logs")) + }) }) } From d6eb5ff98f6896ba5c2e60731e2dae61e303c1cb Mon Sep 17 00:00:00 2001 From: Tymoteusz Makowski Date: Wed, 12 Mar 2025 10:57:28 -0400 Subject: [PATCH 07/27] refactor: replace reactiveValues with plain reactives --- app/view/mod_app_table.R | 22 ++++++++++++---------- app/view/mod_job_list.R | 26 ++++++++++++++------------ app/view/mod_logs.R | 18 +++++++++--------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/app/view/mod_app_table.R b/app/view/mod_app_table.R index 859a4f1..12effa3 100644 --- a/app/view/mod_app_table.R +++ b/app/view/mod_app_table.R @@ -38,7 +38,7 @@ ui <- function(id) { } #' @export -server <- function(id, app_list, state) { +server <- function(id, app_list) { moduleServer(id, function(input, output, session) { output$app_table <- renderReactable({ @@ -99,15 +99,17 @@ server <- function(id, app_list, state) { ) }) - state$selected_app <- reactive({ - index <- getReactableState("app_table", "selected") - if (isTruthy(index) && length(app_list > 0)) { - list( - "guid" = app_list[index, ]$guid, - "name" = app_list[index, ]$name - ) - } - }) + list( + selected_app_ = reactive({ + index <- getReactableState("app_table", "selected") + if (isTruthy(index) && length(app_list > 0)) { + list( + "guid" = app_list[index, ]$guid, + "name" = app_list[index, ]$name + ) + } + }) + ) }) diff --git a/app/view/mod_job_list.R b/app/view/mod_job_list.R index feadeee..1d9508f 100644 --- a/app/view/mod_job_list.R +++ b/app/view/mod_job_list.R @@ -39,12 +39,12 @@ ui <- function(id) { } #' @export -server <- function(id, state) { +server <- function(id, selected_app_) { moduleServer(id, function(input, output, session) { job_list_data <- reactive({ - req(state$selected_app()$guid) - get_job_list(state$selected_app()$guid) + req(selected_app_()$guid) + get_job_list(selected_app_()$guid) }) output$job_list_table <- renderReactable({ @@ -73,15 +73,17 @@ server <- function(id, state) { }) - state$selected_job <- reactive({ - index <- getReactableState("job_list_table", "selected") - if (isTruthy(index) && length(job_list_data()) > 0) { - list( - "key" = job_list_data()[index, ]$key, - "id" = job_list_data()[index, ]$id - ) - } - }) + list( + selected_job_ = reactive({ + index <- getReactableState("job_list_table", "selected") + if (isTruthy(index) && length(job_list_data()) > 0) { + list( + "key" = job_list_data()[index, ]$key, + "id" = job_list_data()[index, ]$id + ) + } + }) + ) }) } diff --git a/app/view/mod_logs.R b/app/view/mod_logs.R index cde7551..0532e59 100644 --- a/app/view/mod_logs.R +++ b/app/view/mod_logs.R @@ -50,7 +50,7 @@ ui <- function(id) { } #' @export -server <- function(id, state) { +server <- function(id, selected_app_, selected_job_) { moduleServer(id, function(input, output, session) { ns <- session$ns @@ -58,20 +58,20 @@ server <- function(id, state) { output$download <- downloadHandler( filename = function() { glue( - "{state$selected_app()$name}_{state$selected_job()$id}.txt" + "{selected_app_()$name}_{selected_job_()$id}.txt" ) }, content = function(file) { logs <- download_job_logs( - state$selected_app()$guid, - state$selected_job()$key + selected_app_()$guid, + selected_job_()$key ) writeLines(logs, file) } ) - observeEvent(state$selected_job()$key, { - req(state$selected_job()$key) + observeEvent(selected_job_()$key, { + req(selected_job_()$key) output$download_logs <- renderUI({ downloadButton( outputId = ns("download"), @@ -83,10 +83,10 @@ server <- function(id, state) { }) logs_data <- reactive({ - req(state$selected_job()$key) + req(selected_job_()$key) get_job_logs( - state$selected_app()$guid, - state$selected_job()$key + selected_app_()$guid, + selected_job_()$key ) }) From c583c899baee9fc9ce7a2454e63751e7d1db9a99 Mon Sep 17 00:00:00 2001 From: Tymoteusz Makowski Date: Wed, 12 Mar 2025 10:57:42 -0400 Subject: [PATCH 08/27] refactor: update checks --- app/view/mod_app_table.R | 4 ++-- app/view/mod_job_list.R | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/view/mod_app_table.R b/app/view/mod_app_table.R index 12effa3..181251b 100644 --- a/app/view/mod_app_table.R +++ b/app/view/mod_app_table.R @@ -43,7 +43,7 @@ server <- function(id, app_list) { output$app_table <- renderReactable({ - if (length(app_list) > 0 && inherits(app_list, "data.frame")) { + if (nrow(app_list) > 0 && inherits(app_list, "data.frame")) { processed_apps <- app_list %>% select( guid, @@ -102,7 +102,7 @@ server <- function(id, app_list) { list( selected_app_ = reactive({ index <- getReactableState("app_table", "selected") - if (isTruthy(index) && length(app_list > 0)) { + if (isTruthy(index) && nrow(app_list) > 0) { list( "guid" = app_list[index, ]$guid, "name" = app_list[index, ]$name diff --git a/app/view/mod_job_list.R b/app/view/mod_job_list.R index 1d9508f..469156c 100644 --- a/app/view/mod_job_list.R +++ b/app/view/mod_job_list.R @@ -76,7 +76,7 @@ server <- function(id, selected_app_) { list( selected_job_ = reactive({ index <- getReactableState("job_list_table", "selected") - if (isTruthy(index) && length(job_list_data()) > 0) { + if (isTruthy(index) && nrow(job_list_data()) > 0) { list( "key" = job_list_data()[index, ]$key, "id" = job_list_data()[index, ]$id From cc4ba3a2cf95b964daf028aa1b0317460221489d Mon Sep 17 00:00:00 2001 From: Tymoteusz Makowski Date: Wed, 12 Mar 2025 10:58:05 -0400 Subject: [PATCH 09/27] refactor: unnest renderUI --- app/view/mod_logs.R | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/view/mod_logs.R b/app/view/mod_logs.R index 0532e59..ceba25b 100644 --- a/app/view/mod_logs.R +++ b/app/view/mod_logs.R @@ -70,16 +70,17 @@ server <- function(id, selected_app_, selected_job_) { } ) - observeEvent(selected_job_()$key, { - req(selected_job_()$key) - output$download_logs <- renderUI({ - downloadButton( - outputId = ns("download"), - label = NULL, - icon = icon("download"), - class = "logs-download" - ) - }) + output$download_logs <- renderUI({ + if (is.null(selected_job_()$key)) { + return(NULL) + } + + downloadButton( + outputId = ns("download"), + label = NULL, + icon = icon("download"), + class = "logs-download" + ) }) logs_data <- reactive({ From 1bd9d7882f2fbc37d395f78654362b31cb976439 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:18:31 +0530 Subject: [PATCH 10/27] test: add unit tests for general_utils.R --- tests/testthat/test-general_utils.R | 83 +++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/testthat/test-general_utils.R diff --git a/tests/testthat/test-general_utils.R b/tests/testthat/test-general_utils.R new file mode 100644 index 0000000..07c9e51 --- /dev/null +++ b/tests/testthat/test-general_utils.R @@ -0,0 +1,83 @@ +box::use( + testthat[ + describe, + expect_identical, + expect_error, + expect_true, + it + ], +) + +box::use( + app/logic/general_utils[ + check_text_error, + format_timestamp, + generate_css_variables, + ], +) + +describe("check_text_error()", { + it("returns TRUE for error keywords", { + expect_true( + check_text_error( + "Error: job failed" + ) + ) + expect_true( + check_text_error( + "Halt: job terminated" + ) + ) + expect_true( + check_text_error( + "Error: file not found" + ) + ) + }) + + it("returns FALSE for non-error keywords", { + expect_true( + !check_text_error( + "Job completed successfully" + ) + ) + expect_true( + !check_text_error( + "Processing" + ) + ) + }) +}) + +describe("format_timestamp()", { + it("correctly formats timestamp", { + expect_identical( + format_timestamp( + "2023-10-01T12:34:56Z" + ), + "2023-10-01 12:34:56" + ) + expect_identical( + format_timestamp( + "2023-10-01T12:34:56.789Z" + ), + "2023-10-01 12:34:56" + ) + }) +}) + +describe("generate_css_variables()", { + it("generates CSS variables correctly", { + config <- list( + colors = list( + primary = "#FF5733", + secondary = "#33FF57" + ) + ) + expected_css <- ":root {\n --primary: #FF5733;\n --secondary: #33FF57;\n}" + expect_identical( + generate_css_variables(config), + expected_css + ) + }) +}) From d8cb1c0c91c863f45c95a23eb2f80405d4e2c1a9 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:19:19 +0530 Subject: [PATCH 11/27] feat: move branding outside ui/server --- app/main.R | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/main.R b/app/main.R index 613b42f..874c1b5 100644 --- a/app/main.R +++ b/app/main.R @@ -18,13 +18,19 @@ box::use( app/view/mod_logs, ) +# Load Branding +branding <- get("branding") +branding_css_variables <- generate_css_variables( + branding +) + #' @export ui <- function(id) { ns <- shiny$NS(id) shiny$fluidPage( class = "dashboard-body", shiny$tags$head( - shiny$uiOutput(ns("dynamic_colors")) + shiny$tags$style(shiny$HTML(branding_css_variables)) ), mod_header$ui("header"), shiny$div( @@ -57,13 +63,6 @@ server <- function(id) { ns <- session$ns - branding <- get("branding") - - output$dynamic_colors <- shiny$renderUI({ - css_content <- generate_css_variables(branding) - shiny$tags$style(shiny$HTML(css_content)) - }) - mod_header$server("header") state <- shiny$reactiveValues() From f2d48df2d19a34368006f7b609ea19a0a0b6e413 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:19:27 +0530 Subject: [PATCH 12/27] chore: remove nolint --- app/main.R | 2 -- app/view/mod_logs.R | 2 -- 2 files changed, 4 deletions(-) diff --git a/app/main.R b/app/main.R index 874c1b5..6fa740a 100644 --- a/app/main.R +++ b/app/main.R @@ -1,4 +1,3 @@ -# nolint start: box_func_import_count_linter box::use( config[get], dplyr[select], @@ -6,7 +5,6 @@ box::use( shiny, shinycssloaders[withSpinner], ) -# nolint end box::use( app/logic/api_utils[get_app_list], diff --git a/app/view/mod_logs.R b/app/view/mod_logs.R index cde7551..f4b5d4a 100644 --- a/app/view/mod_logs.R +++ b/app/view/mod_logs.R @@ -1,4 +1,3 @@ -# nolint start: box_func_import_count_linter box::use( dplyr[mutate], glue[glue], @@ -24,7 +23,6 @@ box::use( uiOutput ], ) -# nolint end box::use( app/logic/api_utils[download_job_logs, get_job_logs], From d12bdccda2ad1cb258f03e617e6342bf6d45ece3 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:19:40 +0530 Subject: [PATCH 13/27] fix: add working default value + docstring improvement for generate_empty_state_ui --- app/logic/empty_state_utils.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/logic/empty_state_utils.R b/app/logic/empty_state_utils.R index e11d5f4..4419781 100644 --- a/app/logic/empty_state_utils.R +++ b/app/logic/empty_state_utils.R @@ -14,11 +14,12 @@ box::use( #' @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 +#' @param color Color to use for the image #' @export generate_empty_state_ui <- function( text = "Select an application and a job to view logs", image_path = "static/illustrations/empty_state.svg", - color = NULL + color = "#0099f9" ) { div( class = "empty-state-container", From 9e1a293b4365fd0cd7f159ced5b959913f21d17a Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:20:16 +0530 Subject: [PATCH 14/27] chore: move primary to the top in config.yml --- config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/config.yml b/config.yml index 4a6c758..9407a26 100644 --- a/config.yml +++ b/config.yml @@ -8,6 +8,7 @@ default: alt: "Appsilon logo" href: "https://demo.appsilon.com" colors: + primary: "#0099f9" red: "#a50e0e" red-highlight: "#fce8e650" green: "#3a5a40" @@ -23,6 +24,3 @@ default: grey-text: grey black-text: "#333" selected-row: "#00000010" - primary: "#0099f9" - - From 9937baa243d12833e8cc3e8caa4092b822cc9baf Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:20:30 +0530 Subject: [PATCH 15/27] chore: remove redundant default variable from generate_css_variables --- app/logic/general_utils.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logic/general_utils.R b/app/logic/general_utils.R index c45cbe2..350eaa5 100644 --- a/app/logic/general_utils.R +++ b/app/logic/general_utils.R @@ -52,7 +52,7 @@ format_timestamp <- function( #' @return a string of CSS variables within :root {} #' @export generate_css_variables <- function( - config = get("branding") + config ) { css_lines <- map_chr( names(config$colors), From 3a226a766162964a24fc85100ebe7e2a27b50efe Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 17:23:09 +0530 Subject: [PATCH 16/27] docs: add context for what the colors do in README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 5a9fa1c..235a415 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ The LogAnalyzer open-source app is a simple, plug and play application developed - So far, one documented reason for this is that OAuth on Posit Connect instances may prevent the `/content` endpoint from sending app data. - How do I rebrand the application? - You can edit the branding in the `config.yml` file. You'll find the `colors` key which will build the CSS. + - The three status colors and their highlights e.g. `red` and `red-highlight` color the logs for you. + - The `primary` color is the primary theme for your application. This also affects SVGs. + - The blacks, whites and greys fill in the rest of the UI elements such as text, separator et al. - How do I recolor the SVGs? - This requires some creativity. We recommend replacing the primary color hex which you can find in the `.svg` file as `fill="#hexcde"` to `PRIMARY`. - We use this as a default value in the function that replaces it but you are welcome to use another value and modify the function. From 937c239dd7d14bc3b6d8e86f92e6d3ab410dc72f Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:35:19 +0530 Subject: [PATCH 17/27] chore: enable box.linters --- .lintr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.lintr b/.lintr index 63fade9..da7cb04 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,5 @@ linters: linters_with_defaults( - line_length_linter = line_length_linter(100), - object_usage_linter = NULL # Does not work with `box::use()`. + defaults = box.linters::rhino_default_linters, + line_length_linter = line_length_linter(100) ) From 70d1163c6046878d4d2855745932e74d59d62ca2 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:35:29 +0530 Subject: [PATCH 18/27] feat: add config for llm mode in config.yml --- config.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config.yml b/config.yml index 9407a26..fd670c4 100644 --- a/config.yml +++ b/config.yml @@ -1,6 +1,12 @@ default: rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO") rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA) + llm: + enabled: TRUE + provider: "openai" + model: "gpt-4o" + api_key: !expr Sys.getenv("LLM_API_KEY", NA) + system_prompt: !expr readLines("app/static/llm_system_prompt.txt") |> paste(collapse = " ") app_role: "owner" branding: logo: From 7ff93c21328c73abf73c4d56522974b0fc52c220 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:35:37 +0530 Subject: [PATCH 19/27] chore: update dependencies --- dependencies.R | 1 + renv.lock | 60 ++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/dependencies.R b/dependencies.R index 03ed7ac..a2db989 100644 --- a/dependencies.R +++ b/dependencies.R @@ -1,5 +1,6 @@ # This file allows packrat (used by rsconnect during deployment) to pick up dependencies. library(dplyr) +library(ellmer) library(httr2) library(magrittr) library(reactable) diff --git a/renv.lock b/renv.lock index a7b9d2a..3be0c31 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.4.1", + "Version": "4.4.3", "Repositories": [ { "Name": "CRAN", @@ -95,6 +95,17 @@ ], "Hash": "6b868847b365672d6c1677b1608da9ed" }, + "S7": { + "Package": "S7", + "Version": "0.2.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "utils" + ], + "Hash": "5deb66b3ae702137e1f4162c11861e76" + }, "askpass": { "Package": "askpass", "Version": "1.2.1", @@ -264,6 +275,17 @@ ], "Hash": "8b7222e9d9eb5178eea545c0c4d33fc2" }, + "coro": { + "Package": "coro", + "Version": "1.1.0", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R", + "rlang" + ], + "Hash": "4998c8836ff95c90993a4eb8d853df71" + }, "crayon": { "Package": "crayon", "Version": "1.5.3", @@ -278,13 +300,13 @@ }, "curl": { "Package": "curl", - "Version": "6.0.1", + "Version": "6.2.2", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "e8ba62486230951fcd2b881c5be23f96" + "Hash": "e4f9e10b18f453a1b7eaf38247dad4fe" }, "cyclocomp": { "Package": "cyclocomp", @@ -362,6 +384,26 @@ ], "Hash": "fedd9d00c2944ff00a0e2696ccf048ec" }, + "ellmer": { + "Package": "ellmer", + "Version": "0.1.1", + "Source": "Repository", + "Repository": "RSPM", + "Requirements": [ + "R6", + "S7", + "cli", + "coro", + "glue", + "httr2", + "jsonlite", + "later", + "lifecycle", + "promises", + "rlang" + ], + "Hash": "754d3a8cbb25b05be2058892f579422f" + }, "evaluate": { "Package": "evaluate", "Version": "1.0.1", @@ -495,7 +537,7 @@ }, "httr2": { "Package": "httr2", - "Version": "1.0.6", + "Version": "1.1.2", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -512,7 +554,7 @@ "vctrs", "withr" ], - "Hash": "3ef5d07ec78803475a94367d71b40c41" + "Hash": "ade531519694081d91036b509eb30594" }, "jquerylib": { "Package": "jquerylib", @@ -552,14 +594,14 @@ }, "later": { "Package": "later", - "Version": "1.3.2", + "Version": "1.4.2", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "Rcpp", "rlang" ], - "Hash": "a3e051d405326b8b0012377434c62b37" + "Hash": "9591aabef9cf988f05bde9324fd3ad99" }, "lazyeval": { "Package": "lazyeval", @@ -755,7 +797,7 @@ }, "promises": { "Package": "promises", - "Version": "1.3.0", + "Version": "1.3.2", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -767,7 +809,7 @@ "rlang", "stats" ], - "Hash": "434cd5388a3979e74be5c219bcd6e77d" + "Hash": "c84fd4f75ea1f5434735e08b7f50fbca" }, "ps": { "Package": "ps", From 56a3dba0d66e62039a75fe44a205f89f24814efd Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:36:04 +0530 Subject: [PATCH 20/27] feat: add llm system prompt --- app/static/llm_system_prompt.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/static/llm_system_prompt.txt diff --git a/app/static/llm_system_prompt.txt b/app/static/llm_system_prompt.txt new file mode 100644 index 0000000..f191670 --- /dev/null +++ b/app/static/llm_system_prompt.txt @@ -0,0 +1,17 @@ +Context: You are an extremely sophisticated, R/Shiny log analyzer and debugger. +Task: You will be given only the problematic logs. Your job is to analyse them. +Result: Generate a div with formatting as given below. +Structure: +-

LogAnalyzer AI Help

+-
+-

Caution: This help is AI generated. Verify the information before taking any action.

+-

Problem Explanation

+- [Add a one- or two-liner explanation here] +-

Suggestions (bullet list of 3 suggestions, be detailed, but no subpoints) +Formatting: +This will be embedded in a modalDialog in a Shiny app. Following this to the last letter is important. Breaking these will get you fired. +- Ensure function, library and related names use tags. +- All code blocks and one-liners should be formatted properly using tags. +- All emphasis should happen with and not _ or *. +- All bold should happen with and not ** or __. +- Return raw HTML. No \```html wrapper. From 1b222024cc31a1c1679b4c9bcd52384f5b4b00ca Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:36:33 +0530 Subject: [PATCH 21/27] fix: independent linting fixes --- app/logic/empty_state_utils.R | 2 -- app/logic/general_utils.R | 3 --- app/view/mod_header.R | 2 +- app/view/mod_job_list.R | 1 - tests/testthat/test-general_utils.R | 1 - 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/logic/empty_state_utils.R b/app/logic/empty_state_utils.R index 4419781..5718197 100644 --- a/app/logic/empty_state_utils.R +++ b/app/logic/empty_state_utils.R @@ -6,8 +6,6 @@ box::use( div, img, p, - renderUI, - tagAppendAttributes ], ) diff --git a/app/logic/general_utils.R b/app/logic/general_utils.R index 350eaa5..1f5fa47 100644 --- a/app/logic/general_utils.R +++ b/app/logic/general_utils.R @@ -1,7 +1,4 @@ box::use( - config[ - get - ], purrr[ map_chr ], diff --git a/app/view/mod_header.R b/app/view/mod_header.R index e6a860c..ae5891e 100644 --- a/app/view/mod_header.R +++ b/app/view/mod_header.R @@ -39,7 +39,7 @@ ui <- function(id) { div( class = "right header-section", actionLink( - "lets-talk", + ns("lets-talk"), label = "Let's Talk", class = "cta-button", onclick = "window.open('https://appsilon.com/#contact', '_blank');" diff --git a/app/view/mod_job_list.R b/app/view/mod_job_list.R index 469156c..c1cc2e4 100644 --- a/app/view/mod_job_list.R +++ b/app/view/mod_job_list.R @@ -1,5 +1,4 @@ box::use( - magrittr[`%>%`], reactable[ colDef, getReactableState, diff --git a/tests/testthat/test-general_utils.R b/tests/testthat/test-general_utils.R index 07c9e51..a298404 100644 --- a/tests/testthat/test-general_utils.R +++ b/tests/testthat/test-general_utils.R @@ -2,7 +2,6 @@ box::use( testthat[ describe, expect_identical, - expect_error, expect_true, it ], From ebe6d6b5390ca1c3e6adc645d16a65e4a03917a0 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:36:48 +0530 Subject: [PATCH 22/27] feat: add llm utils using ellmer --- app/logic/llm_utils.R | 88 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/logic/llm_utils.R diff --git a/app/logic/llm_utils.R b/app/logic/llm_utils.R new file mode 100644 index 0000000..02bf812 --- /dev/null +++ b/app/logic/llm_utils.R @@ -0,0 +1,88 @@ +box::use( + config[ + get + ], + ellmer, #nolint: we do use this package just in a separate notation [[ ]] + glue[ + glue + ], +) + +#' Check if LLM is enabled +#' +#' This function checks the LLM configuration and returns TRUE if enabled, FALSE otherwise. +#' @return Logical indicating if LLM is enabled +#' @export +is_llm_enabled <- function() { + get("llm")$enabled %||% FALSE +} + +#' Get LLM configuration +#' +#' Returns the LLM configuration if LLM is enabled. Otherwise, throws an error. +#' @return A list containing the LLM configuration +get_llm_config <- function() { + if (!is_llm_enabled()) { + stop("Oops! LLM is not enabled in config.yml!") + } + get("llm") +} + +#' Get the LLM function based on the provider +#' +#' Extracts the chat function dynamically from the `ellmer` module. +#' @param llm_config Optional configuration for the LLM +#' @return A function to create a chat object +get_llm_function <- function( + llm_config = get_llm_config() +) { + ellmer[[glue("chat_{llm_config$provider}")]] +} + +#' Create a chat object +#' +#' Uses the configured LLM provider and model to create a chat object. +#' @param llm_config Optional configuration for the LLM +#' @return A chat object +#' @export +create_chat_object <- function( + llm_config = get_llm_config() +) { + fun <- get_llm_function(llm_config) + fun( + api_key = llm_config$api_key, + model = llm_config$model, + system_prompt = llm_config$system_prompt, + seed = 42, + api_args = list( + temperature = 0 + ) + ) +} + +#' Invoke LLM help with the logs data.frame +#' @param logs_data A data frame containing log data +#' @return A response from the LLM +#' @export +get_llm_help <- function( + logs_data +) { + chat <- create_chat_object() + chat$chat( + concatenate_logs( + logs_data + ) + ) +} + +#' Concatenate logs +#' @param processed_logs A data frame containing log data +#' @return A string with concatenated log entries +concatenate_logs <- function( + processed_logs +) { + paste( + processed_logs$entries.data, + collapse = "\n" + ) +} From 54a06fa17d3d7bdbf76774bfcd35ea90759143f8 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:37:15 +0530 Subject: [PATCH 23/27] feat: integrate llm functionality --- app/logic/logs_utils.R | 18 +++--- app/main.R | 42 ++++++------ app/view/mod_logs.R | 143 +++++++++++++++++++++++++++++++---------- 3 files changed, 142 insertions(+), 61 deletions(-) diff --git a/app/logic/logs_utils.R b/app/logic/logs_utils.R index ebbec6e..c5aae50 100644 --- a/app/logic/logs_utils.R +++ b/app/logic/logs_utils.R @@ -18,19 +18,18 @@ process_log_data <- function( log_data ) { log_info <- strsplit(log_data, "_-_")[[1]] - status <- get_status_info(log_info[1], log_info[3]) div( - class = glue("log-entry {status[1]}-highlight"), + class = glue("log-entry {log_info[4]}-highlight"), icon( - name = status[2], + name = log_info[5], class = glue( - "log-status {status[1]}-text fa-solid" + "log-status {log_info[4]}-text fa-solid" ), ), div( class = "log-info-block", div( - class = glue("log-info {status[1]}-text"), + class = glue("log-info {log_info[4]}-text"), log_info[3] ), div( @@ -41,15 +40,18 @@ process_log_data <- function( ) } +#' @export get_status_info <- function( output_type, log_data ) { if (output_type == "stdout") { - c("green", "circle-info") + status_list <- list("green", "circle-info") } else if (output_type == "stderr" && check_text_error(log_data)) { - c("red", "circle-xmark") + status_list <- list("red", "circle-xmark") } else { - c("yellow", "circle-info") + status_list <- list("yellow", "circle-info") } + names(status_list) <- c("entries.status", "entries.icon") + status_list } diff --git a/app/main.R b/app/main.R index 9c3ea8d..62a0121 100644 --- a/app/main.R +++ b/app/main.R @@ -62,15 +62,25 @@ server <- function(id) { mod_header$server("header") - selected_app_ <- mod_app_table$server("app_table", app_list)$selected_app_ - - selected_job_ <- mod_job_list$server("job_list", selected_app_)$selected_job_ - - mod_logs$server("logs", selected_app_, selected_job_) + selected_app_ <- mod_app_table$server( + "app_table", + app_list + )$selected_app_ + + selected_job_ <- mod_job_list$server( + "job_list", + selected_app_ + )$selected_job_ + + mod_logs$server( + "logs", + selected_app_, + selected_job_ + ) output$job_list_pane <- shiny$renderUI({ if (!shiny$isTruthy(selected_app_()$guid)) { - return(NULL) + NULL } mod_job_list$ui(ns("job_list")) @@ -78,22 +88,18 @@ server <- function(id) { output$logs_pane <- shiny$renderUI({ if (!is.data.frame(app_list) || nrow(app_list) == 0) { - return( - generate_empty_state_ui( - text = "Oops! Can't read apps from Posit Connect.", - image_path = "app/static/illustrations/missing_apps.svg", - color = branding$colors$primary - ) + generate_empty_state_ui( + text = "Oops! Can't read apps from Posit Connect.", + image_path = "app/static/illustrations/missing_apps.svg", + color = branding$colors$primary ) } if (!shiny$isTruthy(selected_job_()$key)) { - return( - generate_empty_state_ui( - text = "Select an application and a job to view logs.", - image_path = "app/static/illustrations/empty_state.svg", - color = branding$colors$primary - ) + generate_empty_state_ui( + text = "Select an application and a job to view logs.", + image_path = "app/static/illustrations/empty_state.svg", + color = branding$colors$primary ) } diff --git a/app/view/mod_logs.R b/app/view/mod_logs.R index 52b1a15..4644574 100644 --- a/app/view/mod_logs.R +++ b/app/view/mod_logs.R @@ -1,40 +1,47 @@ box::use( - dplyr[mutate], + dplyr[ + as_tibble, + bind_cols, + filter, + mutate, + tibble + ], glue[glue], magrittr[`%>%`], + purrr[ + pmap_dfr + ], reactable[ colDef, reactable, reactableOutput, renderReactable ], + shiny, shinycssloaders[withSpinner], - shiny[ - div, - downloadButton, - downloadHandler, - icon, - moduleServer, - NS, - observeEvent, - reactive, - renderUI, - req, - uiOutput - ], ) box::use( - app/logic/api_utils[download_job_logs, get_job_logs], - app/logic/logs_utils[process_log_data], + app/logic/api_utils[ + download_job_logs, + get_job_logs + ], + app/logic/llm_utils[ + get_llm_help, + is_llm_enabled + ], + app/logic/logs_utils[ + get_status_info, + process_log_data + ], ) #' @export ui <- function(id) { - ns <- NS(id) - div( + ns <- shiny$NS(id) + shiny$div( class = "logs-container", - uiOutput( + shiny$uiOutput( ns("download_logs") ), withSpinner( @@ -48,12 +55,16 @@ ui <- function(id) { } #' @export -server <- function(id, selected_app_, selected_job_) { - moduleServer(id, function(input, output, session) { +server <- function( + id, + selected_app_, + selected_job_ +) { + shiny$moduleServer(id, function(input, output, session) { ns <- session$ns - output$download <- downloadHandler( + output$download <- shiny$downloadHandler( filename = function() { glue( "{selected_app_()$name}_{selected_job_()$id}.txt" @@ -68,41 +79,68 @@ server <- function(id, selected_app_, selected_job_) { } ) - output$download_logs <- renderUI({ + output$download_logs <- shiny$renderUI({ if (is.null(selected_job_()$key)) { - return(NULL) + NULL } - downloadButton( - outputId = ns("download"), - label = NULL, - icon = icon("download"), - class = "logs-download" + shiny$div( + class = "logs-options", + if (is_llm_enabled()) { + shiny$actionButton( + inputId = ns("llm"), + label = NULL, + icon = shiny$icon("robot"), + class = "llm logs-options-button" + ) + }, + shiny$downloadButton( + outputId = ns("download"), + label = NULL, + icon = shiny$icon("download"), + class = "download logs-options-button" + ) ) }) - logs_data <- reactive({ - req(selected_job_()$key) + logs_data <- shiny$reactive({ + shiny$req(selected_job_()$key) get_job_logs( selected_app_()$guid, selected_job_()$key ) }) - output$logs_table <- renderReactable({ - - processed_logs <- logs_data() %>% + processed_logs <- shiny$reactive({ + logs_data() %>% + pmap_dfr( + ~ { + get_status_info(..1, ..3) |> + as_tibble() |> + bind_cols( + tibble( + entries.source = ..1, + entries.timestamp = ..2, + entries.data = ..3 + ) + ) + } + ) %>% mutate( log_line = paste( entries.source, entries.timestamp, entries.data, + entries.status, + entries.icon, sep = "_-_" ) ) + }) + output$logs_table <- renderReactable({ reactable( - data = processed_logs, + data = processed_logs(), searchable = TRUE, borderless = TRUE, pagination = FALSE, @@ -118,6 +156,12 @@ server <- function(id, selected_app_, selected_job_) { entries.data = colDef( show = FALSE ), + entries.status = colDef( + show = FALSE + ), + entries.icon = colDef( + show = FALSE + ), log_line = colDef( name = "Logs", cell = function(log_data) { @@ -128,5 +172,34 @@ server <- function(id, selected_app_, selected_job_) { ) }) + if (is_llm_enabled()) { + llm_result <- shiny$eventReactive(input$llm, { + shiny$req(processed_logs()) + get_llm_help( + processed_logs() %>% + filter( + entries.status %in% c("red", "yellow") + ) + ) + }) + + shiny$observeEvent(llm_result(), { + shiny$removeModal() + shiny$showModal( + shiny$modalDialog( + easyClose = TRUE, + size = "m", + footer = shiny$modalButton( + shiny$icon( + "xmark", + class = "red-text" + ) + ), + shiny$HTML(llm_result()) + ) %>% + shiny$tagAppendAttributes(class = "logs-llm-modal") + ) + }) + } }) } From 86b1d69dc9880026d606e8a20dd2f6e92f7e2b66 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:37:37 +0530 Subject: [PATCH 24/27] feat: add llm styling --- app/static/css/app.min.css | 2 +- app/styles/_logs.scss | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/static/css/app.min.css b/app/static/css/app.min.css index 79ee1dc..fca6d13 100644 --- a/app/static/css/app.min.css +++ b/app/static/css/app.min.css @@ -1 +1 @@ -@import"https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.logs .rt-td-inner{padding:0 !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:var(--grey-text);margin-bottom:40px}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:.75em}.logs-container .logs-download{position:absolute;z-index:2;right:0;margin:10px;background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:var(--white);color:var(--black-text);height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:rgba(0,0,0,0)}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:var(--selected-row)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:var(--white);width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:var(--grey-text);font-size:.75em}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:.75em;color:var(--grey-text)}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:var(--primary);color:#fff;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro",sans-serif}body{overflow:hidden}.vertical-line{border-left:1px var(--grey2-border) solid;height:80%;align-self:center} +@import "https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.logs .rt-td-inner{padding:0 !important}.logs .rt-search{width:85% !important;align-self:flex-start !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:var(--grey-text);margin-bottom:40px}.logs-llm-placeholder img{width:50%}.logs-llm-modal .modal-dialog{text-align:justify}.logs-llm-modal .modal-dialog h2,.logs-llm-modal .modal-dialog h3{color:var(--primary)}.logs-llm-modal .modal-dialog .btn{background:var(--red-highlight);border-radius:0;border:1px solid var(--red)}.logs-llm-modal .modal-dialog .modal-content{border-radius:0}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:0.75em}.logs-container .logs-options{position:absolute;z-index:2;right:0;margin:10px}.logs-container .logs-options .logs-options-button{background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:var(--white);color:var(--black-text);height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:transparent}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:var(--selected-row)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:var(--white);width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:0.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:var(--grey-text);font-size:0.75em}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:0.75em;color:var(--grey-text)}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:var(--primary);color:white;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro", sans-serif}body{overflow:hidden}.vertical-line{border-left:1px var(--grey2-border) solid;height:80%;align-self:center} diff --git a/app/styles/_logs.scss b/app/styles/_logs.scss index e0d9d50..1532853 100644 --- a/app/styles/_logs.scss +++ b/app/styles/_logs.scss @@ -5,6 +5,11 @@ padding: 0 !important; } + .rt-search { + width: 85% !important; + align-self: flex-start !important; + } + > div { text-align: center; @@ -23,6 +28,27 @@ } } +.logs-llm-modal { + .modal-dialog { + text-align: justify; + + h2, + h3 { + color: var(--primary); + } + + .btn { + background: var(--red-highlight); + border-radius: 0; + border: 1px solid var(--red); + } + + .modal-content { + border-radius: 0; + } + } +} + .logs-container { position: relative; @@ -52,13 +78,16 @@ } } - .logs-download { + .logs-options { position: absolute; z-index: 2; right: 0; margin: 10px; - background: 0; - border-radius: 0; - padding: 5px 10px; + + .logs-options-button { + background: 0; + border-radius: 0; + padding: 5px 10px; + } } } From 3de3d6bce7720b6e118b69c0146a25f6c337deab Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:39:24 +0530 Subject: [PATCH 25/27] chore: disable llm mode by default --- config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.yml b/config.yml index fd670c4..462f155 100644 --- a/config.yml +++ b/config.yml @@ -2,7 +2,7 @@ default: rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO") rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA) llm: - enabled: TRUE + enabled: FALSE provider: "openai" model: "gpt-4o" api_key: !expr Sys.getenv("LLM_API_KEY", NA) From b634e10a3703ff12daab88c6dd7c75f845e52d2c Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Sun, 13 Apr 2025 20:52:21 +0530 Subject: [PATCH 26/27] feat: add logic to validate llm providers --- app/logic/llm_utils.R | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/app/logic/llm_utils.R b/app/logic/llm_utils.R index 02bf812..482f1bb 100644 --- a/app/logic/llm_utils.R +++ b/app/logic/llm_utils.R @@ -17,6 +17,42 @@ is_llm_enabled <- function() { get("llm")$enabled %||% FALSE } +#' Get valid LLM providers +#' @return A character vector of valid LLM providers +get_valid_providers <- function( +) { + ellmer_functions <- ls(ellmer)[ + grepl( + pattern = "^chat_", + x = ls(ellmer) + ) + ] + sub( + pattern = "^chat_", + replacement = "", + x = ellmer_functions + ) +} + +#' Check if the provider is valid +#' @param provider The LLM provider to check +#' @param valid_providers A character vector of valid LLM providers +#' @return Logical indicating if the provider is valid +verify_provider <- function( + provider, + valid_providers = get_valid_providers() +) { + if (!provider %in% valid_providers) { + stop( + glue( + "Invalid LLM provider '{provider}'. ", + "Valid providers are: {paste(valid_providers, collapse = ', ')}" + ) + ) + } + TRUE +} + #' Get LLM configuration #' #' Returns the LLM configuration if LLM is enabled. Otherwise, throws an error. @@ -25,6 +61,10 @@ get_llm_config <- function() { if (!is_llm_enabled()) { stop("Oops! LLM is not enabled in config.yml!") } + verify_provider( + get("llm")$provider, + get_valid_providers() + ) get("llm") } From 687c3115869d6268aafe63f839acc7ae13756f61 Mon Sep 17 00:00:00 2001 From: deepanshkhurana Date: Wed, 23 Apr 2025 13:20:42 +0530 Subject: [PATCH 27/27] chore: update R version + rebase --- renv.lock | 174 +++++++++++++++++++++++------------------------------- 1 file changed, 73 insertions(+), 101 deletions(-) diff --git a/renv.lock b/renv.lock index 3be0c31..ec8eafa 100644 --- a/renv.lock +++ b/renv.lock @@ -1,6 +1,6 @@ { "R": { - "Version": "4.4.3", + "Version": "4.5.0", "Repositories": [ { "Name": "CRAN", @@ -61,7 +61,7 @@ }, "R.utils": { "Package": "R.utils", - "Version": "2.12.3", + "Version": "2.13.0", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -72,28 +72,28 @@ "tools", "utils" ], - "Hash": "3dc2829b790254bfba21e60965787651" + "Hash": "d32373d88da809f8974d5307481862b0" }, "R6": { "Package": "R6", - "Version": "2.5.1", + "Version": "2.6.1", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "470851b6d5d0ac559e9d01bb352b4021" + "Hash": "d4335fe7207f1c01ab8c41762f5840d4" }, "Rcpp": { "Package": "Rcpp", - "Version": "1.0.13-1", + "Version": "1.0.14", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "methods", "utils" ], - "Hash": "6b868847b365672d6c1677b1608da9ed" + "Hash": "e7bdd9ee90e96921ca8a0f1972d66682" }, "S7": { "Package": "S7", @@ -193,9 +193,9 @@ }, "bslib": { "Package": "bslib", - "Version": "0.8.0", + "Version": "0.9.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "base64enc", @@ -211,7 +211,7 @@ "rlang", "sass" ], - "Hash": "b299c6741ca9746fb227debcb0f9fb6c" + "Hash": "70a6489cc254171fb9b4a7f130f44dca" }, "cachem": { "Package": "cachem", @@ -239,14 +239,14 @@ }, "cli": { "Package": "cli", - "Version": "3.6.3", + "Version": "3.6.4", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "b21916dd77a27642b447374a5d30ecf3" + "Hash": "491c34d3d9dd0d2fe13d9f278bb90795" }, "codetools": { "Package": "codetools", @@ -260,10 +260,10 @@ }, "commonmark": { "Package": "commonmark", - "Version": "1.9.2", + "Version": "1.9.5", "Source": "Repository", "Repository": "RSPM", - "Hash": "14eb0596f987c71535d07c3aff814742" + "Hash": "4ac08754c8ed35996b7c343fbb22885a" }, "config": { "Package": "config", @@ -308,20 +308,6 @@ ], "Hash": "e4f9e10b18f453a1b7eaf38247dad4fe" }, - "cyclocomp": { - "Package": "cyclocomp", - "Version": "1.1.1", - "Source": "Repository", - "Repository": "CRAN", - "Requirements": [ - "callr", - "crayon", - "desc", - "remotes", - "withr" - ], - "Hash": "cdc4a473222b0112d4df0bcfbed12d44" - }, "desc": { "Package": "desc", "Version": "1.4.3", @@ -337,9 +323,9 @@ }, "diffobj": { "Package": "diffobj", - "Version": "0.3.5", + "Version": "0.3.6", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "crayon", @@ -348,7 +334,7 @@ "tools", "utils" ], - "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8" + "Hash": "e036ce354ab60e705ac5f40bac87e8cb" }, "digest": { "Package": "digest", @@ -406,13 +392,13 @@ }, "evaluate": { "Package": "evaluate", - "Version": "1.0.1", + "Version": "1.0.3", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R" ], - "Hash": "3fd29944b231036ad67c3edb32e02201" + "Hash": "e9651417729bbe7472e32b5027370e79" }, "fansi": { "Package": "fansi", @@ -447,14 +433,14 @@ }, "fs": { "Package": "fs", - "Version": "1.6.5", + "Version": "1.6.6", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "methods" ], - "Hash": "7f48af39fa27711ea5fbd183b399920d" + "Hash": "7eb1e342eee7e0a7449c49cdaa526d39" }, "generics": { "Package": "generics", @@ -522,7 +508,7 @@ }, "httpuv": { "Package": "httpuv", - "Version": "1.6.15", + "Version": "1.6.16", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -533,7 +519,7 @@ "promises", "utils" ], - "Hash": "d55aa087c47a63ead0f6fc10f8fa1ee0" + "Hash": "6c3c8728e40326de6529a5c46e377e5c" }, "httr2": { "Package": "httr2", @@ -568,17 +554,17 @@ }, "jsonlite": { "Package": "jsonlite", - "Version": "1.8.9", + "Version": "2.0.0", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "methods" ], - "Hash": "4e993b65c2c3ffbffce7bb3e2c6f832b" + "Hash": "b0776f526d36d8bd4a3344a88fe165c4" }, "knitr": { "Package": "knitr", - "Version": "1.49", + "Version": "1.50", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -590,7 +576,7 @@ "xfun", "yaml" ], - "Hash": "9fcb189926d93c636dea94fbe4f44480" + "Hash": "5a07d8ec459d7b80bd4acca5f4a6e062" }, "later": { "Package": "later", @@ -628,14 +614,14 @@ }, "lintr": { "Package": "lintr", - "Version": "3.1.2", + "Version": "3.2.0", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "backports", + "cli", "codetools", - "cyclocomp", "digest", "glue", "knitr", @@ -645,7 +631,7 @@ "xml2", "xmlparsedata" ], - "Hash": "08cff46381a242d44c0d8dd0aabd9f71" + "Hash": "45b44c7f1040cce8f34ec0a9c92d47f3" }, "logger": { "Package": "logger", @@ -681,23 +667,23 @@ }, "mime": { "Package": "mime", - "Version": "0.12", + "Version": "0.13", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "tools" ], - "Hash": "18e9c28c1d3ca1560ce30658b22ce104" + "Hash": "0ec19f34c72fab674d8f2b4b1c6410e1" }, "openssl": { "Package": "openssl", - "Version": "2.2.2", + "Version": "2.3.2", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "askpass" ], - "Hash": "d413e0fef796c9401a4419485f709ca1" + "Hash": "bc54d87ebf858b28de18df4bca6528d3" }, "packrat": { "Package": "packrat", @@ -713,12 +699,11 @@ }, "pillar": { "Package": "pillar", - "Version": "1.9.0", + "Version": "1.10.2", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "cli", - "fansi", "glue", "lifecycle", "rlang", @@ -726,11 +711,11 @@ "utils", "vctrs" ], - "Hash": "15da5a8412f317beeee6175fbc76f4bb" + "Hash": "1098920a19b5cd5a15bacdc74a89979d" }, "pkgbuild": { "Package": "pkgbuild", - "Version": "1.4.5", + "Version": "1.4.7", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -741,7 +726,7 @@ "desc", "processx" ], - "Hash": "30eaaab94db72652e72e3475c1b55278" + "Hash": "ae47817501cafc99f57586b6e5241134" }, "pkgconfig": { "Package": "pkgconfig", @@ -784,7 +769,7 @@ }, "processx": { "Package": "processx", - "Version": "3.8.4", + "Version": "3.8.6", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -793,7 +778,7 @@ "ps", "utils" ], - "Hash": "0c90a7d71988856bad2a2a45dd871bb9" + "Hash": "720161b280b0a35f4d1490ead2fe81d0" }, "promises": { "Package": "promises", @@ -813,20 +798,20 @@ }, "ps": { "Package": "ps", - "Version": "1.8.1", + "Version": "1.9.1", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "b4404b1de13758dea1c0484ad0d48563" + "Hash": "093688087b0bacce6ba2f661f36328e2" }, "purrr": { "Package": "purrr", - "Version": "1.0.2", + "Version": "1.0.4", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "cli", @@ -835,7 +820,7 @@ "rlang", "vctrs" ], - "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc" + "Hash": "cc8b5d43f90551fa6df0a6be5d640a4f" }, "rappdirs": { "Package": "rappdirs", @@ -872,20 +857,6 @@ ], "Hash": "6069eb2a6597963eae0605c1875ff14c" }, - "remotes": { - "Package": "remotes", - "Version": "2.5.0", - "Source": "Repository", - "Repository": "RSPM", - "Requirements": [ - "R", - "methods", - "stats", - "tools", - "utils" - ], - "Hash": "3ee025083e66f18db6cf27b56e23e141" - }, "renv": { "Package": "renv", "Version": "1.0.3", @@ -908,7 +879,7 @@ }, "rhino": { "Package": "rhino", - "Version": "1.10.1", + "Version": "1.11.0", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -916,6 +887,7 @@ "box", "box.linters", "box.lsp", + "callr", "cli", "config", "fs", @@ -933,18 +905,18 @@ "withr", "yaml" ], - "Hash": "cd58cf8f362b8cc8c82aea1de9468218" + "Hash": "65daa659ff338dcf4f1ff79ddecadb00" }, "rlang": { "Package": "rlang", - "Version": "1.1.4", + "Version": "1.1.6", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "R", "utils" ], - "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1" + "Hash": "892124978869b74935dc3934c42bfe5a" }, "rmarkdown": { "Package": "rmarkdown", @@ -981,7 +953,7 @@ }, "rsconnect": { "Package": "rsconnect", - "Version": "1.3.3", + "Version": "1.3.4", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1000,7 +972,7 @@ "tools", "yaml" ], - "Hash": "d466c98fdce812325feb4ad406c6ca4b" + "Hash": "315454d2f50d117ef58691d0bf50fe63" }, "rstudioapi": { "Package": "rstudioapi", @@ -1011,9 +983,9 @@ }, "sass": { "Package": "sass", - "Version": "0.4.9", + "Version": "0.4.10", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R6", "fs", @@ -1021,13 +993,13 @@ "rappdirs", "rlang" ], - "Hash": "d53dbfddf695303ea4ad66f86e99b95d" + "Hash": "3fb78d066fb92299b1d13f6a7c9a90a8" }, "shiny": { "Package": "shiny", - "Version": "1.9.1", + "Version": "1.10.0", "Source": "Repository", - "Repository": "CRAN", + "Repository": "RSPM", "Requirements": [ "R", "R6", @@ -1054,7 +1026,7 @@ "withr", "xtable" ], - "Hash": "6a293995a66e12c48d13aa1f957d09c7" + "Hash": "4b4477baa9a939c5577e5ddb4bf01f28" }, "shinycssloaders": { "Package": "shinycssloaders", @@ -1083,7 +1055,7 @@ }, "stringi": { "Package": "stringi", - "Version": "1.8.4", + "Version": "1.8.7", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1092,7 +1064,7 @@ "tools", "utils" ], - "Hash": "39e1144fd75428983dc3f63aa53dfa91" + "Hash": "2b56088e23bdd58f89aebf43a0913457" }, "stringr": { "Package": "stringr", @@ -1139,7 +1111,7 @@ }, "testthat": { "Package": "testthat", - "Version": "3.2.1.1", + "Version": "3.2.3", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1164,7 +1136,7 @@ "waldo", "withr" ], - "Hash": "3f6e7e5e2220856ff865e4834766bf2b" + "Hash": "42f889439ccb14c55fc3d75c9c755056" }, "tibble": { "Package": "tibble", @@ -1203,17 +1175,17 @@ }, "tinytex": { "Package": "tinytex", - "Version": "0.54", + "Version": "0.57", "Source": "Repository", "Repository": "RSPM", "Requirements": [ "xfun" ], - "Hash": "3ec7e3ddcacc2d34a9046941222bf94d" + "Hash": "02d65e0c0415bf36a7ddc0d2ba50a840" }, "treesitter": { "Package": "treesitter", - "Version": "0.1.0", + "Version": "0.2.0", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1223,7 +1195,7 @@ "rlang", "vctrs" ], - "Hash": "22a579e4ac2ddd021df1791da7fd080f" + "Hash": "cc0472495bc995fabbb58e2de941c384" }, "treesitter.r": { "Package": "treesitter.r", @@ -1288,7 +1260,7 @@ }, "xfun": { "Package": "xfun", - "Version": "0.49", + "Version": "0.52", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1297,11 +1269,11 @@ "stats", "tools" ], - "Hash": "8687398773806cfff9401a2feca96298" + "Hash": "652ce36fe7d57688e6786819b09d9190" }, "xml2": { "Package": "xml2", - "Version": "1.3.6", + "Version": "1.3.8", "Source": "Repository", "Repository": "RSPM", "Requirements": [ @@ -1310,7 +1282,7 @@ "methods", "rlang" ], - "Hash": "1d0336142f4cd25d8d23cd3ba7a8fb61" + "Hash": "f5130b2f3d461964bac93cc618013231" }, "xmlparsedata": { "Package": "xmlparsedata",