diff --git a/.gitignore b/.gitignore index 959f068..7784746 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .Rhistory .RData .Ruserdata +google-analytics.html +tests/testthat/google-analytics.html docs inst/doc www/ diff --git a/DESCRIPTION b/DESCRIPTION index 55ab586..e988f52 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -11,6 +11,7 @@ Description: R package containing preferred methods for creating official DfE R-Shiny dashboards. License: GPL (>= 3) Imports: + magrittr, checkmate, glue, htmltools, diff --git a/NAMESPACE b/NAMESPACE index 50cd4b3..c1bfba7 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,8 +6,10 @@ export(cookies_panel_server) export(cookies_panel_ui) export(custom_disconnect_message) export(dfe_cookie_script) +export(init_analytics) export(init_cookies) export(support_panel) export(tidy_code) importFrom(htmltools,tagList) importFrom(htmltools,tags) +importFrom(magrittr,"%>%") diff --git a/R/analytics.R b/R/analytics.R new file mode 100644 index 0000000..5dfff29 --- /dev/null +++ b/R/analytics.R @@ -0,0 +1,72 @@ +#' init_analytics +#' @description +#' Creates the google-analytics.html script in order to allow the activation of +#' analytics via GA4. For the full steps required to set up analytics, please +#' refer to the documentation in the readme. +#' +#' @param ga_code The Google Analytics code +#' @importFrom magrittr %>% +#' @return TRUE if written, FALSE if not +#' @export +#' +#' @examples init_analytics(ga_code = "0123456789") +init_analytics <- function(ga_code) { + is_valid_ga4_code <- function(ga_code) { + stringr::str_length(ga_code) == 10 & typeof(ga_code) == "character" + } + + if (is_valid_ga4_code(ga_code) == FALSE) { + stop( + 'You have entered an invalid GA4 code in the ga_code argument. + Please enter a 10 digit code as a character string. + e.g. "0123QWERTY"' + ) + } + + github_area <- "https://raw.githubusercontent.com/dfe-analytical-services/" + webpage <- RCurl::getURL( + paste0( + github_area, + "dfeshiny/main/inst/google-analytics.html" + ) + ) + tryCatch( + html_script <- gsub( + "XXXXXXXXXX", + ga_code, + readLines(tc <- textConnection(webpage)) + ), + error = function(e) { + return("Download failed") + }, + message("Downloaded analytics template script") + ) + + close(tc) + if (file.exists("google-analytics.html")) { + message("Analytics file already exists.") + message("If you have any customisations in that file, make sure you've + backed those up before over-writing.") + user_input <- readline( + prompt = "Are you happy to overwrite the existing analytics script (y/N) " + ) |> + stringr::str_trim() + if (user_input %in% c("y", "Y")) { + write_out <- TRUE + } else { + write_out <- FALSE + } + } else { + write_out <- TRUE + } + if (write_out) { + cat(html_script, file = "google-analytics.html", sep = "\n") + message("") + message("Google analytics script created in google-analytics.html.") + message("You'll need to add the following line to your ui.R script to start + recording analytics:") + message('tags$head(includeHTML(("google-analytics.html"))),') + } else { + message("Original Google analytics html script left in place.") + } +} diff --git a/R/cookies.R b/R/cookies.R index dae66be..261da09 100644 --- a/R/cookies.R +++ b/R/cookies.R @@ -235,7 +235,10 @@ init_cookies <- function() { } tryCatch( - download.file(url = "https://raw.githubusercontent.com/dfe-analytical-services/dfeshiny/main/inst/cookie-consent.js", destfile = "www/cookie-consent.js"), # nolint: [line_length_linter] + utils::download.file( + url = "https://raw.githubusercontent.com/dfe-analytical-services/dfeshiny/main/inst/cookie-consent.js", # nolint: [line_length_linter] + destfile = "www/cookie-consent.js" + ), error = function(e) { return("Download failed") }, @@ -243,196 +246,192 @@ init_cookies <- function() { ) } - #' cookies_panel_ui - #' - #' @description - #' Create the standard DfE R-Shiny cookies dashboard panel in the ui. The server - #' side functionality is provided by cookies_panel_server() - #' - #' @param id ID shared with cookies_panel_server() - #' @param google_analytics_key Provide the GA 10 digit key of the form - #' "ABCDE12345" - #' - #' - #' @return a standardised panel for a public R Shiny dashboard in DfE - #' @export - #' - #' @examples - #' \dontrun{ - #' cookies_panel_ui( - #' id = "cookies_panel", - #' google_analytics_key = "ABCDE12345" - #' ) - #' } - #' - #' - cookies_panel_ui <- function( - id, google_analytics_key = NULL) { - # Build the support page ---------------------------------------------------- - shiny::tabPanel( - id = shiny::NS(id, "cookies_panel"), - value = "cookies_panel_ui", - "Cookies", - shinyGovstyle::gov_main_layout( - shinyGovstyle::gov_row( - shiny::column( - width = 12, - shiny::tags$h1("Cookies"), - shiny::tags$p("Cookies are small files saved on your phone, tablet or +#' cookies_panel_ui +#' +#' @description +#' Create the standard DfE R-Shiny cookies dashboard panel in the ui. The server +#' side functionality is provided by cookies_panel_server() +#' +#' @param id ID shared with cookies_panel_server() +#' @param google_analytics_key Provide the GA 10 digit key of the form +#' "ABCDE12345" +#' +#' +#' @return a standardised panel for a public R Shiny dashboard in DfE +#' @export +#' +#' @examples +#' \dontrun{ +#' cookies_panel_ui( +#' id = "cookies_panel", +#' google_analytics_key = "ABCDE12345" +#' ) +#' } +#' +cookies_panel_ui <- function(id, google_analytics_key = NULL) { + # Build the support page ---------------------------------------------------- + shiny::tabPanel( + id = shiny::NS(id, "cookies_panel"), + value = "cookies_panel_ui", + "Cookies", + shinyGovstyle::gov_main_layout( + shinyGovstyle::gov_row( + shiny::column( + width = 12, + shiny::tags$h1("Cookies"), + shiny::tags$p("Cookies are small files saved on your phone, tablet or computer when you visit a website."), - shiny::tags$p("We use cookies to collect information about how you + shiny::tags$p("We use cookies to collect information about how you use our service."), - shiny::tags$h2("Essential cookies"), - shinyGovstyle::govTable( - inputId = "essential_cookies_table", - df = data.frame( - Name = "dfe_analytics", - Purpose = "Saves your cookie consent settings", - Expires = "365 days" - ), - caption = "", - caption_size = "s", - num_col = NULL, - width_overwrite = c("one-quarter", "one-quarter", "one-quarter") + shiny::tags$h2("Essential cookies"), + shinyGovstyle::govTable( + inputId = "essential_cookies_table", + df = data.frame( + Name = "dfe_analytics", + Purpose = "Saves your cookie consent settings", + Expires = "365 days" ), - shiny::tags$h2("Analytics cookies"), - shiny::tags$p("With your permission, we use Google Analytics to + caption = "", + caption_size = "s", + num_col = NULL, + width_overwrite = c("one-quarter", "one-quarter", "one-quarter") + ), + shiny::tags$h2("Analytics cookies"), + shiny::tags$p("With your permission, we use Google Analytics to collect data about how you use this service. This information helps us improve our service"), - shiny::tags$p("Google is not allowed to share our analytics data with + shiny::tags$p("Google is not allowed to share our analytics data with anyone."), - shiny::tags$p("Google Analytics stores anonymised information + shiny::tags$p("Google Analytics stores anonymised information about:"), - shiny::tags$li("How you got to this service"), - shiny::tags$li("The pages you visit on this service and how long you + shiny::tags$li("How you got to this service"), + shiny::tags$li("The pages you visit on this service and how long you spend on them"), - shiny::tags$li("How you interact with these pages"), - shinyGovstyle::govTable( - inputId = "ga_cookies_table", - df = data.frame( - Name = c("_ga", paste0("_ga_", google_analytics_key)), - Purpose = c("Used to distinguish users", "Used to persist + shiny::tags$li("How you interact with these pages"), + shinyGovstyle::govTable( + inputId = "ga_cookies_table", + df = data.frame( + Name = c("_ga", paste0("_ga_", google_analytics_key)), + Purpose = c("Used to distinguish users", "Used to persist session state"), - Expires = c("13 months", "13 months") - ), - caption = "", - caption_size = "s", - num_col = NULL, - width_overwrite = c("one-quarter", "one-quarter", "one-quarter") + Expires = c("13 months", "13 months") ), - shiny::br(), + caption = "", + caption_size = "s", + num_col = NULL, + width_overwrite = c("one-quarter", "one-quarter", "one-quarter") + ), + shiny::br(), + shiny::tags$div( + class = "govuk-grid-row", shiny::tags$div( - class = "govuk-grid-row", + class = "govuk-grid-column-two-thirds", + shiny::tags$h2( + class = "govuk-heading-l", + "Change your cookie settings" + ), + shiny::tags$div( + class = "govuk-form-group", + ), shiny::tags$div( - class = "govuk-grid-column-two-thirds", - shiny::tags$h2( - class = "govuk-heading-l", - "Change your cookie settings" - ), - shiny::tags$div( - class = "govuk-form-group", - ), - shiny::tags$div( - class = "govuk-form-group", - tags$fieldset( - class = "govuk-fieldset", - tags$legend( - class = "govuk-fieldset__legend govuk-fieldset__legend--s", - "Do you want to accept analytics cookies?" - ), + class = "govuk-form-group", + tags$fieldset( + class = "govuk-fieldset", + tags$legend( + class = "govuk-fieldset__legend govuk-fieldset__legend--s", + "Do you want to accept analytics cookies?" + ), + shiny::tags$div( + class = "govuk-radios", + `data-module` = "govuk-radios", shiny::tags$div( - class = "govuk-radios", - `data-module` = "govuk-radios", - shiny::tags$div( - class = "govuk-radios__item", - shiny::radioButtons(shiny::NS(id, "cookies_analytics"), - label = NULL, - choices = list("Yes" = "yes", "No" = "no"), - selected = "no", - inline = TRUE - ) + class = "govuk-radios__item", + shiny::radioButtons(shiny::NS(id, "cookies_analytics"), + label = NULL, + choices = list("Yes" = "yes", "No" = "no"), + selected = "no", + inline = TRUE ) ) ) - ), - shiny::actionButton(shiny::NS(id, "submit_btn"), - "Save cookie settings", - class = "govuk-button" ) + ), + shiny::actionButton(shiny::NS(id, "submit_btn"), + "Save cookie settings", + class = "govuk-button" ) ) ) ) ) ) - } + ) +} - #' cookies_panel_server - #' - #' @description - #' Create the server module of DfE R-Shiny cookies dashboard panel to be used - #' alongside cookies_panel_ui(). - #' - #' @param id ID shared with cookies_panel_ui() - #' @param input_cookies The cookie input passed from cookies.js (should always - #' be reactive(input$cookies)) - #' @param google_analytics_key Provide the GA 10 digit key of the form - #' "ABCDE12345" - #' - #' @export - #' - #' @examples - #' \dontrun{ - #' cookies_panel_server( - #' id = "cookies_panel", - #' input_cookies = reactive(input$cookies), - #' google_analytics_key = "ABCDE12345" - #' ) - #' } - cookies_panel_server <- function( - id, - input_cookies, - google_analytics_key = NULL) { - shiny::moduleServer(id, module = function(input, output, session) { - shiny::observeEvent(input_cookies(), { - if (!is.null(input_cookies())) { - if (!("dfe_analytics" %in% names(input_cookies()))) { +#' cookies_panel_server +#' +#' @description +#' Create the server module of DfE R-Shiny cookies dashboard panel to be used +#' alongside cookies_panel_ui(). +#' +#' @param id ID shared with cookies_panel_ui() +#' @param input_cookies The cookie input passed from cookies.js (should always +#' be reactive(input$cookies)) +#' @param google_analytics_key Provide the GA 10 digit key of the form +#' "ABCDE12345" +#' +#' @export +#' +#' @examples +#' \dontrun{ +#' cookies_panel_server( +#' id = "cookies_panel", +#' input_cookies = reactive(input$cookies), +#' google_analytics_key = "ABCDE12345" +#' ) +#' } +cookies_panel_server <- function(id, + input_cookies, + google_analytics_key = NULL) { + shiny::moduleServer(id, module = function(input, output, session) { + shiny::observeEvent(input_cookies(), { + if (!is.null(input_cookies())) { + if (!("dfe_analytics" %in% names(input_cookies()))) { + shiny::updateRadioButtons(session, "cookies_analytics", + selected = "no" + ) + } else { + if (input_cookies()$dfe_analytics == "denied") { shiny::updateRadioButtons(session, "cookies_analytics", selected = "no" ) - } else { - if (input_cookies()$dfe_analytics == "denied") { - shiny::updateRadioButtons(session, "cookies_analytics", - selected = "no" - ) - } else if (input_cookies()$dfe_analytics == "granted") { - shiny::updateRadioButtons(session, "cookies_analytics", - selected = "yes" - ) - } + } else if (input_cookies()$dfe_analytics == "granted") { + shiny::updateRadioButtons(session, "cookies_analytics", + selected = "yes" + ) } } - }) - - # Observe form submission button - shiny::observeEvent(input$submit_btn, { - # Update reactive values based on the selected radio buttons - if (input$cookies_analytics == "yes") { - msg <- list( - name = "dfe_analytics", - value = "granted" - ) - } else if (input$cookies_analytics == "no") { - msg <- list( - name = "dfe_analytics", - value = "denied" - ) - ga_msg <- list(name = paste0("_ga_", google_analytics_key)) - session$sendCustomMessage("cookie-clear", ga_msg) - } - session$sendCustomMessage("cookie-set", msg) - session$sendCustomMessage("analytics-consent", msg) - }) + } }) - } + # Observe form submission button + shiny::observeEvent(input$submit_btn, { + # Update reactive values based on the selected radio buttons + if (input$cookies_analytics == "yes") { + msg <- list( + name = "dfe_analytics", + value = "granted" + ) + } else if (input$cookies_analytics == "no") { + msg <- list( + name = "dfe_analytics", + value = "denied" + ) + ga_msg <- list(name = paste0("_ga_", google_analytics_key)) + session$sendCustomMessage("cookie-clear", ga_msg) + } + session$sendCustomMessage("cookie-set", msg) + session$sendCustomMessage("analytics-consent", msg) + }) + }) +} diff --git a/R/tidy_code.R b/R/tidy_code.R index 0eacc6c..5005a2e 100644 --- a/R/tidy_code.R +++ b/R/tidy_code.R @@ -6,7 +6,8 @@ #' @param subdirs List of sub-directories to #' (recursively search for R scripts to be styled) #' -#' @return True or False value based on if scripts were changed +#' @return TRUE if any changes have been made to any scripts, FALSE if all +#' passed. #' @export #' #' @examples @@ -17,7 +18,12 @@ tidy_code <- function(subdirs = c("R", "tests")) { message("----------------------------------------") message("App scripts") message("----------------------------------------") - script_changes <- eval(styler::style_dir(recursive = FALSE)$changed) + script_changes <- eval( + styler::style_dir( + recursive = FALSE, + exclude_files = c("dfeshiny-Ex.R") + )$changed + ) for (dir in subdirs) { if (dir.exists(dir)) { message(paste(dir, "scripts")) diff --git a/README.md b/README.md index 0aec6e1..1ad1851 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,15 @@ them meet the necessary standards required of public facing government services. To install, run `renv::install("dfe-analytical-services/dfeshiny")`. -### Potential errors when installing +## Installing functionality in development from a branch + +It can be useful when developing the package in particular to trial new or updated functionality in a shiny app. To do this, you can install the required branch using (replacing `branch-name` with the name of the branch you wish to install): + +`renv::install("dfe-analytical-services/dfeshiny@branch-name")` + +That will install the code from the named branch as dfeshiny in your app. You will need to restart your R session before it will start using the latest version that you've installed. + +## Potential errors when installing If you get `ERROR [curl: (22) The requested URL returned error: 401]`, and don't know why, try running `Sys.unsetenv("GITHUB_PAT")` to temporarily clear your GitHub PAT variable. @@ -24,6 +32,22 @@ If this works, then you will need to look for where that "GITHUB_PAT" variable i ## Using this package in a DfE data dashboard +### Adding analytics to your dashboard + +For analytics to function on your dashboard, you will need to: + +- request a Google Analytics key from the [Explore Educaion Statisics platforms team](mailto:explore.statistics@education.gov.uk) +- create a html file with the javascript required for your dashboard to connect to Google Analytics +- add the line: `tags$head(includeHTML(("google-analytics.html"))),` to the ui.R file. + +To create the latter, we provide the function `dfeshiny::init_analytics()`. You should run this code from the R console providing your Google Analytics code as follows (replacing `ABCDE12345` with the code obtained from the [Explore Education Statistics platforms](explore.statistics@education.gov.uk) team): + +``` +init_analytics("ABCDE12345") +``` + +This will create the file [google-analytics.html](google-analytics.html) within the home directory of your R project. This html file can be edited to add customised analytics recorders for different shiny elements in your dashboard. Feel free to contact our team if you need support in adding additional functionality. + ### Adding a custom disconect message to your dashboard dfeshiny provides a function to add a custom disconnect message to your dashboard - this will appear when a dashboard would otherwise 'grey-screen' and will include options to refresh the page, go to overflow sites or visit the publication directly on Explore education statistics. diff --git a/inst/google-analytics.html b/inst/google-analytics.html new file mode 100644 index 0000000..f7343e8 --- /dev/null +++ b/inst/google-analytics.html @@ -0,0 +1,66 @@ + + + + + diff --git a/man/cookies_panel_ui.Rd b/man/cookies_panel_ui.Rd index aabcb71..e609394 100644 --- a/man/cookies_panel_ui.Rd +++ b/man/cookies_panel_ui.Rd @@ -27,5 +27,4 @@ cookies_panel_ui( ) } - } diff --git a/man/init_analytics.Rd b/man/init_analytics.Rd new file mode 100644 index 0000000..fd38c97 --- /dev/null +++ b/man/init_analytics.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/analytics.R +\name{init_analytics} +\alias{init_analytics} +\title{init_analytics} +\usage{ +init_analytics(ga_code) +} +\arguments{ +\item{ga_code}{The Google Analytics code} +} +\value{ +TRUE if written, FALSE if not +} +\description{ +Creates the google-analytics.html script in order to allow the activation of +analytics via GA4. For the full steps required to set up analytics, please +refer to the documentation in the readme. +} +\examples{ +init_analytics(ga_code = "0123456789") +} diff --git a/man/tidy_code.Rd b/man/tidy_code.Rd index 37bae91..657e7bd 100644 --- a/man/tidy_code.Rd +++ b/man/tidy_code.Rd @@ -11,7 +11,8 @@ tidy_code(subdirs = c("R", "tests")) (recursively search for R scripts to be styled)} } \value{ -True or False value based on if scripts were changed +TRUE if any changes have been made to any scripts, FALSE if all +passed. } \description{ Script to apply styler code styling to scripts within the diff --git a/tests/testthat/test-initialise_analytics.R b/tests/testthat/test-initialise_analytics.R new file mode 100644 index 0000000..52874b4 --- /dev/null +++ b/tests/testthat/test-initialise_analytics.R @@ -0,0 +1,15 @@ +test_that("Blank code throws error", { + expect_error(init_analytics()) +}) + +test_that("Long code throws error", { + expect_error(init_analytics("G-qwertyuiop")) +}) + +test_that("Short code throws error", { + expect_error(init_analytics("qwerty")) +}) + +test_that("Numeric throws error", { + expect_error(init_analytics(1234567890)) +})