From 10ae196c5181f4356ab0741a48c98018c8d0755c Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 4 Nov 2019 16:29:22 -0500 Subject: [PATCH 1/8] :sparkles: initial support for meta tags --- R/dash.R | 12 +++++++++++- R/utils.R | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/R/dash.R b/R/dash.R index b13ea8d4..652cdf95 100644 --- a/R/dash.R +++ b/R/dash.R @@ -11,6 +11,7 @@ #' assets_url_path = '/assets', #' assets_ignore = '', #' serve_locally = TRUE, +#' meta_tags = NULL, #' routes_pathname_prefix = '/', #' requests_pathname_prefix = '/', #' external_scripts = NULL, @@ -34,6 +35,9 @@ #' cannot use this to prevent access to sensitive files. \cr #' `serve_locally` \tab \tab Whether to serve HTML dependencies locally or #' remotely (via URL).\cr +#' `meta_tags` \tab \tab List of lists. HTML ``tags to be added to the index page. +#' Each list element should have the attributes and values for one tag, eg: +#' `list(name = 'description', content = 'My App')`.\cr #' `routes_pathname_prefix` \tab \tab a prefix applied to the backend routes.\cr #' `requests_pathname_prefix` \tab \tab a prefix applied to request endpoints #' made by Dash's front-end.\cr @@ -158,6 +162,7 @@ Dash <- R6::R6Class( assets_url_path = '/assets', assets_ignore = '', serve_locally = TRUE, + meta_tags = NULL, routes_pathname_prefix = NULL, requests_pathname_prefix = NULL, external_scripts = NULL, @@ -181,6 +186,7 @@ Dash <- R6::R6Class( private$suppress_callback_exceptions <- suppress_callback_exceptions private$app_root_path <- getAppPath() private$app_launchtime <- as.integer(Sys.time()) + private$meta_tags <- meta_tags # config options self$config$routes_pathname_prefix <- resolve_prefix(routes_pathname_prefix, "DASH_ROUTES_PATHNAME_PREFIX") @@ -1245,6 +1251,9 @@ Dash <- R6::R6Class( }, index = function() { + # insert meta tags if present + meta_tags <- generate_meta_tags(private$meta_tags) + # generate tags for all assets all_tags <- private$collect_resources() @@ -1261,7 +1270,7 @@ Dash <- R6::R6Class( ' - + %s %s %s %s @@ -1278,6 +1287,7 @@ Dash <- R6::R6Class( ', + meta_tags, private$name, favicon, css_tags, diff --git a/R/utils.R b/R/utils.R index 595ef4d7..548ac1b1 100644 --- a/R/utils.R +++ b/R/utils.R @@ -607,6 +607,28 @@ generate_js_dist_html <- function(href, } } +generate_meta_tags <- function(metas) { + has_ie_compat <- any(vapply(metas, function(x) + x$name == "http-equiv" && x$content == "X-UA-Compatible", + logical(1))) + has_charset <- any(vapply(metas, function(x) + "charset" %in% names(x), + logical(1))) + + tags <- vapply(metas, + function(tag) sprintf("\n", tag$name, tag$content), + character(1)) + + if (!has_ie_compat) { + tags <- c('\n', tags) + } + + if (!has_charset) { + tags <- c('\n', tags) + } + return(tags) +} + # This function takes the list object containing asset paths # for all stylesheets and scripts, as well as the URL path # to search, then returns the absolute local path (when From f0f7b1517eca8f555a546e062e9740eed772d899 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 4 Nov 2019 21:49:55 -0500 Subject: [PATCH 2/8] :hammer: fix tags --- R/dash.R | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/R/dash.R b/R/dash.R index 652cdf95..27685ca5 100644 --- a/R/dash.R +++ b/R/dash.R @@ -821,6 +821,7 @@ Dash <- R6::R6Class( # private fields defined on initiation name = NULL, serve_locally = NULL, + meta_tags = NULL, assets_folder = NULL, assets_url_path = NULL, assets_ignore = NULL, @@ -1245,15 +1246,16 @@ Dash <- R6::R6Class( scripts_invoke_renderer), collapse = "\n") + meta_tags <- paste(generate_meta_tags(private$meta_tags), + collapse = "\n") + return(list(css_tags = css_tags, scripts_tags = scripts_tags, - favicon = favicon)) + favicon = favicon, + meta_tags = meta_tags)) }, index = function() { - # insert meta tags if present - meta_tags <- generate_meta_tags(private$meta_tags) - # generate tags for all assets all_tags <- private$collect_resources() @@ -1266,6 +1268,9 @@ Dash <- R6::R6Class( # retrieve script tags for serving in the index scripts_tags <- all_tags[["scripts_tags"]] + # insert meta tags if present + meta_tags <- all_tags[["meta_tags"]] + private$.index <- sprintf( ' From bb897d34b6b936902c797480d71622330f87b9df Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 4 Nov 2019 21:50:14 -0500 Subject: [PATCH 3/8] :see_no_evil: properly detect paths --- R/utils.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/utils.R b/R/utils.R index 548ac1b1..a5b7f458 100644 --- a/R/utils.R +++ b/R/utils.R @@ -616,15 +616,15 @@ generate_meta_tags <- function(metas) { logical(1))) tags <- vapply(metas, - function(tag) sprintf("\n", tag$name, tag$content), + function(tag) sprintf("", tag$name, tag$content), character(1)) if (!has_ie_compat) { - tags <- c('\n', tags) + tags <- c('', tags) } if (!has_charset) { - tags <- c('\n', tags) + tags <- c('', tags) } return(tags) } @@ -992,7 +992,7 @@ modtimeFromPath <- function(path, recursive = FALSE, asset_path="") { } } else { # check if the path is for a directory or file, and handle accordingly - if (dir.exists(path)) + if (length(path) == 1 && dir.exists(path)) modtime <- as.integer(max(file.info(list.files(path, full.names = TRUE))$mtime, na.rm=TRUE)) else modtime <- as.integer(file.info(path)$mtime) From 253e8bee3091fd1a62c9675198cea91e0078b31b Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Mon, 4 Nov 2019 23:00:09 -0500 Subject: [PATCH 4/8] support arbitrary tags --- R/utils.R | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/R/utils.R b/R/utils.R index a5b7f458..a1932210 100644 --- a/R/utils.R +++ b/R/utils.R @@ -614,9 +614,13 @@ generate_meta_tags <- function(metas) { has_charset <- any(vapply(metas, function(x) "charset" %in% names(x), logical(1))) - + + # allow arbitrary tags with varying numbers of keys tags <- vapply(metas, - function(tag) sprintf("", tag$name, tag$content), + function(tag) sprintf("", paste(sprintf("%s=\"%s\"", + names(tag), + unlist(tag, use.names = FALSE)), + collapse=" ")), character(1)) if (!has_ie_compat) { From 67fb44af27b95fb6326c166133437a5351530e7c Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 5 Nov 2019 01:53:19 -0500 Subject: [PATCH 5/8] :rotating_light: add tests --- R/utils.R | 4 +-- tests/integration/test_meta.py | 53 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 tests/integration/test_meta.py diff --git a/R/utils.R b/R/utils.R index a1932210..311a7529 100644 --- a/R/utils.R +++ b/R/utils.R @@ -610,10 +610,10 @@ generate_js_dist_html <- function(href, generate_meta_tags <- function(metas) { has_ie_compat <- any(vapply(metas, function(x) x$name == "http-equiv" && x$content == "X-UA-Compatible", - logical(1))) + logical(1)), na.rm=TRUE) has_charset <- any(vapply(metas, function(x) "charset" %in% names(x), - logical(1))) + logical(1)), na.rm=TRUE) # allow arbitrary tags with varying numbers of keys tags <- vapply(metas, diff --git a/tests/integration/test_meta.py b/tests/integration/test_meta.py new file mode 100644 index 00000000..74ca4035 --- /dev/null +++ b/tests/integration/test_meta.py @@ -0,0 +1,53 @@ +from selenium.webdriver.support.select import Select +import time, os + + +app = """ +library(dash) +library(dashHtmlComponents) + +app <- Dash$new(meta_tags = list(list(name = "description", content = "some content"))) + +app$layout( + htmlDiv(children = "Hello world!", + id = "hello-div" + ) +) + +app$run_server() +""" + + +def test_rstm001_test_meta(dashr): + dashr.start_server(app) + dashr.wait_for_text_to_equal( + "#hello-div", + "Hello world!" + ) + assert dashr.find_element("meta[name='description']").get_attribute("content") == "some content" + + +app2 = """ +library(dash) +library(dashHtmlComponents) + +app <- Dash$new(meta_tags = list(list(charset = "ISO-8859-1"), list(name = "keywords", content = "dash,pleasant,productive"))) + +app$layout( + htmlDiv(children = "Hello world!", + id = "hello-div" + ) +) + +app$run_server() +""" + + +def test_rstm002_test_meta(dashr): + dashr.start_server(app2) + dashr.wait_for_text_to_equal( + "#hello-div", + "Hello world!" + ) + assert dashr.find_element("meta[charset='ISO-8859-1']") + assert dashr.find_element("meta[name='keywords']").get_attribute("content") == "dash,pleasant,productive" From 3e773279cc865f16bfa07573933246951cd11847 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 5 Nov 2019 10:35:23 -0500 Subject: [PATCH 6/8] :microscope: add asserts --- tests/integration/test_meta.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_meta.py b/tests/integration/test_meta.py index 74ca4035..921bbc0d 100644 --- a/tests/integration/test_meta.py +++ b/tests/integration/test_meta.py @@ -25,13 +25,17 @@ def test_rstm001_test_meta(dashr): "Hello world!" ) assert dashr.find_element("meta[name='description']").get_attribute("content") == "some content" + assert dashr.find_element("meta[charset='UTF-8']") + assert dashr.find_element("meta[http-equiv='X-UA-Compatible']").get_attribute("content") == "IE=edge" app2 = """ library(dash) library(dashHtmlComponents) -app <- Dash$new(meta_tags = list(list(charset = "ISO-8859-1"), list(name = "keywords", content = "dash,pleasant,productive"))) +app <- Dash$new(meta_tags = list(list(charset = "ISO-8859-1"), + list(name = "keywords", content = "dash,pleasant,productive"), + list(`http-equiv` = 'content-type', content = 'text/html'))) app$layout( htmlDiv(children = "Hello world!", @@ -48,6 +52,7 @@ def test_rstm002_test_meta(dashr): dashr.wait_for_text_to_equal( "#hello-div", "Hello world!" - ) + ) assert dashr.find_element("meta[charset='ISO-8859-1']") assert dashr.find_element("meta[name='keywords']").get_attribute("content") == "dash,pleasant,productive" + assert dashr.find_element("meta[http-equiv='content-type']").get_attribute("content") == "text/html" From 1624eef380765c0c9f3546ef1bbef994640da139 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 5 Nov 2019 10:41:23 -0500 Subject: [PATCH 7/8] add reference to meta tag PR --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d67efef..d0d9f58f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. ## Unreleased ### Added +- Support for adding `` tags to index [#142](https://github.com/plotly/dashR/pull/142) - Hot reloading now supported in debug mode [#127](https://github.com/plotly/dashR/pull/127) - Support for displaying Dash for R applications within RStudio's viewer pane when `use_viewer = TRUE` - Clientside callbacks written in JavaScript are now supported [#130](https://github.com/plotly/dashR/pull/130) From d459cbe576c85074530ac5f32f22720e02349847 Mon Sep 17 00:00:00 2001 From: Ryan Patrick Kyle Date: Tue, 5 Nov 2019 11:07:18 -0500 Subject: [PATCH 8/8] :fast_forward: indent meta tags --- R/dash.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/dash.R b/R/dash.R index 27685ca5..5be1b1de 100644 --- a/R/dash.R +++ b/R/dash.R @@ -1238,16 +1238,16 @@ Dash <- R6::R6Class( css_tags <- paste(c(css_deps, css_external, css_assets), - collapse = "\n") + collapse = "\n ") scripts_tags <- paste(c(scripts_deps, scripts_external, scripts_assets, scripts_invoke_renderer), - collapse = "\n") + collapse = "\n ") meta_tags <- paste(generate_meta_tags(private$meta_tags), - collapse = "\n") + collapse = "\n ") return(list(css_tags = css_tags, scripts_tags = scripts_tags,