-
Notifications
You must be signed in to change notification settings - Fork 0
/
debug_apps.qmd
652 lines (482 loc) · 15.8 KB
/
debug_apps.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
# Debugging apps {#sec-debug-apps}
```{r}
#| eval: true
#| echo: false
#| include: false
source("_common.R")
```
```{r}
#| label: co_box_dev
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "r",
header = "Warning",
contents = "The contents for section are under development. Thank you for your patience."
)
```
:::: {.callout-tip collapse='true' appearance='default'}
## [Accessing applications]{style='font-weight: bold; font-size: 1.15em;'}
::: {style='font-size: 0.95em; color: #282b2d;'}
I've created the [`shinypak` R package](https://mjfrigaard.github.io/shinypak/) In an effort to make each section accessible and easy to follow:
Install `shinypak` using `pak` (or `remotes`):
```{r}
#| code-fold: false
#| message: false
#| warning: false
#| eval: false
# install.packages('pak')
pak::pak('mjfrigaard/shinypak')
```
Review the chapters in each section:
```{r}
#| code-fold: false
#| message: false
#| warning: false
#| collapse: true
library(shinypak)
list_apps(regex = 'debug')
```
Launch the app:
```{r}
#| code-fold: false
#| eval: false
launch(app = "23.1_debug-error")
```
Download the app:
```{r}
#| code-fold: false
#| eval: false
get_app(app = "23.1_debug-error")
```
:::
::::
Debugging Shiny applications usually requires using an interactive debugger,
Capturing reactive values with `reactiveValuesToList()` and sending output to the UI. The benefits of print debugging is that it's easy to implement, doesn't require any special tools or setup, and it provides a direct view of variable states and program flow at specific points.
One of the best tried and tested methods of debugging is simply adding a `cat()` or `print()` call somewhere in your code to print variables or objects to the R console. This is a basic but effective way to track variable changes.
In [`mod_var_input`](https://github.com/mjfrigaard/sap/blob/18d_debugging/R/mod_var_input.R):
- Place a `verbatimTextOutput()` in the ui function.
```{r}
#| eval: false
#| code-fold: false
code("module reactive values"), # <1>
verbatimTextOutput(outputId = ns("mod_vals")) # <2>
)
```
1. Optional label
2. Include the `ns()` for the `inputId`
- In a `renderPrint()`, use `reactiveValuesToList()` to gather the `inputId`s and pass them to `print()` (I'm actually using `lobstr::tree()` to give a clearer display).
```{r}
#| eval: false
#| code-fold: false
output$mod_vals <- renderPrint({
lobstr::tree( # <2>
reactiveValuesToList( # <1>
x = input,
all.names = TRUE # <3>
) # <1>
) # <2>
})
```
1. Collect reactive values in module
2. Print these values to the UI
3. Include all reactive objects
Load the package and run the app:
```{r}
#| label: hot_key_print_module
#| echo: false
#| results: asis
#| eval: true
hot_key(fun = "L")
```
```{verbatim}
#| eval: false
#| code-fold: false
ℹ Loading sap
```
```{r}
#| eval: false
#| code-fold: false
launch_app(options = list(test.mode = FALSE), run = 'p')
```
:::{#fig-debug_print_movies_app_05}
!['Print' in `launch_app()`](images/debug_reactive_values_sidebar.png){#fig-debug_print_movies_app_05 width='40%' fig-align='left'}
`reactiveValuesToList()` printed from `mod_var_inputs`
:::
Now we can see the reactive values from our module in the application sidebar!
We can also use this 'print' method to explore reactive values at various locations in our application. For example, if we wanted to print the reactive values for multiple modules in an app, we can use these methods in the top level `movies_ui()` and `movies_server()` functions.
In the `bslib` portion of [`movies_ui()`](https://github.com/mjfrigaard/sap/blob/18d_debugging/R/movies_ui.R):
- Add `verbatimTextOutput()` with an optional label
```{r}
#| eval: false
#| code-fold: false
code("reactive values"),
verbatimTextOutput(outputId = 'vals')
)
```
In [`movies_server()`](https://github.com/mjfrigaard/sap/blob/18d_debugging/R/movies_server.R):
- Collect all the `inputId`s with `reactiveValuesToList()` and print with `print()` or `lobstr::ast()`
```{r}
#| eval: false
#| code-fold: false
all_vals <- reactive({
reactiveValuesToList(x = input, all.names = TRUE)
})
output$vals <- renderPrint({
lobstr::tree(all_vals())
})
```
Load the package and run the app:
```{r}
#| label: hot_key_print_app
#| echo: false
#| results: asis
#| eval: true
hot_key(fun = "L")
```
```{verbatim}
#| eval: false
#| code-fold: false
ℹ Loading sap
```
```{r}
#| eval: false
#| code-fold: false
launch_app(options = list(test.mode = FALSE),
run = 'p', bslib = TRUE)
```
:::{#fig-debug_print_bslib_movies_app_05}
!['Print' in `launch_app(bslib = TRUE)`](images/debug_print_bslib_movies_app_05.png){#fig-debug_print_bslib_movies_app_05 width='100%' align='center'}
`reactiveValuesToList()` printed from `movies_ui()` and `movies_server()`
:::
Here we can see both levels of reactive values (from the module and the UI/server functions). The handy thing about this method is that the values change when we interact with the application:
:::{#fig-debug_change_print_bslib_movies_app_05}
![Changing values in `launch_app(bslib = TRUE)`](images/debug_change_print_bslib_movies_app_05.png){#fig-debug_change_print_bslib_movies_app_05 width='100%' align='center'}
`y` and `vars-y` *both* update when the UI inputs change
:::
```{r}
#| label: git_box_25.0_debug-error
#| echo: false
#| results: asis
#| eval: true
git_margin_box(
contents = "launch",
fig_pw = '65%',
branch = "25.0_debug-error",
repo = 'sap')
```
## Debugging modules {#sec-debug-modules}
The contents of your Shiny app-package can quickly become a complicated and intertwined combination of functions: utility, modules, UI, server, etc. I like to display the relationship between the functions with abstract syntax trees:[^debug-lobstr]
[^debug-lobstr]: Create abstract syntax trees with the [`ast()` function](https://lobstr.r-lib.org/reference/ast.html) from the [`lobstr` package](https://lobstr.r-lib.org/).
:::{layout="[50,50]" layout-valign="top"}
For example, we know `scatter_plot()` is called from *within* the scatter plot display module function:
``` sh
█─mod_scatter_display_server
└─█─scatter_plot
```
:::
```{r}
#| eval: false
#| include: false
lobstr::ast(
mod_scatter_display_server(
scatter_plot()
)
)
```
:::{layout="[50,50]" layout-valign="top"}
And `mod_scatter_display_server()` is called within `movies_server()`:
``` sh
█─movies_server
├─█─mod_scatter_display_server
│ └─█─scatter_plot
└─█─mod_var_input_server
```
:::
```{r}
#| eval: false
#| include: false
lobstr::ast(
movies_server(
mod_scatter_display_server(
scatter_plot()
),
mod_var_input_server()
)
)
```
:::{layout="[50,50]" layout-valign="top"}
Which is called from inside `sap`:
``` sh
█─launch_app
├─█─movies_ui
│ ├─█─mod_var_input_ui
│ └─█─mod_scatter_display_ui
└─█─movies_server
├─█─mod_scatter_display_server
│ └─█─scatter_plot
└─█─mod_var_input_server
```
:::
```{r}
#| eval: false
#| include: false
lobstr::ast(
launch_app(
movies_ui(
mod_var_input_ui(),
mod_scatter_display_ui()
),
movies_server(
mod_scatter_display_server(
scatter_plot()
),
mod_var_input_server()
)
)
)
```
I find these abstract folder trees helpful when I'm debugging or testing Shiny functions. I can use them to try and anticipate the application call stack (especially when I end up with multiple utility functions or nested modules).
We'll add `browser()` and `observe()` in the `movies_server()` function to capture the behaviors of both modules:
```{r}
#| label: git_box_25.1_debug-selected_vars
#| echo: false
#| results: asis
#| eval: true
git_margin_box(contents = "launch",
fig_pw = '65%',
branch = "25.1_debug-selected_vars",
repo = 'sap')
```
```{r}
#| eval: false
#| code-fold: false
movies_server <- function(input, output, session) {
observe({ # <1>
browser() # <2>
selected_vars <- mod_var_input_server("vars")
mod_scatter_display_server("plot", var_inputs = selected_vars)
}) # <1>
}
```
1. Observer scope
2. Activate debugger
Then we'll load the package and display the app in the **Viewer** pane (below the **Console**):
```{r}
#| label: hot_key_movies_server
#| echo: false
#| results: asis
#| eval: true
hot_key(fun = "L")
```
```{verbatim}
#| eval: false
#| code-fold: false
ℹ Loading sap
```
```{r}
#| eval: false
#| code-fold: false
launch_app(options = list(test.mode = FALSE), run = 'p')
```
```{verbatim}
#| eval: false
#| code-fold: false
ℹ shinyViewerType set to pane
```
The application launches, but `browser()` pauses the execution of the modules and activates the IDE's debugger. This allows us to view the objects that are available in `movies_server()` *before* the variables are passed to the graph rendering functions:
:::{#fig-debug_app_server_02}
![Debugger with call to `browser()` inside `observe()`](images/debug_app_server_02.png){#fig-debug_app_server_02 width='100%' align='center'}
Note that the plot hasn't rendered in the application yet because the call to `observe(browser())` suspends the execution of any subsequent code
:::
In the **Source** pane, we can see the call to `browser()` highlighted (`Browse[1]>` tells us the location in the `browser()` function).
:::{#fig-debug_browser_placement_02}
![`R/movies_server.R` with `observe(browser())`](images/debug_browser_placement_02.png){#fig-debug_browser_placement_02 width='100%' align='center'}
Because `browser()` was called inside `observe()`, the execution will pause, and we can interactively examine values
:::
In the debugger, we want to confirm the returned values from the variable input module, `selected_vars`, which requires us to execute the next two lines of code:
```{r}
#| eval: false
#| code-fold: false
Browse[1]> n
Browse[2]> n
```
:::{#fig-debug_mod_display_server_02}
![Execute the function line-by-line with `n` to create `selected_vars`](images/debug_mod_display_server_02.png){#fig-debug_mod_display_server_02 width='100%' align='center'}
Click the **Next** icon twice to create `selected_vars`
:::
Inside `movies_server()`:
:::{layout="[50,50]" layout-valign="top"}
`mod_var_input_server()` collects the following values and returns a reactive list (**`selected_vars`**):
- Three variable names
- `x`, `y`, `z`
- Graph aesthetics
- `alpha` and `size`
- An optional plot title
- `plot_title`
:::
When we inspect `selected_vars` in the debugger console (*without parentheses*) we see the **method** (i.e., the reactive list of inputs), and not the actual values:
::: {layout="[35, 65]" layout-valign="top"}
``` r
Browse[2]> selected_vars
```
``` sh
reactive({
list(y = input$y, x = input$x,
z = input$z, alpha = input$alpha,
size = input$size,
plot_title = input$plot_title)
})
```
:::
If we check `selected_vars()` (*with parentheses*) in the debugger, we see this contains the **values** from the variable input module:
::: {layout="[35, 65]" layout-valign="top"}
``` r
Browse[2]> selected_vars()
```
``` sh
$y
[1] "audience_score"
$x
[1] "imdb_rating"
$z
[1] "mpaa_rating"
$alpha
[1] 0.5
$size
[1] 2
$plot_title
[1] ""
```
:::
These two steps confirm that the UI values are being collected by the variable input module and stored in `selected_vars`, so the error must be coming from inside the scatter plot display module.
## Module communication {#sec-module-comms}
```{r}
#| label: git_box_25.2_debug-var_inputs
#| echo: false
#| results: asis
#| eval: true
git_margin_box(
contents = "launch",
fig_pw = '65%',
branch = "25.2_debug-var_inputs",
repo = 'sap')
```
We'll repeat a similar process in `mod_scatter_display_server()`, but include the call to `observe(browser())` *after* `moduleServer()`. Then we'll load the package and run the application again:
```{r}
#| eval: false
#| code-fold: false
mod_scatter_display_server <- function(id, var_inputs) {
moduleServer(id, function(input, output, session) {
observe({ # <1>
browser()
# module code
}) # <1>
})
}
```
1. Wrap `browser()` in `observe()` and place after the call to `moduleServer()`
```{r}
#| label: hot_key_module
#| echo: false
#| results: asis
#| eval: true
hot_key(fun = "L")
```
```{verbatim}
#| eval: false
#| code-fold: false
ℹ Loading sap
```
```{r}
#| eval: false
#| code-fold: false
launch_app(options = list(test.mode = FALSE), run = 'p')
```
Inside the module, we want to confirm `var_inputs()` is being created correctly from the `var_inputs` object in `movies_server()`.
:::{layout="[50,50]" layout-valign="top"}
`selected_vars` is the input for `mod_scatter_display_server()` (as **`var_inputs`**)
- `var_inputs` is converted to the reactive `inputs`
- `inputs` is passed to `scatter_plot()` inside `renderPlot()`
:::
::: {layout="[35, 65]" layout-valign="top"}
``` r
Browse[2]> var_inputs()
```
``` sh
$y
[1] "audience_score"
$x
[1] "imdb_rating"
$z
[1] "mpaa_rating"
$alpha
[1] 0.5
$size
[1] 2
$plot_title
[1] ""
```
:::
### Verify variable inputs
Inside the scatter plot display module, the `var_inputs` argument is used to create a reactive `input()` object for the graph created by `scatter_plot()`:
```{r}
#| eval: false
#| code-fold: false
inputs <- reactive({ # <2>
plot_title <- tools::toTitleCase(var_inputs()$plot_title) # <1>
list(
x = var_inputs()$x,
y = var_inputs()$y,
z = var_inputs()$z,
alpha = var_inputs()$alpha,
size = var_inputs()$size,
plot_title = plot_title
) # <1>
}) # <2>
```
1. Variable inputs (from `selected_vars`)
2. `inputs()` for `scatter_plot()`
Now that we've confirmed `var_inputs()` has been created, we'll verify the values are passed correctly *from* `var_inputs()` *to* `inputs()` (which is used to create the scatter plot).
To do this, we'll progress through the module function (using `n` in the debugger console or by clicking **Next**) until the `inputs()` reactive has been created,
:::{#fig-debug_mod_display_server_ouput_02}
![Progressing _past_ `inputs()` tells us it's been created](images/debug_mod_display_server_ouput_02.png){#fig-debug_mod_display_server_ouput_02 width='100%'}
Use `n` in the debugger or click **Next** to progress through the function
:::
::: {layout="[35, 65]" layout-valign="top"}
``` r
Browse[2]> inputs()
```
``` sh
$x
[1] "imdb_rating"
$y
[1] "audience_score"
$z
[1] "mpaa_rating"
$alpha
[1] 0.5
$size
[1] 2
$plot_title
[1] ""
```
:::
These two steps have shown us 1) the modules are communicating properly, and 2) the scatter plot display module contains the list of reactive values needed to render the graph.
```{r}
#| label: co_box_browser_observe
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "g",
size = '1.15',
header = "My approach to debugging",
hsize = '1.20',
fold = FALSE,
look = 'default',
contents = "The Shiny documentation also has a [list of methods](https://shiny.posit.co/r/articles/improve/debugging/) for debugging apps, and learning how to read call stacks (or a stacktrace) will help you debug your shiny app.[^debug-call-stacks], [^call-stack-shiny]")
```
[^call-stack-shiny]: Stack traces are also covered in [Advanced R, 2ed](https://adv-r.hadley.nz/debugging.html#traceback), [Mastering Shiny](https://mastering-shiny.org/action-workflow.html#tracebacks-in-shiny), and in the [Shiny documentation](https://github.com/rstudio/shiny/wiki/Stack-traces-in-R). I've summarized some tips on reading Shiny call stacks in the [stack traces](stack_traces.qmd) section on the Appendix.
[^debug-call-stacks]: Watch [this video](https://www.youtube.com/watch?v=g1h-YDWVRLc) to learn about call stacks and abstract folder trees with `lobstr`.