From 4cc8504f1f1a72f747a3947c1f5e0f3f15b8dd95 Mon Sep 17 00:00:00 2001 From: LE DURAND Matteo <matteo.le-durand@developpement-durable.gouv.fr> Date: Thu, 27 Mar 2025 11:45:18 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20r=C3=A9solution=20des=20filtres=20et=20d?= =?UTF-8?q?u=20clignotment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.R | 278 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 209 insertions(+), 69 deletions(-) diff --git a/server.R b/server.R index 7e6b438..9a7b8cc 100644 --- a/server.R +++ b/server.R @@ -1,118 +1,258 @@ server <- function(input, output, session) { + # Stocker les valeurs réactives r <- reactiveValues( - filteredData = all_data, - filteredByProject = all_data, # Étape intermédiaire filtrée par daterange et project_name - filteredetiquette = NULL, - filteredProjects = NULL, - filteredAuthors = NULL, - filteredReCodes = NULL, - filteredCategories = NULL, - filteredByDate = NULL, + filteredByDate = NULL, # Filtrage uniquement par date + filteredData = NULL # Filtrage final après application de tous les filtres + ) - # Filtrage par date et project_name uniquement + # 1️⃣ Filtrage prioritaire par date ---- observeEvent(input$daterange, { - req(input$daterange) + req(input$daterange) # Vérifier que la sélection est valide + + # Filtrage initial sur la plage de dates r$filteredByDate <- all_data %>% filter( updated_at >= as.Date(input$daterange[[1]]) & - updated_at <= as.Date(input$daterange[[2]]) & - (if (length(input$project_name)) project_name %in% input$project_name else TRUE) + updated_at <= as.Date(input$daterange[[2]]) ) - updateFilters() # Mise à jour des choix basés sur le filtrage intermédiaire + # Mise à jour dynamique des autres filtres en fonction des données filtrées par date + updateFilters() }) - # Mise à jour des autres filtres + # 2️⃣ Mise à jour dynamique des filtres ---- + updateFilters <- reactive({ + req(r$filteredByDate) # Vérifier que les données sont disponibles + isolate({ + updateSelectizeInput(session, "project_name", + choices = unique(r$filteredByDate$project_name), + selected = input$project_name) + + updateSelectizeInput(session, "etiquette", + choices = r$filteredByDate$etiquette %>% + strsplit(split = ",") %>% + unlist() %>% + na.omit() %>% + .[. != ""] %>% + trimws() %>% + unique(), + selected = input$etiquette) + + updateSelectizeInput(session, "auteur", + choices = unique(r$filteredByDate$auteur), + selected = input$auteur) + + updateSelectizeInput(session, "categorie", + choices = unique(r$filteredByDate$type), + selected = input$categorie) + + updateCheckboxGroupInput_dsfr( inputId = "re_code", + label = "Type d'évènement :", + choices = unique(r$filteredByDate$re_code), + selected = input$re_code, + inline = TRUE) + }) + }) + # 3️⃣ Filtrage final en fonction des autres filtres ---- observe({ - req(input$daterange) + req(r$filteredByDate) # Vérifier que les données de base sont filtrées par date + r$filteredData <- r$filteredByDate %>% filter( - (if (length(input$etiquette)) sapply(input$etiquette, function(t) grepl(t, etiquette)) %>% rowSums() > 0 else TRUE) & + (if (length(input$project_name)) project_name %in% input$project_name else TRUE) & + (if (length(input$etiquette)) sapply(input$etiquette, function(t) grepl(t, etiquette)) %>% rowSums() > 0 else TRUE) & (if (length(input$auteur)) auteur %in% input$auteur else TRUE) & (if (length(input$re_code)) re_code %in% input$re_code else TRUE) & - (if (length(input$categorie)) categorie %in% input$categorie else TRUE) + (if (length(input$categorie)) type %in% input$categorie else TRUE) ) - updateFilters() - }) - # Fonction de mise à jour des choix pour les filtres - updateFilters <- reactive({ - # Basé sur les données filtrées par date et projet - updateSelectizeInput(session, "etiquette", choices = unique(r$filteredByDate$etiquette)%>% - strsplit(split = ",") %>% # Divise les chaînes en éléments séparés - unlist() %>% # Aplatit la liste obtenue - na.omit() %>% # Supprime les NA (au cas où) - .[. != ""] %>% # Supprime les chaînes vides - trimws() %>% # suppremie les espace avnt et apres - unique() , selected = input$etiquette) - updateSelectizeInput(session, "author", choices = unique(r$filteredByDate$auteur), selected = input$auteur) - updateSelectizeInput(session, "categorie", choices = unique(r$filteredByDate$categorie), selected = input$categorie) - - # Basé uniquement sur les données globales pour éviter que project_name soit affecté - updateSelectizeInput(session, "project_name", choices = sort(unique(r$filteredByDate$project_name)), selected = input$project_name) + # Mettre à jour les filtres restants + updateFilters() }) - # Réinitialisation des filtres + # 4️⃣ Réinitialisation des filtres ---- observeEvent(input$reset, { r$filteredByDate <- all_data %>% - filter(updated_at >= as.Date(input$daterange[[1]]) & - updated_at <= as.Date(input$daterange[[2]])) - r$filteredData <- r$filteredByDate + filter( + updated_at >= as.Date(input$daterange[[1]]) & + updated_at <= as.Date(input$daterange[[2]]) + ) + r$filteredData <- r$filteredByDate # Reset total + updateSelectizeInput(session, "project_name", selected = NULL) updateSelectizeInput(session, "etiquette", selected = NULL) - updateSelectizeInput(session, "author", selected = NULL) + updateSelectizeInput(session, "auteur", selected = NULL) updateSelectizeInput(session, "categorie", selected = NULL) + updateCheckboxGroupInput_dsfr( "re_code", selected = NULL,inline = TRUE) updateFilters() }) - output$lien <- renderUI({ - projects_with_links <- r$filteredData %>% - filter(origine == "Gitlab_Forge" & project_name %in% input$project_name) - - if (nrow(projects_with_links) == 0) { - return(NULL) # Aucun lien à afficher - } - - urls <- paste0("https://gitlab-forge.din.developpement-durable.gouv.fr/dreal-pdl/csd/", projects_with_links$project_name) - links <- lapply(seq_along(unique(projects_with_links$project_name)), function(i) { - a(href = urls[i], target = "_blank", projects_with_links$project_name[i]) - }) - do.call(tagList, links) - }) - # Graphique interactif + # 5️⃣ Graphique interactif ---- output$filteredPlot2 <- plotly::renderPlotly({ - req(r$filteredData) + req(r$filteredData) # Vérifier qu'on a des données filtrées p <- ggplot2::ggplot(r$filteredData, ggplot2::aes(x = updated_at, y = as.factor(project_name))) + ggplot2::geom_point(ggplot2::aes(color = re_code, text = paste(updated_at, message, sep = "\n")), size = 3, alpha = 0.5) + ggplot2::labs(shape = "Événement", color = "Événement", x = "") + ggplot2::scale_x_datetime(timezone = "Europe/Paris") + - gouvdown::theme_gouv()+ + gouvdown::theme_gouv() + ggplot2::theme( axis.text.x = ggplot2::element_text(angle = 0, hjust = 1), axis.title.y = ggplot2::element_blank() ) - plotly::ggplotly(p, tooltip = "text") + plotly::ggplotly(p, tooltip = "text", dynamicTicks = TRUE) %>% + plotly::config(locale = "fr", displaylogo = FALSE) }) - - output$table <- renderDataTable({ + # 6️⃣ Graphique des indicateurs ---- + output$bar_chart <- renderPlotly({ req(r$filteredData) - datatable(r$filteredData , - options = list( - pageLength = 10, # Nombre de lignes affichées par page - lengthMenu = c(5, 10, 25, 50), # Choix du nombre de lignes - autoWidth = TRUE, # Ajuste automatiquement la largeur des colonnes - scrollX = TRUE, # Active le défilement horizontal - class = "display", # Ajoute du style CSS - rownames = FALSE, # Supprime les numéros de ligne, - searchHighlight = TRUE - ) + + percent_time <- as.numeric(max(r$filteredData$updated_at) - min(r$filteredData$updated_at)) / + as.numeric(max(all_data$updated_at) - min(all_data$updated_at)) * 100 + + # Création du dataset + data <- data.frame( + dataset = c("Commit", "Projet", "Heure"), + total = c(sum(r$filteredData$type == "commit"), + length(unique(r$filteredData$project_name)), + percent_time) ) + plotly::ggplotly( + ggplot(data, aes(x = dataset, y = total, fill = dataset)) + + geom_bar(stat = "identity") + + coord_flip() + + scale_fill_manual(values = c("#3498db", "#e74c3c", "#2ecc71")) + + labs( + x = "Comparaison", + y = "Pourcentage / Nombre", + title = "Comparaison des Commits, Projets et Temps" + ) + + theme_minimal() + + theme(legend.position = "none") + + geom_text( + aes(label = paste0(round(total, 1),"%")), + hjust = -0.2 + ) + ) %>% + plotly::config(locale = "fr", + displaylogo = FALSE) }) + output$nb_projet <- renderText({ + req(r$filteredData) + paste("Nombre de projets visualisés",length(unique(r$filteredData$project_name)),sep = " : ") + }) + output$nb_commit <- renderText({ + req(r$filteredData) + paste("Nombre de commits",sum(r$filteredData$type == "commit"),sep = " : ") + }) + output$nb_temps <- renderText({ + req(r$filteredData) + paste("Estimation du temps passé sur la période :",sep = " ",round(difftime( + max(r$filteredData$updated_at) , min(r$filteredData$updated_at),units = "hours")), " heures") + }) + + + # 7️⃣ Table des résultats ---- + output$table <- renderDT({ + req(r$filteredData) + datatable(r$filteredData, options = list( + pageLength = 10, + lengthMenu = c(5, 10, 25, 50), + autoWidth = TRUE, + scrollX = TRUE, + class = "display", + rownames = FALSE, + searchHighlight = TRUE + )) + }) + + # 8️⃣ Graphique des indicateurs ----- + + output$min_value <- renderText({ + req(input$project_name) # Vérifie que l'input existe + + # Nombre de projets sélectionnés + selected_projects <- length(unique(input$project_name)) + + # Condition : Si 1 seul projet est sélectionné + if (selected_projects == 1) { + # Filtrer les données pour ce projet spécifique + projet_data <- all_data %>% + filter(project_name == input$project_name) + + min_value <- min(projet_data$updated_at, na.rm = TRUE) + + paste("Date de création du projet : ", format(min_value, "%d %B %Y, %H:%M")) + } else { + "Sélectionnez un seul projet pour voir la date de création." + } + }) + + + output$temps <- renderText({ + req(input$project_name) # Vérifie que l'input existe + + # Nombre de projets sélectionnés + selected_projects <- length(unique(input$project_name)) + # Condition : Si 1 seul projet est sélectionné + if (selected_projects == 1) { + # Filtrer les données pour ce projet spécifique + projet_data <- all_data %>% + filter(project_name == input$project_name) + + temps <- difftime(max(projet_data$updated_at), min(projet_data$updated_at), units = "secs") + + jours <- as.numeric(temps, units = "days") %/% 1 + heures <- (as.numeric(temps, units = "hours") %% 24) %/% 1 + minutes <- (as.numeric(temps, units = "mins") %% 60) %/% 1 + print(temps) + paste("Estimation du temps passé : ", jours, "jours,", heures, "heures,", minutes, "minutes") + } else { + "Sélectionnez un seul projet pour voir l'estimation." + } + }) + + output$bar <- renderPlot({ + req(input$project_name) # Vérifie que l'input existe + + selected_projects <- length(unique(input$project_name)) + if (selected_projects == 1) { + # Filtrer les données pour le project_name sélectionné + filtered_data <- all_data %>% + filter(project_name == input$project_name) %>% + mutate(month = format(as.Date(updated_at), "%Y-%m")) + # Définir la plage de mois spécifique au projet sélectionné + first_month <- floor_date(min(as.Date(filtered_data$updated_at)), "month") + last_month <- ceiling_date(max(as.Date(filtered_data$updated_at)), "month") + # Générer tous les mois entre le premier et le dernier + full_months <- seq(first_month, last_month, by = "month") %>% + tibble(month = format(., "%Y-%m")) + # Compter les entrées par mois et compléter les mois manquants + complete_data <- full_months %>% + left_join(filtered_data %>% count(month), by = "month") %>% + replace_na(list(n = 0)) + + # Créer le bar plot + ggplot(complete_data, aes(x = month, y = n)) + + geom_bar(stat = "identity", fill = "#ff7f27", width = 0.7) + + theme_minimal() + + scale_x_discrete( + name = "Mois", + labels = function(x) format(as.Date(paste0(x, "-01")), "%B %Y") + ) + + labs(y = "Nombre d'entrées") + + gouvdown::theme_gouv() + + theme(axis.text.x = element_text(angle = 45, hjust = 1)) + }else{ + ggplot() + + annotate("text", x = 1, y = 1, label = "Sélectionnez un seul projet pour voir la timeline", size = 5) + + theme_void() + } + }) } -- GitLab