Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/prepare_connect.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Setup system dependencies
run: >
sudo apt-get update && sudo apt-get install --yes
libcurl4-openssl-dev libsodium-dev
libcurl4-openssl-dev libsodium-dev unixodbc-dev

- name: Checkout repository
uses: actions/checkout@v3
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# shiny.telemetry 0.3.1.9001
# shiny.telemetry 0.3.1.9002

### New Features

- Added a dropdown to the `analytics_app()` to switch between applications.
- Resolve bug#192 that ignored excluded input regex.

### Bug Fixes

Expand Down
28 changes: 28 additions & 0 deletions R/auxiliary.R
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,31 @@ process_row_details <- function(details_json) {

tmp_result
}

#' Merge a list of regular expressions into a single one
#'
#' @param regex_l list of regular expressions to be merged.
#' @param trim_whitespace boolean indicating if whitespace should be trim
#' from the start and end of the regex list.
#'
#' @returns Single regular expression string.
#' @keywords internal
merge_excluded_regex <- function(regex_l, trim_whitespace = TRUE) {
checkmate::assert(
combine = "or",
.var.name = "regex_l",
checkmate::check_character(regex_l),
checkmate::check_list(regex_l, types = "character"),
checkmate::check_null(regex_l)
)
checkmate::assert_flag(trim_whitespace)

if (is.null(regex_l)) return(NULL)

if (trim_whitespace) regex_l <- purrr::map_chr(regex_l, trimws)

clean_regex_l <- purrr::keep(regex_l, ~ nzchar(.x))

# Prevent any | at the end of the regex
sub("\\|$", "", paste(clean_regex_l, collapse = "|"))
}
19 changes: 11 additions & 8 deletions R/telemetry.R
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,16 @@ Telemetry <- R6::R6Class( # nolint object_name.
checkmate::assert_character(excluded_inputs, null.ok = TRUE)
checkmate::assert_character(excluded_inputs_regex, null.ok = TRUE)

checkmate::assert(
tryCatch({
lapply(excluded_inputs_regex, regexpr, text = "mock_text")
TRUE
}, error = function(err) "Regular expression is not valid"),
.var.name = "excluded_inputs_regex"
)

merged_regex <- merge_excluded_regex(excluded_inputs_regex)

if (is.null(navigation_inputs)) {
navigation_inputs <- c()
}
Expand Down Expand Up @@ -754,15 +764,8 @@ Telemetry <- R6::R6Class( # nolint object_name.

# Filter out excluded inputs by regular expression
if (length(excluded_inputs_regex) > 0) {
excluded_inputs_regex <- excluded_inputs_regex %>%
purrr::map_chr(trimws) %>%
purrr::keep(~ nzchar(.x)) %>%
purrr::map_chr(stringr::str_escape) %>%
paste(collapse = "|") %>%
sub(pattern = "\\|$", replacement = "")
filtered_names <- setdiff(
filtered_names,
grep(excluded_inputs_regex, filtered_names, value = TRUE)
filtered_names, grep(merged_regex, filtered_names, value = TRUE)
)
}

Expand Down
2 changes: 1 addition & 1 deletion inst/examples/mssql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The database instance has to be running for the application and analytics dashbo
The database needs to be created manually. Chunk below allows to create databse using the `sqlcmd` utility.

```
CREATE DATABSAE my_db
CREATE DATABASE shiny_telemetry
GO
```

Expand Down
21 changes: 21 additions & 0 deletions man/merge_excluded_regex.Rd

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

22 changes: 22 additions & 0 deletions tests/testthat/test-auxiliary_functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,25 @@ test_that("build_mongo_connection_string: Build valid string with all parameters
"mongodb://a_user:a_pass@localhost:27017/path_to_authdb?option1=value1&option2=value2"
)
})

describe("merge_excluded_regex generates valid regular expressions with", {
it("* or +", {
expect_true(grepl(merge_excluded_regex(list("[b-z]", "aa+")), "aaa"))
expect_true(grepl(merge_excluded_regex(list("[b-z]", "a+")), "a"))
expect_true(grepl(merge_excluded_regex(list("[b-z]", "ba*")), "ba"))
expect_true(grepl(merge_excluded_regex(list("[b-z]", "ba*")), "b"))
})

it("count of characters", {
expect_true(grepl(merge_excluded_regex(list("[b-z]", "a{2}", "1234")), "aa"))
expect_false(grepl(merge_excluded_regex(list("[b-z]", "^a{2}$", "1234")), "aaaa"))
})

it("range of characters", {
expect_true(grepl(merge_excluded_regex(list("^[b-z]+$", "a{2}", "1234")), "chrome"))
})

it("escape characters", {
expect_false(grepl(merge_excluded_regex(list("a", "\\[a-z\\]")), "y"))
})
})
39 changes: 39 additions & 0 deletions tests/testthat/test-telemetry.R
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,42 @@ test_that("Telemetry tests with mock data_storage layer", {
# Manual call to revert mock_binding of 'observeEvent'
withr::deferred_run()
})

describe("excluded_inputs_regex", {
data_storage <- list(
insert = function(
app_name, type, session = NULL, details = NULL, time = NULL
) {
message(glue::glue(
"Writing type={type} value: ",
"{jsonlite::toJSON(details, auto_unbox = TRUE)}"
))
},
event_bucket = "event_log"
)

telemetry <- Telemetry$new(data_storage = data_storage)

session <- shiny::MockShinySession$new()
class(session) <- c("ShinySession", class(session))

it("throws error with invalid regular expression", {
expect_error(
telemetry$log_all_inputs(excluded_inputs_regex = c("(", "[b-z]", ")"), session = session),
"Regular expression is not valid"
) %>%
expect_warning("pattern compilation error")
})

it("accepts valid expressions", {
expect_no_error(
telemetry$log_all_inputs(excluded_inputs_regex = c("(a)", "[b-z]", "(b)"), session = session)
)
})

it("accepts NULL default", {
expect_no_error(
telemetry$log_all_inputs(excluded_inputs_regex = NULL, session = session)
)
})
})
2 changes: 1 addition & 1 deletion vignettes/databases.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The following databases are supported by `{shiny.telemetry}`:
- [MariaDB](https://mariadb.org/documentation/) or [MySQL](https://dev.mysql.com/doc/refman/en/)
- [MS SQL Server](https://learn.microsoft.com/en-us/sql/sql-server/)
- [MongoDB](https://www.mongodb.com/docs/manual/)
- [SQLite](https://www.sqlite.org/docs.html)
- [SQLite](https://sqlite.org/docs.html)

A requirements to use `{shiny.telemetry}` with external databases in a production environment is to have the database server running and a user with the necessary permissions to insert.
A minimal setup should have a user that only has write/insert permissions to the `{shiny.telemetry}` table storing the events.
Expand Down