Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 30-afficher-la-date-de-derniere-modification-d-une-table
  • 61-restructurer-l-automatisation-et-le-deploiement
  • 65-documenter-le-datamart-de-l-app
  • 76-golemisation
  • dev
  • dev_old
  • main
  • station_travail_juliette_19nov24
8 results

Target

Select target project
  • dreal-pdl/csd/catalogueR
1 result
Select Git revision
  • 30-afficher-la-date-de-derniere-modification-d-une-table
  • 61-restructurer-l-automatisation-et-le-deploiement
  • 65-documenter-le-datamart-de-l-app
  • 76-golemisation
  • dev
  • dev_old
  • main
  • station_travail_juliette_19nov24
8 results
Show changes
Commits on Source (145)
Showing with 1214 additions and 0 deletions
.Rproj.user
.Rhistory
.Rdata
.httr-oauth
.DS_Store
explo_projet*
explo_projet_attachments.zip
.RDataTmp
*.RData
downloadbutton <- function(outputId,
label = "Download",
class = "fr-link fr-link--download",
...,
icon = shiny::icon("download")) {
tags$a(id = outputId,
class = paste('btn btn-default shiny-download-link', class),
href = '',
target = '_blank',
download = NA,
icon,
label, ...)
}
create_breadcrumb <- function(base, schema, table) {
breadcrumb_items <- list(
if (!is.null(base) && base != "") tags$span(id = "base_text", class = "fr-breadcrumb__item", base),
if (!is.null(schema) && schema != "Tous") tags$span(id = "schema_text", class = "fr-breadcrumb__item", schema),
if (!is.null(table) && table != "Toutes") tags$span(id = "table_text", class = "fr-breadcrumb__item", table)
)
# Filtrer les éléments NULL (non utilisés)
breadcrumb_items <- Filter(Negate(is.null), breadcrumb_items)
# Construire le fil d'Ariane
tags$nav(
class = "fr-breadcrumb",
tags$ol(
class = "fr-breadcrumb__list",
lapply(breadcrumb_items, function(item) {
tags$li(class = "fr-breadcrumb__item", item)
})
)
)
}
search_bar <- function() {
shiny::tags$div(
class = "fr-search-bar",
id = "header-search",
role = "search",
shiny::tags$label(class = "fr-label", `for` = "search-784-input", "Recherche"),
shiny::tags$input(
class = "fr-input",
placeholder = "Rechercher",
type = "search",
id = "search_784_input",
name = "search_784_input"
),
shiny::tags$button(
class = "fr-btn",
title = "Rechercher",
type = "button",
`onclick` = "Shiny.onInputChange('search_button', Math.random());",
"Rechercher"
)
)
}
Prototype de catalogue interne, réalisée avec RShiny, pour un serveur de base de données RPostgreSQL/postgis.
https://app.moqups.com/k1K5vhD4tNIqdX403sCSy0auWvLPDNUk/edit/page/ac0c42cd4
Version: 1.0
RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default
EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8
RnwWeave: Sweave
LaTeX: pdfLaTeX
AutoAppendNewline: Yes
StripTrailingWhitespace: Yes
library(tidyverse)
library(datalibaba)
# 1- Bases ------------------------------------------------------------------------------------
# Création des connexions aux différentes base de données
role <- "does"
db_list <- c(consultation = datalibaba::connect_to_db("consultation", user = role),
referentiels = datalibaba::connect_to_db("referentiels", user = role),
datamart = datalibaba::connect_to_db("datamart", user = role),
si_eau = datalibaba::connect_to_db("si_eau", user = role))
# Récupération des commentaires pour chaque base de données
bases_avec_commentaires <- purrr::map_dfr(.x = names(db_list),
.f = ~datalibaba::get_db_comment(db = .x, user = role)) %>%
rename(base = nom_base)%>%
dplyr::mutate(commentaire = tidyr::replace_na(commentaire, "")) %>%
dplyr::mutate(display_name = paste(base, commentaire, sep = " : "))
# 2- Schémas ----------------------------------------------------------------------------------
# Création de la liste des schémas chaque base à partir des connexions
db_schema_list0 <- purrr::map2_dfr(.x = db_list, .y = names(db_list),
.f = ~datalibaba::list_schemas(.x) %>%
tibble::enframe(name = NULL, value = "nom_schema") %>%
dplyr::arrange(nom_schema) %>%
dplyr::mutate(base = .y)) %>%
# on écarte les schémas "techniques" information_schema de chaque base + le datamart du catalogue + schema d'archives
dplyr::filter(!(nom_schema %in% c("pg_catalog", "public", "information_schema")), nom_schema != "catalogue", !grepl("^zz_|^z_", nom_schema))
# Récupération des commentaires des schémas
commentaires_schema <- purrr::map2_dfr(.x = db_schema_list0$nom_schema, .y = db_schema_list0$base,
.f = ~(datalibaba::get_schema_comment(schema = .x, db = .y, user = role) %>%
dplyr::mutate(base = .y))
)
db_schema_list <- db_schema_list0 %>%
dplyr::left_join(commentaires_schema %>% dplyr::mutate(commentaire = tidyr::replace_na(commentaire, "")),
by = c("base", "nom_schema")) %>%
dplyr::mutate(display_name = paste(nom_schema, commentaire, sep = " : ") %>%
gsub(": $", "", .))
rm(db_schema_list0)
#3- Tables ------------------------------------------------------------------------------------
## 3-1 Liste des tables accessibles pour le role
# Création d'une fonction pour récupérer la liste des tables de chaque schéma
# (elle prend en argument l'index d'une ligne de db_schema_list)
lister_tables <- function(i = 1) {
datalibaba::list_tables(db = db_schema_list$base[i], schema = db_schema_list$nom_schema[i],
con = db_list[[db_schema_list$base[i]]]) %>%
tibble::enframe(name = NULL, value = "table") %>%
# ajout des colonnes 'base' et 'schema'
dplyr::cross_join(db_schema_list[i, ])
}
# Creation de la table qui liste toutes les tables de tous les schémas des 4 bases
tb_sch_base_0 <- purrr::map_df(1:nrow(db_schema_list), .f = lister_tables) %>%
dplyr::arrange(base, nom_schema, table)
# Vérif bonne pratique noms tables : on écarte les tables dont le nom comprend des majuscules car il faut les quoter dans les requêtes SQL
anomalies_noms_tables <- tb_sch_base_0 %>%
dplyr::filter(tolower(table) != table)
## Pour mémoire : voici comment ajouter les quotes au besoin
# mutate(table = if_else(tolower(table) != table, paste0('"', table, '"'), table)) %>%
# on écarte les tables avec majuscules et les tables d'archives
tb_sch_base <- tb_sch_base_0 %>%
dplyr::filter(tolower(table) == table, !grepl("^zz_|^z_", table)) #, nom_schema != "culture_societe_service"
## 3.2 Récupération des métadonnées des tables
### A- Récupération des types
# référentiel des types de champs, associant pour chaque type de postgis, un type compréhensible par l'utilisateur
types_ref <- read.csv2(file = "data-raw//ref_type_champs.csv")
# lecture des types des champs dans le schéma d'information de chaque base listée dans db_list
types_champs <- purrr::map_dfr(db_list, ~DBI::dbGetQuery(.x, "SELECT table_catalog AS base, table_schema AS nom_schema, table_name AS nom_table,
column_name AS nom_col, ordinal_position AS num_ordre, udt_name AS type
FROM information_schema.columns")) %>%
# on ne garde que les enregistrement correspondant à la liste tb_sch_base
dplyr::semi_join(tb_sch_base, by = c("nom_schema", "nom_table" = "table")) %>%
dplyr::arrange(base, nom_schema, nom_table, num_ordre) %>%
dplyr::left_join(types_ref, by = "type")
### B- Récupération des commentaires de table et de champs
commentaires_tables <- purrr::map_dfr(.x = c(1:nrow(tb_sch_base)),
.f = ~(datalibaba::get_table_comments(table = tb_sch_base$table[.x], schema = tb_sch_base$nom_schema[.x],
db = tb_sch_base$base[.x], user = role) %>%
dplyr::mutate(nom_col = dplyr::coalesce(nom_col, ""), base = tb_sch_base$base[.x]))
)
tables_catalog <- dplyr::full_join(types_champs, commentaires_tables, by = c("base", "nom_schema", "nom_table", "nom_col")) %>%
dplyr::distinct() %>%
dplyr::arrange(base, nom_schema, nom_table)
### C- Création d'une base pour la recherche plein texte
base_recherche_txt <- tables_catalog %>%
dplyr::mutate(col_info = dplyr::if_else(is.na(commentaire), nom_col, paste0(commentaire, " : ", nom_col))) %>%
dplyr::group_by(base, nom_schema, nom_table) %>%
dplyr::summarise(description = paste(col_info, collapse = '\n'), .groups = "keep") %>%
dplyr::mutate(description = paste(nom_table, description, collapse = '\n', sep = "\n")) %>%
# Ajouter un identifiant unique à chaque ligne
dplyr::mutate(id = row_number()) %>%
dplyr::select(id, base, nom_schema, nom_table, description)
# 4- Exports du datamart au format RData ---------------------------------------------------------
date_datamart <- Sys.Date() %>% format.Date("%d/%m/%Y")
tb_datamart <- c("commentaires_schema", "tables_catalog", "base_recherche_txt","bases_avec_commentaires", "commentaires_tables",
"db_schema_list", "tb_sch_base", "date_datamart") # liste des objets à inclure
nom_datamart <- "datamart_catalogue.RData" # nom du RData
# Sauvegarde dans le répertoire du projet et dans le répertoire du projet sur T:
save(list = tb_datamart, file = nom_datamart)
save(list = tb_datamart, file = paste0("T:/datalab/SCTE/CATALOGUE/PRODUCTION/", nom_datamart))
# 5- exports des anomalies en vue d'une remédiation ultérieure
if(nrow(anomalies_noms_tables) > 0) {
write.csv2(x = anomalies_noms_tables,
file = paste0("T:/datalab/SCTE/CATALOGUE/DONNEES_INTERNES/anomalies_noms_tables_", Sys.Date(), ".csv"))
}
type;type_fr
_text;texte
bool;booléen (Vrai/Faux)
bpchar;texte
date;date
float8;numérique
geometry;spatial
int2;entier
int4;entier
int8;entier
numeric;numérique
polygon;spatial
text;texte
timestamp;date/heure
timestamptz;date/heure
uuid;texte
varchar;texte
Table bases_avec_commentaires {
base VARCHAR [pk]
commentaire TEXT
display_name TEXT
}
Table db_schema_list {
nom_schema VARCHAR [pk]
base VARCHAR [pk]
commentaire TEXT
display_name TEXT
}
Table tb_sch_base {
table VARCHAR [pk]
nom_schema VARCHAR [pk]
base VARCHAR [pk]
commentaire TEXT
display_name TEXT
}
Table tables_catalog {
base VARCHAR [pk]
nom_schema VARCHAR [pk]
nom_table VARCHAR [pk]
nom_col VARCHAR
num_ordre VARCHAR
type VARCHAR
type_fr VARCHAR
commentaire TEXT
}
Table base_recherche_txt {
id INT
base VARCHAR [pk]
nom_schema VARCHAR [pk]
nom_table VARCHAR [pk]
description TEXT
}
Table commentaires_schema {
nom_schema VARCHAR [pk]
base VARCHAR [pk]
commentaire TEXT
}
Table commentaires_tables {
base VARCHAR [pk]
nom_schema VARCHAR [pk]
nom_table VARCHAR [pk]
commentaire TEXT
nom_col VARCHAR
}
Ref: db_schema_list.base > bases_avec_commentaires.base
Ref: tb_sch_base.base > db_schema_list.base
Ref: tb_sch_base.nom_schema > db_schema_list.nom_schema
Ref: tables_catalog.base > tb_sch_base.base
Ref: tables_catalog.nom_schema > tb_sch_base.nom_schema
Ref: tables_catalog.nom_table > tb_sch_base.table
Ref: base_recherche_txt.base > tables_catalog.base
Ref: base_recherche_txt.nom_schema > tables_catalog.nom_schema
Ref: base_recherche_txt.nom_table > tables_catalog.nom_table
Ref: commentaires_schema.base > db_schema_list.base
Ref: commentaires_schema.nom_schema > db_schema_list.nom_schema
Ref: commentaires_tables.base > tables_catalog.base
Ref: commentaires_tables.nom_schema > tables_catalog.nom_schema
Ref: commentaires_tables.nom_table > tables_catalog.nom_table
# deploiement_automatique.R
library(RCurl)
# Connexion FTP vers le serveur de dataviz
con_ftp_svr_dataviz <- paste0("ftp://", Sys.getenv("svr_dataviz_user"), ":", Sys.getenv("svr_dataviz_mdp"),
"@", Sys.getenv("svr_dataviz_ip"), "/catalogueR/")
# Fonction pour le transfert FTP du fichier RData
to_svr_dataviz_ftp <- function(fic = "datamart_catalogue.RData") {
ftpUpload(what = fic, to = paste0(con_ftp_svr_dataviz, fic))
}
# Exécution du transfert pour le fichier .RData uniquement
to_svr_dataviz_ftp("datamart_catalogue.RData")
message("Déploiement automatique du datamart terminé.")
# deploiement vers le sserveur interne de dataviz
library(RCurl)
# Connexion FTP vers le serveur de dataviz
con_ftp_svr_dataviz <- paste0("ftp://", Sys.getenv("svr_dataviz_user"), ":", Sys.getenv("svr_dataviz_mdp"),
"@", Sys.getenv("svr_dataviz_ip"), "/catalogueR/")
# creation d'une fonction de transfert FTP vers le serveur à l'aide de ftpUpload de RCurl
## Les adresses des fichiers à transferer parte de la racine du projet RStudio
to_svr_dataviz_ftp <- function(fic = "datamart_catalogue.RData") {
ftpUpload(what = fic, to = paste0(con_ftp_svr_dataviz, fic))
}
# fichiers à transférer
app_files <- c("datamart_catalogue.RData", "global.R", list.files("R/", full.names = TRUE, recursive = TRUE),list.files("www/", full.names = TRUE, recursive = TRUE),
"server.R", "ui.R", "catalogue.Rproj")
# Exécution des transferts
lapply(X = app_files, FUN = to_svr_dataviz_ftp)
message("Déploiement manuel complet terminé.")
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = TRUE)
load("../data-raw/datamart_catalogue.RData")
```
## Schéma du datamart du "CatalogueR"
Ce schéma représente la structure du datamart du CatalogueR. Il inclut les tables suivantes :
- **bases_avec_commentaires :** Cette table contient la liste des `r nrow(bases_avec_commentaires)` bases de données disponibles dans le CatalogueR avec leurs commentaires.
**Champs:**
- base : Nom de la base de données.
- commentaire : Description associée à la base de données.
- display_name : Nom combinant le nom de la base et son commentaire.
- **db_schema_list :** Cette table répertorie tous les schémas présents dans chaque base de données du CatalogueR.
**Champs:**
- nom_schema : Nom du schéma au sein de la base de données.
- base : Nom de la base de données à laquelle appartient le schéma.
- commentaire : Description associée au schéma.
- display_name : Nom combinant nom_schema et commentaire.
- **tb_sch_base :** Cette table liste toutes les tables disponibles dans chaque schéma des bases de données du CatalogueR.
**Champs:**
- table : Nom de la table.
- nom_schema : Nom du schéma auquel appartient la table.
- base : Nom de la base de données.
- commentaire : Description du schéma.
- display_name : Nom combinant nom_schema et commentaire.
- **tables_catalog :** Cette table contient les métadonnées détaillées de chaque table et colonnes du CatalogueR. Elle inclut les types de données des colonnes, ainsi que les commentaires associés aux tables et aux colonnes.
**Champs:**
- base : Nom de la base de données.
- nom_schema : Nom du schéma.
- nom_table : Nom de la table.
- nom_col : Nom de la colonne dans la table.
- num_ordre : Ordre de la colonne dans la table.
- type : Type de la colonne (ex. integer, text).
- type_fr : Type de la colonne traduit en français.
- commentaire : Description associée à la colonne ou à la table.
- **commentaires_tables :** Cette table contient les descriptions des tables et des colonnes du CatalogueR.
**Champs:**
- base : Nom de la base de données.
- nom_schema : Nom du schéma.
- nom_table : Nom de la table.
- commentaire : Description associée à la table.
- nom_col : Nom de la colonne.
- **base_recherche_txt : ** Cette table regroupe et indexe en texte libre les métadonnées des tables afin de faciliter leur recherche et consultation dans le CatalogueR.
**Champs:**
- id : Identifiant.
- base : Nom de la base de données.
- nom_schema : Nom du schéma.
- nom_table : Nom de la table.
- description : Texte combinant le commentaite de table ainqi que les noms des colonnes et leurs commentaires, afin de faciliter la recherche.
- **commentaires_schema :** Cette table regroupe les commentaires spécifiques aux schémas des bases de données du CatalogueR.
**Champs:**
- base : Nom de la base de données.
- nom_schema : Nom du schéma.
- commentaire : Description associée au schéma.
```{r out.width="100%", echo=FALSE}
knitr::include_graphics("../www/schema.png")
```
This diff is collapsed.
library(gmailr)
library(logging)
library(base64enc)
library(dplyr)
# Configuration du logging
basicConfig(level = 'DEBUG')
# Fonction pour encoder le sujet en Base64
encode_base64_utf8 <- function(string) {
raw_string <- charToRaw(string)
encoded_string <- base64encode(raw_string)
return(sprintf("=?UTF-8?B?%s?=", encoded_string))
}
# Fonction pour envoyer un email
send_email <- function(subject, body, to = "adl.dreal-pdl@developpement-durable.gouv.fr", cc = "lisa.smah@developpement-durable.gouv.fr") {
gm_auth_configure(path = "X:/SCTE/CSD/APPLICATIONS_SIG_BUREAUTIQUES/RESSOURCES_R/client_secret_api_gmail_csd.json")
gm_auth(
scopes = c(
"https://www.googleapis.com/auth/gmail.send",
"https://www.googleapis.com/auth/gmail.compose"
)
)
# Encoder le sujet en Base64 pour éviter les problèmes d'encodage
subject_base64 <- encode_base64_utf8(subject)
email <- gm_mime() %>%
gm_to(to) %>%
gm_cc(cc) %>%
gm_from("lisa.smah@developpement-durable.gouv.fr") %>%
gm_subject(subject_base64) %>%
gm_text_body(body)
gm_send_message(email)
}
# Fonction pour lire et filtrer les logs
read_logs <- function(log_file) {
logs <- readLines(log_file)
# Filtrer les lignes indésirables
filtered_logs <- logs[!grepl("Attaching core tidyverse packages", logs) &
!grepl("Conflicts", logs) &
!grepl("package ‘", logs) &
!grepl("✔", logs) &
!grepl("ℹ Use the conflicted package", logs) &
!grepl("─", logs) &
!grepl("✖", logs)&
!grepl("Messages d'avis", logs) &
!grepl("compilé avec la version R", logs) &
!grepl("Attachement du package", logs) &
!grepl("L'objet suivant est masqué depuis", logs)&
!grepl("complete", logs)]
return(filtered_logs)
}
# Fonction pour analyser les logs et préparer le contenu de l'email
prepare_email_content <- function(logs) {
tryCatch({
datamart_success <- any(grepl("Mise à jour du datamart catalogueR réussie", logs))
deploy_success <- any(grepl("Déploiement de l'application réussi", logs))
error_messages <- logs[grepl("Erreur|Échec", logs)]
date <- Sys.Date()
if (datamart_success && deploy_success) {
subject <- "Succès mise à jour datamart catalogueR et déploiement de l'application"
body <- paste(logs, collapse = "\n")
} else if (!datamart_success) {
subject <- "Échec mise à jour datamart catalogueR"
body <- sprintf("La mise à jour du datamart a échoué le %s. Erreurs:\n%s", date, paste(error_messages, collapse = "\n"))
} else if (!deploy_success) {
subject <- "Échec du déploiement de l'application"
body <- sprintf("Le déploiement de l'application a échoué le %s. Erreurs:\n%s", date, paste(error_messages, collapse = "\n"))
}
list(subject = subject, body = body)
}, error = function(e) {
subject <- "Erreur lors de l'analyse des logs"
body <- sprintf("Une erreur est survenue lors de l'analyse des logs le %s. Erreur:\n%s", Sys.Date(), e$message)
list(subject = subject, body = body)
})
}
# Chemin vers le fichier de log généré par script_routine
args <- commandArgs(trailingOnly = FALSE)
script_path <- sub("--file=", "", args[grep("--file=", args)])
project_path <- normalizePath(file.path(dirname(script_path), ".."))
setwd(project_path)
log_file <- "logs/script_routine.log"
# Lire et analyser les logs
logs <- read_logs(log_file)
# Préparer le sujet et le contenu de l'email basé sur l'analyse des logs
email_content <- prepare_email_content(logs)
# Envoi de l'email de notification
send_email(email_content$subject, email_content$body)
library(logging)
Sys.setenv("HTTP_PROXY" = "http://pfrie-std.proxy.e2.rie.gouv.fr:8080")
Sys.setenv("HTTPS_PROXY" = "http://pfrie-std.proxy.e2.rie.gouv.fr:8080")
# a n'utiliser que lorsque le fichier est exécuté par la tache planifiée
args <- commandArgs(trailingOnly = FALSE)
script_path <- sub("--file=", "", args[grep("--file=", args)])
project_path <- normalizePath(file.path(dirname(script_path), ".."))
setwd(project_path)
# Configuration du logging ------------------------------------------------------------------------------------------------------------
basicConfig(level = 'DEBUG')
log_file <- "logs/script_routine.log"
log_con <- file(log_file, open = "wt")
# Fonction pour exécuter un script et capturer les logs -------------------------------------------------------------------------------
execute_script <- function(script_path) {
tryCatch({
source(script_path)
loginfo("Script exécuté avec succès : %s", script_path)
return(TRUE)
}, error = function(e) {
logerror("Erreur lors de l'exécution du script %s : %s", script_path, e$message)
return(FALSE)
})
}
sink(log_con, type = "message")
sink(log_con, type = "output")
# Exécution de la mise à jour du datamart ---------------------------------------------------------------------------------------------
start_time <- Sys.time()
datamart_success <- execute_script("data-raw/datamartage.R")
end_time <- Sys.time()
if (datamart_success) {
execution_time <- end_time - start_time
loginfo("Mise à jour du datamart catalogueR réussie")
# Récupérer la taille du datamart
datamart_file <- "datamart_catalogue.RData"
datamart_size <- file.info(datamart_file)$size
loginfo("Taille du datamart : %s bytes", datamart_size)
loginfo("Temps d'exécution : %s minutes", execution_time)
} else {
logerror("Échec de la mise à jour du datamart catalogueR")
}
# Exécution du déploiement de l'application -------------------------------------------------------------------------------------------
deploy_success <- execute_script("dev/deploiement_automatique.R")
if (deploy_success) {
loginfo("Déploiement automatique du datamart réussi")
} else {
logerror("Échec du déploiement automatique du datamart")
}
# Fermer les connexions de fichier ----------------------------------------------------------------------------------------------------
sink(type = "message")
sink(type = "output")
close(log_con)
library(shiny)
library(shinyjs)
library(tidyverse)
library(datalibaba)
library(purrr)
library(shinygouv)
library(sf)
library(mapview)
library(DBI)
# chargement du datamart en fonction du contexte d'exécution de l'app
nom_local <- "datamart_catalogue.RData"
adresse_T <- paste0("T:/datalab/SCTE/CATALOGUE/PRODUCTION/", nom_local)
if(file.exists(adresse_T)) load(file = adresse_T) else load(nom_local)
# A passer dans datamartage.R
choix_bases <- setNames(bases_avec_commentaires$base, bases_avec_commentaires$display_name)
commentaires_tables <- filter(commentaires_tables, nom_col == "" | is.na(nom_col)) %>%
distinct()
## Explo différence nombre de tables dans les deux datasets ~ 30 enregistrements d'écarts
# anti_join(commentaires_tables, tb_sch_base, by = c("nom_schema", "nom_table" = "table", "base")) %>% View
# anti_join(tb_sch_base, commentaires_tables, by = c("nom_schema", "table" = "nom_table", "base")) %>% View
── Attaching core tidyverse packages ──────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
2024-11-12 11:43:51.77173 INFO::Script exécuté avec succès : data-raw/datamartage.R
Messages d'avis :
1: le package 'tidyverse' a été compilé avec la version R 4.3.3
2: le package 'tibble' a été compilé avec la version R 4.3.3
3: le package 'tidyr' a été compilé avec la version R 4.3.3
4: le package 'readr' a été compilé avec la version R 4.3.3
5: le package 'purrr' a été compilé avec la version R 4.3.3
6: le package 'dplyr' a été compilé avec la version R 4.3.3
7: le package 'stringr' a été compilé avec la version R 4.3.3
8: le package 'forcats' a été compilé avec la version R 4.3.3
9: le package 'lubridate' a été compilé avec la version R 4.3.3
2024-11-12 11:43:51.778827 INFO::Mise à jour du datamart catalogueR réussie
2024-11-12 11:43:51.791693 INFO::Taille du datamart : 845910 bytes
2024-11-12 11:43:51.792459 INFO::Temps d'exécution : 8.68305060068766 minutes
Attachement du package : 'RCurl'
L'objet suivant est masqué depuis 'package:tidyr':
complete
Déploiement automatique du datamart terminé.
2024-11-12 11:43:52.130901 INFO::Script exécuté avec succès : dev/deploiement_automatique.R
Message d'avis :
le package 'RCurl' a été compilé avec la version R 4.3.2
2024-11-12 11:43:52.132734 INFO::Déploiement automatique du datamart réussi
server <- function(input, output, session) {
# initilisation de la liste des valeurs réactives-----
r <- reactiveValues(
base = NULL, # la base choisie par l'utilisateur
schemas_pour_base = NULL, # la liste des schémas de la base choisie par l'utilisateur
schema = "Tous", # le schéma choisi par l'utilisateur
tables_pour_schema = NULL, # la liste des schémas de la base choisie par l'utilisateur
table = "Toutes", # la table choisie par l'utilisateur
metadata_csv = NULL, # table de métadonnées à télécharger
nom_meta_csv = NULL, # nom du fichier CSV contenant les métadonnées à télécharger
selected_metadata = NULL, # table de métadonnées à afficher
commentaire = NULL, # commentaires de base/schéma/table/
fil_ariane = "",
title_metadata = NULL, # Titre de présentation des métadonnées
selected_data = NULL, # table sélectionnée
isSpatial = FALSE, # booléen pour identifier les tables spatiales
role = "dreal", # on initialise la connexion au SGBD avec le rôle de connexion dreal par défaut
acces = FALSE,
selected_row10 = NULL,
table_rows = NULL,
table_size = NULL,
showDataMapTabs = FALSE
)
# Onglet explorer : inputs---------
observeEvent(input$base, {
req(input$base)
r$base <- input$base
# on réinitialise la liste des objets réactifs à chaque changement de input$base
r$schema <- "Tous"
r$table <- "Toutes"
r$schemas_pour_base <- dplyr::filter(db_schema_list, base == r$base)
# Vérifiez que schemas_pour_base a des éléments avant de mettre à jour
if(nrow(r$schemas_pour_base) > 0) {
shinygouv::updateSelectInput_dsfr(session = session, inputId = "schema",
choices = c("Tous", r$schemas_pour_base$nom_schema %>%
setNames(r$schemas_pour_base$display_name)),
selected = "Tous")
} else {
shinygouv::updateSelectInput_dsfr(inputId = "schema", choices = c("Tous"), selected = "Tous")
}
})
observeEvent(c(r$schemas_pour_base, input$schema), {
req(input$schema)
r$schema <- input$schema
r$table <- "Toutes"
req(r$schemas_pour_base, r$schema)
if (r$schema == "Tous") {
r$tables_pour_schema <- data.frame() # Pas de tables à afficher
} else {
r$tables_pour_schema <- tb_sch_base %>%
dplyr::filter(base == r$base, nom_schema == r$schema)
}
if(length(r$tables_pour_schema) > 0) {
shinygouv::updateSelectInput_dsfr(session = session, inputId = "table",
choices = c("Toutes", r$tables_pour_schema$table), selected = "Toutes")
} else {
shinygouv::updateSelectInput_dsfr(session = session, inputId = "table", choices = c("Toutes"), selected = "Toutes")
}
})
# Recherche plein texte : résultats de recherche -------------------------------------
observeEvent(input$search_button, {
r$filtered_data <- NULL
r$message_recherche_infructueuse <- ""
# filtrer la table de recherche plein texte selon le texte recherché
req(input$search_784_input)
search_results <- base_recherche_txt %>%
dplyr::filter(grepl(input$search_784_input, description, ignore.case = TRUE)) %>%
dplyr::select(base, nom_schema, nom_table)
if (nrow(search_results) > 0) {
search_results <- search_results %>%
dplyr::left_join(tables_catalog %>%
dplyr::filter(nom_col == "") %>%
dplyr::select(base, nom_schema, nom_table, commentaire),
by = c("base", "nom_schema", "nom_table")) %>%
dplyr::rename(`Libellé` = commentaire) %>%
dplyr::distinct()
r$filtered_data <- search_results %>%
dplyr::mutate(
base_js_on_click = paste0( '"Shiny.setInputValue(\'clic_base\', \'', base, '\'); '),
schema_js_on_click = paste0(base_js_on_click,
'Shiny.setInputValue(\'clic_schema\', \'', nom_schema, '\'); '),
table_js_on_click = paste0(schema_js_on_click,
'Shiny.setInputValue(\'clic_table\', \'', nom_table, '\'); '),
# on ajoute les inputs clic_schema = "Tous" et clic_table = "Toutes"
base_js_on_click = paste0(base_js_on_click, 'Shiny.setInputValue(\'clic_schema\', \'Tous\'); ',
'Shiny.setInputValue(\'clic_table\', \'Toutes\'); '),
schema_js_on_click = paste0(schema_js_on_click, 'Shiny.setInputValue(\'clic_table\', \'Toutes\'); '),
nav_onglet = 'Shiny.onInputChange(\'nav_to_explorer\', Math.random()); return false;">',
base = paste0('<a href = "#" onclick = ', base_js_on_click, nav_onglet, base, '</a>'),
nom_schema = paste0('<a href = "#" onclick = ', schema_js_on_click, nav_onglet, nom_schema, '</a>'),
nom_table = paste0('<a href = "#" onclick = ', table_js_on_click, nav_onglet, nom_table, '</a>')
) %>%
select(-nav_onglet, -ends_with("_js_on_click"))
} else {
r$message_recherche_infructueuse <- tagList(
p("Aucun lot de données ne correspond à votre recherche,
veuillez la renouveler ou explorer les bases de données disponibles (2e onglet)")
)
}
})
# Affichage des résultats dans un tableau
output$search_results <- DT::renderDataTable({
DT::datatable(r$filtered_data, escape = FALSE,
options = list(pageLength = 20, lengthMenu = c(20, 50, 100),
language = list(url = 'https://cdn.datatables.net/plug-ins/1.10.21/i18n/French.json')))
})
# Affichage du message si aucun résultat n'est trouvé
output$search_message <- renderUI({
r$message_recherche_infructueuse
})
# Recherche plein texte : inputs -------------------------------------
# Observer l'événement nav_to_explorer pour cliquer sur visualiser les métadonnées après avoir mis à jour les inputs de sélection base/schema/table
observeEvent(input$nav_to_explorer, {
duree_totale = 3 * 250
# On met à jour la base choisie dans le selectInput
shinygouv::updateSelectInput_dsfr(session = session, inputId = "base",
choices = bases_avec_commentaires$base,
selected = input$clic_base)
# on prévoit un peu de délai avant de lancer la suite
shinyjs::delay(ms = duree_totale, expr = {
req(input$clic_base == input$base)
message("Mise à jour de input base réalisée : ", input$base, ".")
req(r$schemas_pour_base)
# On met à jour le schema choisi dans le selectInput
shinygouv::updateSelectInput_dsfr(inputId = "schema", session = session, selected = input$clic_schema,
choices = unique(c("Tous", r$schemas_pour_base$nom_schema)))
shinyjs::delay(ms = duree_totale/3*2, expr = {
req(input$clic_schema == input$schema)
message(paste0("Mise à jour de l'input schema réalisée : ", input$schema))
# Vérification que la liste de choix des tables est prête avant de poursuivre
req(r$tables_pour_schema )
# Mise à jour de l'input table
shinygouv::updateSelectInput_dsfr(inputId = "table", session = session, selected = input$clic_table,
choices = c("Toutes", r$tables_pour_schema$table))
shinyjs::delay(ms = duree_totale/3, expr = {
req(input$clic_table == input$table)
message(paste("Mise à jour de l'input table réalisée : ", input$table))
shinyjs::click("visualiserMetadata")
shinyjs::runjs("navigateToExplorer();")
})
})
})
})
# Afficher le tableau selected_metadata uniquement si le bouton est cliqué -------------
observeEvent(input$visualiserMetadata, {
r$showDataMapTabs <- FALSE
req(input$table)
r$table <- input$table
req(r$schemas_pour_base, r$tables_pour_schema, r$table)
r$selected_metadata <- NULL
r$selected_data <- NULL
r$metadata_csv <- NULL
r$fil_ariane <- ""
# Définir le fil d'Ariane une fois, indépendamment des cas spécifiques
r$fil_ariane <- create_breadcrumb(base = r$base, schema = r$schema, table = r$table)
if (r$table == "Toutes") {
# on vide toutes les variables réactives liées à la table précédemment sélectionnée
r$selected_row10 <- NULL
r$isSpatial <- NULL
r$table_rows <- NULL
r$table_size <- NULL
if (r$schema == "Tous") {
# Cas où "Toutes" est sélectionné pour table et schema
r$title_metadata <- paste0("Liste des schémas de la base : ", r$base)
r$metadata_csv <- r$schemas_pour_base %>%
dplyr::select(-display_name)
r$selected_metadata <- r$metadata_csv %>%
dplyr::select(-base)
r$nom_meta_csv <- paste("metadonnees", r$base, Sys.Date(), sep = ".")
# Commentaire descriptif de la base
r$commentaire <- bases_avec_commentaires %>%
filter(base == r$base) %>%
pull(commentaire) %>%
unique()
} else {
# Cas où une base, un schéma sont sélectionnés et "Toutes" pour les tables
r$title_metadata <- paste0("Tables du schéma : ", r$base, '.', r$schema)
r$metadata_csv <- r$tables_pour_schema %>%
dplyr::select(base, nom_schema, nom_table = table, commentaire)
r$selected_metadata <- r$metadata_csv %>%
dplyr::select(-base, -nom_schema)
r$nom_meta_csv <- paste("metadonnees", r$base, r$schema, Sys.Date(), sep = ".")
# commentaire descriptif du schema
r$commentaire <- commentaires_schema %>%
dplyr::filter(base == r$base, nom_schema == r$schema) %>%
dplyr::filter(base == r$base, nom_schema == r$schema) %>%
dplyr::pull(commentaire) %>%
unique()
}
} else {
# Cas où une base, un schéma, et une table sont sélectionnés
r$title_metadata <- paste0("Dictionnaire d'attribut de la table ", r$table)
r$metadata_csv <- tables_catalog %>%
dplyr::filter(r$base == base, nom_schema == r$schema, nom_table == r$table)
# dictionnaire d'attribut
r$selected_metadata <- r$metadata_csv %>%
dplyr::filter(nom_col != "") %>%
dplyr::select(nom_col, commentaire, type_valeur = type_fr)
r$nom_meta_csv <- paste("metadonnees", r$base, r$schema, r$table, Sys.Date(), sep = ".")
# commentaire descriptif de table
r$commentaire <- r$metadata_csv %>%
dplyr::filter(nom_col == "") %>%
dplyr::pull(commentaire)
# Établir la connexion à la base de données
conn <- datalibaba::connect_to_db(db = r$base, user = r$role)
# Récupérer le nombre de lignes et la taille de la table
r$table_rows <- dbGetQuery(conn, paste0("SELECT COUNT(*) AS n_rows FROM ", r$schema, ".", r$table))$n_rows
r$table_size <- dbGetQuery(conn, paste0("SELECT pg_size_pretty(pg_relation_size('", r$schema, ".", r$table, "')) AS table_size"))$table_size
# Fermer la connexion par prudence
dbDisconnect(conn)
rm(conn)
# tenter de lire la table pour vérifier accès
tryCatch({
r$selected_row10 <- datalibaba::importer_data(table = r$table, schema = r$schema,
db = r$base, user = r$role, limit = 10)
}, error = function(e) {
message("Erreur lors de la récupération des données de la table : ", e$message)
r$selected_row10 <- NULL
})
r$acces <- isTruthy(r$selected_row10)
r$isSpatial <- any(grepl("spatial", r$selected_metadata$type_valeur))
}
})
# Mettre à jour l'affichage du fil d'Ariane en utilisant les variables réactives
output$breadcrumb_metadata <- renderUI({
r$fil_ariane
})
# Mise à jour de l'affichage du titre des métadonnées (utilisation de title_metadata de dev)
output$title_metadata <- renderUI({
req(r$title_metadata)
tags$h4(r$title_metadata)
})
# Mise à jour de l'affichage du commentaire
output$commentaire_metadata <- renderUI({
req(r$commentaire)
tags$p(tags$strong("Descriptif :"), r$commentaire)
})
# Mise à jour des informations sur le nombre de lignes et la taille de la table
output$table_metadata <- renderUI({
req(r$table_rows, r$table_size)
div(id = "div_meta", class = "fr-highlight",
tags$p(tags$strong("Nombre de lignes :"), format(r$table_rows, big.mark = "\u202f", scientific = FALSE)),
tags$p(tags$strong("Taille de la table :"), r$table_size)
)
})
# Mise à jour du tableau de métadonnées avec options d'export CSV et pagination
output$metadata_table <- DT::renderDataTable({
req(r$selected_metadata) # Vérifie que les données sont disponibles
DT::datatable(
r$selected_metadata,
filter = 'bottom',
extensions = 'Buttons', # Active l'extension Buttons pour l'exportation
options = list(
pageLength = 20, # Nombre de lignes par défaut affichées
lengthMenu = list(
c(20, 50, 100, -1), # Valeurs disponibles
c("20", "50", "100", "Tous") # Libellés associés
),
dom = 'Blfrtip', # Inclut les boutons d'exportation
language = list(url = 'https://cdn.datatables.net/plug-ins/1.10.21/i18n/French.json'),
buttons = list(
list(
extend = 'csv',
text = 'Télécharger CSV',
filename = r$nom_meta_csv # Nom du fichier CSV
)
),
scrollX = TRUE # Active le défilement horizontal si nécessaire
),
rownames = FALSE,
)
})
# Rendre le bouton de visualisation des données conditionnellement (seulement si métadonnées visualisées ET droits accès à la table ok)
output$viz_data <- renderUI({
req(r$acces)
if(input$table != "Toutes") {
shinygouv::actionButton_dsfr("data_a_viz", "Visualiser les données", class = "fr-my-3w")
} else {
return(NULL)
}
})
observeEvent(input$data_a_viz, {
r$showDataMapTabs <- TRUE
})
output$conditional_tabs <- renderUI({
if (r$showDataMapTabs) {
tabsetPanel_dsfr(
id = "data_map_tabs",
tabPanel_dsfr(
id = "data_preview_tab",
title = "Données",
content = fluidPage_dsfr(
uiOutput("data_table_preview_title"),
DT::dataTableOutput("data_table_preview")
)
),
tabPanel_dsfr(
id = "map_tab",
title = "Aperçu spatial",
content = fluidPage_dsfr(
uiOutput("map_title"),
uiOutput("map_message"),
leaflet::leafletOutput("map", height = "600px")
)
)
)
} else {
NULL
}
})
# Visualiser les données attributaires de la table-----------------------------------------
observeEvent(input$data_a_viz, {
req(r$acces)
r$selected_data <- datalibaba::importer_data(table = r$table, schema = r$schema, db = r$base, user = r$role)
r$data_csv <- sf::st_drop_geometry(r$selected_data)
r$nom_csv <- paste(r$base, r$schema, r$table, Sys.Date(), sep = ".")
})
# Titre des données attributaires
output$data_table_preview_title <- renderUI({
if (isTruthy(r$selected_data)) {
h4("Données attributaires")
} else {
return(NULL) # Vider le titre si les données sont vides
}
})
#Titre de la carte (Aperçu spatial)
output$map_title <- renderUI({
if (isTruthy(r$selected_data) && r$isSpatial) {
h4("Aperçu spatial")
} else {
return(NULL) # Vider le titre si les données spatiales sont vides
}
})
# Mettre à jour l'affichage de la table dans la zone principale de la page
output$data_table_preview <- DT::renderDataTable({
validate(
need(input$schema != "Tous", ""),
need(input$table != "Toutes", "")
)
req(r$selected_data)
DT::datatable(head(r$data_csv, 100),
filter = 'bottom',
extensions = 'Buttons',
options = list(
pageLength = 20, # Nombre de lignes par défaut affichées
lengthMenu = list(
c(20, 50, 100, -1), # Valeurs disponibles
c("20", "50", "100", "Tous") # Libellés associés
),
dom = 'Blfrtip', # Inclut les boutons d'exportation
language = list(url = 'https://cdn.datatables.net/plug-ins/1.10.21/i18n/French.json'),
buttons = list(
list(
extend = 'csv',
text = 'Télécharger CSV',
filename = r$nom_csv
)
),
scrollX = TRUE # Activer le défilement horizontal
),
rownames = FALSE
)
})
# Mettre à jour la viz carto de la table dans la zone principale de la page
output$map <- renderLeaflet({
req(r$selected_data)
message <- NULL
if (r$isSpatial) {
if (nrow(r$selected_data) > 10000) {
# Sélectionner les 10 000 premières lignes pour l'affichage
data_to_display <- r$selected_data[1:10000, ]
message <- "Seules les 10 000 premières lignes sont affichées pour optimiser les performances."
} else {
# Si la table est petite( nrow < 10000), ne pas simplifier
data_to_display <- r$selected_data
message <- NULL # Aucun message supplémentaire
}
# Affichage du message
output$map_message <- renderUI({
if (!is.null(message)) {
div(
class = "alert alert-info",
style = "margin-bottom: 15px;",
message
)
} else {
NULL
}
})
# Rendu de la carte
mapview(data_to_display, layer.name = "aperçu")@map
} else {
# Cas où les données ne sont pas spatiales
output$map_message <- renderUI({
div(
class = "alert alert-warning",
style = "margin-bottom: 15px;",
"Les données sélectionnées ne contiennent pas d'informations spatiales."
)
})
return(NULL)
}
})
#
# # Téléchargement des données attributaires au format CSV, géré par DT, a garder pour le projet QGIS
# output$downloadData <- downloadHandler(
# filename = function() {
# r$nom_csv
# },
# content = function(file) {
# write.csv2(r$data_csv, file, row.names = FALSE)
# },
# contentType = "text/csv"
# )
}
library(shinygouv)
library(shiny)
library(leaflet)
library(DT)
library(shinyjs)
ui <- tagList(
# Activation de shinyjs
shinyjs::useShinyjs(),
navbarPage_dsfr(
id = "navbar_dsfr",
title = "Catalogue de données",
header = header_dsfr(
intitule = c("PREFET", "DE LA REGION", "PAYS DE LA LOIRE"),
nom_site_service = "Catalogue de données de la DREAL Pays de la Loire",
),
footer = footer_dsfr(
intitule = c("PREFET", "DE LA REGION", "PAYS DE LA LOIRE"),
description = "Une application de la DREAL des Pays de la Loire",
accessibilite = "non"
),
#useShinyjs(),
# Onglet Rechercher
navbarPanel_dsfr(
id = "rechercher_panel",
title = "Rechercher",
fluidPage_dsfr(
h3("Rechercher un lot de données dans le patrimoine de la DREAL"),
search_bar(),
tags$br(),
DT::dataTableOutput("search_results"),
uiOutput("search_message"),
tags$head(
tags$script(HTML("
function navigateToExplorer() {
showTabFromHash('explorer');
}
"))
)
)
),
# Onglet Explorer
navbarPanel_dsfr(
id = "explorer_panel",
title = "Explorer",
fluidPage_dsfr(
sidebarLayout(
sidebarPanel(
h3("Explorer les bases de données du patrimoine de la DREAL"),
selectInput_dsfr("base", "Choisir la base", choices = choix_bases, selected = NULL), # Sélection de la base
shinygouv::selectInput_dsfr("schema", "Choisir le schéma", NULL), # Sélection du nom du schéma
shinygouv::selectInput_dsfr("table", "Choisir la table", NULL), # Sélection du nom de la table
shinygouv::actionButton_dsfr("visualiserMetadata", "Visualiser les métadonnées")# Bouton de validation pour visualiser les métadonnées
),
mainPanel(
tags$br(),
uiOutput("breadcrumb_metadata"),
uiOutput("title_metadata"), # Titre de l'objet sélectionné
uiOutput("commentaire_metadata"),
uiOutput("table_metadata"), #Nb de lignes et taille de la table
DT::dataTableOutput("metadata_table"),
#uiOutput("metadata_section"), # Section des métadonnées avec "Dictionnaire d'attributs"
uiOutput("viz_data"), # Visualisation de la table des donnée
br(),
# Sous-onglets pour les données et la carte
uiOutput("conditional_tabs")
)
)
)
),
# Onglet À propos
navbarPanel_dsfr(
id = "a_propos_panel",
title = "À propos",
fluidPage_dsfr(
h3("À propos"),
shiny::includeMarkdown("www/about.md"),
div(paste0("Date de mise à jour des métadonnées affichées : ", Sys.Date()))
)
),
# Onglet Mentions légales
navbarPanel_dsfr(
id = "mentions_legales_panel",
title = "Mentions légales",
fluidPage_dsfr(
h3("Mentions légales"),
shiny::includeMarkdown("www/legal_notice.md")
)
)
)
)
#### Objectif
Le Catalogue de données de la DREAL vise à faciliter la découverte et l'accès aux données stockées dans les bases de donnés internes de la DREAL.
#### Fonctionnalités
L'onglet **"Rechercher"** permet :
- d'effectuer une recherche textuelle à partir de mots-clés au d'expression,
- d'afficher en résultat un tableau de [tables](https://fr.wikipedia.org/wiki/Table_(base_de_donn%C3%A9es)) correspondant à la recherche.
L'onglet **"Explorer"** :
- de naviguer parmi les bases de données, schémas et tables associés,
- d'afficher en résultat un tableau de schémas ou de tables correspondants à la recherche.
Les deux options permettent ensuite de visualiser :
- le [dictionnaire des données](https://fr.wikipedia.org/wiki/Dictionnaire_des_donn%C3%A9es),
- le tableau des données,
- un aperçu des données spatiales.
#### Charte de nommage
Les noms de tous les objets des bases de données (schémas, tables, champs, etc.) doivent être choisis selon une charte présentée dans la fiche de procédure [Organisation des bases de données sur le SGBD PostgreSQL](http://set-pdl-wiki.dreal-pdl.ad.e2.rie.gouv.fr/outils/sgbd/organisation-bases-postgresql).