diff --git a/flake.lock b/flake.lock index 940fef6..0826ac6 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -44,11 +44,11 @@ ] }, "locked": { - "lastModified": 1724996935, - "narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=", + "lastModified": 1730479402, + "narHash": "sha256-79NLeNjpCa4mSasmFsE3QA6obURezF0TUO5Pm+1daog=", "owner": "nlewo", "repo": "nix2container", - "rev": "fa6bb0a1159f55d071ba99331355955ae30b3401", + "rev": "5fb215a1564baa74ce04ad7f903d94ad6617e17a", "type": "github" }, "original": { @@ -59,11 +59,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1725930920, - "narHash": "sha256-RVhD9hnlTT2nJzPHlAqrWqCkA7T6CYrP41IoVRkciZM=", + "lastModified": 1731797254, + "narHash": "sha256-df3dJApLPhd11AlueuoN0Q4fHo/hagP75LlM5K1sz9g=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "44a71ff39c182edaf25a7ace5c9454e7cba2c658", + "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index bb84077..71c02f9 100644 --- a/flake.nix +++ b/flake.nix @@ -43,6 +43,7 @@ uuid shinyTree prettyunits + reactable (pkgs.rPackages.buildRPackage { name = "rutilstimflutre"; diff --git a/global.R b/global.R index a9a81c2..259044a 100644 --- a/global.R +++ b/global.R @@ -30,6 +30,7 @@ source("./src/fun/func_dbRequests.R", local = TRUE, encoding = "UTF-8") source("./src/fun/module_gameInit_params.R", local = TRUE, encoding = "UTF-8") source("./src/fun/func_gameInit_validation.R", local = TRUE, encoding = "UTF-8") source("./src/fun/func_ui_util.R", local = TRUE, encoding = "UTF-8") +source("./src/fun/module_data-filtering.R", local = TRUE, encoding = "UTF-8") ## ------------------------------------------------------------------- ## parameters diff --git a/src/fun/func_admin.R b/src/fun/func_admin.R index df4d661..4f1f301 100644 --- a/src/fun/func_admin.R +++ b/src/fun/func_admin.R @@ -52,8 +52,6 @@ addNewBreeder <- function(breederName, status, psw, progressNewBreeder = NULL) { stop("breeder already exist") } - - #### add breeder in the "breeders" table of database: if (!is.null(progressNewBreeder)) { progressNewBreeder$set( @@ -63,60 +61,7 @@ addNewBreeder <- function(breederName, status, psw, progressNewBreeder = NULL) { } hashed.psw <- digest(psw, "md5", serialize = FALSE) - tbl <- "breeders" - query <- paste0( - "INSERT INTO ", tbl, " VALUES", - " ('", breederName, "','", status, "','", hashed.psw, "')" - ) - db_execute_request(query) - - - - - #### create "plant_material_newBreeder" - if (!is.null(progressNewBreeder)) { - progressNewBreeder$set( - value = 3, - detail = "create \"plant_material\" table" - ) - } - tbl <- paste0("plant_material_", breederName) - query <- paste0( - "CREATE TABLE ", tbl, - " (parent1 TEXT", - ", parent2 TEXT", - ", child TEXT PRIMARY KEY", - ", avail_from TEXT)" - ) - db_execute_request(query) - - - - #### fill "plant_material_newBreeder" - if (!is.null(progressNewBreeder)) { - progressNewBreeder$set( - value = 4, - detail = "fill \"plant_material\" table" - ) - } - coll.ids <- gsub("_haplos.RData", "", initIndsHaplo) - query <- paste0( - "INSERT INTO ", tbl, - " (parent1, parent2, child, avail_from)", - " VALUES", - " ('", paste(gsub("Coll", "ind", coll.ids), - rep(NA, length(coll.ids)), - coll.ids, - rep( - paste0(getBreedingGameConstants()$first.year, "-01-01 00:00:00"), - length(coll.ids) - ), - sep = "','", collapse = "'),('" - ), - "')" - ) - db_execute_request(query) - + db_add_breeder(breederName, status, hashed.psw) #### create folders of the new breeder: if (!is.null(progressNewBreeder)) { diff --git a/src/fun/func_data-viz.R b/src/fun/func_data-viz.R index 19dddfc..6b3ae01 100644 --- a/src/fun/func_data-viz.R +++ b/src/fun/func_data-viz.R @@ -127,7 +127,7 @@ data_viz_server <- function(id, plot_data) { filter <- "none" } DT::datatable(data, - filter = filter, + # filter = filter, # No filter because it can conflict with the manually implemented filters can be worked on later style = "bootstrap4", options = list( language = list(emptyTable = 'Empty'), diff --git a/src/fun/func_dbRequests.R b/src/fun/func_dbRequests.R index ed2c188..5a62497 100644 --- a/src/fun/func_dbRequests.R +++ b/src/fun/func_dbRequests.R @@ -152,7 +152,7 @@ db_execute_safe <- function(query, dbname = getOption("DATA_DB"), ...) { #' add data to a table from a data.frame. #' The data.frame must have have a structure matching the table -db_add_data <- function(table, data, append = TRUE, overwrite = FALSE, dbname = DATA_DB, ...) { +db_add_data <- function(table, data) { conn <- connect_to_db() out <- tryCatch({ db_col_names <- paste(colnames(data), collapse = ", ") @@ -604,10 +604,24 @@ db_get_individual <- function(ind_id = NULL, cross_type = NULL, request_name = NULL, n_pheno_min = NULL, - n_geno_min = NULL) { n_geno_min = NULL, - control = NULL) { - base_query <- "SELECT * FROM v_plant_material WHERE 1=1" + control = NULL, + public_columns = FALSE) { + + columns <- "*" + if (public_columns) { + columns_to_keep_as <- c( + "name" = "Name", + "parent1_name" = "Parent 1", + "parent2_name" = "Parent 2", + "avail_from" = "Available date", + "cross_type" = "Crossing type", + "request_name" = "From plant material request", + "control" = "Is control" + ) + columns <- paste(c(names(columns_to_keep_as)), collapse = ", ") + } + base_query <- paste("SELECT", columns, "FROM v_plant_material WHERE 1=1") breeder_condition <- "" if (!is.null(breeder)) { @@ -619,15 +633,20 @@ db_get_individual <- function(ind_id = NULL, condition("AND", "id", "IN", ind_id), breeder_condition, condition("AND", "name", "IN", name), - condition("AND", "parent1", "IN", parent1), - condition("AND", "parent2", "IN", parent2), + condition("AND", "parent1_name", "IN", parent1), + condition("AND", "parent2_name", "IN", parent2), condition("AND", "cross_type", "IN", cross_type), condition("AND", "request_name", "IN", request_name), condition("AND", "n_pheno", ">=", n_pheno_min), condition("AND", "n_geno", ">=", n_geno_min), condition("AND", "control", "=", control) ) - db_get(query) + individuals <- db_get(query) + + if (public_columns) { + colnames(individuals) <- columns_to_keep_as + } + individuals } @@ -757,9 +776,22 @@ db_get_phenotypes <- function(id = NULL, t3 = NULL, pathogen = NULL, year = NULL, - initial_data_only = NULL - ) { - base_query <- "SELECT * FROM v_phenotypes WHERE 1=1" + initial_data_only = NULL, + public_columns = FALSE) { + columns <- "*" + if (public_columns) { + columns <- paste(c( + "ind", + "control_ind", + "year", + "plot", + "pathogen", + "trait1", + "trait2", + "trait3" + ), collapse = ", ") + } + base_query <- paste("SELECT", columns, "FROM v_phenotypes WHERE 1=1") breeder_condition <- "" if (!is.null(breeder)) { @@ -796,6 +828,27 @@ db_get_phenotypes <- function(id = NULL, } +db_get_pheno_summary <- function(breeder) { + query <- " + SELECT + MIN(trait1) AS minT1, + MAX(trait1) AS maxT1, + MIN(trait2) AS minT2, + MAX(trait2) AS maxT2, + MIN(year) AS minYear, + MAX(year) AS maxYear + FROM + v_phenotypes + WHERE 1=1 + " + query <- paste( + query, + condition("AND", "breeder", "IN", c(breeder, "@ALL")) + ) + as.list(db_get(query)) +} + + ## genotype requests ---- db_add_geno_req_data <- function(req_id, request_data) { @@ -885,6 +938,13 @@ db_get_genotypes <- function(id = NULL, db_get(query) } +db_get_genotypes_data_list <- function(breeder) { + query <- paste( + "SELECT result_file FROM v_genotypes WHERE 1=1", + condition("AND", "breeder", "IN", c(breeder, "@ALL")), + "GROUP BY result_file") + db_get(query)[,1] +} diff --git a/src/fun/func_id.R b/src/fun/func_id.R index 85ec3f7..2621ab5 100644 --- a/src/fun/func_id.R +++ b/src/fun/func_id.R @@ -19,34 +19,6 @@ ## functions for the "id part" -getDataFileList <- function(type, breeder) { - # function to get the list of data file of the breeder - # type (char) type of data (pheno or geno) - # breeder (char) name of the breeder - - stopifnot(type %in% c("pheno", "geno", "pltMat", "request")) - - dirPath <- file.path(DATA_SHARED, breeder) - dataFile <- list.files(dirPath) - dataFile <- c(dataFile, list.files(DATA_INITIAL_DATA)) - - ## Get the ids of the files - if (type == "pheno") { - matchId <- as.logical(lapply(dataFile, FUN = grepl, pattern = "Result_pheno")) - } else if (type == "geno") { - matchId <- matchId <- as.logical(lapply(dataFile, FUN = grepl, pattern = "Result_geno")) - } else if (type == "pltMat") { - matchId <- matchId <- as.logical(lapply(dataFile, FUN = grepl, pattern = "IndList_")) - } else if (type == "request") { - matchId <- as.logical(lapply(dataFile, FUN = grepl, pattern = "(^Request)|(^example_request_)|(^controls.txt$)|(^snp_coords_)")) - } - - - return(as.list(dataFile[matchId])) -} - - - availToDwnld <- function(fileName, gameTime) { # function to check if files are available to download diff --git a/src/fun/module_data-filtering.R b/src/fun/module_data-filtering.R new file mode 100644 index 0000000..892d8c4 --- /dev/null +++ b/src/fun/module_data-filtering.R @@ -0,0 +1,127 @@ + +# this file list all the game initialisation parameters (implemented in the +# application) +# It takes the forms of "shiny modules" to: +# - include the "input validation" in the module +# - have more complex UI that provides informations to users based on several +# "groups of inputs" (eg. all inputs related to budgets) +# - lighten the code related to "Admin server". + +# in the "server" parts of these modules, the `iv` argument is an +# `InputValidator` object + +library(shinyvalidate) +library(shiny) +library(plotly) + + +individual_filtering_ui <- function(id, breeder) { + ns <- NS(id) + div( + selectInput( + ns("pheno_inds"), + "Individuals", + choices = c( + "All", + db_get_game_requests(breeder = breeder, type = "pltmat")$name, + "Controls" + ), + width = "75%"), + ) +} + +individual_filtering_server <- function(id, breeder) { + moduleServer(id, function(input, output, session) { + return( + list( + inds_ids = reactive({ + ind_filter <- input$pheno_inds + if (is.null(ind_filter)) { + return(NULL) + } + + inds_ids <- NULL + if (ind_filter == "Controls") { + inds_ids <- db_get_individual( + breeder = breeder, + control = TRUE + )$id + } else if (ind_filter != "All") { + inds_ids <- db_get_individual( + breeder = breeder, + request_name = ind_filter + )$id + } + inds_ids + }) + ) + ) + + }) +} + + + +phenotype_filtering_ui <- function(id, breeder) { + ns <- NS(id) + pheno_summary <- db_get_pheno_summary(breeder) + div( + selectizeInput( + ns("pheno_requests"), + "Phenotype requests", + choices = db_get_game_requests(breeder = breeder, type = "pheno")$name, + multiple = TRUE, + width = "75%", + options = list( + placeholder = "All" + ) + ), + + selectizeInput( + ns("pheno_year"), + "Phenotype year", + choices = seq(pheno_summary$minYear, pheno_summary$maxYear), + multiple = TRUE, + width = "75%", + options = list( + placeholder = "All" + ) + ), + + selectInput( + ns("pheno_pahtogen"), + "Pathogen", + choices = c("All", TRUE, FALSE), + multiple = FALSE, + width = "75%" + ), + ) +} + +phenotype_filtering_server <- function(id, breeder) { + moduleServer(id, function(input, output, session) { + return( + list( + pheno_request = reactive({ + input$pheno_requests + }), + pathogen = reactive({ + if (is.null(input$pheno_pahtogen)) { + return(NULL) + } + if (input$pheno_pahtogen != "All") { + return(input$pheno_pahtogen) + } + return(NULL) + }), + years = reactive({ + input$pheno_year + }) + ) + ) + }) +} + + + + diff --git a/src/server/server_id.R b/src/server/server_id.R index db1718e..33d6244 100644 --- a/src/server/server_id.R +++ b/src/server/server_id.R @@ -69,11 +69,10 @@ accessGranted <- eventReactive(input$submitPSW, } # 1. get breeder status - status <- getBreederStatus(input$breederName) + breeder <- db_get_breeder(input$breederName) # 2. check given password - query <- paste0("SELECT h_psw FROM breeders WHERE name = '", input$breederName, "'") - hashPsw <- db_get_request(query) + hashPsw <- breeder$h_psw if (hashPsw == digest(input$psw, "md5", serialize = FALSE)) { goodPswd <- TRUE @@ -84,7 +83,7 @@ accessGranted <- eventReactive(input$submitPSW, # 3. check disk usage goodDiskUsage <- FALSE - if (goodPswd && status != "game master") { + if (goodPswd && breeder$status != "game master") { withProgress( { maxDiskUsage <- getBreedingGameConstants()$max.disk.usage @@ -94,7 +93,7 @@ accessGranted <- eventReactive(input$submitPSW, if (currentSize < maxDiskUsage) { goodDiskUsage <- TRUE - } else if (status != "game master") { + } else if (breeder$status != "game master") { goodDiskUsage <- FALSE alert("Sorry, the game is currently not available because of disk usage.\nPlease contact your game master to figure out what to do.") } else { @@ -110,32 +109,11 @@ accessGranted <- eventReactive(input$submitPSW, }, message = "Connecting..." ) - } else if (goodPswd && status == "game master") { + } else if (goodPswd && breeder$status == "game master") { # the game master can always log in goodDiskUsage <- TRUE } - # 4. check db (in case of "corrupted" data-base) - if (goodPswd) { - allTbls <- db_list_tables() - tbl_pltMat <- paste0("plant_material_", input$breederName) - if (!tbl_pltMat %in% allTbls) { - alert(paste( - "Sorry, our data-base have corrupted information", - "regarding your account, and you will not be able to play", - "the game anymore. Please ask a game master to delete your", - "account, and create a new one for you.\n", - "If you are a game master, you can connect, but please", - "be aware that some game features will not work." - )) - if (status != "game master") { - # do not allow access if user is not "game master" - return(FALSE) - } - } - } - - # 5. output if (goodPswd && goodDiskUsage) { removeUI("#logInDiv") @@ -157,7 +135,7 @@ breeder <- reactive({ breederStatus <- reactive({ if (accessGranted()) { - return(getBreederStatus(input$breederName)) + return(db_get_breeder(input$breederName)$status) } else { return("No Identification") } @@ -168,33 +146,11 @@ budget <- reactive({ input$requestGeno input$id_submitInds if (breeder() != "No Identification") { - query <- paste0("SELECT * FROM log WHERE breeder='", breeder(), "'") - res <- db_get_request(query) - - - constants <- getBreedingGameConstants() - prices <- list( - "allofecundation" = constants$cost.allof * constants$cost.pheno.field, - "autofecundation" = constants$cost.autof * constants$cost.pheno.field, - "haplodiploidization" = constants$cost.haplodiplo * constants$cost.pheno.field, - "pheno-field" = constants$cost.pheno.field, - "pheno-patho" = constants$cost.pheno.patho * constants$cost.pheno.field, - "geno-hd" = constants$cost.geno.hd * constants$cost.pheno.field, - "geno-ld" = round(constants$cost.geno.ld * constants$cost.pheno.field, 2), - "geno-single-snp" = constants$cost.geno.single * constants$cost.pheno.field, - "register" = constants$cost.register * constants$cost.pheno.field - ) - if (nrow(res) > 0) { - funApply <- function(x) { - prices[x[3]][[1]] * as.integer(x[4]) - } - expenses <- sum(apply(res, MARGIN = 1, FUN = funApply)) - } else { - expenses <- 0 - } + req_history <- db_get_game_requests_history(breeder = breeder()) + expenses <- sum(req_history$costs, na.rm = TRUE) - initialBuget <- constants$initialBudget + initialBuget <- getBreedingGameConstants()$initialBudget return(round(initialBuget - expenses, 2)) } }) @@ -209,22 +165,21 @@ budget <- reactive({ # list of avaiable files (this must be reactive value to be refresh) phenoFiles <- reactive({ input$leftMenu - getDataFileList(type = "pheno", breeder = breeder()) + db_get_game_requests(breeder = breeder(), type = "pheno")$name + # getDataFileList(type = "pheno", breeder = breeder()) }) -genoFiles <- reactive({ +genoData <- reactive({ input$leftMenu - choices <- tools::file_path_sans_ext( - getDataFileList(type = "geno", breeder = breeder()), - compression = TRUE - ) + full_files <- db_get_genotypes_data_list(breeder = breeder()) + tools::file_path_sans_ext(basename(full_files), compression = TRUE) }) pltMatFiles <- reactive({ input$leftMenu - choices <- getDataFileList(type = "pltMat", breeder = breeder()) + # choices <- getDataFileList(type = "pltMat", breeder = breeder()) }) requestFiles <- reactive({ input$leftMenu - choices <- getDataFileList(type = "request", breeder = breeder()) + # choices <- getDataFileList(type = "request", breeder = breeder()) }) @@ -312,21 +267,6 @@ output$dwnlRequest <- downloadHandler( } ) -# UI of dwnl buttons ---- -output$UIdwnlPheno <- renderUI({ - if (input$phenoFile != "") { - if (breederStatus() == "player" && !availToDwnld(input$phenoFile, currentGTime())$isAvailable) { - p(paste0( - "Sorry, your data are not available yet. Delivery date: ", - availToDwnld(input$phenoFile, currentGTime())$availDate - )) - } else { - downloadButton("dwnlPheno", "Download your file") - } - } else { - p("No file selected.") - } -}) output$UIdwnlGeno <- renderUI({ if (input$genoFile != "") { @@ -347,13 +287,6 @@ output$UIdwnlGeno <- renderUI({ } }) -output$UIdwnlPltMat <- renderUI({ - if (input$pltMatFile != "") { - downloadButton("dwnlPltMat", "Download your file") - } else { - p("No file selected.") - } -}) output$UIdwnlRequest <- renderUI({ if (input$requestFile != "") { @@ -367,16 +300,57 @@ output$UIdwnlRequest <- renderUI({ ## My plant-material ---- myPltMat <- reactive({ if (input$leftMenu == "id") { - tbl <- paste0("plant_material_", breeder()) - query <- paste0("SELECT * FROM ", tbl) - res <- db_get_request(query) - res$avail_from <- strftime(res$avail_from, format = "%Y-%m-%d") - res + individuals <- db_get_individual(breeder = breeder()) + + columns_to_keep_as <- c( + "name" = "Name", + "parent1_name" = "Parent 1", + "parent2_name" = "Parent 2", + "avail_from" = "Available date", + "cross_type" = "Crossing type", + "request_name" = "From plant material request", + "control" = "Is control" + ) + individuals <- individuals[, c( + names(columns_to_keep_as) + )] + colnames(individuals) <- columns_to_keep_as + return(individuals) } }) -output$myPltMatDT <- DT::renderDataTable({ - DT::datatable(myPltMat(), + + + +pltmat_preview_filter <- individual_filtering_server("inds_download_ind_filter", breeder = breeder()) + +plant_mat_preview_data <- reactive({ + + individuals <- db_get_individual(breeder = breeder(), + ind_id = pltmat_preview_filter$inds_ids(), + public_columns = TRUE + ) + + # columns_to_keep_as <- c( + # "name" = "Name", + # "parent1_name" = "Parent 1", + # "parent2_name" = "Parent 2", + # "avail_from" = "Available date", + # "cross_type" = "Crossing type", + # "request_name" = "From plant material request", + # "control" = "Is control" + # ) + # individuals <- individuals[, c( + # names(columns_to_keep_as) + # )] + # colnames(individuals) <- columns_to_keep_as + return(individuals) +}) + +output$plant_mat_preview <- DT::renderDataTable({ + DT::datatable( + plant_mat_preview_data(), + selection = 'single', options = list( lengthMenu = c(10, 20, 50), pageLength = 10, @@ -385,7 +359,135 @@ output$myPltMatDT <- DT::renderDataTable({ ) }) +.download_inds <- function(){ + downloadHandler( + filename = "plant-material.tsv", + content = function(file) { + write.table( + plant_mat_preview_data(), + file = file, + sep = "\t", + row.names = FALSE + ) + } + ) +} +output$dwnlInds_1 <- .download_inds() +output$dwnlInds_2 <- .download_inds() + +output$selected_ind_info <- renderUI({ + selected_row <- input$plant_mat_preview_rows_selected + + if (is.null(selected_row)) { + return( + div( + h3("Selected individual information:"), + p("No individual selected. Click on an individual on the table to view more information.") + ) + ) + } + ind_name <- plant_mat_preview_data()[selected_row, "Name"] + ind_id <- db_get_individuals_ids(breeder = breeder(), name = ind_name) + ind_info <- db_get_individual(breeder = breeder(), ind_id = ind_id) + phenotypes <- db_get_phenotypes(breeder = breeder(), ind_id = ind_id, public_columns = TRUE) + genotypes <- db_get_genotypes(breeder = breeder(), ind_id = ind_id) + offsprings <- rbind(db_get_individual(breeder = breeder(), parent1 = ind_id, public_columns = TRUE), + db_get_individual(breeder = breeder(), parent2 = ind_id, public_columns = TRUE)) + offsprings <- offsprings[!duplicated(offsprings),] + + div( + h3(ind_info$name, ":"), + tags$ul( + tags$li("Requested on", ind_info$request_date, "with", code(ind_info$request_name)), + tags$li("Available on", ind_info$avail_from), + tags$li("Cross type:", ind_info$cross_type), + tags$li("Parent 1:", code(ind_info$parent1_name)), + tags$li("Parent 2:", code(ind_info$parent2_name)), + tags$li("Offsprings:", nrow(offsprings)), + tags$li("Phenotypic records:", nrow(phenotypes)), + tags$li("Genotypic records:", nrow(genotypes), + tags$ul( + lapply(seq_len(nrow(genotypes)), function(x) { + geno_res_file <- genotypes$result_file[x] + geno_res_file <- tools::file_path_sans_ext( + basename(geno_res_file), + compression = TRUE + ) + tags$li(genotypes$type[x], ":", code(geno_res_file)) + }) + ) + ), + ), + h4("Phenotypic records:"), + reactable::reactable(phenotypes), + + h4("Offsprings records:"), + reactable::reactable(offsprings) + ) +}) + + + +pheno_inds_filters <- individual_filtering_server("pheno_download_ind_filter", breeder = breeder()) +pheno_pheno_filters <- phenotype_filtering_server("pheno_download_pheno_filter", breeder = breeder()) + +pheno_data_preview <- reactive({ + + pheno_data <- db_get_phenotypes( + breeder = breeder(), + # ind_id = inds_ids, + ind_id = pheno_inds_filters$inds_ids(), + request_name = pheno_pheno_filters$pheno_request(), + pathogen = pheno_pheno_filters$pathogen(), + year = pheno_pheno_filters$years(), + public_columns = TRUE + ) +}) + + +output$pheno_preview_DT <- DT::renderDataTable({ + DT::datatable( + isolate(pheno_data_preview()), # use isolate, the data refresh is done with the observer below + # filter = "top", # this can conflict with manual filters, + # add this only if this filtering is taken + # into account for data-download (and it is explained in the UI) + # style = "bootstrap4", + rownames = FALSE, + options = list( + language = list(emptyTable = 'Empty'), + pageLength = 10, + lengthMenu = c(10, 25, 50, 100), + searchDelay = 500 + ) + ) +}, server = TRUE) + +observe({ + pheno_table_proxy <- DT::dataTableProxy("pheno_preview_DT", deferUntilFlush = FALSE) + DT::replaceData(pheno_table_proxy, pheno_data_preview(), resetPaging = TRUE, rownames = FALSE) + # we could use `resetPaging = FALSE` to keep the current page, + # but the table will be empty if the current page would not exist with + # the updated data +}) + + +.download_pheno <- function(){ + downloadHandler( + filename = "phenotypes.tsv", + content = function(file) { + write.table( + pheno_data_preview(), + file = file, + sep = "\t", + row.names = FALSE + ) + } + ) +} + +output$dwnlPheno_1 <- .download_pheno() +output$dwnlPheno_2 <- .download_pheno() @@ -393,17 +495,13 @@ output$myPltMatDT <- DT::renderDataTable({ ## Change Password ---- pswChanged <- eventReactive(input$"changePsw", { - query <- paste0("SELECT h_psw FROM breeders WHERE name = '", input$breederName, "'") - hashPsw <- db_get_request(query)[, 1] + hashPsw <- db_get_breeder(breeder = breeder())$h_psw if (digest(input$prevPsw, "md5", serialize = FALSE) == hashPsw) { newHashed <- digest(input$newPsw, "md5", serialize = FALSE) - - query <- paste0("UPDATE breeders SET h_psw = '", newHashed, "' WHERE name = '", breeder(), "'") - db_execute_request(query) + db_update_breeder(breeder = breeder(), new_h_psw = newHashed) return(TRUE) - } else { - return(FALSE) } + return(FALSE) }) output$UIpswChanged <- renderUI({ diff --git a/src/ui/ui_id_loggedIn.R b/src/ui/ui_id_loggedIn.R index fbdf4c9..bbd8047 100644 --- a/src/ui/ui_id_loggedIn.R +++ b/src/ui/ui_id_loggedIn.R @@ -30,24 +30,14 @@ div( "My files", div( style = "display: inline-block; vertical-align:top; width: 50%;", - div( - h3("Phenotyping data:"), - selectInput("phenoFile", "", choices = phenoFiles(), width = "75%"), - uiOutput("UIdwnlPheno") - ), div( h3("Genotyping data:"), - selectInput("genoFile", "", choices = genoFiles(), width = "75%"), + selectInput("genoFile", "", choices = genoData(), width = "75%"), uiOutput("UIdwnlGeno") ) ), div( style = "display: inline-block; vertical-align:top; width: 49%;", - div( - h3("Plant material data:"), - selectInput("pltMatFile", "", choices = pltMatFiles(), width = "75%"), - uiOutput("UIdwnlPltMat") - ), div( h3("Other:"), p( @@ -64,10 +54,71 @@ div( ) ) ), + + tabPanel( + "Phenotype data", + h2("Phenotype data"), + div(id = "pheno_info", + p("Three phenotypic traits are investigated:"), + tags$ul( + tags$li(code("trait1"), ": flower production in kg/ha"), + tags$li(code("trait2"), ": sepmetin content in g/kg"), + tags$li(code("trait3"), ": presence of symptoms caused by P. psychedelica", + tags$ul( + tags$li(code("1"), "indicates the individual showed symptoms"), + tags$li(code("0"), "indicates the individual did not show symptoms.") + ), + "Note: If", code("pathogen"), "is", code("FALSE"), + ", the pathogen was not observed and therefore not any individuals will show symptoms.") + ), + + p("Additionally, the phenotypic data provides the following variables:"), + tags$ul( + tags$li(code("ind"), ": the individual name"), + tags$li(code("control_ind"), ": boolean indicating if the individual will be used as control for the final evaluation"), + tags$li(code("year"), ": the year when this phenotyping happens."), + tags$li(code("plot"), ": the plot id of the phenotyping observation"), + tags$li(code("pathogen"), ": boolean value indicating if the pathogen have been observed during the phenotyping."), + ) + ), + + # h3("Summary"), + div(id = "pheno_filters", + h3("Filters"), + p("Records matching", strong("all conditions"), "are shown."), + individual_filtering_ui("pheno_download_ind_filter", breeder = breeder()), + phenotype_filtering_ui("pheno_download_pheno_filter", breeder = breeder()), + downloadButton("dwnlPheno_1", "Download") + ), + + div(id = "pheno_preview_div", + h3("Preview"), + dataTableOutput("pheno_preview_DT") + ), + downloadButton("dwnlPheno_2", "Download"), + ), + tabPanel( "My plant material", - dataTableOutput("myPltMatDT") + h2("Plant material"), + div(id = "inds_filters", + h3("Filters"), + p("Records matching", strong("all conditions"), "are shown."), + individual_filtering_ui("inds_download_ind_filter", breeder = breeder()), + downloadButton("dwnlInds_1", "Download") + ), + + div(id = "inds_preview", + h3("Preview"), + dataTableOutput("plant_mat_preview") + ), + downloadButton("dwnlInds_2", "Download"), + + div(id = "inds_ind_info", + uiOutput("selected_ind_info") + ), ), + tabPanel( "Change my password", div( diff --git a/tests/testthat/test_db_requests.R b/tests/testthat/test_db_requests.R index 286322c..0951fa7 100644 --- a/tests/testthat/test_db_requests.R +++ b/tests/testthat/test_db_requests.R @@ -1051,6 +1051,11 @@ test_that("genotype data", { expect_no_error({ db_get_genotypes(breeder = "A", result_file = "result_geno_test_geno_2-hd") }) + + expect_no_error({ + geno_data_list <- db_get_genotypes_data_list(breeder = "A") + }) + expect_true(length(geno_data_list) != 0) })