Merge pull request #6 from agdamsbo/shiny-app

Shiny app
This commit is contained in:
Andreas Gammelgaard Damsbo 2024-02-26 19:59:24 +00:00 committed by GitHub
commit f25361fb79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 451 additions and 16 deletions

2
.gitignore vendored
View File

@ -8,3 +8,5 @@ logo.R
.DS_Store
docs
drafting
\.DS_Store
.DS_Store

View File

@ -36,11 +36,13 @@ Suggests:
here,
styler,
devtools,
roxygen2
roxygen2,
openxlsx2,
rsconnect
License: GPL (>= 3)
Encoding: UTF-8
LazyData: true
RoxygenNote: 7.3.0
RoxygenNote: 7.3.1
URL: https://github.com/agdamsbo/REDCapCAST, https://agdamsbo.github.io/REDCapCAST/
BugReports: https://github.com/agdamsbo/REDCapCAST/issues
Imports:
@ -64,5 +66,6 @@ Collate:
'redcap_wider.R'
'redcapcast_data.R'
'redcapcast_meta.R'
'shiny_cast.R'
Language: en-US
VignetteBuilder: knitr

View File

@ -6,17 +6,22 @@ export(d2w)
export(ds2dd)
export(ds2dd_detailed)
export(easy_redcap)
export(file_extension)
export(focused_metadata)
export(get_api_key)
export(guess_time_only_filter)
export(is_repeated_longitudinal)
export(match_fields_to_form)
export(read_input)
export(read_redcap_instrument)
export(read_redcap_tables)
export(redcap_wider)
export(sanitize_split)
export(server_factory)
export(shiny_cast)
export(split_non_repeating_forms)
export(strsplitx)
export(ui_factory)
importFrom(REDCapR,redcap_event_instruments)
importFrom(REDCapR,redcap_metadata_read)
importFrom(REDCapR,redcap_read)

58
R/shiny_cast.R Normal file
View File

@ -0,0 +1,58 @@
#' Shiny server factory
#'
#' @return shiny server
#' @export
server_factory <- function() {
source(here::here("app/server.R"))
server
}
#' UI factory for shiny app
#'
#' @return shiny ui
#' @export
ui_factory <- function() {
# require(ggplot2)
source(here::here("app/ui.R"))
}
#' Launch the included Shiny-app for database casting and upload
#'
#' @return shiny app
#' @export
#'
#' @examples
#' # shiny_cast()
#'
shiny_cast <- function() {
# shiny::runApp(appDir = here::here("app/"), launch.browser = TRUE)
shiny::shinyApp(
ui_factory(),
server_factory()
)
}
#' Deploy the Shiny app with rsconnect
#'
#' @return deploy
#' @export
#'
#' @examples
#' # deploy_shiny
#'
deploy_shiny <- function(path=here::here("app/"), name.app="shiny_cast"){
# Ensure to install latest package version
renv::install("agdamsbo/REDCapCAST")
# Connecting
rsconnect::setAccountInfo(
name = "cognitiveindex",
token = keyring::key_get(service = "rsconnect_cognitiveindex_token"),
secret = keyring::key_get(service = "rsconnect_cognitiveindex_secret")
)
# Deploying
rsconnect::deployApp(appDir = path,lint = TRUE,appName = name.app,)
}

View File

@ -490,3 +490,53 @@ is_repeated_longitudinal <- function(data, generics = c(
}
any(generics %in% names)
}
#' Helper to import files correctly
#'
#' @param filenames file names
#'
#' @return character vector
#' @export
#'
#' @examples
#' file_extension(list.files(here::here(""))[[2]])[[1]]
file_extension <- function(filenames) {
sub(pattern = "^(.*\\.|[^.]+)(?=[^.]*)", replacement = "", filenames, perl = TRUE)
}
#' Flexible file import based on extension
#'
#' @param file file name
#' @param consider.na character vector of strings to consider as NAs
#'
#' @return tibble
#' @export
#'
#' @examples
#' read_input("https://raw.githubusercontent.com/agdamsbo/cognitive.index.lookup/main/data/sample.csv")
read_input <- function(file, consider.na= c("NA", '""',"")){
ext <- file_extension(file)
tryCatch(
{
if (ext == "csv") {
df <- readr::read_csv(file = file,na = consider.na)
} else if (ext %in% c("xls", "xlsx")) {
df <- openxlsx2::read_xlsx(file = file, na.strings = consider.na)
} else if (ext == "dta"){
df <- haven::read_dta(file = file)
} else {
stop("Input file format has to be either '.csv', '.xls' or '.xlsx'")
}
},
error = function(e) {
# return a safeError if a parsing error occurs
stop(shiny::safeError(e))
}
)
df
}

View File

@ -18,4 +18,4 @@ StripTrailingWhitespace: Yes
BuildType: Package
PackageUseDevtools: Yes
PackageInstallArgs: --no-multiarch --with-keep.source
PackageRoxygenize: rd,collate,namespace
PackageRoxygenize: rd,collate,namespace,vignette

81
app/server.R Normal file
View File

@ -0,0 +1,81 @@
server <- function(input, output, session) {
require(REDCapCAST)
dat <- shiny::reactive({
shiny::req(input$ds)
read_input(input$ds$datapath)
})
dd <- shiny::reactive({
ds2dd_detailed(data = dat())
})
output$data.tbl <- shiny::renderTable({
dd() |>
purrr::pluck("data") |>
head(20) |>
dplyr::tibble()
})
output$meta.tbl <- shiny::renderTable({
dd() |>
purrr::pluck("meta") |>
dplyr::tibble()
})
# Downloadable csv of dataset ----
output$downloadData <- shiny::downloadHandler(
filename = "data_ready.csv",
content = function(file) {
write.csv(purrr::pluck(dd(), "data"), file, row.names = FALSE)
}
)
# Downloadable csv of data dictionary ----
output$downloadMeta <- shiny::downloadHandler(
filename = "dictionary_ready.csv",
content = function(file) {
write.csv(purrr::pluck(dd(), "meta"), file, row.names = FALSE)
}
)
output_staging <- shiny::reactiveValues()
output_staging$meta <- output_staging$data <- NA
shiny::observeEvent(input$upload.meta,{ upload_meta() })
shiny::observeEvent(input$upload.data,{ upload_data() })
upload_meta <- function(){
shiny::req(input$uri)
shiny::req(input$api)
output_staging$meta <- REDCapR::redcap_metadata_write(
ds = purrr::pluck(dd(), "meta"),
redcap_uri = input$uri,
token = input$api
)|> purrr::pluck("success")
}
upload_data <- function(){
shiny::req(input$uri)
shiny::req(input$api)
output_staging$data <- REDCapR::redcap_write(
ds = purrr::pluck(dd(), "data"),
redcap_uri = input$uri,
token = input$api
) |> purrr::pluck("success")
}
output$upload.meta.print <- renderText(output_staging$meta)
output$upload.data.print <- renderText(output_staging$data)
}

89
app/ui.R Normal file
View File

@ -0,0 +1,89 @@
ui <- shiny::fluidPage(
## -----------------------------------------------------------------------------
## Application title
## -----------------------------------------------------------------------------
shiny::titlePanel("Simple REDCap data base creation and data upload from data set file via API",
windowTitle = "REDCap databse creator"
),
shiny::h5("Please note, that this tool serves as a demonstration of some of the functionality
of the REDCapCAST package. No responsibility for data loss or any other
problems will be taken."),
## -----------------------------------------------------------------------------
## Side panel
## -----------------------------------------------------------------------------
shiny::sidebarPanel(
shiny::h4("REDCap database and dataset"),
shiny::fileInput("ds", "Choose data file",
multiple = FALSE,
accept = c(
".csv",
".xls",
".xlsx",
".dta"
)
),
shiny::h6("Below you can download the dataset formatted for upload and the
corresponding data dictionary for a new data base."),
# Button
shiny::downloadButton("downloadData", "Download data"),
# Button
shiny::downloadButton("downloadMeta", "Download dictionary"),
# Horizontal line ----
shiny::tags$hr(),
shiny::h4("REDCap upload"),
shiny::textInput(
inputId = "uri",
label = "URI",
value = "https://redcap.au.dk/api/"
),
shiny::textInput(
inputId = "api",
label = "API key",
value = "21CF2C17EA1CA4F3688DF991C8FE3EBF"
),
shiny::actionButton(
inputId = "upload.meta",
label = "Upload dictionary", icon = shiny::icon("book-bookmark")
),
shiny::h6("Please note, that before uploading any real data, put your project
into production mode."),
shiny::actionButton(
inputId = "upload.data",
label = "Upload data", icon = shiny::icon("upload")
),
# Horizontal line ----
shiny::tags$hr()
),
shiny::mainPanel(
shiny::tabsetPanel(
## -----------------------------------------------------------------------------
## Summary tab
## -----------------------------------------------------------------------------
shiny::tabPanel(
"Summary",
shiny::h3("Data overview (first 20)"),
shiny::htmlOutput("data.tbl", container = shiny::span),
shiny::h3("Dictionary overview"),
shiny::htmlOutput("meta.tbl", container = shiny::span)
),
## -----------------------------------------------------------------------------
## Upload tab
## -----------------------------------------------------------------------------
shiny::tabPanel(
"Upload",
shiny::h3("Meta upload overview"),
shiny::htmlOutput("upload.meta.print", container = shiny::span),
shiny::h3("Data upload overview"),
shiny::htmlOutput("upload.data.print", container = shiny::span)
)
)
)
)

View File

@ -0,0 +1,5 @@
mtcars |> dplyr::mutate(record_id=seq_len(n()),
name=rownames(mtcars)
) |>
dplyr::select(record_id,dplyr::everything()) |>
write.csv(here::here("data/mtcars_redcap.csv"),row.names = FALSE)

33
data/mtcars_redcap.csv Normal file
View File

@ -0,0 +1,33 @@
"record_id","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb","name"
1,21,6,160,110,3.9,2.62,16.46,0,1,4,4,"Mazda RX4"
2,21,6,160,110,3.9,2.875,17.02,0,1,4,4,"Mazda RX4 Wag"
3,22.8,4,108,93,3.85,2.32,18.61,1,1,4,1,"Datsun 710"
4,21.4,6,258,110,3.08,3.215,19.44,1,0,3,1,"Hornet 4 Drive"
5,18.7,8,360,175,3.15,3.44,17.02,0,0,3,2,"Hornet Sportabout"
6,18.1,6,225,105,2.76,3.46,20.22,1,0,3,1,"Valiant"
7,14.3,8,360,245,3.21,3.57,15.84,0,0,3,4,"Duster 360"
8,24.4,4,146.7,62,3.69,3.19,20,1,0,4,2,"Merc 240D"
9,22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2,"Merc 230"
10,19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4,"Merc 280"
11,17.8,6,167.6,123,3.92,3.44,18.9,1,0,4,4,"Merc 280C"
12,16.4,8,275.8,180,3.07,4.07,17.4,0,0,3,3,"Merc 450SE"
13,17.3,8,275.8,180,3.07,3.73,17.6,0,0,3,3,"Merc 450SL"
14,15.2,8,275.8,180,3.07,3.78,18,0,0,3,3,"Merc 450SLC"
15,10.4,8,472,205,2.93,5.25,17.98,0,0,3,4,"Cadillac Fleetwood"
16,10.4,8,460,215,3,5.424,17.82,0,0,3,4,"Lincoln Continental"
17,14.7,8,440,230,3.23,5.345,17.42,0,0,3,4,"Chrysler Imperial"
18,32.4,4,78.7,66,4.08,2.2,19.47,1,1,4,1,"Fiat 128"
19,30.4,4,75.7,52,4.93,1.615,18.52,1,1,4,2,"Honda Civic"
20,33.9,4,71.1,65,4.22,1.835,19.9,1,1,4,1,"Toyota Corolla"
21,21.5,4,120.1,97,3.7,2.465,20.01,1,0,3,1,"Toyota Corona"
22,15.5,8,318,150,2.76,3.52,16.87,0,0,3,2,"Dodge Challenger"
23,15.2,8,304,150,3.15,3.435,17.3,0,0,3,2,"AMC Javelin"
24,13.3,8,350,245,3.73,3.84,15.41,0,0,3,4,"Camaro Z28"
25,19.2,8,400,175,3.08,3.845,17.05,0,0,3,2,"Pontiac Firebird"
26,27.3,4,79,66,4.08,1.935,18.9,1,1,4,1,"Fiat X1-9"
27,26,4,120.3,91,4.43,2.14,16.7,0,1,5,2,"Porsche 914-2"
28,30.4,4,95.1,113,3.77,1.513,16.9,1,1,5,2,"Lotus Europa"
29,15.8,8,351,264,4.22,3.17,14.5,0,1,5,4,"Ford Pantera L"
30,19.7,6,145,175,3.62,2.77,15.5,0,1,5,6,"Ferrari Dino"
31,15,8,301,335,3.54,3.57,14.6,0,1,5,8,"Maserati Bora"
32,21.4,4,121,109,4.11,2.78,18.6,1,1,4,2,"Volvo 142E"
1 record_id mpg cyl disp hp drat wt qsec vs am gear carb name
2 1 21 6 160 110 3.9 2.62 16.46 0 1 4 4 Mazda RX4
3 2 21 6 160 110 3.9 2.875 17.02 0 1 4 4 Mazda RX4 Wag
4 3 22.8 4 108 93 3.85 2.32 18.61 1 1 4 1 Datsun 710
5 4 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1 Hornet 4 Drive
6 5 18.7 8 360 175 3.15 3.44 17.02 0 0 3 2 Hornet Sportabout
7 6 18.1 6 225 105 2.76 3.46 20.22 1 0 3 1 Valiant
8 7 14.3 8 360 245 3.21 3.57 15.84 0 0 3 4 Duster 360
9 8 24.4 4 146.7 62 3.69 3.19 20 1 0 4 2 Merc 240D
10 9 22.8 4 140.8 95 3.92 3.15 22.9 1 0 4 2 Merc 230
11 10 19.2 6 167.6 123 3.92 3.44 18.3 1 0 4 4 Merc 280
12 11 17.8 6 167.6 123 3.92 3.44 18.9 1 0 4 4 Merc 280C
13 12 16.4 8 275.8 180 3.07 4.07 17.4 0 0 3 3 Merc 450SE
14 13 17.3 8 275.8 180 3.07 3.73 17.6 0 0 3 3 Merc 450SL
15 14 15.2 8 275.8 180 3.07 3.78 18 0 0 3 3 Merc 450SLC
16 15 10.4 8 472 205 2.93 5.25 17.98 0 0 3 4 Cadillac Fleetwood
17 16 10.4 8 460 215 3 5.424 17.82 0 0 3 4 Lincoln Continental
18 17 14.7 8 440 230 3.23 5.345 17.42 0 0 3 4 Chrysler Imperial
19 18 32.4 4 78.7 66 4.08 2.2 19.47 1 1 4 1 Fiat 128
20 19 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2 Honda Civic
21 20 33.9 4 71.1 65 4.22 1.835 19.9 1 1 4 1 Toyota Corolla
22 21 21.5 4 120.1 97 3.7 2.465 20.01 1 0 3 1 Toyota Corona
23 22 15.5 8 318 150 2.76 3.52 16.87 0 0 3 2 Dodge Challenger
24 23 15.2 8 304 150 3.15 3.435 17.3 0 0 3 2 AMC Javelin
25 24 13.3 8 350 245 3.73 3.84 15.41 0 0 3 4 Camaro Z28
26 25 19.2 8 400 175 3.08 3.845 17.05 0 0 3 2 Pontiac Firebird
27 26 27.3 4 79 66 4.08 1.935 18.9 1 1 4 1 Fiat X1-9
28 27 26 4 120.3 91 4.43 2.14 16.7 0 1 5 2 Porsche 914-2
29 28 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2 Lotus Europa
30 29 15.8 8 351 264 4.22 3.17 14.5 0 1 5 4 Ford Pantera L
31 30 19.7 6 145 175 3.62 2.77 15.5 0 1 5 6 Ferrari Dino
32 31 15 8 301 335 3.54 3.57 14.6 0 1 5 8 Maserati Bora
33 32 21.4 4 121 109 4.11 2.78 18.6 1 1 4 2 Volvo 142E

20
man/file_extension.Rd Normal file
View File

@ -0,0 +1,20 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.r
\name{file_extension}
\alias{file_extension}
\title{Helper to import files correctly}
\usage{
file_extension(filenames)
}
\arguments{
\item{filenames}{file names}
}
\value{
character vector
}
\description{
Helper to import files correctly
}
\examples{
file_extension(list.files(here::here(""))[[2]])[[1]]
}

22
man/read_input.Rd Normal file
View File

@ -0,0 +1,22 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/utils.r
\name{read_input}
\alias{read_input}
\title{Flexible file import based on extension}
\usage{
read_input(file, consider.na = c("NA", "\\"\\"", ""))
}
\arguments{
\item{file}{file name}
\item{consider.na}{character vector of strings to consider as NAs}
}
\value{
tibble
}
\description{
Flexible file import based on extension
}
\examples{
read_input("https://raw.githubusercontent.com/agdamsbo/cognitive.index.lookup/main/data/sample.csv")
}

14
man/server_factory.Rd Normal file
View File

@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/shiny_cast.R
\name{server_factory}
\alias{server_factory}
\title{Shiny server factory}
\usage{
server_factory()
}
\value{
shiny server
}
\description{
Shiny server factory
}

18
man/shiny_cast.Rd Normal file
View File

@ -0,0 +1,18 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/shiny_cast.R
\name{shiny_cast}
\alias{shiny_cast}
\title{Launch the included Shiny-app for database casting and upload}
\usage{
shiny_cast()
}
\value{
shiny app
}
\description{
Launch the included Shiny-app for database casting and upload
}
\examples{
# shiny_cast()
}

14
man/ui_factory.Rd Normal file
View File

@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/shiny_cast.R
\name{ui_factory}
\alias{ui_factory}
\title{UI factory for shiny app}
\usage{
ui_factory()
}
\value{
shiny ui
}
\description{
UI factory for shiny app
}

View File

@ -428,13 +428,13 @@
},
"renv": {
"Package": "renv",
"Version": "1.0.3",
"Version": "1.0.4",
"Source": "Repository",
"Repository": "CRAN",
"Requirements": [
"utils"
],
"Hash": "41b847654f567341725473431dd0d5ab"
"Hash": "11abaf7c540ff33f94514d50f929bfd1"
},
"rlang": {
"Package": "rlang",
@ -512,7 +512,7 @@
},
"tidyr": {
"Package": "tidyr",
"Version": "1.3.0",
"Version": "1.3.1",
"Source": "Repository",
"Repository": "CRAN",
"Requirements": [
@ -531,7 +531,7 @@
"utils",
"vctrs"
],
"Hash": "e47debdc7ce599b070c8e78e8ac0cfcf"
"Hash": "915fb7ce036c22a6a33b5a8adb712eb1"
},
"tidyselect": {
"Package": "tidyselect",

View File

@ -2,7 +2,7 @@
local({
# the requested version of renv
version <- "1.0.3"
version <- "1.0.4"
attr(version, "sha") <- NULL
# the project directory
@ -31,6 +31,14 @@ local({
if (!is.null(override))
return(override)
# if we're being run in a context where R_LIBS is already set,
# don't load -- presumably we're being run as a sub-process and
# the parent process has already set up library paths for us
rcmd <- Sys.getenv("R_CMD", unset = NA)
rlibs <- Sys.getenv("R_LIBS", unset = NA)
if (!is.na(rlibs) && !is.na(rcmd))
return(FALSE)
# next, check environment variables
# TODO: prefer using the configuration one in the future
envvars <- c(
@ -50,9 +58,22 @@ local({
})
if (!enabled)
# bail if we're not enabled
if (!enabled) {
# if we're not enabled, we might still need to manually load
# the user profile here
profile <- Sys.getenv("R_PROFILE_USER", unset = "~/.Rprofile")
if (file.exists(profile)) {
cfg <- Sys.getenv("RENV_CONFIG_USER_PROFILE", unset = "TRUE")
if (tolower(cfg) %in% c("true", "t", "1"))
sys.source(profile, envir = globalenv())
}
return(FALSE)
}
# avoid recursion
if (identical(getOption("renv.autoloader.running"), TRUE)) {
warning("ignoring recursive attempt to run renv autoloader")
@ -1041,7 +1062,7 @@ local({
# if jsonlite is loaded, use that instead
if ("jsonlite" %in% loadedNamespaces()) {
json <- catch(renv_json_read_jsonlite(file, text))
json <- tryCatch(renv_json_read_jsonlite(file, text), error = identity)
if (!inherits(json, "error"))
return(json)
@ -1050,7 +1071,7 @@ local({
}
# otherwise, fall back to the default JSON reader
json <- catch(renv_json_read_default(file, text))
json <- tryCatch(renv_json_read_default(file, text), error = identity)
if (!inherits(json, "error"))
return(json)
@ -1063,14 +1084,14 @@ local({
}
renv_json_read_jsonlite <- function(file = NULL, text = NULL) {
text <- paste(text %||% read(file), collapse = "\n")
text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
jsonlite::fromJSON(txt = text, simplifyVector = FALSE)
}
renv_json_read_default <- function(file = NULL, text = NULL) {
# find strings in the JSON
text <- paste(text %||% read(file), collapse = "\n")
text <- paste(text %||% readLines(file, warn = FALSE), collapse = "\n")
pattern <- '["](?:(?:\\\\.)|(?:[^"\\\\]))*?["]'
locs <- gregexpr(pattern, text, perl = TRUE)[[1]]
@ -1118,14 +1139,14 @@ local({
map <- as.list(map)
# remap strings in object
remapped <- renv_json_remap(json, map)
remapped <- renv_json_read_remap(json, map)
# evaluate
eval(remapped, envir = baseenv())
}
renv_json_remap <- function(json, map) {
renv_json_read_remap <- function(json, map) {
# fix names
if (!is.null(names(json))) {
@ -1152,7 +1173,7 @@ local({
# recurse
if (is.recursive(json)) {
for (i in seq_along(json)) {
json[i] <- list(renv_json_remap(json[[i]], map))
json[i] <- list(renv_json_read_remap(json[[i]], map))
}
}