-
Notifications
You must be signed in to change notification settings - Fork 0
/
rhino.qmd
396 lines (301 loc) · 12.2 KB
/
rhino.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
# rhino {#sec-rhino}
```{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."
)
```
```{r}
#| label: co_box_tldr
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "o",
look = "default", hsize = "1.10", size = "1.05",
header = "TLDR   ![](images/rhino.png){width='10%'}",
fold = TRUE,
contents = "
<br>
**WARNING**: `rhino` isn't like the previous two frameworks we've covered in this section, because `rhino` doesn't create an app-package:
<br>
- `rhino` apps rely on [`renv`](https://rstudio.github.io/renv/articles/renv.html) and [`box`](https://klmr.me/box/) for managing imported dependencies (instead of the `DESCRIPTION` and `NAMESPACE` files in an R package).
- `rhino` requires [node.js](https://www.wikiwand.com/en/Node.js), an open-source JavaScript runtime environment.
"
)
```
---
This chapter briefly describes a version of `sap` built using [`rhino`](https://appsilon.github.io/rhino/). The resulting app (`rap`) is in the [`21_rhino`](https://github.com/mjfrigaard/sap/tree/21_rhino) branch.
The branch in this chapter is slightly different than the previous `golem` and `leprechaun` branches, because instead of loading, documenting, and installing `rap`, we're going to re-initialize the IDE by selecting **Session** > **Terminate R...**
::: {#fig-rhino_session_terminate}
![Re-initialize the IDE](images/rhino_session_terminate.png){#fig-rhino_session_terminate width='30%' fig-align='center'}
On the `21_rhino` branch, re-initialize the IDE (instead of loading, documenting, and installing)
:::
When the IDE re-opens, we see the `rap` files and notice the **Build** pane has been removed:
::: {#fig-rhino_app_ide}
![`rhino` app IDE](images/rhino_app_ide.png){#fig-rhino_session_terminate width='100%' fig-align='center'}
Notice the **Build** pane has been removed from the `21_rhino` branch
:::
The **Build** pane is deactivated because **`rhino` applications aren't R packages**.[^rhino-terminate]
Launch the application in `rap` by opening the `app.R` file and clicking **Run App** (or by passing `rhino::app()` into the **Console**).
```{r}
#| label: git_box_rap
#| echo: false
#| results: asis
#| eval: true
git_margin_box(
contents = "launch",
fig_pw = '75%',
branch = "21_rhino",
repo = 'sap')
```
::: {#fig-rhino_run_app}
![Calling `rhino::app()`](images/rhino_run_app.png){#fig-rhino_run_app width='100%' fig-align='center'}
Running the application in `rap`
:::
:::: {.callout-tip collapse='true' appearance='simple'}
## [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 = 'rhino')
```
Launch the app:
```{r}
#| code-fold: false
#| eval: false
launch(app = "21_rhino")
```
Download the app:
```{r}
#| code-fold: false
#| eval: false
get_app(app = "21_rhino")
```
:::
::::
[^rhino-terminate]: I re-initialize the session on the `21_rhino` branch so I'm not tempted to load, document, install, or test the code using the IDE.
## `rap` (a `rhino` app)
The files in `rap` are below:
```{bash}
#| eval: false
#| code-fold: false
├── .Rprofile # <1>
├── .github/ # <2>
│ └── workflows # <2>
├── .gitignore
├── .lintr # <3>
├── .renvignore # <4>
├── .rscignore
├── README.md
├── app
│ ├── js
│ ├── logic
│ ├── main.R
│ ├── static
│ ├── styles
│ └── view
├── app.R
├── config.yml
├── dependencies.R. # <5>
├── sap.Rproj
├── renv # <6>
│ ├── .gitignore.
│ ├── activate.R
│ ├── library
│ ├── settings.json
│ └── staging
├── renv.lock # <6>
├── rhino.yml
└── tests
├── cypress
├── cypress.json
└── testthat
24 directories, 31 files
```
1. Activates the [`renv` package](https://rstudio.github.io/renv/articles/renv.html)
2. CI/CD via [GitHub actions](https://github.com/r-lib/actions)
3. Lintr (from [`lintr`](https://lintr.r-lib.org/) package)
4. `renv` ignore (works like `.gitignore`)
5. `rhino` app dependencies
6. `renv` library of packages in app project
As we can see, most of the standard R package folders and files are missing from `rap`, because `rhino` applications use the [`box` package](https://klmr.me/box/) for importing dependencies and organizing code.[^rhino-box-depends]
## `rhino` features
The [`rhino` website](https://appsilon.github.io/rhino/articles/explanation/application-structure.html) explains the philosophy behind the application structure above, so I won't repeat that information here. However, I highly recommend reading the available documentation on [`rhino`](https://appsilon.github.io/rhino/articles/tutorial/create-your-first-rhino-app.html) and [`box`](https://klmr.me/box/articles/box.html) before deciding to adopt this framework.[^rhino-recommended-documentation]
[^rhino-box-depends]: Imported dependencies in `rhino` apps use [`box` modules](https://klmr.me/box/articles/box.html) instead of the `DESCRIPTION` and `NAMESPACE`.
[^rhino-recommended-documentation]: Be sure to read up on [testing box modules](https://klmr.me/box/articles/testing.html) and `rhino` applications [with cypress](https://appsilon.github.io/rhino/articles/tutorial/write-end-to-end-tests-with-cypress.html) and [`shinytest2`](https://appsilon.github.io/rhino/articles/how-to/use-shinytest2.html).
## `box` modules
A `box` module (not to be confused with a Shiny module) is a collection of `.R` scripts in a specified folder. The modules in a new `rhino` app are stored in the `app/logic/` and `app/view/` folders:[^rhino-code-structure]
[^rhino-code-structure]: `rhino` [recommends](https://appsilon.github.io/rhino/articles/explanation/application-structure.html#philosophy) placing non-Shiny code in the `app/logic` folder and keeping all Shiny modules and reactive code in `app/view`.
```{bash}
#| eval: false
#| code-fold: false
app
├── js/ # <1>
├── logic/ # <2>
├── main.R # <3>
├── static/ # <4>
├── styles/ # <5>
└── view/ # <6>
6 directories, 1 file
```
1. JavaScript code
2. Non-shiny code
3. Primary app file
4. Static `.js` or `.css`
5. App CSS files
6. Shiny modules and app code
### Utility functions
In `rap`, I've placed the non-Shiny utility functions (i.e., the business logic) in `app/logic`:
```{bash}
#| eval: false
#| code-fold: false
app/logic
├── __init__.R
├── data.R # <1>
└── plot.R # <2>
1 directory, 4 files
```
1. Load `movies` data
2. `scatter_plot()` utility function
### Shiny modules
Our Shiny `box` modules are placed in `app/view`, and separated into `inputs` and `display`:
```{bash}
#| eval: false
#| code-fold: false
app/view
├── __init__.R
├── display.R # <1>
└── inputs.R # <2>
1 directory, 3 files
```
1. similar to the code from `R/mod_var_input.R`
2. similar to the code from `R/mod_scatter_display.R`
`app/view/inputs` collects and returns the reactive values from the UI. The `app/view/display` module includes the `app/logic/data` and `app/logic/plot` modules.
```{r}
#| eval: false
#| code-fold: false
# app/view/display.R
# import data and plot modules
box::use(
app / logic / data,
app / logic / plot
)
```
## [`app/main.R`]{style="font-size: 1.05em;"}
The `app/main.R` file contains the primary UI and Server functions for the application. This file adds the `shiny` functions *and* the `inputs` and `display` modules from `app/view`:
```{r}
#| eval: false
#| code-fold: false
# app/main.R
# shiny functions
box::use(
shiny[
NS, fluidPage, sidebarLayout, sidebarPanel,
mainPanel, fluidRow, column, tags, icon,
textOutput, moduleServer, renderText
]
)
# import modules
box::use(
# load inputs module ----
app / view / inputs,
# load display module ----
app / view / display
)
```
Note that we don't need to import `app/logic` modules in `app/main.R`, because they're imported in their respective `app/view` modules.
## Tests
`rhino` apps have support for testing with `testthat`, `shiny::testServer()`, `shinytest2`, and [Cypress](https://appsilon.github.io/rhino/articles/tutorial/write-end-to-end-tests-with-cypress.html).
```{bash}
#| eval: false
#| code-fold: false
tests/
├── cypress # <1>
│ └── integration
│ └── app.spec.js
├── cypress.json # <1>
└── testthat # <2>
└── test-main.R # <2>
4 directories, 3 files
```
1. Cypress test infrastructure
1. `testthat` test infrastructure
Below is the boilerplate test code in the `tests/testthat/test-main.R` file:
```{r}
#| eval: false
#| code-fold: false
box::use( # <1>
shiny[testServer],
testthat[...],
)
box::use(
app/main[...],
) # <1>
test_that("main server works", { # <2>
testServer(server, {
expect_equal(output$message, "Hello!")
})
}) # <2>
```
1. `box` module importing test package functions
2. Using `shiny::testServer()` and `testthat::test_that()` functions in test.
I've included tests for the utility functions and modules in the `21_rhino` branch, but I'll cover testing with rhino elsewhere.[^testing-rhino-apps]
[^testing-rhino-apps]: See the [Shiny frameworks](https://mjfrigaard.github.io/sfw/) supplemental website for more information on testing your `rhino` app.
## `rhino` dependencies
In `rhino` apps, dependencies are managed by [`renv`](https://rstudio.github.io/renv/articles/renv.html) and the `dependencies.R` file. The `renv` package is designed to,
> *"create[s] and manage[s] project-local R libraries, save[s] the state of these libraries to a 'lockfile', and later restore[s] the library as required."* [^rhino-renv-description]
The `rhino::pkg_install()` helper function updates *both* the `dependencies.R` file and `renv` library. Using `dependencies.R`, `renv`, and `box` modules removes the need to manage dependencies in a `DESCRIPTION` or `NAMESPACE` file.[^rhino-renv-config]
[^rhino-renv-description]: As described in `renv`'s [DESCRIPTION file](https://github.com/rstudio/renv/blob/main/DESCRIPTION)
[^rhino-renv-config]: Be sure to read the [`renv` configuration article](https://appsilon.github.io/rhino/articles/explanation/renv-configuration.html) for a better understanding on how it works with rhino apps.
## Recap {.unnumbered}
```{r}
#| label: co_box_recap
#| echo: false
#| results: asis
#| eval: true
co_box(
color = "g",
look = "default", hsize = "1.10", size = "1.05",
header = "RECAP   ![](images/rhino.png){width='10%'}",
fold = FALSE,
contents = "
<br>
`rhino` takes a novel and innovative approach to developing Shiny applications (and covering all the ways they differ from app-packages is beyond the scope of this book). Feel free to review the code in the [`21_rhino` branch](https://github.com/mjfrigaard/sap/tree/21_rhino) for a better understanding of how the `box` modules are structured and used within the `ui` and `server`.
The `rhino` framework isn't used as wildly `golem`,[^rhino-cran-downloads] but it's been gaining popularity (and has been used in a recent [pilot FDA submission](https://github.com/appsilon/rhino-fda-pilot)).
![`rhino` CRAN downloads](images/rhino_cran_downloads.png){width='100%' fig-align='center'}")
```
```{r}
#| label: git_contrib_box
#| echo: false
#| results: asis
#| eval: true
git_contrib_box()
```
[^rhino-cran-downloads]: Check for yourself on [cran-downloads](https://hadley.shinyapps.io/cran-downloads/)