Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for arbitrary file extensions within Dash for R component libraries #186

Merged
merged 10 commits into from
Apr 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Temporary, for (1) the modified standard test component, (2) the modified generator

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")