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)
)
diff --git a/README.md b/README.md
index aaea915..235a415 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,14 @@ 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.
+ - 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.
# Credits
diff --git a/app/logic/empty_state_utils.R b/app/logic/empty_state_utils.R
index 3acbd10..5718197 100644
--- a/app/logic/empty_state_utils.R
+++ b/app/logic/empty_state_utils.R
@@ -1,19 +1,23 @@
box::use(
+ base64enc[
+ base64encode
+ ],
shiny[
div,
img,
p,
- renderUI
],
)
#' @description Function to generate an empty state UI
#' @param text Text to display in the empty state
#' @param image_path Path to the image to display in the empty state
+#' @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"
+ image_path = "static/illustrations/empty_state.svg",
+ color = "#0099f9"
) {
div(
class = "empty-state-container",
@@ -21,10 +25,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..1f5fa47 100644
--- a/app/logic/general_utils.R
+++ b/app/logic/general_utils.R
@@ -1,3 +1,9 @@
+box::use(
+ purrr[
+ map_chr
+ ],
+)
+
#' Function to check if a string of log text has error keywords
#'
#' @param text Character. The log string
@@ -37,3 +43,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
+) {
+ 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/logic/llm_utils.R b/app/logic/llm_utils.R
new file mode 100644
index 0000000..482f1bb
--- /dev/null
+++ b/app/logic/llm_utils.R
@@ -0,0 +1,128 @@
+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 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.
+#' @return A list containing the LLM configuration
+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")
+}
+
+#' 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"
+ )
+}
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 4adf1dd..62a0121 100644
--- a/app/main.R
+++ b/app/main.R
@@ -1,62 +1,52 @@
-# nolint start: box_func_import_count_linter
box::use(
- dplyr[select],
- magrittr[`%>%`],
- shiny[
- div,
- fluidPage,
- img,
- isTruthy,
- moduleServer,
- NS,
- observeEvent,
- p,
- reactive,
- reactiveValues,
- removeUI,
- renderUI,
- tagList,
- tags,
- uiOutput
- ],
- shinycssloaders[withSpinner],
+ config[get],
+ shiny,
)
-# nolint end
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,
app/view/mod_logs,
)
+# Load Branding
+branding <- get("branding")
+branding_css_variables <- generate_css_variables(
+ branding
+)
+
#' @export
ui <- function(id) {
- ns <- NS(id)
- fluidPage(
+ ns <- shiny$NS(id)
+ shiny$fluidPage(
class = "dashboard-body",
+ shiny$tags$head(
+ shiny$tags$style(shiny$HTML(branding_css_variables))
+ ),
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,80 +54,57 @@ ui <- function(id) {
#' @export
server <- function(id) {
- moduleServer(id, function(input, output, session) {
+ shiny$moduleServer(id, function(input, output, session) {
ns <- session$ns
- mod_header$server("header")
-
- state <- reactiveValues()
- state$selected_app <- reactive({})
- state$selected_job <- reactive({})
+ app_list <- get_app_list()
- app_list <- reactive({
- get_app_list()
- })
+ mod_header$server("header")
- mod_app_table$server(
+ selected_app_ <- mod_app_table$server(
"app_table",
- app_list(),
- state
+ app_list
+ )$selected_app_
+
+ selected_job_ <- mod_job_list$server(
+ "job_list",
+ selected_app_
+ )$selected_job_
+
+ mod_logs$server(
+ "logs",
+ selected_app_,
+ selected_job_
)
- observeEvent(state$selected_app()$guid, {
-
- if (isTruthy(state$selected_app()$guid)) {
+ output$job_list_pane <- shiny$renderUI({
+ if (!shiny$isTruthy(selected_app_()$guid)) {
+ NULL
+ }
- output$job_list_pane <- 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) {
+ 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 {
-
- removeUI(ns("job_list_pane"))
-
}
- }, ignoreNULL = FALSE)
- observeEvent(state$selected_job()$key, {
-
- if (isTruthy(state$selected_job()$key)) {
-
- output$logs_pane <- renderUI({
- mod_logs$ui(ns("logs"))
- })
-
- mod_logs$server(
- "logs",
- state
+ if (!shiny$isTruthy(selected_job_()$key)) {
+ 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 <- renderUI({
- generate_empty_state_ui(
- text = "Oops! Can't read apps from Posit Connect.",
- image_path = "static/illustrations/missing_apps.svg"
- )
- })
- } else {
- empty_state <- renderUI({
- generate_empty_state_ui(
- text = "Select an application and a job to view logs.",
- image_path = "static/illustrations/empty_state.svg"
- )
- })
- }
-
- output$logs_pane <- empty_state
}
- }, ignoreNULL = FALSE)
+ mod_logs$ui(ns("logs"))
+ })
})
}
diff --git a/app/static/css/app.min.css b/app/static/css/app.min.css
index 1060d16..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:#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 .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/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 @@
Caution: This help is AI generated. Verify the information before taking any action.
+- 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.
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/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..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;
@@ -16,13 +21,34 @@
}
.empty-state-text {
- color: $grey-text;
+ color: var(--grey-text);
margin-bottom: 40px;
}
}
}
}
+.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;
+ }
}
}
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/app/view/mod_app_table.R b/app/view/mod_app_table.R
index 52c6e0c..181251b 100644
--- a/app/view/mod_app_table.R
+++ b/app/view/mod_app_table.R
@@ -38,12 +38,12 @@ 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({
- 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,
@@ -80,6 +80,7 @@ server <- function(id, app_list, state) {
searchable = TRUE,
borderless = TRUE,
pagination = FALSE,
+ onClick = "select",
selection = "single",
columns = list(
guid = colDef(
@@ -98,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) && nrow(app_list) > 0) {
+ list(
+ "guid" = app_list[index, ]$guid,
+ "name" = app_list[index, ]$name
+ )
+ }
+ })
+ )
})
diff --git a/app/view/mod_header.R b/app/view/mod_header.R
index e94e1d0..ae5891e 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"
@@ -31,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 c4a6fbf..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,
@@ -39,12 +38,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({
@@ -58,6 +57,7 @@ server <- function(id, state) {
selection = "single",
borderless = TRUE,
pagination = FALSE,
+ onClick = "select",
columns = list(
job = colDef(
cell = function(job_data) {
@@ -72,15 +72,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) && nrow(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..4644574 100644
--- a/app/view/mod_logs.R
+++ b/app/view/mod_logs.R
@@ -1,42 +1,47 @@
-# nolint start: box_func_import_count_linter
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
- ],
)
-# nolint end
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(
@@ -50,60 +55,92 @@ ui <- function(id) {
}
#' @export
-server <- function(id, state) {
- 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(
- "{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)
- output$download_logs <- renderUI({
- downloadButton(
+ output$download_logs <- shiny$renderUI({
+ if (is.null(selected_job_()$key)) {
+ NULL
+ }
+
+ 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 = icon("download"),
- class = "logs-download"
+ icon = shiny$icon("download"),
+ class = "download logs-options-button"
)
- })
+ )
})
- logs_data <- reactive({
- req(state$selected_job()$key)
+ logs_data <- shiny$reactive({
+ shiny$req(selected_job_()$key)
get_job_logs(
- state$selected_app()$guid,
- state$selected_job()$key
+ 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,
@@ -119,6 +156,12 @@ server <- function(id, state) {
entries.data = colDef(
show = FALSE
),
+ entries.status = colDef(
+ show = FALSE
+ ),
+ entries.icon = colDef(
+ show = FALSE
+ ),
log_line = colDef(
name = "Logs",
cell = function(log_data) {
@@ -129,5 +172,34 @@ server <- function(id, state) {
)
})
+ 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")
+ )
+ })
+ }
})
}
diff --git a/config.yml b/config.yml
index 8e5f085..462f155 100644
--- a/config.yml
+++ b/config.yml
@@ -1,5 +1,32 @@
default:
rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO")
rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA)
+ llm:
+ enabled: FALSE
+ 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:
+ src: "static/logo.png"
+ alt: "Appsilon logo"
+ href: "https://demo.appsilon.com"
+ colors:
+ primary: "#0099f9"
+ 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"
diff --git a/dependencies.R b/dependencies.R
index 289f81d..a2db989 100644
--- a/dependencies.R
+++ b/dependencies.R
@@ -1,8 +1,11 @@
# This file allows packrat (used by rsconnect during deployment) to pick up dependencies.
library(dplyr)
+library(ellmer)
library(httr2)
library(magrittr)
library(reactable)
library(rhino)
library(rsconnect)
library(shinycssloaders)
+library(treesitter)
+library(treesitter.r)
diff --git a/renv.lock b/renv.lock
index 2c0c389..ec8eafa 100644
--- a/renv.lock
+++ b/renv.lock
@@ -1,6 +1,6 @@
{
"R": {
- "Version": "4.3.2",
+ "Version": "4.5.0",
"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.13.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R.methodsS3",
@@ -61,48 +72,59 @@
"tools",
"utils"
],
- "Hash": "325f01db13da12c04d8f6e7be36ff514"
+ "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.11",
+ "Version": "1.0.14",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"methods",
"utils"
],
- "Hash": "ae6cbbe1492f4de79c45fce06f967ce8"
+ "Hash": "e7bdd9ee90e96921ca8a0f1972d66682"
+ },
+ "S7": {
+ "Package": "S7",
+ "Version": "0.2.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R",
+ "utils"
+ ],
+ "Hash": "5deb66b3ae702137e1f4162c11861e76"
},
"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 +138,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.9.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"base64enc",
"cachem",
+ "fastmap",
"grDevices",
"htmltools",
"jquerylib",
"jsonlite",
+ "lifecycle",
"memoise",
"mime",
"rlang",
"sass"
],
- "Hash": "283015ddfbb9d7bf15ea9f0b5698f0d9"
+ "Hash": "70a6489cc254171fb9b4a7f130f44dca"
},
"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.4",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "89e6d8219950eac806ae0c489052048a"
+ "Hash": "491c34d3d9dd0d2fe13d9f278bb90795"
},
"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.5",
"Source": "Repository",
- "Repository": "CRAN",
- "Hash": "d691c61bff84bd63c383874d2d0c3307"
+ "Repository": "RSPM",
+ "Hash": "4ac08754c8ed35996b7c343fbb22885a"
},
"config": {
"Package": "config",
@@ -214,61 +275,57 @@
],
"Hash": "8b7222e9d9eb5178eea545c0c4d33fc2"
},
+ "coro": {
+ "Package": "coro",
+ "Version": "1.1.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R",
+ "rlang"
+ ],
+ "Hash": "4998c8836ff95c90993a4eb8d853df71"
+ },
"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.2.2",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R"
],
- "Hash": "ce88d13c0b10fe88a37d9c59dba2d7f9"
- },
- "cyclocomp": {
- "Package": "cyclocomp",
- "Version": "1.1.1",
- "Source": "Repository",
- "Repository": "CRAN",
- "Requirements": [
- "callr",
- "crayon",
- "desc",
- "remotes",
- "withr"
- ],
- "Hash": "cdc4a473222b0112d4df0bcfbed12d44"
+ "Hash": "e4f9e10b18f453a1b7eaf38247dad4fe"
},
"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",
- "Version": "0.3.5",
+ "Version": "0.3.6",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"crayon",
@@ -277,18 +334,18 @@
"tools",
"utils"
],
- "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8"
+ "Hash": "e036ce354ab60e705ac5f40bac87e8cb"
},
"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 +370,77 @@
],
"Hash": "fedd9d00c2944ff00a0e2696ccf048ec"
},
- "ellipsis": {
- "Package": "ellipsis",
- "Version": "0.3.2",
+ "ellmer": {
+ "Package": "ellmer",
+ "Version": "0.1.1",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
- "R",
+ "R6",
+ "S7",
+ "cli",
+ "coro",
+ "glue",
+ "httr2",
+ "jsonlite",
+ "later",
+ "lifecycle",
+ "promises",
"rlang"
],
- "Hash": "bb0eec2fe32e88d9e2836c2f73ea2077"
+ "Hash": "754d3a8cbb25b05be2058892f579422f"
},
"evaluate": {
"Package": "evaluate",
- "Version": "0.23",
+ "Version": "1.0.3",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
- "R",
- "methods"
+ "R"
],
- "Hash": "daf4a1246be12c1fa8c7705a0935c1a0"
+ "Hash": "e9651417729bbe7472e32b5027370e79"
},
"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.6",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"methods"
],
- "Hash": "47b5f30c720c23999b913a1a635cf0bb"
+ "Hash": "7eb1e342eee7e0a7449c49cdaa526d39"
},
"generics": {
"Package": "generics",
@@ -390,42 +455,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 +508,9 @@
},
"httpuv": {
"Package": "httpuv",
- "Version": "1.6.12",
+ "Version": "1.6.16",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
@@ -455,11 +519,11 @@
"promises",
"utils"
],
- "Hash": "c992f75861325961c29a188b45e549f7"
+ "Hash": "6c3c8728e40326de6529a5c46e377e5c"
},
"httr2": {
"Package": "httr2",
- "Version": "1.0.0",
+ "Version": "1.1.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -476,7 +540,7 @@
"vctrs",
"withr"
],
- "Hash": "e2b30f1fc039a0bab047dd52bb20ef71"
+ "Hash": "ade531519694081d91036b509eb30594"
},
"jquerylib": {
"Package": "jquerylib",
@@ -490,19 +554,19 @@
},
"jsonlite": {
"Package": "jsonlite",
- "Version": "1.8.7",
+ "Version": "2.0.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"methods"
],
- "Hash": "266a20443ca13c65688b2116d5220f76"
+ "Hash": "b0776f526d36d8bd4a3344a88fe165c4"
},
"knitr": {
"Package": "knitr",
- "Version": "1.45",
+ "Version": "1.50",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"evaluate",
@@ -512,18 +576,18 @@
"xfun",
"yaml"
],
- "Hash": "1ec462871063897135c1bcbe0fc8f07d"
+ "Hash": "5a07d8ec459d7b80bd4acca5f4a6e062"
},
"later": {
"Package": "later",
- "Version": "1.3.1",
+ "Version": "1.4.2",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"Rcpp",
"rlang"
],
- "Hash": "40401c9cf2bc2259dfe83311c9384710"
+ "Hash": "9591aabef9cf988f05bde9324fd3ad99"
},
"lazyeval": {
"Package": "lazyeval",
@@ -537,27 +601,27 @@
},
"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.2.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"backports",
+ "cli",
"codetools",
- "cyclocomp",
"digest",
"glue",
"knitr",
@@ -567,17 +631,18 @@
"xml2",
"xmlparsedata"
],
- "Hash": "2b4b803af6017e93b67ddaf0eacba918"
+ "Hash": "45b44c7f1040cce8f34ec0a9c92d47f3"
},
"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",
@@ -602,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.1.1",
+ "Version": "2.3.2",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"askpass"
],
- "Hash": "2a0dc8c6adfb6f032e4d4af82d258ab5"
+ "Hash": "bc54d87ebf858b28de18df4bca6528d3"
},
"packrat": {
"Package": "packrat",
@@ -634,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",
@@ -647,25 +711,22 @@
"utils",
"vctrs"
],
- "Hash": "15da5a8412f317beeee6175fbc76f4bb"
+ "Hash": "1098920a19b5cd5a15bacdc74a89979d"
},
"pkgbuild": {
"Package": "pkgbuild",
- "Version": "1.4.2",
+ "Version": "1.4.7",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
"callr",
"cli",
- "crayon",
"desc",
- "prettyunits",
- "processx",
- "rprojroot"
+ "processx"
],
- "Hash": "beb25b32a957a22a5c301a9e441190b3"
+ "Hash": "ae47817501cafc99f57586b6e5241134"
},
"pkgconfig": {
"Package": "pkgconfig",
@@ -679,24 +740,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 +767,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.6",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
"ps",
"utils"
],
- "Hash": "3efbd8ac1be0296a46c55387aeace0f3"
+ "Hash": "720161b280b0a35f4d1490ead2fe81d0"
},
"promises": {
"Package": "promises",
- "Version": "1.2.1",
+ "Version": "1.3.2",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R6",
"Rcpp",
@@ -742,24 +794,24 @@
"rlang",
"stats"
],
- "Hash": "0d8a15c9d000970ada1ab21405387dee"
+ "Hash": "c84fd4f75ea1f5434735e08b7f50fbca"
},
"ps": {
"Package": "ps",
- "Version": "1.7.5",
+ "Version": "1.9.1",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "709d852d33178db54b17c722e5b1e594"
+ "Hash": "093688087b0bacce6ba2f661f36328e2"
},
"purrr": {
"Package": "purrr",
- "Version": "1.0.2",
+ "Version": "1.0.4",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"cli",
@@ -768,7 +820,7 @@
"rlang",
"vctrs"
],
- "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc"
+ "Hash": "cc8b5d43f90551fa6df0a6be5d640a4f"
},
"rappdirs": {
"Package": "rappdirs",
@@ -782,13 +834,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,30 +857,6 @@
],
"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",
- "Source": "Repository",
- "Repository": "CRAN",
- "Requirements": [
- "R",
- "methods",
- "stats",
- "tools",
- "utils"
- ],
- "Hash": "63d15047eb239f95160112bcadc4fcb9"
- },
"renv": {
"Package": "renv",
"Version": "1.0.3",
@@ -851,12 +879,15 @@
},
"rhino": {
"Package": "rhino",
- "Version": "1.7.0",
+ "Version": "1.11.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"box",
+ "box.linters",
+ "box.lsp",
+ "callr",
"cli",
"config",
"fs",
@@ -872,27 +903,26 @@
"testthat",
"utils",
"withr",
- "xml2",
"yaml"
],
- "Hash": "59ee79b26dd590b08dd1a3111d093832"
+ "Hash": "65daa659ff338dcf4f1ff79ddecadb00"
},
"rlang": {
"Package": "rlang",
- "Version": "1.1.1",
+ "Version": "1.1.6",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "a85c767b55f0bf9b7ad16c6d7baee5bb"
+ "Hash": "892124978869b74935dc3934c42bfe5a"
},
"rmarkdown": {
"Package": "rmarkdown",
- "Version": "2.25",
+ "Version": "2.29",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"bslib",
@@ -903,31 +933,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.4",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
+ "PKI",
"R",
"cli",
"curl",
@@ -942,20 +972,20 @@
"tools",
"yaml"
],
- "Hash": "94bb3a2125b01b13dd2e4a784c2a9639"
+ "Hash": "315454d2f50d117ef58691d0bf50fe63"
},
"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.10",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R6",
"fs",
@@ -963,13 +993,13 @@
"rappdirs",
"rlang"
],
- "Hash": "6bd4d33b50ff927191ec9acbf52fd056"
+ "Hash": "3fb78d066fb92299b1d13f6a7c9a90a8"
},
"shiny": {
"Package": "shiny",
- "Version": "1.7.5.1",
+ "Version": "1.10.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
@@ -977,7 +1007,6 @@
"cachem",
"commonmark",
"crayon",
- "ellipsis",
"fastmap",
"fontawesome",
"glue",
@@ -997,21 +1026,22 @@
"withr",
"xtable"
],
- "Hash": "5ec01cc255f2138fc2f0dc74d2b1a1a1"
+ "Hash": "4b4477baa9a939c5577e5ddb4bf01f28"
},
"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 +1055,16 @@
},
"stringi": {
"Package": "stringi",
- "Version": "1.8.3",
+ "Version": "1.8.7",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"stats",
"tools",
"utils"
],
- "Hash": "058aebddea264f4c99401515182e656a"
+ "Hash": "2b56088e23bdd58f89aebf43a0913457"
},
"stringr": {
"Package": "stringr",
@@ -1055,9 +1085,9 @@
},
"styler": {
"Package": "styler",
- "Version": "1.10.2",
+ "Version": "1.10.3",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R.cache",
@@ -1070,20 +1100,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.3",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
@@ -1092,7 +1122,6 @@
"cli",
"desc",
"digest",
- "ellipsis",
"evaluate",
"jsonlite",
"lifecycle",
@@ -1107,7 +1136,7 @@
"waldo",
"withr"
],
- "Hash": "877508719fcb8c9525eccdadf07a5102"
+ "Hash": "42f889439ccb14c55fc3d75c9c755056"
},
"tibble": {
"Package": "tibble",
@@ -1130,9 +1159,9 @@
},
"tidyselect": {
"Package": "tidyselect",
- "Version": "1.2.0",
+ "Version": "1.2.1",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"cli",
@@ -1142,17 +1171,41 @@
"vctrs",
"withr"
],
- "Hash": "79540e5fcd9e0435af547d885f184fd5"
+ "Hash": "829f27b9c4919c16b593794a6344d6c0"
},
"tinytex": {
"Package": "tinytex",
- "Version": "0.49",
+ "Version": "0.57",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"xfun"
],
- "Hash": "5ac22900ae0f386e54f1c307eca7d843"
+ "Hash": "02d65e0c0415bf36a7ddc0d2ba50a840"
+ },
+ "treesitter": {
+ "Package": "treesitter",
+ "Version": "0.2.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R",
+ "R6",
+ "cli",
+ "rlang",
+ "vctrs"
+ ],
+ "Hash": "cc0472495bc995fabbb58e2de941c384"
+ },
+ "treesitter.r": {
+ "Package": "treesitter.r",
+ "Version": "1.1.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R"
+ ],
+ "Hash": "067c058dbd496c4cd696449870b11d52"
},
"utf8": {
"Package": "utf8",
@@ -1166,9 +1219,9 @@
},
"vctrs": {
"Package": "vctrs",
- "Version": "0.6.4",
+ "Version": "0.6.5",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"cli",
@@ -1176,60 +1229,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.52",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
+ "R",
+ "grDevices",
"stats",
"tools"
],
- "Hash": "460a5e0fe46a80ef87424ad216028014"
+ "Hash": "652ce36fe7d57688e6786819b09d9190"
},
"xml2": {
"Package": "xml2",
- "Version": "1.3.5",
+ "Version": "1.3.8",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
- "methods"
+ "cli",
+ "methods",
+ "rlang"
],
- "Hash": "6c40e5cfcc6aefd88110666e18c31f40"
+ "Hash": "f5130b2f3d461964bac93cc618013231"
},
"xmlparsedata": {
"Package": "xmlparsedata",
@@ -1255,10 +1308,10 @@
},
"yaml": {
"Package": "yaml",
- "Version": "2.3.7",
+ "Version": "2.3.10",
"Source": "Repository",
- "Repository": "CRAN",
- "Hash": "0d0056cc5383fbc240ccd0cb584bf436"
+ "Repository": "RSPM",
+ "Hash": "51dab85c6c98e50a18d7551e9d49f76c"
}
}
}
diff --git a/tests/testthat/test-general_utils.R b/tests/testthat/test-general_utils.R
new file mode 100644
index 0000000..a298404
--- /dev/null
+++ b/tests/testthat/test-general_utils.R
@@ -0,0 +1,82 @@
+box::use(
+ testthat[
+ describe,
+ expect_identical,
+ 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
+ )
+ })
+})