Compare commits

...

4 Commits

Author SHA1 Message Date
Garrick Aden-Buie
bd7e1a5203 Disable private seed in testmode
Fixes #3816

Enables deterministic tests when the `shiny.testmode` option
is TRUE or when an app is run with `test.mode = TRUE`.
2023-04-28 11:22:29 -04:00
Winston Chang
62bb21d5b6 Merge pull request #3804 from rstudio/docs/ex-download-button 2023-04-14 15:48:19 -05:00
Garrick Aden-Buie
4f85268d44 More complete downloadButton() example 2023-04-13 09:04:40 -04:00
Carson Sievert
611e517bb8 Rename actionQueue to taskQueue, add more context to the NEWS item (#3801) 2023-03-31 14:38:28 -05:00
12 changed files with 52 additions and 23 deletions

View File

@@ -6,7 +6,7 @@
### New features and improvements
* Closed #789: `<script>` loaded from dynamic UI are no longer loaded using synchronous `XMLHttpRequest` (via jQuery). (#3666)
* Closed #789: Dynamic UI is now rendered asynchronously, thanks in part to the newly exported `Shiny.renderDependenciesAsync()`, `Shiny.renderHtmlAsync()`, and `Shiny.renderContentAsync()`. Importantly, this means `<script>` tags are now loaded asynchronously (the old way used `XMLHttpRequest`, which is synchronous). In addition, `Shiny` now manages a queue of async tasks (exposed via `Shiny.shinyapp.taskQueue`) so that order of execution is preserved. (#3666)
* For `reactiveValues()` objects, whenever the `$names()` or `$values()` methods are called, the keys are now returned in the order that they were inserted. (#3774)

View File

@@ -1200,19 +1200,25 @@ uiOutput <- htmlOutput
#' @examples
#' \dontrun{
#' ui <- fluidPage(
#' p("Choose a dataset to download."),
#' selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
#' downloadButton("downloadData", "Download")
#' )
#'
#' server <- function(input, output) {
#' # Our dataset
#' data <- mtcars
#' # The requested dataset
#' data <- reactive({
#' get(input$dataset)
#' })
#'
#' output$downloadData <- downloadHandler(
#' filename = function() {
#' paste("data-", Sys.Date(), ".csv", sep="")
#' # Use the selected dataset as the suggested file name
#' paste0(input$dataset, ".csv")
#' },
#' content = function(file) {
#' write.csv(data, file)
#' # Write the dataset to the `file` that will be downloaded
#' write.csv(data(), file)
#' }
#' )
#' }

View File

@@ -52,6 +52,11 @@ repeatable <- function(rngfunc, seed = stats::runif(1, 0, .Machine$integer.max))
# Evaluate an expression using Shiny's own private stream of
# randomness (not affected by set.seed).
withPrivateSeed <- function(expr) {
# Don't use the private seed in testmode for deterministic tests
if (isTRUE(getOption("shiny.testmode", getShinyOption("testmode")))) {
return(expr)
}
# Save the old seed if present.
if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)) {
hasOrigSeed <- TRUE

View File

@@ -15583,7 +15583,7 @@
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(function() {
this.shinyapp.taskQueue.enqueue(function() {
_this.sendIsEnqueued = false;
_this._sendNow();
});
@@ -17666,7 +17666,7 @@
function ShinyApp2() {
_classCallCheck36(this, ShinyApp2);
_defineProperty18(this, "$socket", null);
_defineProperty18(this, "actionQueue", new AsyncQueue());
_defineProperty18(this, "taskQueue", new AsyncQueue());
_defineProperty18(this, "config", null);
_defineProperty18(this, "$inputValues", {});
_defineProperty18(this, "$initialInput", null);
@@ -17875,7 +17875,7 @@
_this.startActionQueueLoop();
};
socket.onmessage = function(e) {
_this.actionQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
_this.taskQueue.enqueue(/* @__PURE__ */ _asyncToGenerator8(/* @__PURE__ */ _regeneratorRuntime8().mark(function _callee2() {
return _regeneratorRuntime8().wrap(function _callee2$(_context2) {
while (1)
switch (_context2.prev = _context2.next) {
@@ -17918,7 +17918,7 @@
break;
}
_context3.next = 3;
return this.actionQueue.dequeue();
return this.taskQueue.dequeue();
case 3:
action = _context3.sent;
_context3.prev = 4;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -36,19 +36,25 @@ function.
\examples{
\dontrun{
ui <- fluidPage(
p("Choose a dataset to download."),
selectInput("dataset", "Dataset", choices = c("mtcars", "airquality")),
downloadButton("downloadData", "Download")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
# The requested dataset
data <- reactive({
get(input$dataset)
})
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".csv", sep="")
# Use the selected dataset as the suggested file name
paste0(input$dataset, ".csv")
},
content = function(file) {
write.csv(data, file)
# Write the dataset to the `file` that will be downloaded
write.csv(data(), file)
}
)
}

View File

@@ -22,7 +22,7 @@ class InputBatchSender implements InputPolicy {
if (opts.priority === "event") {
this._sendNow();
} else if (!this.sendIsEnqueued) {
this.shinyapp.actionQueue.enqueue(() => {
this.shinyapp.taskQueue.enqueue(() => {
this.sendIsEnqueued = false;
this._sendNow();
});

View File

@@ -114,7 +114,7 @@ class ShinyApp {
// without overlapping. This is used for handling incoming messages from the
// server and scheduling outgoing messages to the server, and can be used for
// other things tasks as well.
actionQueue = new AsyncQueue<() => Promise<void> | void>();
taskQueue = new AsyncQueue<() => Promise<void> | void>();
config: {
workerId: string;
@@ -240,7 +240,7 @@ class ShinyApp {
this.startActionQueueLoop();
};
socket.onmessage = (e) => {
this.actionQueue.enqueue(async () => await this.dispatchMessage(e.data));
this.taskQueue.enqueue(async () => await this.dispatchMessage(e.data));
};
// Called when a successfully-opened websocket is closed, or when an
// attempt to open a connection fails.
@@ -266,7 +266,7 @@ class ShinyApp {
async startActionQueueLoop(): Promise<void> {
// eslint-disable-next-line no-constant-condition
while (true) {
const action = await this.actionQueue.dequeue();
const action = await this.taskQueue.dequeue();
try {
await action();

View File

@@ -20,7 +20,7 @@ type MessageValue = Parameters<WebSocket["send"]>[0];
declare function addCustomMessageHandler(type: string, handler: Handler): void;
declare class ShinyApp {
$socket: ShinyWebSocket | null;
actionQueue: AsyncQueue<() => Promise<void> | void>;
taskQueue: AsyncQueue<() => Promise<void> | void>;
config: {
workerId: string;
sessionId: string;

View File

@@ -50,6 +50,18 @@ test_that("Setting the private seed explicitly results in identical values", {
expect_identical(id7, id8)
})
test_that("Private seed is disabled in testmode", {
oldopts <- options(shiny.testmode = TRUE)
on.exit(options(oldopts), add = TRUE)
set.seed(0)
id1 <- createUniqueId(4)
set.seed(0)
id2 <- createUniqueId(4)
expect_identical(id1, id2)
})
test_that("Private and 'public' random streams are independent and work the same", {
set.seed(0)
public <- c(runif(1), runif(1), runif(1))