-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.R
290 lines (218 loc) · 9.67 KB
/
server.R
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
#-------------------------------------------------------------
# Server function assembles input and outputs from ui object
# and renders *in from and *out of a web page
# inputs can be toggled by user
# outputs are information displays
#-------------------------------------------------------------
server <- function(input, output, session) {
# use the same name from output functions in ui
# render function creates the type of output
observe({
updateCheckboxGroupInput(
session, 'cat', choices = categories,
selected = if (!input$allNone) categories
)
})
dataOut <- reactive({
if (length(input$cat) > 0) {
# filter by user-defined project category
dat[which(dat$module %in% input$cat),]
}else{
if (length(input$cat) == 0) {
shiny::validate(
need(length(input$cat) != 0, "Select a Sage Bionetworks module.\n\n You may also upload your annotations to download a manifest. \n\n ")
)
}else{
dat
}
}
})
userData <- reactive({
file <- input$userAnnot
# check if file exists
# shiny::validate(
# need(file, "Your csv file can't be located. Please try again! \n\n ")
# )
user.project <- input$projectName
# check if project name exists
# shiny::validate(
# need(user.project, "Please enter your module name. \n\n ")
# )
# Trim whitespaces in project name
user.project <- trimws(user.project)
# Upload user annotaions
# Use fread function to catch user defined formats, handle large files, and execute correct errors as needed
user.dat <- data.table::fread(file$datapath, encoding = "UTF-8", fill = TRUE, blank.lines.skip = TRUE, na.strings = c("",NA,"NULL") , data.table = FALSE)
# then check for standard input columns
# shiny::validate(
# need(c("key", "value") %in% colnames(user.dat), "Please provide key and value fields in your csv")
# )
standard.sage.colnames <- c("description", "columnType", "maximumSize", "valueDescription", "source", "module")
columns <- which(colnames(user.dat) %in% standard.sage.colnames)
# Keep key and value column first, then other Sage columns
user.dat <- user.dat[, c("key", "value", colnames(user.dat)[columns])]
# Remove rows with empty string in key column or NAs in all columns
user.dat <- user.dat[user.dat$key != "", ]
user.dat <- user.dat[rowSums(is.na(user.dat)) < ncol(user.dat), ]
# extract complete cases of values or keys
value <- user.dat$value[!is.na(user.dat$value)]
key.values <- user.dat$key[!is.na(user.dat$value)]
many.values <- as.data.frame(cbind(key.values, value))
# check if user provided list of comma seperated values
if (any(grepl(",", user.dat$value))) {
# seperate values by , and make one-to-one relation between key-values
values <- strsplit(many.values$value, "[,]")
# unnest or normalize each value to key relation
normalized <- lapply(seq_along(many.values$key.values), function(i){
df <- lapply(seq_along(values[[i]]), function(j){
cbind(many.values$key.values[[i]], values[[i]][[j]])
})
df <- do.call(rbind, df)
return(df)
})
normalized <- as.data.frame(do.call(rbind, normalized), stringsAsFactors = F)
names(normalized) <- c("key", "value")
# extract keys without pre-defined values (ex. patientID where it could be left as NA due to privacy or timing)
only.keys <- as.data.frame(user.dat$key[!is.na(user.dat$key)][which(!user.dat$key[!is.na(user.dat$key)] %in% unique(normalized$key))], stringsAsFactors = F)
if (length(only.keys) == 0) {
names(only.keys) <- "key"
only.keys[ ,"value"] <- NA
final.dat <- rbind(only.keys, normalized)
}else{
final.dat <- normalized
}
}else{
final.dat <- user.dat
}
if (length(columns) == 0) {
# build standard schema
final.dat[,c("description", "columnType", "maximumSize", "valueDescription", "source")] <- NA
}
if (length(columns) > 0) {
missing.columns <- standard.sage.colnames[which(!c(c("key", "value"), columns) %in% standard.sage.colnames)]
# build standard schema
final.dat[ ,missing.columns] <- NA
}
# pass in projects name
final.dat[ ,"module"] <- user.project
if (length(input$cat) != 0) {
final.dat <- rbind(dataOut(), final.dat)
final.dat
}else{
final.dat
}
})
output$annotationTable <- DT::renderDataTable({
if (!is.null(input$userAnnot)) {
table <- userData()
}else{
table <- dataOut()
}
table
}, options = list(pageLength = 10, lengthMenu = c(10, 25, 50, 100, 1000), style = 'overflow-x: auto'), rownames = FALSE, server = FALSE, filter = "bottom")
output$downloadSchema <- downloadHandler(
filename <- function() {'annotations_manifest.xlsx'},
content <- function(filename) {
# get user-defined table to download
if (!is.null(input$userAnnot)) {
user.table <- userData()
}else{
user.table <- dataOut()
}
# extract a unique key to define the manifest columns
if (length(input$annotationTable_rows_selected)) {
user.cols <- unique(user.table[input$annotationTable_rows_selected, 'key'])
user.table <- user.table[which(user.table$key %in% user.cols), ]
}else{
user.cols <- unique(user.table[["key"]])
}
# create the manifest schema
schema <- generate_manifest_template(user.cols)
# create the key and key-value description dataframes
key.description <- generate_key_description(user.table)
value.description <- generate_value_description(user.table)
# create three sheets including:
# 1. manifest columns # 2. key descriptions
# 3. value descriptions (key-value)
sheets <- list(manifest = schema , keyDescription = key.description, keyValueDescription = value.description)
write_manifest(sheets, filename)
}
)
output$keyDescription <- DT::renderDataTable({
if (!is.null(input$userAnnot)) {
selected.table <- userData()
}else{
selected.table <- dataOut()
}
# create the key description dataframes
key.description <- generate_key_description(selected.table)
key.description
}, options = list(pageLength = 10, lengthMenu = c(10, 25, 50, 100, 1000), style = 'overflow-x: auto'), rownames = FALSE, server = FALSE, filter = "bottom")
output$valueDescription <- DT::renderDataTable({
if (!is.null(input$userAnnot)) {
selected.table <- userData()
}else{
selected.table <- dataOut()
}
value.description <- generate_value_description(selected.table)
value.description
}, options = list(pageLength = 10, lengthMenu = c(10, 25, 50, 100, 1000), style = 'overflow-x: auto'), rownames = FALSE, server = FALSE, filter = "bottom")
output$downloadJSON <- downloadHandler(
filename <- function() {'annotations.json'},
content <- function(filename) {
# get user-defined table to download
if (!is.null(input$userAnnot)) {
user.table <- userData()
}else{
user.table <- dataOut()
}
# extract selected rows keys to slice the dataframe
if (length(input$annotationTable_rows_selected)) {
user.cols <- unique(user.table[input$annotationTable_rows_selected, 'key'])
user.table <- user.table[which(user.table$key %in% user.cols), ]
}
user.table <- as.data.frame(user.table, stringsAsFactors = F)
nested.list <- lapply(unique(user.table$module), function(m){
this.module <- user.table[which(user.table$module %in% m),]
each.key.slice <- lapply(unique(this.module$key), function(k){
this.key <- user.table[which(user.table$key %in% k),]
return(this.key)
})
nested.value.list <- lapply(each.key.slice, function(v){
# replace NA's with empty string
v[is.na(v)] <- ""
# select the value metadata columns
enumValues <- dplyr::select(v, value, valueDescription, source)
names(enumValues)[2] <- "description"
# removes _row
rownames(enumValues) <- NULL
enumValues.json <- toJSON(enumValues, pretty = T)
# select the key metadata columns
key <- dplyr::select(v, key, description, columnType, maximumSize)
# replace key with name to match json
names(key)[1] <- "name"
# only need one unique key row
key <- key[1, ]
if (key$columnType == "BOOLEAN") {
enumValues$value <- as.logical(enumValues$value)
}
# remove all empty enumvalues
enumValues <- enumValues[!(enumValues$value == "" & enumValues$description == "" & enumValues$source == ""), ]
key$enumValues <- list(enumValues)
# removes _row
rownames(key) <- NULL
return(key)
})
nv <- do.call(rbind, nested.value.list)
return(nv)
})
all.modules <- do.call(rbind, nested.list)
all.modules.json <- jsonlite::toJSON(all.modules , pretty = T)
writeLines(all.modules.json, filename)
}
)
# allow refresh to run locally without a server
# session$allowReconnect("force")
# automatically stop the app session after closing the browser tab
session$onSessionEnded(stopApp)
}