Skip to content

Commit

Permalink
Fix setCallbackContext for wildcard and ordinary inputs (#237)
Browse files Browse the repository at this point in the history
* Update setCallbackContext

* Adding graphs test

* Slight fix

* bump version and update CHANGELOG

* Less flaky test

Co-authored-by: rpkyle <ryan@plotly.com>
  • Loading branch information
HammadTheOne and rpkyle authored Oct 30, 2020
1 parent b238a57 commit ff858ac
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to `dash` will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [0.8.1] - 2020-10-29
### Fixed
- Fixes a minor bug in `setCallbackContext` (described in [#236](https://github.com/plotly/dashR/issues/236)) which prevented pattern-matching callbacks from working properly if one or more `input` statements did not include a selector. [#237](https://github.com/plotly/dashR/pull/237)

## [0.8.0] - 2020-10-27
### Fixed
- Usage of `glue` has been corrected to address [#232](https://github.com/plotly/dashR/issues/232) via [#233](https://github.com/plotly/dashR/pull/233).
Expand Down
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: dash
Title: An Interface to the Dash Ecosystem for Authoring Reactive Web Applications
Version: 0.8.0
Version: 0.8.1
Authors@R: c(person("Chris", "Parmer", role = c("aut"), email = "chris@plotly.com"), person("Ryan Patrick", "Kyle", role = c("aut", "cre"), comment = c(ORCID = "0000-0001-5829-9867"), email = "ryan@plotly.com"), person("Carson", "Sievert", role = c("aut"), comment = c(ORCID = "0000-0002-4958-2844")), person("Hammad", "Khan", role = c("aut"), comment = c(ORCID = "0000-0003-2479-9841"), email = "hammadkhan@plotly.com"), person(family = "Plotly Technologies", role = "cph"))
Description: A framework for building analytical web applications, Dash offers a pleasant and productive development experience. No JavaScript required.
Depends:
Expand Down
14 changes: 9 additions & 5 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -1054,26 +1054,30 @@ setCallbackContext <- function(callback_elements) {
function(x) {
input_id <- splitIdProp(x)[1]
prop <- splitIdProp(x)[2]

# The following conditionals check whether the callback is a pattern-matching callback and if it has been triggered.
if (startsWith(input_id, "{")){
id_match <- vapply(callback_elements$inputs, function(x) {
x <- unlist(x)
any(x[grepl("id.", names(x))] %in% jsonlite::fromJSON(input_id)[[1]])
}, logical(1))[[1]]
} else {
id_match <- vapply(callback_elements$inputs, function(x) x$id %in% input_id, logical(1))
id_match <- vapply(callback_elements$inputs, function(x) {
unlist(x)
any(x$id %in% input_id)}, logical(1))
}

if (startsWith(input_id, "{")){
prop_match <- vapply(callback_elements$inputs, function(x) {
x <- unlist(x)
any(x[names(x) == "property"] %in% prop)
}, logical(1))[[1]]
} else {
prop_match <- vapply(callback_elements$inputs, function(x) x$property %in% prop, logical(1))
prop_match <- vapply(callback_elements$inputs, function(x) {
unlist(x)
any(x$property %in% prop)}, logical(1))
}

if (startsWith(input_id, "{")){
if (length(callback_elements$inputs) == 1 || !is.null(unlist(callback_elements$inputs, recursive = F)$value)) {
value <- sapply(callback_elements$inputs[id_match & prop_match], `[[`, "value")
Expand Down
127 changes: 127 additions & 0 deletions tests/integration/callbacks/test_pattern_matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,120 @@
"""


graphs_app = """
library(dash)
library(dashHtmlComponents)
library(dashCoreComponents)
library(plotly)
df <- read.csv(
file = "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv",
stringsAsFactor=FALSE,
check.names=FALSE
)
app <- Dash$new()
app$layout(
htmlDiv(
list(
htmlDiv(
list(
dccDropdown(options = lapply(unique(df[,"country"]), function(x) {
list(label = x, value = x)
}),
value = "Canada",
id = "country",
style = list(display = "inline-block",
width = 200)
),
htmlButton(
"add Chart",
id = "add-chart",
n_clicks = 0,
style = list(display = "inline-block")
)
)
),
htmlDiv(id = "container", children=list()),
htmlDiv(id = "output-delay")
)
)
)
create_figure <- function(df, column_x, column_y, country) {
df <- df[which(df[, "country"] == country),]
if (column_x == "year") {
fig <- plot_ly(df, x = df[,column_x], y = df[,column_y], name = column_x, type = "scatter", mode = "lines")
} else {
fig <- plot_ly(df, x = df[,column_x], y = df[,column_y], name = column_x, type = "scatter", mode = "markers")
}
fig <- plotly::layout(fig, plot_bgcolor="lightblue", xaxis = list(title=""),
yaxis = list(title=""), title=list(text=paste(country, column_y, "vs", column_x),
xanchor="right", margin_l=10, margin_r=0, margin_b=30))
return(fig)
}
app$callback(
output = list(
output(id = "container", property = "children"),
output(id = "output-delay", property = "children")
),
params = list(
input(id = "add-chart", property = "n_clicks"),
state(id = "country", property = "value"),
state(id = "container", property = "children")
),
function(n_clicks, country, children) {
default_column_x <- "year"
default_column_y <- "gdpPercap"
new_element <- htmlDiv(
style = list(width = "23%", display = "inline-block", outline = "thin lightgrey solid", padding = 10),
children = list(
dccGraph(
id = list(type = "dynamic-output", index = n_clicks),
style = list(height = 300),
figure = create_figure(df, default_column_x, default_column_y, country)
),
dccDropdown(
id = list(type = "dynamic-dropdown-x", index = n_clicks),
options = lapply(colnames(df), function(x) {
list(label = x, value = x)
}),
value = default_column_x
),
dccDropdown(
id = list(type = "dynamic-dropdown-y", index = n_clicks),
options = lapply(colnames(df), function(x) {
list(label = x, value = x)
}),
value = default_column_y
)
)
)
children <- c(children, list(new_element))
return(list(children, n_clicks))
}
)
app$callback(
output(id = list("index" = MATCH, "type" = "dynamic-output"), property = "figure"),
params = list(
input(id = list("index" = MATCH, "type" = "dynamic-dropdown-x"), property = "value"),
input(id = list("index" = MATCH, "type" = "dynamic-dropdown-y"), property = "value"),
input(id = "country", property = "value")
),
function(column_x, column_y, country) {
return(create_figure(df, column_x, column_y, country))
}
)
app$run_server()
"""


def test_rpmc001_pattern_matching_all(dashr):
dashr.start_server(all_app)
dashr.find_element("#add-filter").click()
Expand Down Expand Up @@ -370,3 +484,16 @@ def test_rpmc004_pattern_matching_todo(dashr):
dashr.find_element("#add").click()
dashr.find_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"done\\"\\}').click()
assert dashr.wait_for_text_to_equal("#totals", "1 of 1 items completed - 100%")


def test_rpmc005_pattern_matching_graphs(dashr):
dashr.start_server(graphs_app)
dashr.select_dcc_dropdown("#country", "Cameroon")
dashr.wait_for_text_to_equal("#output-delay", "0")
dashr.find_element("#add-chart").click()
dashr.wait_for_text_to_equal("#output-delay", "1")
dashr.find_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-output\\"\\}')
dashr.select_dcc_dropdown('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-dropdown-x\\"\\}', "year")
dashr.select_dcc_dropdown('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-dropdown-y\\"\\}', "pop")
dashr.percy_snapshot("r-pmc-graphs")
dashr.wait_for_element('#\\{\\"index\\"\\:1\\,\\"type\\"\\:\\"dynamic-output\\"\\}')

0 comments on commit ff858ac

Please sign in to comment.