diff --git a/R/mod_userstats.R b/R/mod_userstats.R index e2aef3d..da19147 100644 --- a/R/mod_userstats.R +++ b/R/mod_userstats.R @@ -107,13 +107,20 @@ userStatsServer <- function( max_time_per_vote <- max(time_vals) } + average_votes_per_session <- NA + max_votes_per_session <- NA + if (nrow(session_counts_df) > 0) { + average_votes_per_session <- mean(session_counts_df$images_voted) + max_votes_per_session <- max(session_counts_df$images_voted) + } + voting_stats_df <- data.frame( user_id = session$userData$userId, voting_institute = session$userData$votingInstitute, total_votes = sum(session_counts_df$images_voted), total_sessions_with_at_least_1_vote = nrow(session_counts_df), - average_votes_per_session = mean(session_counts_df$images_voted), - max_votes_per_session = max(session_counts_df$images_voted), + average_votes_per_session = average_votes_per_session, + max_votes_per_session = max_votes_per_session, average_session_length_in_minutes = average_session_length, max_session_length_in_minutes = max_session_length, average_time_per_vote_in_seconds = average_time_per_vote, diff --git a/tests/testthat/test-user-stats-module.R b/tests/testthat/test-user-stats-module.R index 8cbffe2..bca0207 100644 --- a/tests/testthat/test-user-stats-module.R +++ b/tests/testthat/test-user-stats-module.R @@ -157,6 +157,83 @@ session$userData$userAnnotationsFile <- tempfile(fileext = ".tsv") unlink(db_file) }) +testthat::test_that("User stats shows NA instead of -Inf when user has not voted yet", { + # Create a mock database pool + db_file <- tempfile(fileext = ".sqlite") + pool <- dbPool(RSQLite::SQLite(), dbname = db_file) + + # Create sessionids table (no rows inserted - user has never logged a session) + DBI::dbExecute(pool, " + CREATE TABLE sessionids ( + userid TEXT, + sessionid TEXT, + login_time TEXT, + logout_time TEXT + ) + ") + + login_trigger <- shiny::reactiveVal(list(user_id = "test_user", voting_institute = "CNAG")) + + cfg <- ShinyImgVoteR::load_config( + config_file_path = system.file( + "shiny-app", + "default_env", + "config", + "config.yaml", + package = "ShinyImgVoteR" + ) + ) + testServer(userStatsServer, args = list( + cfg, + login_trigger = login_trigger, + db_pool = pool + ), { + session$userData$userId <- "test_user" + session$userData$votingInstitute <- "CNAG" + # Create an annotations file with only a header (no vote rows) + annotations_file <- tempfile(fileext = ".tsv") + write.table( + data.frame( + coordinates = character(0), + agreement = character(0), + observation = character(0), + comment = character(0), + shinyauthr_session_id = character(0), + time_till_vote_casted_in_seconds = character(0), + stringsAsFactors = FALSE + ), + file = annotations_file, + sep = "\t", + row.names = FALSE, + col.names = TRUE, + quote = FALSE + ) + session$userData$userAnnotationsFile <- annotations_file + + result <- stats() + testthat::expect_true(is.data.frame(result)) + + # Extract key metrics from the transposed data frame + get_val <- function(df, metric_name) { + df[df$metric == metric_name, "value"] + } + + max_val <- get_val(result, "max_votes_per_session") + avg_val <- get_val(result, "average_votes_per_session") + + # Must not be "-Inf" or "NaN" + testthat::expect_false(identical(max_val, "-Inf")) + testthat::expect_false(identical(max_val, "NaN")) + testthat::expect_false(identical(avg_val, "-Inf")) + testthat::expect_false(identical(avg_val, "NaN")) + + unlink(annotations_file) + }) + + poolClose(pool) + unlink(db_file) +}) + testthat::test_that("User stats server works without tab trigger (backward compatibility)", { # Create a mock database pool db_file <- tempfile(fileext = ".sqlite")