Skip to content

Commit

Permalink
Provide support for arbitrary file extensions within Dash for R compo…
Browse files Browse the repository at this point in the history
…nent libraries (#186)

* ✨ +binary file loader to dash_suite handler
* use guess_type in get_mimetype

* - use DashPy arbitrary extension branch
- test arbitrary extension + snapshots

* use the styled_app...

* wait for font to be loaded

* changelog

* improve test!

Co-authored-by: Marc-André Rivet <marc-andre.rivet@plot.ly>
  • Loading branch information
rpkyle and Marc-Andre-Rivet authored Apr 24, 2020
2 parents e082bc4 + c0c9201 commit a4d9dec
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
command: |
python -m venv venv
. venv/bin/activate
git clone --depth 1 https://github.com/plotly/dash.git dash-main
git clone -b 481-arbitrary-extensions --depth 1 https://github.com/plotly/dash.git dash-main
cd dash-main && pip install -e .[dev,testing] --progress-bar off && cd ..
cd dash-main/\@plotly/dash-generator-test-component-nested && npm ci && npm run build && sudo R CMD INSTALL . && cd ../../..
cd dash-main/\@plotly/dash-generator-test-component-standard && npm ci && npm run build && sudo R CMD INSTALL . && cd ../../..
Expand Down
15 changes: 8 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file.
## Unreleased
### Added
- Support for inline clientside callbacks in JavaScript [#140](https://github.com/plotly/dashR/pull/140)
- Support for arbitrary file extensions for assets within component libraries [#186](https://github.com/plotly/dashR/pull/186)

## [0.3.0] - 2020-02-12
### Added
Expand Down Expand Up @@ -53,8 +54,8 @@ All notable changes to this project will be documented in this file.
- Initial release
- Support for `plot_ly` and `ggplotly` "subplots" [#84](https://github.com/plotly/dashR/pull/84)
- Improved debugging support [#87](https://github.com/plotly/dashR/pull/87), including Dash Dev Tools and `debug` mode
- Provide a useful warning message when JS dependencies cannot be found [#81](https://github.com/plotly/dashR/pull/81)
- Support for externalized PropTypes introduced
- Provide a useful warning message when JS dependencies cannot be found [#81](https://github.com/plotly/dashR/pull/81)
- Support for externalized PropTypes introduced
- Support for `callback_context` added
- Options to set `dev_tools_ui` and `dev_tools_props_check` added

Expand All @@ -69,11 +70,11 @@ All notable changes to this project will be documented in this file.

### Fixed
- CSS dependencies are now properly loaded [#94](https://github.com/plotly/dashR/pull/94)


## [0.0.7] - 2019-04-09
### Removed
- `dependencies_set`, `dependencies_get`, and `dependencies_get_internal` methods removed from package
- `dependencies_set`, `dependencies_get`, and `dependencies_get_internal` methods removed from package


### [0.0.6] - 2019-04-08
Expand Down Expand Up @@ -107,9 +108,9 @@ All notable changes to this project will be documented in this file.

### [0.0.3] - 2019-03-08
### Added
- `assert_valid_callbacks` to validate callback handler definitions and ordering of `input` and `state` using
- `assert_valid_callbacks` to validate callback handler definitions and ordering of `input` and `state` using
new `valid_seq` function

### Changed
- Callback method and handling refactored to match current Dash for Python API [#51](https://github.com/plotly/dashR/pull/51)
- Handler function for callbacks now passed via `func` argument to `app$callback()`
Expand Down
32 changes: 21 additions & 11 deletions R/dash.R
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,27 @@ Dash <- R6::R6Class(
# if debug mode is not active
dep_path <- system.file(dep_pkg$rpkg_path,
package = dep_pkg$rpkg_name)

response$type <- get_mimetype(filename)

response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")

if (grepl("text|javascript", response$type)) {
response$body <- readLines(dep_path,
warn = FALSE,
encoding = "UTF-8")

if (private$compress && length(response$body) > 0) {
response <- tryCompress(request, response)
}
} else {
file_handle <- file(dep_path, "rb")
file_size <- file.size(dep_path)

response$body <- readBin(dep_path,
raw(),
file_size)
close(file_handle)
}

if (!private$debug && has_fingerprint) {
response$status <- 200L
response$set_header('Cache-Control',
Expand All @@ -587,13 +603,8 @@ Dash <- R6::R6Class(
} else {
response$status <- 200L
}

response$type <- get_mimetype(filename)
}

if (private$compress && length(response$body) > 0)
response <- tryCompress(request, response)

TRUE
})

Expand Down Expand Up @@ -629,8 +640,7 @@ Dash <- R6::R6Class(
# and opens/closes a file handle if the type is assumed to be binary
if (!(is.null(asset_path)) && file.exists(asset_path)) {
response$type <- request$headers[["Content-Type"]] %||%
mime::guess_type(asset_to_match,
empty = "application/octet-stream")
get_mimetype(asset_to_match)

if (grepl("text|javascript", response$type)) {
response$body <- readLines(asset_path,
Expand Down
16 changes: 9 additions & 7 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,9 @@ assert_no_names <- function (x)
# filtered out by the subsequent vapply statement
clean_dependencies <- function(deps) {
dep_list <- lapply(deps, function(x) {
if (is.null(x$src$file) | (is.null(x$script) & is.null(x$stylesheet)) | (is.null(x$package))) {
if (is.null(x$src$file) | (is.null(x$script) & is.null(x$stylesheet) & is.null(x$other)) | (is.null(x$package))) {
if (is.null(x$src$href))
stop(sprintf("Script or CSS dependencies with NULL href fields must include a file path, dependency name, and R package name."), call. = FALSE)
stop(sprintf("Script, CSS, or other dependencies with NULL href fields must include a file path, dependency name, and R package name."), call. = FALSE)
else
return(NULL)
}
Expand Down Expand Up @@ -504,7 +504,9 @@ get_package_mapping <- function(script_name, url_package, dependencies) {
dep_path <- file.path(x$src$file, x$script)
else if (!is.null(x$stylesheet))
dep_path <- file.path(x$src$file, x$stylesheet)

else if (!is.null(x$other))
dep_path <- file.path(x$src$file, x$other)

# remove n>1 slashes and replace with / if present;
# htmltools seems to permit // in pathnames, but
# this complicates string matching unless they're
Expand All @@ -531,17 +533,17 @@ get_package_mapping <- function(script_name, url_package, dependencies) {
}

get_mimetype <- function(filename) {
# the tools package is available to all
filename_ext <- file_ext(filename)
filename_ext <- getFileExt(filename)

if (filename_ext == 'js')
return('application/JavaScript')
else if (filename_ext == 'css')
return('text/css')
else if (filename_ext == 'map')
else if (filename_ext %in% c('js.map', 'map'))
return('application/json')
else
return(NULL)
return(mime::guess_type(filename,
empty = "application/octet-stream"))
}

generate_css_dist_html <- function(href,
Expand Down
49 changes: 46 additions & 3 deletions tests/integration/test_generation.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from selenium.webdriver.support.select import Select
import time, os

from selenium.webdriver.support.ui import WebDriverWait

app = """
library(dash)
Expand All @@ -17,9 +16,53 @@
app$run_server()
"""

styled_app = """
library(dash)
library(dashHtmlComponents)
library(dashGeneratorTestComponentStandard)
app <- Dash$new()
app$layout(htmlDiv(list(
htmlButton(id='btn', list('Click')),
htmlDiv(id='container')
)))
app$callback(output(id = 'container', property = 'children'),
list(input(id = 'btn', property = 'n_clicks')),
function(n_clicks) {
if (is.null(unlist(n_clicks))) {
return(dashNoUpdate())
} else {
return(list(dgtc_standardMyStandardComponent(id="standard", value="Standard", style=list(fontFamily="godfather"))))
}
})
app$run_server()
"""


def test_gene001_simple_callback(dashr):
dashr.start_server(app)

assert dashr.wait_for_element("#standard").text == "Standard"
assert dashr.wait_for_element("#nested").text == "Nested"
assert dashr.wait_for_element("#nested").text == "Nested"

dashr.percy_snapshot("gene001-simple-callback")


def test_gene002_arbitrary_resources(dashr):
dashr.start_server(styled_app)

assert (
dashr.driver.execute_script("return document.fonts.check('1em godfather')")
is False
)

dashr.wait_for_element("#btn").click()
assert dashr.wait_for_element("#standard").text == "Standard"

WebDriverWait(dashr.driver, 10).until(
lambda _: dashr.driver.execute_script("return document.fonts.check('1em godfather')") is True,
)

dashr.percy_snapshot("gene002-arbitrary-resource")

0 comments on commit a4d9dec

Please sign in to comment.