diff --git a/.Rbuildignore b/.Rbuildignore index b923408..d681f82 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -6,3 +6,4 @@ ^_pkgdown\.yml$ ^docs$ ^pkgdown$ +^.lintr$ diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f121d3a --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,126 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at explore.statistics@education.gov.uk. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/inclusion). + +For answers to common questions about this code of conduct, see the FAQ at +. Translations are available at . + +[homepage]: https://www.contributor-covenant.org diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..b5cec5d --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,71 @@ +# Contributing to dfeshiny + +Try and make use of the [usethis](https://usethis.r-lib.org/) package wherever possible. + +When you initially clone the package, the first thing you'll need to do is install [devtools](https://devtools.r-lib.org/): + +``` +install.packages("devtools") +``` + +Then to load in the package in its current form: + +``` +devtools::load_all() +``` + +## Adding a package/dependency + +`usethis::use_package()` + +Note that when adding a function from another package into one of the dfeshiny functions you will need to explicitly state the package in the function call, e.g.: + +```package::function()``` + +Alternatively, if there's a lot of uses of a single function within one of our R scripts, you can call that function once at the top of the R script, e.g: + +``` +@' importFrom package function +``` + +For more information see the [roxygen2 documentation on declaring dependencies](https://roxygen2.r-lib.org/articles/namespace.html). + +## Creating a new function script + +`usethis::use_r(name = )` + +This will create a new blank script within the package R/ folder. + +## Creating a new function test script + +`usethis::use_test(name = )` + +This will create a new blank test script within the package testthat/ folder. + +## Updating the package version + +Once changes have been completed, reviewed and are ready for use in the wild, you +can increment the package version using: + +`usethis::use_version()` + +Once you've incremented the version number, it'll add a new heading to news.md. + +Add a summary under news.md and then accept it's offer to commit on your behalf. + +Once pushed and on the main branch, create a new release in GitHub itself. + +## Running tests + +You should run the following lines to test the package locally: +``` +# To check functionality +devtools::check() # Ctrl-Shft-E +shinytest2::test_app("tests/test_dashboard") # important as not currently ran in CI checks + +# For code styling +styler::style_pkg() +lintr::lint_package() +``` + +If you get a lot of lintr errors, particularly around things not being defined, make sure to load the package first using Ctrl-Shft-L or `devtools::load_all(".")`, then run again. There's a known issue with lintr not picking up on bindings until packages are loaded diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..21ac0ab --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +## Describe the bug +A clear and concise description of what the bug is. + +## How to reproduce +Steps to reproduce the behaviour: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +## Expected behaviour +A clear and concise description of what you expected to happen. + +## Screenshots +If applicable, add screenshots to help explain your problem. + +## Additional context +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..9946b93 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +## Is your feature request related to a problem? Please describe. +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...], or wouldn't it be nice if [...]. + +## Describe the solution you'd like +A clear and concise description of what you want to happen. + +## Describe alternatives you've considered +A clear and concise description of any alternative solutions or features you've considered. + +## Additional context +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4431387 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,24 @@ + + +# Brief overview of changes + +... + +## Why are these changes being made? + +... + +## Detailed description of changes + +... + +## Additional information for reviewers + +... + +## Issue ticket number/s and link + +... diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index 74d8c97..fbf2a84 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -4,7 +4,6 @@ on: push: branches: [main, master] pull_request: - branches: [main, master] name: R-CMD-check diff --git a/.github/workflows/lintr.yml b/.github/workflows/lintr.yml index 8c8f5a9..4abb5b2 100644 --- a/.github/workflows/lintr.yml +++ b/.github/workflows/lintr.yml @@ -33,15 +33,16 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - name: Setup R - uses: r-lib/actions/setup-r@4e1feaf90520ec1215d1882fdddfe3411c08e492 + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true - - name: Setup lintr - uses: r-lib/actions/setup-r-dependencies@4e1feaf90520ec1215d1882fdddfe3411c08e492 + - uses: r-lib/actions/setup-r-dependencies@v2 with: - extra-packages: lintr + extra-packages: any::lintr, local::. + needs: lint - name: Run lintr run: lintr::sarif_output(lintr::lint_dir("."), "lintr-results.sarif") @@ -49,7 +50,7 @@ jobs: continue-on-error: true - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: lintr-results.sarif wait-for-processing: true diff --git a/.github/workflows/test_dashboard.yaml b/.github/workflows/test_dashboard.yaml new file mode 100644 index 0000000..14bfb25 --- /dev/null +++ b/.github/workflows/test_dashboard.yaml @@ -0,0 +1,46 @@ +on: + push: + branches: + - main + pull_request: + +name: Test dashboard + +jobs: + automatedTests: + runs-on: ${{ matrix.config.os }} + + name: shiny-tests + + strategy: + fail-fast: false + matrix: + config: + - {os: ubuntu-latest, r: 'release'} + + env: + R_REMOTES_NO_ERRORS_FROM_WARNINGS: true + RSPM: ${{ matrix.config.rspm }} + RENV_PATHS_ROOT: ~/.local/share/renv + + steps: + - uses: actions/checkout@v4 + + - uses: r-lib/actions/setup-r@v2 + with: + use-public-rspm: true + + - name: Install dependencies + uses: r-lib/actions/setup-r-dependencies@v2 + + - name: Run tests + shell: Rscript {0} + run: | + shiny::runTests("tests/test_dashboard", assert = TRUE) + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@master + with: + name: ${{ runner.os }}-r${{ matrix.config.r }}-tests + path: tests diff --git a/.lintr b/.lintr new file mode 100644 index 0000000..934db5c --- /dev/null +++ b/.lintr @@ -0,0 +1,3 @@ +linters: linters_with_defaults( + line_length_linter = line_length_linter(100L) # limit CRAN sets for lines in PDF documentation + ) diff --git a/DESCRIPTION b/DESCRIPTION index cfe669d..3f41a2b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: dfeshiny Title: DfE R-Shiny Standards -Version: 0.2.0 +Version: 0.3.0 Authors@R: c( person("Rich", "Bielby", , "richard.bielby@education.gov.uk", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-9070-9969")), @@ -26,6 +26,7 @@ Suggests: knitr, rmarkdown, shinytest2, + devtools, testthat (>= 3.0.0) Config/testthat/edition: 3 Encoding: UTF-8 diff --git a/NAMESPACE b/NAMESPACE index c1bfba7..e8a8dcd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,11 +1,11 @@ # Generated by roxygen2: do not edit by hand -export(cookie_banner_server) -export(cookie_banner_ui) +export(cookies_banner_server) +export(cookies_banner_ui) export(cookies_panel_server) export(cookies_panel_ui) export(custom_disconnect_message) -export(dfe_cookie_script) +export(dfe_cookies_script) export(init_analytics) export(init_cookies) export(support_panel) diff --git a/NEWS.md b/NEWS.md index 70e97dc..a46f81e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,33 @@ +# dfeshiny 0.3.0 + +## New features + +* `init_analytics()` to add the necessary analytics script into a repository. + +* New cookies module for the panel page added, completing the cookies family of +functions, shared examples and a vignette to walk through how to use them. + +## Breaking changes + +* Ironed out inconsistencies in cookies family to use plural of 'cookies' +consistently. Backwards compatibility has not been maintained as we are still +in early development and adoption is low. + * `cookie_banner_server()` is now `cookies_banner_server()` + * `cookie_banner_ui()` is now `cookie_banner_ui()` + * `dfe_cookie_script()` is now `dfe_cookies_script()` + +* Have removed dependency on using `shiny::tabPanel()` within `support_panel()` +so now it will return only the content rather than a tabPanel. + +## Improvements + +* Automated testing of test dashboard using GitHub actions. +* Fixed favicons in pkgdown site. +* Moved code for `init_cookies()` to inline, to prevent dependency on main +GitHub branch. +* Added separate contributing guidelines to make the README more user focused. +* Added issues and PR templates. + # dfeshiny 0.2.0 ## New features @@ -10,13 +40,13 @@ app. * A module is now provided to produce a standardised cookie consent banner and implement the associated functionality. The server part is -`cookie_banner_server()` and the ui part is `cookie_banner_ui()`. In addition, -`dfe_cookie_script()` is provided to implement the necessary javascript. +`cookies_banner_server()` and the ui part is `cookies_banner_ui()`. In addition, +`dfe_cookies_script()` is provided to implement the necessary javascript. ## Improvements * Implemented basic unit testing (currently just on e-mail validation) and UI -testing using a test dashboard; +testing using a test dashboard. * Instructions within the package README.md on how to install `dfeshiny` have been updated. diff --git a/R/analytics.R b/R/analytics.R index 5dfff29..c5238f2 100644 --- a/R/analytics.R +++ b/R/analytics.R @@ -1,16 +1,27 @@ #' 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. +#' refer to the documentation in the README. +#' +#' @param ga_code The Google Analytics code for the dashboard +#' @param create_file Boolean TRUE or FALSE, default is TRUE, false will return +#' the HTML in the console and is used mainly for testing or comparisons #' -#' @param ga_code The Google Analytics code #' @importFrom magrittr %>% -#' @return TRUE if written, FALSE if not +#' @return NULL #' @export #' -#' @examples init_analytics(ga_code = "0123456789") -init_analytics <- function(ga_code) { +#' @examples +#' if (interactive()) { +#' init_analytics(ga_code = "0123456789") +#' } +init_analytics <- function(ga_code, create_file = TRUE) { + if (!is.logical(create_file)) { + stop("create_file must always be TRUE or FALSE") + } + is_valid_ga4_code <- function(ga_code) { stringr::str_length(ga_code) == 10 & typeof(ga_code) == "character" } @@ -23,50 +34,112 @@ init_analytics <- function(ga_code) { ) } - 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 + # Google analytics HTML ===================================================== + html_script <- " + + + + " + + # Swap in the GA ID + html_script_with_id <- gsub("XXXXXXXXXX", ga_code, html_script) + + # End of google analytics HTML ============================================== + + if (create_file == FALSE) { + # Just return without options or messages + cat(html_script_with_id, file = "", sep = "\n") + } else { + 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) " + 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")) { + 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_with_id, file = "google-analytics.html", sep = "\n") + message("") + message("Google analytics script created as google-analytics.html.") + message("You'll need to add the following line to your ui.R script to start using analytics:") + message("") + message("tags$head(includeHTML((google-analytics.html))),") } else { - write_out <- FALSE + message("Original Google analytics html script left in place.") } - } 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 909457b..6b599cb 100644 --- a/R/cookies.R +++ b/R/cookies.R @@ -1,4 +1,4 @@ -#' dfe_cookie_script +#' dfe_cookies_script #' #' Calls in JavaScript dependencies to the shiny app used to set and unset the #' cookies. Function should be placed in the ui.R script. @@ -10,46 +10,59 @@ #' @family cookies #' @examples #' if (interactive()) { -#' # This example shows how to use the full family of cookie functions together -#' # This will be in your global.R script ===================================== +#' # This example shows how to use the full family of cookies functions together +#' # This will be in your global.R script =================================== #' #' library(shiny) #' library(shinyjs) #' library(dfeshiny) -#' ga_key <- "ABCDE12345" +#' google_analytics_key <- "ABCDE12345" #' -#' # This will be what is in your ui.R script ================================= +#' # This will be what is in your ui.R script =============================== #' #' ui <- fluidPage( -#' # Place these lines above your header ------------------------------------ -#' dfe_cookie_script(), +#' # Place these lines above your header ---------------------------------- #' useShinyjs(), -#' cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), +#' dfe_cookies_script(), +#' cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), #' -#' # Place the cookies panel under the header but in the main content ------- -#' cookies_panel_ui(google_analytics_key = ga_key) +#' # Place the cookies panel under the header but in the main content ----- +#' # Example of placing as a panel within navlistPanel +#' shiny::navlistPanel( +#' "", +#' id = "navlistPanel", +#' widths = c(2, 8), +#' well = FALSE, +#' ## Cookies panel ----------------------------------------------------- +#' shiny::tabPanel( +#' value = "cookies_panel_ui", +#' "Cookies", +#' cookies_panel_ui(google_analytics_key = google_analytics_key) +#' ) +#' ) #' ) #' -#' # This will be in your server.R file ======================================= +#' # This will be in your server.R file ===================================== #' #' server <- function(input, output, session) { #' # Server logic for the pop up banner, can be placed anywhere in server.R - -#' output$cookie_status <- dfeshiny::cookie_banner_server( +#' output$cookies_status <- dfeshiny::cookies_banner_server( #' input_cookies = reactive(input$cookies), -#' google_analytics_key = ga_key +#' google_analytics_key = google_analytics_key, +#' parent_session = session #' ) #' -#' # Server logic for the panel, can be placed anywhere in server.R --------- +#' # Server logic for the panel, can be placed anywhere in server.R ------- #' cookies_panel_server( #' input_cookies = reactive(input$cookies), -#' google_analytics_key = ga_key +#' google_analytics_key = google_analytics_key #' ) #' } #' -#' # How to run the minimal app given in this example ========================= +#' # How to run the minimal app given in this example ======================= #' shinyApp(ui, server) #' } -dfe_cookie_script <- function() { +dfe_cookies_script <- function() { shiny::tags$head( shiny::tags$script( src = paste0( @@ -62,16 +75,16 @@ dfe_cookie_script <- function() { ) } -#' cookie_banner_ui +#' cookies_banner_ui #' #' @description #' This function provides a cookie authorisation banner on DfE R-Shiny #' dashboards for users to be able to accept or reject cookies. The server side -#' functionality is provided by cookie_banner_server(), whilst users will also -#' need to include the dfe_cookie_script() function in their ui.R file. +#' functionality is provided by cookies_banner_server(), whilst users will also +#' need to include the dfe_cookies_script() function in their ui.R file. #' -#' @param id Shiny tag shared with cookie_banner_server(), can be any string set -#' by the user as long as it matches the id in the cookie_banner_server() +#' @param id Shiny tag shared with cookies_banner_server(), can be any string set +#' by the user as long as it matches the id in the cookies_banner_server() #' @param name Name of the dashboard on which the cookie authorisation is being #' applied #' @@ -79,14 +92,14 @@ dfe_cookie_script <- function() { #' @return shiny::tags$div() #' @export #' @inherit cookies examples -cookie_banner_ui <- function(id = "cookies_banner", name = "DfE R-Shiny dashboard template") { +cookies_banner_ui <- function(id = "cookies_banner", name = "DfE R-Shiny dashboard template") { shiny::tags$div( - id = shiny::NS(id, "cookie_div"), + id = shiny::NS(id, "cookies_div"), class = "govuk-cookie-banner", `data-nosnippet role` = "region", `aria-label` = "Cookies on name", shiny::tags$div( - id = shiny::NS(id, "cookie_main"), + id = shiny::NS(id, "cookies_main"), class = "govuk-cookie-banner__message govuk-width-container", shiny::tags$div( class = "govuk-grid-row", @@ -113,15 +126,15 @@ cookie_banner_ui <- function(id = "cookies_banner", name = "DfE R-Shiny dashboar shiny::tags$div( class = "govuk-button-group", shinyGovstyle::button_Input( - shiny::NS(id, "cookie_accept"), + shiny::NS(id, "cookies_accept"), "Accept analytics cookies" ), shinyGovstyle::button_Input( - shiny::NS(id, "cookie_reject"), + shiny::NS(id, "cookies_reject"), "Reject analytics cookies" ), shiny::actionLink( - shiny::NS(id, "cookie_link"), + shiny::NS(id, "cookies_link"), "View cookie information" ) ) @@ -129,23 +142,24 @@ cookie_banner_ui <- function(id = "cookies_banner", name = "DfE R-Shiny dashboar ) } -#' cookie_banner_server +#' cookies_banner_server #' #' @description -#' cookie_banner_server() provides the server module to be used alongside -#' cookie_banner_ui(). Place cookie_banner_server() as a call in your server.R +#' cookies_banner_server() provides the server module to be used alongside +#' cookies_banner_ui(). Place cookies_banner_server() as a call in your server.R #' file to provide the server functions to control users being able to accept or #' reject cookie consent for the provision of Google Analytics tracking on DfE #' R-Shiny dashboards. #' -#' @param id Shiny tag shared with cookie_banner_ui(), can be any string set by -#' the user as long as it matches the id in the cookie_banner_ui() +#' @param id Shiny tag shared with cookies_banner_ui(), can be any string set by +#' the user as long as it matches the id in the cookies_banner_ui() #' @param input_cookies The cookie input passed from cookies.js (should always #' be `reactive(input$cookies)`) -#' @param parent_session This should be the R Shiny app session +#' @param parent_session This should be the R Shiny app session, expect it to +#' always be `parent_session = session` #' @param google_analytics_key Provide the GA 10 digit key of the form #' "ABCDE12345" -#' @param cookie_link_panel name of the navlistPanel that the cookie banner +#' @param cookies_link_panel name of the navlistPanel that the cookie banner #' provides a link to, usually "cookies_panel_ui" #' #' @family cookies @@ -153,12 +167,12 @@ cookie_banner_ui <- function(id = "cookies_banner", name = "DfE R-Shiny dashboar #' @export #' #' @inherit cookies examples -cookie_banner_server <- function( +cookies_banner_server <- function( id = "cookies_banner", - input_cookies = reactive(input$cookies), - parent_session = session, + input_cookies, + parent_session, google_analytics_key = NULL, - cookie_link_panel = "cookies_panel_ui") { + cookies_link_panel = "cookies_panel_ui") { shiny::moduleServer(id, function(input, output, session) { if (is.null(google_analytics_key)) { warning("Please provide a valid Google Analytics key") @@ -166,9 +180,9 @@ cookie_banner_server <- function( shiny::observeEvent(input_cookies(), { if (!is.null(input_cookies())) { if (!("dfe_analytics" %in% names(input_cookies()))) { - shinyjs::show(id = "cookie_main") + shinyjs::show(id = "cookies_main") } else { - shinyjs::hide(id = "cookie_main") + shinyjs::hide(id = "cookies_main") msg <- list( name = "dfe_analytics", value = input_cookies()$dfe_analytics @@ -184,52 +198,52 @@ cookie_banner_server <- function( } } } else { - shinyjs::hide(id = "cookie_main", asis = TRUE) - shinyjs::toggle(id = "cookie_div", asis = TRUE) + shinyjs::hide(id = "cookies_main", asis = TRUE) + shinyjs::toggle(id = "cookies_div", asis = TRUE) } }) # Check for the cookies being authorised - shiny::observeEvent(input$cookie_accept, { + shiny::observeEvent(input$cookies_accept, { msg <- list( name = "dfe_analytics", value = "granted" ) session$sendCustomMessage("cookie-set", msg) session$sendCustomMessage("analytics-consent", msg) - shinyjs::hide(id = "cookie_main", asis = TRUE) + shinyjs::hide(id = "cookies_main", asis = TRUE) }) # Check for the cookies being rejected - shiny::observeEvent(input$cookie_reject, { + shiny::observeEvent(input$cookies_reject, { msg <- list( name = "dfe_analytics", value = "denied" ) session$sendCustomMessage("cookie-set", msg) session$sendCustomMessage("analytics-consent", msg) - shinyjs::hide(id = "cookie_main", asis = TRUE) + shinyjs::hide(id = "cookies_main", asis = TRUE) }) - shiny::observeEvent(input$cookie_link, { + shiny::observeEvent(input$cookies_link, { # Need to link here to where further info is located. You can # updateTabsetPanel to have a cookie page for instance shiny::updateTabsetPanel( session = parent_session, "navlistPanel", - selected = cookie_link_panel + selected = cookies_link_panel ) }) return(shiny::renderText({ - cookie_text_stem <- "You have chosen to" - cookie_text_tail <- "the use of cookies on this website." + cookies_text_stem <- "You have chosen to" + cookies_text_tail <- "the use of cookies on this website." if (!is.null(input_cookies())) { if ("dfe_analytics" %in% names(input_cookies())) { if (input_cookies()$dfe_analytics == "granted") { - paste(cookie_text_stem, "accept", cookie_text_tail) + paste(cookies_text_stem, "accept", cookies_text_tail) } else { - paste(cookie_text_stem, "reject", cookie_text_tail) + paste(cookies_text_stem, "reject", cookies_text_tail) } } } else { @@ -242,15 +256,18 @@ cookie_banner_server <- function( #' init_cookies #' #' @description -#' init_cookies() creates a local copy of the JavaScript file -#' required for cookies to work. -#' It checks whether there is already a www/ folder and if not, it creates one. -#' It then checks whether the cookie-consent.js file exists in the www/ folder. -#' If the file exists, it will print a message in the console to let you know. -#' If the file doesn't exist, it will pull a copy from the GitHub repo. -#' If it cannot connect to the repo then it will print "Download failed". -#' No input parameters are required -#' Call init_cookies() in the console to run the function +#' init_cookies() creates a local copy of the JavaScript file required for +#' cookies to work. It checks whether there is already a www/ folder and if +#' not, it creates one. It then checks whether the cookie-consent.js file +#' exists in the www/ folder. If the file exists, it will print a message in +#' the console to let you know it has overwritten it. If the file doesn't +#' exist, it will make one and confirm it has done so. +#' +#' No input parameters are required, run `init_cookies()` in the console to run +#' the function +#' +#' @param create_file Boolean, TRUE by default, if FALSE then will print to +#' the console rather than create a new file #' #' @return NULL #' @export @@ -259,27 +276,58 @@ cookie_banner_server <- function( #' if (interactive()) { #' init_cookies() #' } -init_cookies <- function() { - sub_dir <- "www" +init_cookies <- function(create_file = TRUE) { + if (!is.logical(create_file)) { + stop("create_file must always be TRUE or FALSE") + } + + # Create the JS for the file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + cookie_js <- "function getCookies(){ + var res = Cookies.get(); + Shiny.setInputValue('cookies', res); +} + +Shiny.addCustomMessageHandler('cookie-set', function(msg){ + Cookies.set(msg.name, msg.value); + getCookies(); +}) + +Shiny.addCustomMessageHandler('cookie-clear', function(msg){ + Cookies.remove(msg.name); + getCookies(); +}) + +$(document).on('shiny:connected', function(ev){ + getCookies(); +}) - output_dir <- file.path(sub_dir) +Shiny.addCustomMessageHandler('analytics-consent', function(msg){ + gtag('consent', 'update', { + 'analytics_storage': msg.value + }); +})" + # End of JS for the file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - if (!dir.exists(output_dir)) { - dir.create(output_dir) + if (create_file == TRUE) { + sub_dir <- "www" + + output_dir <- file.path(sub_dir) + + if (!dir.exists(output_dir)) { + dir.create(output_dir) + } + + if (file.exists("www/cookie-consent.js")) { + message("www/cookie-consent.js already exists, updating file...") + cat(cookie_js, file = "www/cookie-consent.js", sep = "\n") + message("...file successfully updated") + } else { + cat(cookie_js, file = "www/cookie-consent.js", sep = "\n") + message("Created cookies script as www/cookie-consent.js") + } } else { - message("www folder already exists!") + cat(cookie_js, file = "", sep = "\n") } - - tryCatch( - 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") - }, - message("Cookie script updated") - ) } #' cookies_panel_ui @@ -293,105 +341,86 @@ init_cookies <- function() { #' @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 +#' @family cookies +#' @return a html div, containing standard cookises content for a public R +#' Shiny dashboard in DfE #' @export #' @inherit cookies examples cookies_panel_ui <- function(id = "cookies_panel", 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 + shiny::tags$div( + 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("Analytics cookies"), - shiny::tags$p("With your permission, we use Google Analytics to + 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("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 + information helps us improve our service."), + 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$ul( + 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") - ), - shiny::br(), + Expires = c("13 months", "13 months") + ), + caption = "", + caption_size = "s", + num_col = NULL, + width_overwrite = c("one-quarter", "one-quarter", "one-quarter") + ), + shiny::br(), + shiny::tags$h2("Change your cookie settings"), + 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?" + ), + shiny::tags$div( + class = "govuk-radios", + `data-module` = "govuk-radios", shiny::tags$div( - class = "govuk-grid-row", - 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?" - ), - 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 - ) - ) - ) - ) - ), - shiny::actionButton(shiny::NS(id, "submit_btn"), - "Save cookie settings", - class = "govuk-button" - ) + 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" ) ) } @@ -405,15 +434,15 @@ cookies_panel_ui <- function(id = "cookies_panel", google_analytics_key = NULL) #' @param id Shiny tag shared with cookies_panel_ui(), can be any string set by #' the user as long as it matches the id in the cookies_panel_ui() #' @param input_cookies The cookie input passed from cookies.js (should always -#' be `reactive(input$cookies))` +#' be `reactive(input$cookies)`) #' @param google_analytics_key Provide the GA 10 digit key of the form #' "ABCDE12345" #' +#' @family cookies #' @export #' @inherit cookies examples cookies_panel_server <- function(id = "cookies_panel", - input_cookies = reactive(input$cookies), - + input_cookies, google_analytics_key = NULL) { shiny::moduleServer(id, module = function(input, output, session) { shiny::observeEvent(input_cookies(), { diff --git a/R/custom_disconnect_message.R b/R/custom_disconnect_message.R index 59de73e..f4060b5 100644 --- a/R/custom_disconnect_message.R +++ b/R/custom_disconnect_message.R @@ -12,7 +12,6 @@ #' #' @importFrom htmltools tags tagList #' -#' #' @return A html overlay panel that appears when RSConnect disconnects for a #' public R Shiny dashboard in DfE #' @export @@ -22,11 +21,11 @@ #' refresh = "Refresh page", #' links = c( #' "https://department-for-education.shinyapps.io/dfe-shiny-template/", -#' "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" # nolint: [line_length_linter] +#' "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" #' ), #' publication_name = "Explore Education Statistics Publication", #' publication_link = -#' "https://explore-education-statistics.service.gov.uk/find-statistics/pupil-attendance-in-schools" # nolint: [line_length_linter] +#' "https://explore-education-statistics.service.gov.uk/find-statistics/apprenticeships" #' ) #' custom_disconnect_message <- function(refresh = "Refresh page", diff --git a/R/support_panel.R b/R/support_panel.R index 752d124..c132386 100644 --- a/R/support_panel.R +++ b/R/support_panel.R @@ -15,7 +15,8 @@ #' Explore Education Statistics) #' @param form_url URL for a feedback form for the dashboard #' -#' @return a standardised panel for a public R Shiny dashboard in DfE +#' @return a html div, containing standard support content for a public R Shiny +#' dashboard in DfE #' @export #' #' @examples @@ -26,6 +27,24 @@ #' publication_slug = "my-publication-title", #' form_url = "www.myform.com" #' ) +#' +#' # Often you will use this inside a set of navigation tabs, e.g. +#' shiny::navlistPanel( +#' "", +#' id = "navlistPanel", +#' widths = c(2, 8), +#' well = FALSE, +#' ## Support panel -------------------------------------------------------- +#' shiny::tabPanel( +#' value = "support_panel", +#' "Support and feedback", +#' support_panel( +#' team_email = "explore.statistics@@education.gov.uk", +#' repo_name = "https://github.com/dfe-analytical-services/dfeshiny/", +#' form_url = "https://forms.office.com" +#' ) +#' ) +#' ) support_panel <- function( team_email = "", repo_name = "", @@ -76,119 +95,110 @@ support_panel <- function( } # Build the support page ---------------------------------------------------- - shiny::tabPanel( - value = "support_panel", - "Support and feedback", - shinyGovstyle::gov_main_layout( - shinyGovstyle::gov_row( - shiny::column( - width = 12, - shiny::tags$h1("Support and feedback"), - shiny::tags$h2("Give us feedback"), - if (!is.null(form_url)) { - shiny::tags$p( - "This dashboard is a new service that we are developing. If you + shiny::tags$div( + shiny::tags$h1("Support and feedback"), + shiny::tags$h2("Give us feedback"), + if (!is.null(form_url)) { + shiny::tags$p( + "This dashboard is a new service that we are developing. If you have any feedback or suggestions for improvements, please submit them using our ", - shiny::tags$a( - href = form_url, - "feedback form", - .noWS = c("after") - ) - ) - } else { - shiny::tags$p( - "This dashboard is a new service that we are developing." - ) - }, - shiny::tags$p( - paste0( - ifelse( - !is.null(form_url), - "Alternatively, i", - "I" - ), - "f you spot any errors or bugs while using this dashboard, please + shiny::tags$a( + href = form_url, + "feedback form", + .noWS = c("after") + ) + ) + } else { + shiny::tags$p( + "This dashboard is a new service that we are developing." + ) + }, + shiny::tags$p( + paste0( + ifelse( + !is.null(form_url), + "Alternatively, i", + "I" + ), + "f you spot any errors or bugs while using this dashboard, please screenshot and email them to " - ), - shiny::tags$a( - href = paste0("mailto:", team_email), - team_email, - .noWS = c("after") - ), "." - ), - shiny::tags$h2("Find more information on the data"), - if (ees_publication) { - shiny::tags$p( - "The parent statistical release of this dashboard, along with + ), + shiny::tags$a( + href = paste0("mailto:", team_email), + team_email, + .noWS = c("after") + ), "." + ), + shiny::tags$h2("Find more information on the data"), + if (ees_publication) { + shiny::tags$p( + "The parent statistical release of this dashboard, along with methodological information, is available at the following link: ", - shiny::tags$a( - href = paste0( - "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] - publication_slug - ), - ifelse( - !is.null(publication_name), - publication_name, - "Explore Education Statistics" - ), - .noWS = c("after") - ), - ". The statistical release provides additional ", - shiny::tags$a( - href = paste0( - "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] - publication_slug, "/data-guidance" - ), - "data guidance", - .noWS = c("after") - ), - " and ", - shiny::tags$a( - href = paste0( - "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] - publication_slug, "#explore-data-and-files" - ), - "tools to access and interogate the underling data", - .noWS = c("after") - ), - " contained in this dashboard." - ) - } else { - shiny::tags$p( - "The parent statistical release of this dashboard, along with + shiny::tags$a( + href = paste0( + "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] + publication_slug + ), + ifelse( + !is.null(publication_name), + publication_name, + "Explore Education Statistics" + ), + .noWS = c("after") + ), + ". The statistical release provides additional ", + shiny::tags$a( + href = paste0( + "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] + publication_slug, "/data-guidance" + ), + "data guidance", + .noWS = c("after") + ), + " and ", + shiny::tags$a( + href = paste0( + "https://explore-education-statistics.service.gov.uk/find-statistics/", # nolint: [line_length_linter] + publication_slug, "#explore-data-and-files" + ), + "tools to access and interogate the underling data", + .noWS = c("after") + ), + " contained in this dashboard." + ) + } else { + shiny::tags$p( + "The parent statistical release of this dashboard, along with methodological information, is available at the following link: ", - shiny::tags$a( - href = alt_href, - publication_name, - .noWS = c("after") - ) - ) - }, - shiny::tags$h2("Contact us"), - shiny::tags$p( - "If you have questions about the dashboard or data within it, - please contact us at ", - shiny::tags$a( - href = paste0("mailto:", team_email), - team_email, .noWS = c("after") - ) - ), - shiny::tags$h2("See the source code"), - shiny::tags$p( - "The source code for this dashboard is available in our ", - shiny::tags$a( - href = paste0( - repo_name - ), - "GitHub repository", .noWS = c("after") - ), - "." - ) + shiny::tags$a( + href = alt_href, + publication_name, + .noWS = c("after") ) ) + }, + shiny::tags$h2("Contact us"), + shiny::tags$p( + "If you have questions about the dashboard or data within it, + please contact us at ", + shiny::tags$a( + href = paste0("mailto:", team_email), + team_email, .noWS = c("after") + ) + ), + shiny::tags$h2("See the source code"), + shiny::tags$p( + "The source code for this dashboard is available in our ", + shiny::tags$a( + href = paste0( + repo_name + ), + "GitHub repository", .noWS = c("after") + ), + "." ) ) } diff --git a/README.md b/README.md index 1ad1851..f566460 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,33 @@ +# dfeshiny dfeshiny website + [![R-CMD-check](https://github.com/dfe-analytical-services/dfeshiny/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/dfe-analytical-services/dfeshiny/actions/workflows/R-CMD-check.yaml) [![Codecov test coverage](https://codecov.io/gh/dfe-analytical-services/dfeshiny/branch/main/graph/badge.svg)](https://app.codecov.io/gh/dfe-analytical-services/dfeshiny?branch=main) +[![Lifecycle: experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental) - - -# dfeshiny - An R package to support analysts in developing official DfE dashboards and help them meet the necessary standards required of public facing government services. -## Installing the package +## Installation -To install, run `renv::install("dfe-analytical-services/dfeshiny")`. +dfeshiny is not currently available on CRAN. For the time being you can +install the development version from GitHub. + +If you are using +[renv](https://rstudio.github.io/renv/articles/renv.html) in your +project (recommended): + +``` r +renv::install("dfe-analytical-services/dfeshiny") +``` + +Otherwise: + +``` r +# install.packages("devtools") +devtools::install_github("dfe-analytical-services/dfeshiny") +``` ## Installing functionality in development from a branch @@ -36,11 +51,11 @@ If this works, then you will need to look for where that "GITHUB_PAT" variable i 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) +- request a Google Analytics key from the [explore education 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): +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") @@ -74,72 +89,9 @@ should work well. ## Contributing -Try and make use of the [usethis](https://usethis.r-lib.org/) package wherever possible. - -When you initially clone the package, the first thing you'll need to do is install [devtools](https://devtools.r-lib.org/): - -``` -install.packages("devtools") -``` - -Then to load in the package in its current form: - -``` -devtools::load_all() -``` - -### Adding a package/dependency - -`usethis::use_package()` - -This will create a new script within the package R/ folder. - -Note that when adding a function from another package into one of the dfeshiny functions you will need to explicitly state the package in the function call, e.g.: +Ideas for dfeshiny should first be raised as a [GitHub +issue](https://github.com/dfe-analytical-services/dfeshiny) after which +anyone is free to write the code and create a pull request for review. -```package::function()``` - -Alternatively, if there's a lot of uses of a single function within one of our R scripts, you can call that function once at the top of the R script, e.g: - -``` -@' importFrom package function -``` - -For more information see the [roxygen2 documentation on declaring dependencies](https://roxygen2.r-lib.org/articles/namespace.html). - -### Creating a new function script - -`usethis::use_r(name = )` - -This will create a new script within the package R/ folder. - -### Creating a new function test script - -`usethis::use_test(name = )` - -This will create a new test script within the package testthat/ folder. - -### Updating the package version - -Once changes have been completed, reviewed and are ready for use in the wild, you -can increment the package version using: - -`usethis::use_version()` - -Once you've incremented the version number, it'll add a new heading to news.md. - -Add a summary under news.md and then accept it's offer to commit on your behalf. - -Once pushed and on the main branch, create a new release in GitHub itself. - -### Running tests - -You should run the following lines to test the package locally: -``` -# To check functionality -devtools::check() # Ctrl-Shft-E -shinytest2::test_app("tests/test_dashboard") # important as not currently ran in CI checks, need to move this over - -# For code styling -styler::style_pkg() -lintr::lint_package() -``` +For more details on contributing to dfeshiny, see our [contributing +guidelines](https://dfe-analytical-services.github.io/dfeshiny/CONTRIBUTING.html). diff --git a/_pkgdown.yml b/_pkgdown.yml index c605ea1..ffe107a 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -11,8 +11,7 @@ reference: - tidy_code - title: Cookies contents: - - starts_with("cookie") - - dfe_cookie_script + - has_concept("cookies") - title: Standard panels contents: - support_panel @@ -23,4 +22,3 @@ reference: desc: One time functions used to set up or update standardised scripts and workflows needed for your dashboard contents: - starts_with("init") - diff --git a/inst/cookie-consent.js b/inst/cookie-consent.js deleted file mode 100644 index d3f220f..0000000 --- a/inst/cookie-consent.js +++ /dev/null @@ -1,26 +0,0 @@ -function getCookies(){ - var res = Cookies.get(); - Shiny.setInputValue('cookies', res); -} - -Shiny.addCustomMessageHandler('cookie-set', function(msg){ - Cookies.set(msg.name, msg.value); - getCookies(); -}) - -Shiny.addCustomMessageHandler('cookie-clear', function(msg){ - Cookies.remove(msg.name); - getCookies(); -}) - -$(document).on('shiny:connected', function(ev){ - getCookies(); -}) - -Shiny.addCustomMessageHandler('analytics-consent', function(msg){ - gtag('consent', 'update', { - 'analytics_storage': msg.value - }); -}) - - diff --git a/inst/google-analytics.html b/inst/google-analytics.html deleted file mode 100644 index f7343e8..0000000 --- a/inst/google-analytics.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - diff --git a/man/cookie_banner_server.Rd b/man/cookie_banner_server.Rd deleted file mode 100644 index 3367981..0000000 --- a/man/cookie_banner_server.Rd +++ /dev/null @@ -1,84 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/cookies.R -\name{cookie_banner_server} -\alias{cookie_banner_server} -\title{cookie_banner_server} -\usage{ -cookie_banner_server( - id = "cookies_banner", - input_cookies = reactive(input$cookies), - parent_session = session, - google_analytics_key = NULL, - cookie_link_panel = "cookies_panel_ui" -) -} -\arguments{ -\item{id}{Shiny tag shared with cookie_banner_ui(), can be any string set by -the user as long as it matches the id in the cookie_banner_ui()} - -\item{input_cookies}{The cookie input passed from cookies.js (should always -be \code{reactive(input$cookies)})} - -\item{parent_session}{This should be the R Shiny app session} - -\item{google_analytics_key}{Provide the GA 10 digit key of the form -"ABCDE12345"} - -\item{cookie_link_panel}{name of the navlistPanel that the cookie banner -provides a link to, usually "cookies_panel_ui"} -} -\description{ -cookie_banner_server() provides the server module to be used alongside -cookie_banner_ui(). Place cookie_banner_server() as a call in your server.R -file to provide the server functions to control users being able to accept or -reject cookie consent for the provision of Google Analytics tracking on DfE -R-Shiny dashboards. -} -\examples{ -if (interactive()) { - # This example shows how to use the full family of cookie functions together - # This will be in your global.R script ===================================== - - library(shiny) - library(shinyjs) - library(dfeshiny) - ga_key <- "ABCDE12345" - - # This will be what is in your ui.R script ================================= - - ui <- fluidPage( - # Place these lines above your header ------------------------------------ - dfe_cookie_script(), - useShinyjs(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), - - # Place the cookies panel under the header but in the main content ------- - cookies_panel_ui(google_analytics_key = ga_key) - ) - - # This will be in your server.R file ======================================= - - server <- function(input, output, session) { - # Server logic for the pop up banner, can be placed anywhere in server.R - - output$cookie_status <- dfeshiny::cookie_banner_server( - input_cookies = reactive(input$cookies), - google_analytics_key = ga_key - ) - - # Server logic for the panel, can be placed anywhere in server.R --------- - cookies_panel_server( - input_cookies = reactive(input$cookies), - google_analytics_key = ga_key - ) - } - - # How to run the minimal app given in this example ========================= - shinyApp(ui, server) -} -} -\seealso{ -Other cookies: -\code{\link{cookie_banner_ui}()}, -\code{\link{cookies}} -} -\concept{cookies} diff --git a/man/cookie_banner_ui.Rd b/man/cookie_banner_ui.Rd deleted file mode 100644 index 8a48f18..0000000 --- a/man/cookie_banner_ui.Rd +++ /dev/null @@ -1,75 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/cookies.R -\name{cookie_banner_ui} -\alias{cookie_banner_ui} -\title{cookie_banner_ui} -\usage{ -cookie_banner_ui( - id = "cookies_banner", - name = "DfE R-Shiny dashboard template" -) -} -\arguments{ -\item{id}{Shiny tag shared with cookie_banner_server(), can be any string set -by the user as long as it matches the id in the cookie_banner_server()} - -\item{name}{Name of the dashboard on which the cookie authorisation is being -applied} -} -\value{ -shiny::tags$div() -} -\description{ -This function provides a cookie authorisation banner on DfE R-Shiny -dashboards for users to be able to accept or reject cookies. The server side -functionality is provided by cookie_banner_server(), whilst users will also -need to include the dfe_cookie_script() function in their ui.R file. -} -\examples{ -if (interactive()) { - # This example shows how to use the full family of cookie functions together - # This will be in your global.R script ===================================== - - library(shiny) - library(shinyjs) - library(dfeshiny) - ga_key <- "ABCDE12345" - - # This will be what is in your ui.R script ================================= - - ui <- fluidPage( - # Place these lines above your header ------------------------------------ - dfe_cookie_script(), - useShinyjs(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), - - # Place the cookies panel under the header but in the main content ------- - cookies_panel_ui(google_analytics_key = ga_key) - ) - - # This will be in your server.R file ======================================= - - server <- function(input, output, session) { - # Server logic for the pop up banner, can be placed anywhere in server.R - - output$cookie_status <- dfeshiny::cookie_banner_server( - input_cookies = reactive(input$cookies), - google_analytics_key = ga_key - ) - - # Server logic for the panel, can be placed anywhere in server.R --------- - cookies_panel_server( - input_cookies = reactive(input$cookies), - google_analytics_key = ga_key - ) - } - - # How to run the minimal app given in this example ========================= - shinyApp(ui, server) -} -} -\seealso{ -Other cookies: -\code{\link{cookie_banner_server}()}, -\code{\link{cookies}} -} -\concept{cookies} diff --git a/man/cookies.Rd b/man/cookies.Rd index 1eeaf47..a069577 100644 --- a/man/cookies.Rd +++ b/man/cookies.Rd @@ -2,10 +2,10 @@ % Please edit documentation in R/cookies.R \name{cookies} \alias{cookies} -\alias{dfe_cookie_script} -\title{dfe_cookie_script} +\alias{dfe_cookies_script} +\title{dfe_cookies_script} \usage{ -dfe_cookie_script() +dfe_cookies_script() } \value{ shiny::tags$head() @@ -16,49 +16,64 @@ cookies. Function should be placed in the ui.R script. } \examples{ if (interactive()) { - # This example shows how to use the full family of cookie functions together - # This will be in your global.R script ===================================== + # This example shows how to use the full family of cookies functions together + # This will be in your global.R script =================================== library(shiny) library(shinyjs) library(dfeshiny) - ga_key <- "ABCDE12345" + google_analytics_key <- "ABCDE12345" - # This will be what is in your ui.R script ================================= + # This will be what is in your ui.R script =============================== ui <- fluidPage( - # Place these lines above your header ------------------------------------ - dfe_cookie_script(), + # Place these lines above your header ---------------------------------- useShinyjs(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), + dfe_cookies_script(), + cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), - # Place the cookies panel under the header but in the main content ------- - cookies_panel_ui(google_analytics_key = ga_key) + # Place the cookies panel under the header but in the main content ----- + # Example of placing as a panel within navlistPanel + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Cookies panel ----------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = google_analytics_key) + ) + ) ) - # This will be in your server.R file ======================================= + # This will be in your server.R file ===================================== server <- function(input, output, session) { # Server logic for the pop up banner, can be placed anywhere in server.R - - output$cookie_status <- dfeshiny::cookie_banner_server( + output$cookies_status <- dfeshiny::cookies_banner_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key, + parent_session = session ) - # Server logic for the panel, can be placed anywhere in server.R --------- + # Server logic for the panel, can be placed anywhere in server.R ------- cookies_panel_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key ) } - # How to run the minimal app given in this example ========================= + # How to run the minimal app given in this example ======================= shinyApp(ui, server) } } \seealso{ Other cookies: -\code{\link{cookie_banner_server}()}, -\code{\link{cookie_banner_ui}()} +\code{\link{cookies_banner_server}()}, +\code{\link{cookies_banner_ui}()}, +\code{\link{cookies_panel_server}()}, +\code{\link{cookies_panel_ui}()} } \concept{cookies} diff --git a/man/cookies_banner_server.Rd b/man/cookies_banner_server.Rd new file mode 100644 index 0000000..dd94f30 --- /dev/null +++ b/man/cookies_banner_server.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/cookies.R +\name{cookies_banner_server} +\alias{cookies_banner_server} +\title{cookies_banner_server} +\usage{ +cookies_banner_server( + id = "cookies_banner", + input_cookies, + parent_session, + google_analytics_key = NULL, + cookies_link_panel = "cookies_panel_ui" +) +} +\arguments{ +\item{id}{Shiny tag shared with cookies_banner_ui(), can be any string set by +the user as long as it matches the id in the cookies_banner_ui()} + +\item{input_cookies}{The cookie input passed from cookies.js (should always +be \code{reactive(input$cookies)})} + +\item{parent_session}{This should be the R Shiny app session, expect it to +always be \code{parent_session = session}} + +\item{google_analytics_key}{Provide the GA 10 digit key of the form +"ABCDE12345"} + +\item{cookies_link_panel}{name of the navlistPanel that the cookie banner +provides a link to, usually "cookies_panel_ui"} +} +\description{ +cookies_banner_server() provides the server module to be used alongside +cookies_banner_ui(). Place cookies_banner_server() as a call in your server.R +file to provide the server functions to control users being able to accept or +reject cookie consent for the provision of Google Analytics tracking on DfE +R-Shiny dashboards. +} +\examples{ +if (interactive()) { + # This example shows how to use the full family of cookies functions together + # This will be in your global.R script =================================== + + library(shiny) + library(shinyjs) + library(dfeshiny) + google_analytics_key <- "ABCDE12345" + + # This will be what is in your ui.R script =============================== + + ui <- fluidPage( + # Place these lines above your header ---------------------------------- + useShinyjs(), + dfe_cookies_script(), + cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), + + # Place the cookies panel under the header but in the main content ----- + # Example of placing as a panel within navlistPanel + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Cookies panel ----------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = google_analytics_key) + ) + ) + ) + + # This will be in your server.R file ===================================== + + server <- function(input, output, session) { + # Server logic for the pop up banner, can be placed anywhere in server.R - + output$cookies_status <- dfeshiny::cookies_banner_server( + input_cookies = reactive(input$cookies), + google_analytics_key = google_analytics_key, + parent_session = session + ) + + # Server logic for the panel, can be placed anywhere in server.R ------- + cookies_panel_server( + input_cookies = reactive(input$cookies), + google_analytics_key = google_analytics_key + ) + } + + # How to run the minimal app given in this example ======================= + shinyApp(ui, server) +} +} +\seealso{ +Other cookies: +\code{\link{cookies}}, +\code{\link{cookies_banner_ui}()}, +\code{\link{cookies_panel_server}()}, +\code{\link{cookies_panel_ui}()} +} +\concept{cookies} diff --git a/man/cookies_banner_ui.Rd b/man/cookies_banner_ui.Rd new file mode 100644 index 0000000..cbae8af --- /dev/null +++ b/man/cookies_banner_ui.Rd @@ -0,0 +1,90 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/cookies.R +\name{cookies_banner_ui} +\alias{cookies_banner_ui} +\title{cookies_banner_ui} +\usage{ +cookies_banner_ui( + id = "cookies_banner", + name = "DfE R-Shiny dashboard template" +) +} +\arguments{ +\item{id}{Shiny tag shared with cookies_banner_server(), can be any string set +by the user as long as it matches the id in the cookies_banner_server()} + +\item{name}{Name of the dashboard on which the cookie authorisation is being +applied} +} +\value{ +shiny::tags$div() +} +\description{ +This function provides a cookie authorisation banner on DfE R-Shiny +dashboards for users to be able to accept or reject cookies. The server side +functionality is provided by cookies_banner_server(), whilst users will also +need to include the dfe_cookies_script() function in their ui.R file. +} +\examples{ +if (interactive()) { + # This example shows how to use the full family of cookies functions together + # This will be in your global.R script =================================== + + library(shiny) + library(shinyjs) + library(dfeshiny) + google_analytics_key <- "ABCDE12345" + + # This will be what is in your ui.R script =============================== + + ui <- fluidPage( + # Place these lines above your header ---------------------------------- + useShinyjs(), + dfe_cookies_script(), + cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), + + # Place the cookies panel under the header but in the main content ----- + # Example of placing as a panel within navlistPanel + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Cookies panel ----------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = google_analytics_key) + ) + ) + ) + + # This will be in your server.R file ===================================== + + server <- function(input, output, session) { + # Server logic for the pop up banner, can be placed anywhere in server.R - + output$cookies_status <- dfeshiny::cookies_banner_server( + input_cookies = reactive(input$cookies), + google_analytics_key = google_analytics_key, + parent_session = session + ) + + # Server logic for the panel, can be placed anywhere in server.R ------- + cookies_panel_server( + input_cookies = reactive(input$cookies), + google_analytics_key = google_analytics_key + ) + } + + # How to run the minimal app given in this example ======================= + shinyApp(ui, server) +} +} +\seealso{ +Other cookies: +\code{\link{cookies}}, +\code{\link{cookies_banner_server}()}, +\code{\link{cookies_panel_server}()}, +\code{\link{cookies_panel_ui}()} +} +\concept{cookies} diff --git a/man/cookies_panel_server.Rd b/man/cookies_panel_server.Rd index 778dcd7..223b720 100644 --- a/man/cookies_panel_server.Rd +++ b/man/cookies_panel_server.Rd @@ -6,7 +6,7 @@ \usage{ cookies_panel_server( id = "cookies_panel", - input_cookies = reactive(input$cookies), + input_cookies, google_analytics_key = NULL ) } @@ -15,7 +15,7 @@ cookies_panel_server( the user as long as it matches the id in the cookies_panel_ui()} \item{input_cookies}{The cookie input passed from cookies.js (should always -be \verb{reactive(input$cookies))}} +be \code{reactive(input$cookies)})} \item{google_analytics_key}{Provide the GA 10 digit key of the form "ABCDE12345"} @@ -26,43 +26,64 @@ alongside cookies_panel_ui(). } \examples{ if (interactive()) { - # This example shows how to use the full family of cookie functions together - # This will be in your global.R script ===================================== + # This example shows how to use the full family of cookies functions together + # This will be in your global.R script =================================== library(shiny) library(shinyjs) library(dfeshiny) - ga_key <- "ABCDE12345" + google_analytics_key <- "ABCDE12345" - # This will be what is in your ui.R script ================================= + # This will be what is in your ui.R script =============================== ui <- fluidPage( - # Place these lines above your header ------------------------------------ - dfe_cookie_script(), + # Place these lines above your header ---------------------------------- useShinyjs(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), + dfe_cookies_script(), + cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), - # Place the cookies panel under the header but in the main content ------- - cookies_panel_ui(google_analytics_key = ga_key) + # Place the cookies panel under the header but in the main content ----- + # Example of placing as a panel within navlistPanel + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Cookies panel ----------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = google_analytics_key) + ) + ) ) - # This will be in your server.R file ======================================= + # This will be in your server.R file ===================================== server <- function(input, output, session) { # Server logic for the pop up banner, can be placed anywhere in server.R - - output$cookie_status <- dfeshiny::cookie_banner_server( + output$cookies_status <- dfeshiny::cookies_banner_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key, + parent_session = session ) - # Server logic for the panel, can be placed anywhere in server.R --------- + # Server logic for the panel, can be placed anywhere in server.R ------- cookies_panel_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key ) } - # How to run the minimal app given in this example ========================= + # How to run the minimal app given in this example ======================= shinyApp(ui, server) } } +\seealso{ +Other cookies: +\code{\link{cookies}}, +\code{\link{cookies_banner_server}()}, +\code{\link{cookies_banner_ui}()}, +\code{\link{cookies_panel_ui}()} +} +\concept{cookies} diff --git a/man/cookies_panel_ui.Rd b/man/cookies_panel_ui.Rd index 831dbb2..040c2f4 100644 --- a/man/cookies_panel_ui.Rd +++ b/man/cookies_panel_ui.Rd @@ -14,7 +14,8 @@ the user as long as it matches the id in the cookies_panel_server()} "ABCDE12345"} } \value{ -a standardised panel for a public R Shiny dashboard in DfE +a html div, containing standard cookises content for a public R +Shiny dashboard in DfE } \description{ Create the standard DfE R-Shiny cookies dashboard panel in the ui. The server @@ -22,43 +23,64 @@ side functionality is provided by cookies_panel_server() } \examples{ if (interactive()) { - # This example shows how to use the full family of cookie functions together - # This will be in your global.R script ===================================== + # This example shows how to use the full family of cookies functions together + # This will be in your global.R script =================================== library(shiny) library(shinyjs) library(dfeshiny) - ga_key <- "ABCDE12345" + google_analytics_key <- "ABCDE12345" - # This will be what is in your ui.R script ================================= + # This will be what is in your ui.R script =============================== ui <- fluidPage( - # Place these lines above your header ------------------------------------ - dfe_cookie_script(), + # Place these lines above your header ---------------------------------- useShinyjs(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), + dfe_cookies_script(), + cookies_banner_ui(name = "My DfE R-Shiny data dashboard"), - # Place the cookies panel under the header but in the main content ------- - cookies_panel_ui(google_analytics_key = ga_key) + # Place the cookies panel under the header but in the main content ----- + # Example of placing as a panel within navlistPanel + shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Cookies panel ----------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = google_analytics_key) + ) + ) ) - # This will be in your server.R file ======================================= + # This will be in your server.R file ===================================== server <- function(input, output, session) { # Server logic for the pop up banner, can be placed anywhere in server.R - - output$cookie_status <- dfeshiny::cookie_banner_server( + output$cookies_status <- dfeshiny::cookies_banner_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key, + parent_session = session ) - # Server logic for the panel, can be placed anywhere in server.R --------- + # Server logic for the panel, can be placed anywhere in server.R ------- cookies_panel_server( input_cookies = reactive(input$cookies), - google_analytics_key = ga_key + google_analytics_key = google_analytics_key ) } - # How to run the minimal app given in this example ========================= + # How to run the minimal app given in this example ======================= shinyApp(ui, server) } } +\seealso{ +Other cookies: +\code{\link{cookies}}, +\code{\link{cookies_banner_server}()}, +\code{\link{cookies_banner_ui}()}, +\code{\link{cookies_panel_server}()} +} +\concept{cookies} diff --git a/man/custom_disconnect_message.Rd b/man/custom_disconnect_message.Rd index c5abacb..afe2964 100644 --- a/man/custom_disconnect_message.Rd +++ b/man/custom_disconnect_message.Rd @@ -33,11 +33,11 @@ custom_disconnect_message( refresh = "Refresh page", links = c( "https://department-for-education.shinyapps.io/dfe-shiny-template/", - "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" # nolint: [line_length_linter] + "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" ), publication_name = "Explore Education Statistics Publication", publication_link = - "https://explore-education-statistics.service.gov.uk/find-statistics/pupil-attendance-in-schools" # nolint: [line_length_linter] + "https://explore-education-statistics.service.gov.uk/find-statistics/apprenticeships" ) } diff --git a/man/figures/logo.png b/man/figures/logo.png new file mode 100644 index 0000000..2725b80 Binary files /dev/null and b/man/figures/logo.png differ diff --git a/man/init_analytics.Rd b/man/init_analytics.Rd index fd38c97..01dcdc0 100644 --- a/man/init_analytics.Rd +++ b/man/init_analytics.Rd @@ -4,19 +4,21 @@ \alias{init_analytics} \title{init_analytics} \usage{ -init_analytics(ga_code) +init_analytics(ga_code, create_file = TRUE) } \arguments{ -\item{ga_code}{The Google Analytics code} -} -\value{ -TRUE if written, FALSE if not +\item{ga_code}{The Google Analytics code for the dashboard} + +\item{create_file}{Boolean TRUE or FALSE, default is TRUE, false will return +the HTML in the console and is used mainly for testing or comparisons} } \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. +refer to the documentation in the README. } \examples{ -init_analytics(ga_code = "0123456789") +if (interactive()) { + init_analytics(ga_code = "0123456789") +} } diff --git a/man/init_cookies.Rd b/man/init_cookies.Rd index fc03492..58c4475 100644 --- a/man/init_cookies.Rd +++ b/man/init_cookies.Rd @@ -4,18 +4,22 @@ \alias{init_cookies} \title{init_cookies} \usage{ -init_cookies() +init_cookies(create_file = TRUE) +} +\arguments{ +\item{create_file}{Boolean, TRUE by default, if FALSE then will print to +the console rather than create a new file} } \description{ -init_cookies() creates a local copy of the JavaScript file -required for cookies to work. -It checks whether there is already a www/ folder and if not, it creates one. -It then checks whether the cookie-consent.js file exists in the www/ folder. -If the file exists, it will print a message in the console to let you know. -If the file doesn't exist, it will pull a copy from the GitHub repo. -If it cannot connect to the repo then it will print "Download failed". -No input parameters are required -Call init_cookies() in the console to run the function +init_cookies() creates a local copy of the JavaScript file required for +cookies to work. It checks whether there is already a www/ folder and if +not, it creates one. It then checks whether the cookie-consent.js file +exists in the www/ folder. If the file exists, it will print a message in +the console to let you know it has overwritten it. If the file doesn't +exist, it will make one and confirm it has done so. + +No input parameters are required, run \code{init_cookies()} in the console to run +the function } \examples{ if (interactive()) { diff --git a/man/support_panel.Rd b/man/support_panel.Rd index 844e69f..8d9395c 100644 --- a/man/support_panel.Rd +++ b/man/support_panel.Rd @@ -34,7 +34,8 @@ Explore Education Statistics)} \item{form_url}{URL for a feedback form for the dashboard} } \value{ -a standardised panel for a public R Shiny dashboard in DfE +a html div, containing standard support content for a public R Shiny +dashboard in DfE } \description{ Create the standard DfE R-Shiny support and feedback dashboard panel. @@ -47,4 +48,22 @@ support_panel( publication_slug = "my-publication-title", form_url = "www.myform.com" ) + +# Often you will use this inside a set of navigation tabs, e.g. +shiny::navlistPanel( + "", + id = "navlistPanel", + widths = c(2, 8), + well = FALSE, + ## Support panel -------------------------------------------------------- + shiny::tabPanel( + value = "support_panel", + "Support and feedback", + support_panel( + team_email = "explore.statistics@education.gov.uk", + repo_name = "https://github.com/dfe-analytical-services/dfeshiny/", + form_url = "https://forms.office.com" + ) + ) +) } diff --git a/pkgdown/favicon/apple-touch-icon-120x120.png b/pkgdown/favicon/apple-touch-icon-120x120.png new file mode 100644 index 0000000..31d9b7b Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon-120x120.png differ diff --git a/pkgdown/favicon/apple-touch-icon-152x152.png b/pkgdown/favicon/apple-touch-icon-152x152.png new file mode 100644 index 0000000..3f67e9d Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon-152x152.png differ diff --git a/pkgdown/favicon/apple-touch-icon-180x180.png b/pkgdown/favicon/apple-touch-icon-180x180.png new file mode 100644 index 0000000..3ba70bf Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon-180x180.png differ diff --git a/pkgdown/favicon/apple-touch-icon-60x60.png b/pkgdown/favicon/apple-touch-icon-60x60.png new file mode 100644 index 0000000..759eb4d Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon-60x60.png differ diff --git a/pkgdown/favicon/apple-touch-icon-76x76.png b/pkgdown/favicon/apple-touch-icon-76x76.png new file mode 100644 index 0000000..dabd160 Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon-76x76.png differ diff --git a/pkgdown/favicon/apple-touch-icon.png b/pkgdown/favicon/apple-touch-icon.png new file mode 100644 index 0000000..ef8470d Binary files /dev/null and b/pkgdown/favicon/apple-touch-icon.png differ diff --git a/pkgdown/favicon/favicon-16x16.png b/pkgdown/favicon/favicon-16x16.png new file mode 100644 index 0000000..01717f4 Binary files /dev/null and b/pkgdown/favicon/favicon-16x16.png differ diff --git a/pkgdown/favicon/favicon-32x32.png b/pkgdown/favicon/favicon-32x32.png new file mode 100644 index 0000000..8050a2d Binary files /dev/null and b/pkgdown/favicon/favicon-32x32.png differ diff --git a/pkgdown/favicon/favicon.ico b/pkgdown/favicon/favicon.ico index 65cbc52..efa5884 100644 Binary files a/pkgdown/favicon/favicon.ico and b/pkgdown/favicon/favicon.ico differ diff --git a/tests/test_dashboard/server.R b/tests/test_dashboard/server.R index d8a539a..56103a8 100644 --- a/tests/test_dashboard/server.R +++ b/tests/test_dashboard/server.R @@ -1,16 +1,18 @@ server <- function(input, output, session) { + # Cookies testing =========================================================== shinyjs::runjs( 'Cookies.remove("dfe_analytics"); getCookies();' ) - output$cookie_status <- cookie_banner_server( - input_cookies = reactive(input$cookies), - google_analytics_key = ga_key # nolint: [object_usage_linter] + output$cookies_status <- cookies_banner_server( + input_cookies = shiny::reactive(input$cookies), + google_analytics_key = ga_key, # nolint: [object_usage_linter] + parent_session = session ) cookies_panel_server( - input_cookies = reactive(input$cookies), + input_cookies = shiny::reactive(input$cookies), google_analytics_key = ga_key # nolint: [object_usage_linter] ) } diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001.json b/tests/test_dashboard/tests/testthat/_snaps/UI-01-basic_load/basic_load-001.json similarity index 63% rename from tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001.json rename to tests/test_dashboard/tests/testthat/_snaps/UI-01-basic_load/basic_load-001.json index 1fb8c63..d806c7d 100644 --- a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001.json +++ b/tests/test_dashboard/tests/testthat/_snaps/UI-01-basic_load/basic_load-001.json @@ -3,9 +3,9 @@ "cookies": { }, - "cookies_banner-cookie_accept": 0, - "cookies_banner-cookie_link": 0, - "cookies_banner-cookie_reject": 0, + "cookies_banner-cookies_accept": 0, + "cookies_banner-cookies_link": 0, + "cookies_banner-cookies_reject": 0, "cookies_panel-cookies_analytics": "no", "cookies_panel-submit_btn": 0, "navlistPanel": "support_panel" diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002.json b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-001.json similarity index 66% rename from tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002.json rename to tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-001.json index 3abae31..4a10e92 100644 --- a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002.json +++ b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-001.json @@ -3,9 +3,9 @@ "cookies": { "dfe_analytics": "granted" }, - "cookies_banner-cookie_accept": 1, - "cookies_banner-cookie_link": 0, - "cookies_banner-cookie_reject": 0, + "cookies_banner-cookies_accept": 1, + "cookies_banner-cookies_link": 0, + "cookies_banner-cookies_reject": 0, "cookies_panel-cookies_analytics": "yes", "cookies_panel-submit_btn": 0, "navlistPanel": "support_panel" diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003.json b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-002.json similarity index 66% rename from tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003.json rename to tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-002.json index 0f73b32..59f3493 100644 --- a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003.json +++ b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-002.json @@ -3,9 +3,9 @@ "cookies": { "dfe_analytics": "denied" }, - "cookies_banner-cookie_accept": 1, - "cookies_banner-cookie_link": 0, - "cookies_banner-cookie_reject": 1, + "cookies_banner-cookies_accept": 1, + "cookies_banner-cookies_link": 0, + "cookies_banner-cookies_reject": 1, "cookies_panel-cookies_analytics": "no", "cookies_panel-submit_btn": 0, "navlistPanel": "support_panel" diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004.json b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-003.json similarity index 66% rename from tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004.json rename to tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-003.json index 318fc7f..8b97e70 100644 --- a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004.json +++ b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-003.json @@ -3,9 +3,9 @@ "cookies": { "dfe_analytics": "granted" }, - "cookies_banner-cookie_accept": 1, - "cookies_banner-cookie_link": 0, - "cookies_banner-cookie_reject": 1, + "cookies_banner-cookies_accept": 1, + "cookies_banner-cookies_link": 0, + "cookies_banner-cookies_reject": 1, "cookies_panel-cookies_analytics": "yes", "cookies_panel-submit_btn": 1, "navlistPanel": "support_panel" diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005.json b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-004.json similarity index 66% rename from tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005.json rename to tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-004.json index fd79ef6..015edf3 100644 --- a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005.json +++ b/tests/test_dashboard/tests/testthat/_snaps/UI-02-cookies/cookies_consent-004.json @@ -3,9 +3,9 @@ "cookies": { "dfe_analytics": "denied" }, - "cookies_banner-cookie_accept": 1, - "cookies_banner-cookie_link": 0, - "cookies_banner-cookie_reject": 1, + "cookies_banner-cookies_accept": 1, + "cookies_banner-cookies_link": 0, + "cookies_banner-cookies_reject": 1, "cookies_panel-cookies_analytics": "no", "cookies_panel-submit_btn": 2, "navlistPanel": "support_panel" diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001_.png b/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001_.png deleted file mode 100644 index 88b5e7a..0000000 Binary files a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-001_.png and /dev/null differ diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002_.png b/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002_.png deleted file mode 100644 index 1b77109..0000000 Binary files a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-002_.png and /dev/null differ diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003_.png b/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003_.png deleted file mode 100644 index 1b77109..0000000 Binary files a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-003_.png and /dev/null differ diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004_.png b/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004_.png deleted file mode 100644 index 1b77109..0000000 Binary files a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-004_.png and /dev/null differ diff --git a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005_.png b/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005_.png deleted file mode 100644 index 1b77109..0000000 Binary files a/tests/test_dashboard/tests/testthat/_snaps/cookie-auth/cookie_consent-005_.png and /dev/null differ diff --git a/tests/test_dashboard/tests/testthat/test-UI-01-basic_load.R b/tests/test_dashboard/tests/testthat/test-UI-01-basic_load.R new file mode 100644 index 0000000..11db292 --- /dev/null +++ b/tests/test_dashboard/tests/testthat/test-UI-01-basic_load.R @@ -0,0 +1,18 @@ +# To run the diffviewer on these tests, you need to add the path: +# Doesn't work? testthat::snapshot_review('cookie-auth/', path='tests/test_dashboard/') +app <- AppDriver$new( + name = "basic_load", + height = 846, + width = 1445, + load_timeout = 45 * 1000, + timeout = 20 * 1000, + wait = TRUE, + expect_values_screenshot_args = FALSE +) + +app$wait_for_idle(5) + +test_that("App loads", { + # Capture all values as it's a very basic app + app$expect_values() +}) diff --git a/tests/test_dashboard/tests/testthat/test-cookie-auth.R b/tests/test_dashboard/tests/testthat/test-UI-02-cookies.R similarity index 55% rename from tests/test_dashboard/tests/testthat/test-cookie-auth.R rename to tests/test_dashboard/tests/testthat/test-UI-02-cookies.R index 6939ed3..3d83c43 100644 --- a/tests/test_dashboard/tests/testthat/test-cookie-auth.R +++ b/tests/test_dashboard/tests/testthat/test-UI-02-cookies.R @@ -1,48 +1,47 @@ -library(shinytest2) - # To run the diffviewer on these tests, you need to add the path: -# Doesn't work? testthat::snapshot_review('cookie-auth/', -# path='tests/test_dashboard/') - +# Doesn't work? testthat::snapshot_review('cookie-auth/', path='tests/test_dashboard/') app <- AppDriver$new( - name = "cookie_consent", + name = "cookies_consent", height = 846, width = 1445, load_timeout = 45 * 1000, timeout = 20 * 1000, wait = TRUE, - expect_values_screenshot_args = TRUE + expect_values_screenshot_args = FALSE ) -app$wait_for_idle(500) +app$wait_for_idle(50) -test_that("App loads", { - # Capture initial values - app$expect_values() -}) +app$click("cookies_banner-cookies_accept") +app$wait_for_idle(50) -app$click("cookies_banner-cookie_accept") test_that("Cookies accepted banner", { - # Capture initial values app$expect_values() }) -app$click("cookies_banner-cookie_reject") +app$click("cookies_banner-cookies_reject") +app$wait_for_idle(50) + test_that("Cookies rejected banner", { - # Capture initial values app$expect_values() }) app$set_inputs(`cookies_panel-cookies_analytics` = "yes") +app$wait_for_idle(50) + app$click("cookies_panel-submit_btn") +app$wait_for_idle(50) + test_that("Cookies accepted page", { - # Capture initial values app$expect_values() }) app$set_inputs(`cookies_panel-cookies_analytics` = "no") +app$wait_for_idle(50) + app$click("cookies_panel-submit_btn") +app$wait_for_idle(50) + test_that("Cookies rejected page", { - # Capture initial values app$expect_values() }) diff --git a/tests/test_dashboard/ui.R b/tests/test_dashboard/ui.R index 7dd123a..9ec7925 100644 --- a/tests/test_dashboard/ui.R +++ b/tests/test_dashboard/ui.R @@ -1,28 +1,46 @@ ui <- function(input, output, session) { shiny::fluidPage( shinyjs::useShinyjs(), + + # Custom disconnect ======================================================= custom_disconnect_message( refresh = "Refresh page", links = c( "https://department-for-education.shinyapps.io/dfe-shiny-template/", - "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" # nolint: [line_length_linter] + "https://department-for-education.shinyapps.io/dfe-shiny-template-overflow/" ), publication_name = "Explore Education Statistics Publication", - publication_link = "https://explore-education-statistics.service.gov.uk/find-statistics/pupil-attendance-in-schools" # nolint: [line_length_linter] + publication_link = + "https://explore-education-statistics.service.gov.uk/find-statistics/apprenticeships" ), - dfe_cookie_script(), - cookie_banner_ui(name = "My DfE R-Shiny data dashboard"), + + # Cookies ================================================================= + dfe_cookies_script(), + cookies_banner_ui(name = "The dfeshiny test dashboard"), + + # Panels ================================================================== shiny::navlistPanel( "", id = "navlistPanel", widths = c(2, 8), well = FALSE, - support_panel( - team_email = "statistics.development@education.gov.uk", - repo_name = "https://github.com/dfe-analytical-services/dfeshiny/", - form_url = "https://forms.office.com" + ## Support panel -------------------------------------------------------- + shiny::tabPanel( + value = "support_panel", + "Support and feedback", + support_panel( + team_email = "explore.statistics@education.gov.uk", + repo_name = "https://github.com/dfe-analytical-services/dfeshiny/", + form_url = "https://forms.office.com" + ) ), - cookies_panel_ui(google_analytics_key = ga_key) # nolint: [object_usage_linter] + + ## Cookies panel -------------------------------------------------------- + shiny::tabPanel( + value = "cookies_panel_ui", + "Cookies", + cookies_panel_ui(google_analytics_key = ga_key) # nolint: [object_usage_linter] + ) ) ) } diff --git a/tests/testthat/test-cookies_panel_ui.R b/tests/testthat/test-cookies_panel_ui.R new file mode 100644 index 0000000..0717104 --- /dev/null +++ b/tests/testthat/test-cookies_panel_ui.R @@ -0,0 +1,19 @@ +# Example output object ======================================================= +# This is used in the following tests +output <- cookies_panel_ui(google_analytics_key = "TESTYTESTY") + +test_that("Output has class 'shiny.tag'", { + expect_s3_class(output, class = "shiny.tag") +}) + +test_that("HTML headings output from function", { + # This checks the headings are in the expected positions in the HTML output the function returns + expect_equal(paste(output$children[[1]]), "

Cookies

") + expect_equal(paste(output$children[[4]]), "

Essential cookies

") + expect_equal(paste(output$children[[6]]), "

Analytics cookies

") + expect_equal(paste(output$children[[13]]), "

Change your cookie settings

") +}) + +test_that("GA key pulls into table output", { + expect_true(grepl("_ga_TESTYTESTY", paste(output$children[[11]]))) +}) diff --git a/tests/testthat/test-init_analytics.R b/tests/testthat/test-init_analytics.R new file mode 100644 index 0000000..007bc84 --- /dev/null +++ b/tests/testthat/test-init_analytics.R @@ -0,0 +1,36 @@ +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)) +}) + +test_that("GA key pulls into html", { + output <- capture.output(init_analytics("TESTYTESTY", create_file = FALSE)) + + # Expect the id to pull through on line 15 + expect_true(grepl("G-TESTYTESTY", output[15])) + + # Expect the id to pull through on line 22 + expect_true(grepl("G-TESTYTESTY", output[22])) + + # Expect to find a couple of other key parts + expect_equal(output[1], "