Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes problems with #176 #178

Merged
merged 9 commits into from
Jun 11, 2024
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
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Authors@R: c(
person("Krystian", "Igras", , "[email protected]", role = "aut"),
person("Recle", "Vibal", , "[email protected]", role = "aut"),
person("Arun", "Kodati", , "[email protected]", role = "aut"),
person("Wahaduzzaman", "Khan", , "[email protected]", role = "aut"),
person("Appsilon Sp. z o.o.", , , "[email protected]", role = "cph")
)
Description: Enables instrumentation of 'Shiny' apps for tracking user
Expand Down
40 changes: 40 additions & 0 deletions R/auxiliary.R
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,46 @@ build_query_mongodb <- function(date_from, date_to) {
jsonlite::toJSON(query, auto_unbox = TRUE)
}

#' Create the connection string for mongodb
#'
#' @noRd
#' @keywords internal
#' @examples
#' build_mongo_connection_string(
#' "localhost",
#' 31,
#' "user",
#' "pass",
#' "authdb",
#' list("option1" = "value1", "option2" = "value2")
#' )
build_mongo_connection_string <- function(
hostname, port, username, password, authdb, options) {
checkmate::assert_string(hostname)
checkmate::assert_int(port)
checkmate::assert_string(username, null.ok = TRUE)
checkmate::assert_string(password, null.ok = TRUE)
checkmate::assert_string(authdb, null.ok = TRUE)
checkmate::assert_list(options, null.ok = TRUE)

paste0(
"mongodb://",
sprintf("%s:%s@", username, password),
hostname,
":",
port,
sprintf("/%s", authdb %||% ""),
ifelse(
isFALSE(is.null(options)),
sprintf(
"?%s",
paste(names(options), "=", options, collapse = "&", sep = "")
),
""
)
)
}

#' Process a row's detail (from DB) in JSON format to a data.frame
#'
#' @param details_json string containing details a valid JSON, NULL or NA
Expand Down
121 changes: 61 additions & 60 deletions R/data-storage-mongodb.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
#'
#' @examples
#' \dontrun{
#' data_storage <- DataStorageMariaDB$new(
#' url = "mongodb://localhost",
#' data_storage <- DataStorageMongoDB$new(
#' host = "localhost",
#' db = "test",
#' collection = "test",
#' options = mongolite::ssl_options()
#' ssl_options = mongolite::ssl_options()
#' )
#' data_storage$insert("example", "test_event", "session1")
#' data_storage$insert("example", "input", "s1", list(id = "id1"))
Expand All @@ -36,91 +35,83 @@ DataStorageMongoDB <- R6::R6Class( # nolint object_name.

#' @description
#' Initialize the data storage class
#' @param host the hostname or IP address of the MongoDB server.
#' @param hostname the hostname or IP address of the MongoDB server.
#' @param port the port number of the MongoDB server (default is 27017).
#' @param username the username for authentication (optional).
#' @param password the password for authentication (optional).
#' @param authdb the default authentication database (optional).
#' @param db name of database (default is "shiny_telemetry").
#' @param collection name of collection (default is "event_log").
#' @param dbname name of database (default is "shiny_telemetry").
#' @param options Additional connection options in a named list format
#' (e.g., list(ssl = "true", replicaSet = "myreplicaset")) (optional).
#' @param ssl_options additional connection options such as SSL keys/certs
#' (default is [`mongolite::ssl_options()`]).

initialize = function(
host = "localhost",
hostname = "localhost",
port = 27017,
username = NULL,
password = NULL,
authdb = NULL,
db = "shiny_telemetry",
collection = "event_log",
dbname = "shiny_telemetry",
options = NULL,
ssl_options = mongolite::ssl_options()
) {
connection_string <- private$create_connection_string(
host = host,
port = port,
username = username,
password = password,
authdb = authdb,
options = options
)
private$connect(url = connection_string, db, collection, options = ssl_options)
private$db_name <- db
private$collection_name <- collection
}
),
active = list(

#' @field event_bucket string that identifies the bucket to store user
#' related and action data
event_bucket = function() private$collection_name
),
#
# Private
private = list(
# Private Fields
db_con = NULL,
db_name = NULL,
collection_name = NULL,

# Private methods
create_connection_string = function(
host, port, username, password, authdb, options
) {
# create the connection string for mongodb
checkmate::assert_string(host)
checkmate::assert_string(hostname)
checkmate::assert_int(port)
checkmate::assert_string(username, null.ok = TRUE)
checkmate::assert_string(password, null.ok = TRUE)
checkmate::assert_string(authdb, null.ok = TRUE)
checkmate::assert_string(dbname)
checkmate::assert_list(options, null.ok = TRUE)
checkmate::assert_list(ssl_options, null.ok = TRUE)

if (!is.null(username) && !is.null(password)) {
auth_string <- paste0(username, ":", password, "@")
password_debug <- if (is.null(password)) {
"(empty)"
} else {
auth_string <- ""
digest::digest(password, algo = "sha256")
}

authdb_string <- ifelse(!is.null(authdb), paste0("/", authdb), "")

connection_string <- paste0("mongodb://", auth_string, host, ":", port, authdb_string)
# Add options if provided
if (!is.null(options)) {
options_string <- paste0("?", paste(names(options), "=", options, collapse = "&"))
connection_string <- paste0(connection_string, options_string)
}
logger::log_debug(
"Parameters for MongoDB:\n",
" * username: {username %||% \"(empty)\"}\n",
" * password (sha256): {password_debug}\n",
" * hostname:port: {hostname}:{port}\n",
" * db name: {dbname}\n",
" * authdb: {authdb %||% \"(empty)\"}\n",
" * options: {jsonlite::toJSON(options, auto_unbox = TRUE)}\n",
" * ssl_options: ",
"{jsonlite::toJSON(unclass(mongolite::ssl_options()), auto_unbox = TRUE)}\n",
namespace = "shiny.telemetry"
)

return(connection_string)
},
private$connect(
url = build_mongo_connection_string(
hostname = hostname,
port = port,
username = username,
password = password,
authdb = authdb,
options = options
),
dbname,
options = ssl_options
)
}
),
#
# Private
private = list(
# Private Fields
db_con = NULL,

connect = function(url, db, collection, options) {
# Private methods
connect = function(url, dbname, options) {
# Initialize connection with database
private$db_con <- mongolite::mongo(
url = url,
db = db,
collection = collection,
db = dbname,
collection = self$event_bucket,
options = options
)
},
Expand All @@ -133,7 +124,9 @@ DataStorageMongoDB <- R6::R6Class( # nolint object_name.
checkmate::assert_choice(bucket, choices = c(self$event_bucket))
checkmate::assert_list(values)

values$details <- jsonlite::fromJSON(values$details)
if (!is.null(values$details)) {
values$details <- jsonlite::fromJSON(values$details)
}

private$db_con$insert(values, auto_unbox = TRUE, POSIXt = "epoch")
},
Expand All @@ -147,10 +140,18 @@ DataStorageMongoDB <- R6::R6Class( # nolint object_name.
)

if (nrow(event_data) > 0) {
event_data %>%
result <- event_data %>%
dplyr::tibble() %>%
tidyr::unnest(cols = "details") %>%
dplyr::mutate(time = lubridate::as_datetime(as.integer(time / 1000)))

# Force value column to be a character data type
if ("value" %in% colnames(result)) {
dplyr::mutate(result, value = format(value))
} else {
# If there is no column, then it should still be a character data type
dplyr::mutate(result, value = NA_character_)
}
} else {
dplyr::tibble(
app_name = character(),
Expand Down
26 changes: 7 additions & 19 deletions man/DataStorageMongoDB.Rd

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

3 changes: 1 addition & 2 deletions tests/testthat/helper-init-data-storage.R
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ init_test_mongodb <- function(.local_envir = parent.frame()) {
password = Sys.getenv("TEST_MONGODB_PASSWORD"),
host = Sys.getenv("TEST_MONGODB_HOSTNAME"),
port = Sys.getenv("TEST_MONGODB_PORT"),
db = Sys.getenv("TEST_MONGODB_DBNAME"),
collection = Sys.getenv("TEST_MONGODB_COLLECTION")
db = Sys.getenv("TEST_MONGODB_DBNAME")
)

testthat::skip_on_cran()
Expand Down
70 changes: 70 additions & 0 deletions tests/testthat/test-auxiliary_functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,73 @@ test_that("Build valid SQL query", {
)

})

test_that("build_mongo_connection_string: Build valid string with NULL", {
expect_equal(
build_mongo_connection_string(
host = "localhost",
port = 27017,
username = NULL,
password = NULL,
authdb = NULL,
options = NULL
),
"mongodb://localhost:27017/"
)
})

test_that("build_mongo_connection_string: Build valid string with user and pass", {
expect_equal(
build_mongo_connection_string(
host = "localhost",
port = 27017,
username = "a_user",
password = "a_pass",
authdb = NULL,
options = NULL
),
"mongodb://a_user:a_pass@localhost:27017/"
)
})

test_that("build_mongo_connection_string: Build valid string with `authdb`", {
expect_equal(
build_mongo_connection_string(
host = "localhost",
port = 27017,
username = NULL,
password = NULL,
authdb = "path_to_authdb",
options = NULL
),
"mongodb://localhost:27017/path_to_authdb"
)
})

test_that("build_mongo_connection_string: Build valid string with `options`", {
expect_equal(
build_mongo_connection_string(
host = "localhost",
port = 27017,
username = NULL,
password = NULL,
authdb = NULL,
options = list("option1" = "value1", "option2" = "value2")
),
"mongodb://localhost:27017/?option1=value1&option2=value2"
)
})

test_that("build_mongo_connection_string: Build valid string with all parameters", {
expect_equal(
build_mongo_connection_string(
host = "localhost",
port = 27017,
username = "a_user",
password = "a_pass",
authdb = "path_to_authdb",
options = list("option1" = "value1", "option2" = "value2")
),
"mongodb://a_user:a_pass@localhost:27017/path_to_authdb?option1=value1&option2=value2"
)
})
Loading