• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dondi / GRNsight / 25699375138

11 May 2026 09:49PM UTC coverage: 80.785% (-1.0%) from 81.764%
25699375138

Pull #1581

github

ntran18
Remove network option arguments when fetching and populate data from database
Pull Request #1581: Remove network option arguments when fetching and populate for database

458 of 573 branches covered (79.93%)

Branch coverage included in aggregate %.

1270 of 1566 relevant lines covered (81.1%)

8958.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.97
/server/controllers/spreadsheet-controller.js
1
var multiparty = require("multiparty");
3✔
2
var xlsx = require("node-xlsx");
3✔
3
var path = require("path");
3✔
4
const { NETWORK_GRN_MODE, NETWORK_PPI_MODE } = require("./constants");
3✔
5
var parseAdditionalSheets = require(__dirname + "/additional-sheet-parser");
3✔
6
var parseExpressionSheets = require(__dirname + "/expression-sheet-parser");
3✔
7
var parseNetworkSheet = require(__dirname + "/network-sheet-parser");
3✔
8
var demoWorkbooks = require(__dirname + "/demo-workbooks");
3✔
9
var constants = require(__dirname + "/workbook-constants");
3✔
10
// var cytoscape = require("cytoscape"); //NOTE: Commented out for issue #474
11

12
var helpers = require(__dirname + "/helpers");
3✔
13

14
var EXPRESSION_SHEET_SUFFIXES = ["_expression", "_optimized_expression", "_sigmas"];
3✔
15

16
var SPECIES = [
3✔
17
    "Arabidopsis thaliana",
18
    "Caenorhabditis elegans",
19
    "Drosophila melanogaster",
20
    "Homo sapiens",
21
    "Mus musculus",
22
    "Saccharomyces cerevisiae",
23
];
24

25
const WORKBOOK_TYPES = [NETWORK_GRN_MODE, NETWORK_PPI_MODE];
3✔
26

27
var TAXON_ID = ["3702", "6293", "7227", "9606", "10090", "4932", "559292"];
3✔
28

29
var isExpressionSheet = function (sheetName) {
3✔
30
    return EXPRESSION_SHEET_SUFFIXES.some(function (suffix) {
885✔
31
        return sheetName.includes(suffix);
1,755✔
32
    });
33
};
34

35
var doesSpeciesExist = function (speciesInfo) {
3✔
36
    for (var s in SPECIES) {
51✔
37
        if (SPECIES[s] === speciesInfo) {
306✔
38
            return true;
45✔
39
        }
40
    }
41
    for (var t in TAXON_ID) {
6✔
42
        if (TAXON_ID[t] === speciesInfo) {
42!
43
            return true;
×
44
        }
45
    }
46
    return false;
6✔
47
};
48

49
var supportWorkbookType = function (type) {
3✔
50
    for (var t in WORKBOOK_TYPES) {
48✔
51
        if (WORKBOOK_TYPES[t] === type) {
48✔
52
            return true;
48✔
53
        }
54
    }
55
    return false;
×
56
};
57

58
var addMessageToArray = function (messageArray, message) {
3✔
59
    messageArray.push(message);
21✔
60
};
61

62
var addWarning = function (workbook, message) {
3✔
63
    var warningsCount = workbook.warnings.length;
12✔
64
    var MAX_WARNINGS = 75;
12✔
65
    if (warningsCount < MAX_WARNINGS) {
12!
66
        addMessageToArray(workbook.warnings, message);
12✔
67
    } else {
68
        addMessageToArray(workbook.errors, constants.errors.warningsCountError);
×
69
        return false;
×
70
    }
71
};
72

73
var addError = function (workbook, message) {
3✔
74
    var errorsCount = workbook.errors.length;
9✔
75
    var MAX_ERRORS = 20;
9✔
76
    if (errorsCount < MAX_ERRORS) {
9!
77
        addMessageToArray(workbook.errors, message);
9✔
78
    } else {
79
        addMessageToArray(workbook.errors, constants.errors.errorsCountError);
×
80
        return false;
×
81
    }
82
};
83

84
var difference = function (setA, setB) {
3✔
85
    let _difference = new Set(setA);
594✔
86
    for (let elemB of setB) {
8,268✔
87
        if (_difference.has(elemB)) {
8,268✔
88
            _difference.delete(elemB);
8,262✔
89
        }
90
    }
594✔
91
    return _difference;
594✔
92
};
93

94
var deepClone = function (object, isArray) {
3✔
95
    var clone = isArray ? [] : {};
1,521✔
96
    if (isArray) {
1,521✔
97
        for (let i of object) {
1,779✔
98
            if (i !== null && typeof i === "object") {
1,779✔
99
                clone.push(deepClone(i, Array.isArray(i)));
1,089✔
100
            } else {
101
                clone.push(i);
690✔
102
            }
103
        }
288✔
104
    } else {
105
        for (let i in object) {
1,233✔
106
            if (object[i] !== null && typeof object[i] === "object") {
4,281✔
107
                clone[i] = deepClone(object[i], Array.isArray(object[i]));
384✔
108
            } else {
109
                clone[i] = object[i];
3,897✔
110
            }
111
        }
112
    }
113
    return clone;
1,521✔
114
};
115

116
var crossSheetInteractions = function (workbookFile) {
3✔
117
    var workbook = {};
48✔
118

119
    // Refactored the parseNetworkSheet function to preserve all network type sheets including "network",
120
    // "network_optimized_weights",and "network_weights" restructuring workbook object as a result
121

122
    var networks = parseNetworkSheet.networks(workbookFile);
48✔
123
    const { network, networkOptimizedWeights, networkWeights } = networks;
48✔
124
    const genesSource = network.genes || networkOptimizedWeights.genes || networkWeights.genes;
48!
125
    const genes = genesSource.map(gene => gene.name);
399✔
126

127
    // Parse expression and 2-column data, then add to workbook object
128
    // Eventually, will split this up into parsing for each type of sheet.
129
    const additionalData = parseAdditionalSheets(workbookFile, genes);
48✔
130

131
    var expressionData = parseExpressionSheets(workbookFile);
48✔
132

133
    if (
48✔
134
        networks &&
192✔
135
        networkOptimizedWeights &&
136
        typeof networkOptimizedWeights === "object" &&
137
        Object.keys(networkOptimizedWeights).length > 0
138
    ) {
139
        // Base workbook is a clone of the prefered Optimized weights sheet
140
        workbook = deepClone(networkOptimizedWeights, false);
12✔
141
        // Add errors from network sheet if it exists
142
        if (network && typeof network === "object" && Object.keys(network).length > 0) {
12✔
143
            if (network.errors !== undefined) {
12✔
144
                network.errors.forEach(data => workbook.errors.push(data));
12✔
145
            }
146

147
            if (network.warnings !== undefined) {
12✔
148
                network.warnings.forEach(data => workbook.warnings.push(data));
12✔
149
            }
150
        }
151
    } else {
152
        // Set base workbook to a deep copy of the default network if network optimized weights does not exist
153
        workbook = deepClone(network, false);
36✔
154
    }
155
    // Add errors and warnings from network weights to preserve the sheet
156
    if (
48✔
157
        networkWeights &&
144✔
158
        typeof networkWeights === "object" &&
159
        Object.keys(networkWeights).length > 0
160
    ) {
161
        if (networkWeights.errors !== undefined) {
30✔
162
            networkWeights.errors.forEach(data => workbook.errors.push(data));
30✔
163
        }
164

165
        if (networkWeights.warnings !== undefined) {
30✔
166
            networkWeights.warnings.forEach(data => workbook.warnings.push(data));
30✔
167
        }
168
    }
169

170
    // Add errors and warnings from meta sheets
171
    if (additionalData && additionalData.meta) {
48✔
172
        if (additionalData.meta.errors !== undefined) {
48✔
173
            additionalData.meta.errors.forEach(data => workbook.errors.push(data));
48✔
174
        }
175

176
        if (additionalData.meta.warnings !== undefined) {
48✔
177
            additionalData.meta.warnings.forEach(data => workbook.warnings.push(data));
48✔
178
        }
179
    }
180

181
    if (additionalData && additionalData.twoColumnSheets) {
48✔
182
        // Add errors and warnings from two column sheets
183
        for (let sheet in additionalData.twoColumnSheets) {
48✔
184
            additionalData.twoColumnSheets[sheet].errors.forEach(data =>
123✔
185
                workbook.errors.push(data)
186
            );
187
        }
188

189
        for (let sheet in additionalData.twoColumnSheets) {
48✔
190
            additionalData.twoColumnSheets[sheet].warnings.forEach(data =>
123✔
191
                workbook.warnings.push(data)
192
            );
193
        }
194
    }
195

196
    if (additionalData && additionalData.meta2) {
48✔
197
        // Add errors and warnings from two column sheets
198
        if (additionalData.meta2.errors !== undefined) {
48✔
199
            additionalData.meta2.errors.forEach(data => workbook.errors.push(data));
12✔
200
        }
201

202
        if (additionalData.meta2.warnings !== undefined) {
48✔
203
            additionalData.meta2.warnings.forEach(data => workbook.warnings.push(data));
12✔
204
        }
205
    }
206

207
    if (additionalData && additionalData.warnings) {
48!
208
        workbook.warnings.push(...additionalData.warnings);
×
209
    }
210

211
    additionalData.meta.data.workbookType = parseNetworkSheet.workbookType(workbookFile);
48✔
212
    if (additionalData.meta.data.workbookType === undefined) {
48!
213
        addWarning(workbook, constants.warnings.noWorkbookTypeDetected);
×
214
        additionalData.meta.data.workbookType = NETWORK_GRN_MODE;
×
215
    } else if (!supportWorkbookType(additionalData.meta.data.workbookType)) {
48!
216
        addWarning(
×
217
            workbook,
218
            constants.warnings.unsupportedWorkbookTypeDetected(
219
                additionalData.meta.data.workbookType
220
            )
221
        );
222
        additionalData.meta.data.workbookType = NETWORK_GRN_MODE;
×
223
    }
224

225
    if (
48!
226
        additionalData.meta.data.species === undefined &&
48!
227
        additionalData.meta.data.taxon_id === undefined
228
    ) {
229
        addWarning(workbook, constants.warnings.noSpeciesInformationDetected);
×
230
        additionalData.meta.data.species = "Saccharomyces cerevisiae";
×
231
        additionalData.meta.data["taxon_id"] = "559292";
×
232
    } else if (
48✔
233
        !doesSpeciesExist(additionalData.meta.data.species) &&
51✔
234
        !doesSpeciesExist(additionalData.meta.data.taxon_id)
235
    ) {
236
        addWarning(
3✔
237
            workbook,
238
            constants.warnings.unknownSpeciesDetected(
239
                additionalData.meta.data.species,
240
                additionalData.meta.data.taxon_id
241
            )
242
        );
243
        additionalData.meta.data.species = "Saccharomyces cerevisiae";
3✔
244
        additionalData.meta.data["taxon_id"] = 559292;
3✔
245
    }
246

247
    // Add errors and warnings from expression sheets
248
    // FUTURE IMPROVEMENT: not all expression sheets are specifically named 'wt_log2_expression.'
249
    // We need to account for all the different possible expression sheet names.
250
    if (expressionData) {
48✔
251
        if (additionalData.meta.data.workbookType === constants.NETWORK_GRN_MODE) {
48!
252
            if (
×
253
                expressionData["expression"] &&
×
254
                Object.keys(expressionData["expression"]).length === 0
255
            ) {
256
                addWarning(expressionData, constants.warnings.missingExpressionWarning());
×
257
            }
258
        }
259
        if (expressionData.errors !== undefined) {
48✔
260
            expressionData.errors.forEach(data => workbook.errors.push(data));
48✔
261
        }
262
        if (expressionData.warnings !== undefined) {
48✔
263
            expressionData.warnings.forEach(data => workbook.warnings.push(data));
48✔
264
        }
265
    }
266

267
    if (expressionData && expressionData.expression) {
48✔
268
        if (expressionData.expression.errors !== undefined) {
48!
269
            expressionData.expression.errors.forEach(data => workbook.errors.push(data));
×
270
        }
271

272
        if (expressionData.expression.warnings !== undefined) {
48!
273
            expressionData.expression.warnings.forEach(data => workbook.warnings.push(data));
×
274
        }
275
    }
276

277
    if (
48✔
278
        expressionData &&
144✔
279
        expressionData.expression &&
280
        expressionData.expression.wt_log2_expression
281
    ) {
282
        if (expressionData.expression.wt_log2_expression.errors !== undefined) {
48✔
283
            expressionData.expression.wt_log2_expression.errors.forEach(data =>
48✔
284
                workbook.errors.push(data)
285
            );
286
        }
287

288
        if (expressionData.expression.wt_log2_expression.warnings !== undefined) {
48✔
289
            expressionData.expression.wt_log2_expression.warnings.forEach(data =>
48✔
290
                workbook.warnings.push(data)
291
            );
292
        }
293
    }
294

295
    // Gene Mismatch and Label Error Tests
296

297
    workbookFile.forEach(function (sheet) {
48✔
298
        if (isExpressionSheet(sheet.name)) {
579✔
299
            var tempWorkbookGenes = new Set();
297✔
300
            for (let i = 0; i < workbook.genes.length; i++) {
297✔
301
                tempWorkbookGenes.add(workbook.genes[i].name);
4,134✔
302
            }
303
            var tempExpressionGenes = new Set(
297✔
304
                expressionData.expression[sheet.name].columnGeneNames
305
            );
306
            var extraExpressionGenes = difference(tempExpressionGenes, tempWorkbookGenes);
297✔
307
            var extraWorkbookGenes = difference(tempWorkbookGenes, tempExpressionGenes);
297✔
308

309
            if (extraExpressionGenes.size === 0 && extraWorkbookGenes.size === 0) {
297✔
310
                for (var i = 0; i < workbook.genes.length; i++) {
291✔
311
                    if (
4,107✔
312
                        workbook.genes[i].name !==
313
                        expressionData.expression[sheet.name].columnGeneNames[i]
314
                    ) {
315
                        addError(workbook, constants.errors.geneMismatchError(sheet.name));
3✔
316
                        break;
3✔
317
                    }
318
                }
319
            } else {
320
                if (extraWorkbookGenes.size > 0) {
6✔
321
                    addError(workbook, constants.errors.missingGeneNamesError(sheet.name));
3✔
322
                }
323
                if (extraExpressionGenes.size > 0) {
6✔
324
                    addError(workbook, constants.errors.extraGeneNamesError(sheet.name));
3✔
325
                }
326
            }
327
        }
328
    });
329

330
    const validSheetNames = [
48✔
331
        ...constants.TWO_COL_SHEET_NAMES,
332
        ...constants.NETWORK_SHEET_NAMES,
333
        ...constants.OPTIONAL_TWO_COL_SHEET_NAMES,
334
    ];
335

336
    workbookFile.forEach(function (sheet) {
48✔
337
        const isRecognizedSheet =
338
            validSheetNames.includes(sheet.name) || isExpressionSheet(sheet.name);
579✔
339
        if (!isRecognizedSheet) {
579✔
340
            addWarning(workbook, constants.warnings.unrecognizedSheetWarning(sheet.name));
9✔
341
        }
342
    });
343

344
    // Integrate the desired properties from the other objects.
345
    workbook.network = network;
48✔
346
    workbook.networkOptimizedWeights = networkOptimizedWeights;
48✔
347
    workbook.networkWeights = networkWeights;
48✔
348
    workbook.meta = additionalData.meta;
48✔
349
    workbook.twoColumnSheets = additionalData.twoColumnSheets;
48✔
350
    workbook.meta2 = additionalData.meta2;
48✔
351
    workbook.expression = expressionData.expression;
48✔
352
    return workbook;
48✔
353
};
354

355
var processGRNmap = function (path, res, app) {
3✔
356
    var sheet;
357

358
    helpers.attachCorsHeader(res, app);
×
359

360
    try {
×
361
        sheet = xlsx.parse(path);
×
362
    } catch (err) {
363
        return res.json(400, "Unable to read input. The file may be corrupt.");
×
364
    }
365

366
    helpers.attachFileHeaders(res, path);
×
367

368
    var workbook = crossSheetInteractions(sheet);
×
369

370
    return workbook.errors.length === 0
×
371
        ? // If all looks well, return the workbook with an all clear
372
          res.json(workbook)
373
        : // If all does not look well, return the workbook with an error 400
374
          res.status(400).json(workbook);
375
};
376

377
var grnSightToCytoscape = function (workbook) {
3✔
378
    var result = [];
×
379
    workbook.genes.forEach(function (gene) {
×
380
        result.push({
×
381
            data: {
382
                id: gene.name,
383
            },
384
        });
385
    });
386

387
    workbook.links.forEach(function (link) {
×
388
        var sourceGene = workbook.genes[link.source];
×
389
        var targetGene = workbook.genes[link.target];
×
390
        result.push({
×
391
            data: {
392
                id: sourceGene.name + targetGene.name,
393
                source: sourceGene.name,
394
                target: targetGene.name,
395
            },
396
        });
397
    });
398

399
    return result;
×
400
};
401

402
/* NOTE: See above. Commented out until resolution of #474
403
var graphStatisticsReport = function(workbook)  {
404
    var betweennessCentrality = [];
405
    var shortestPath = [];
406
    var cytoscapeElements = grnSightToCytoscape(workbook);
407
    var cy = cytoscape({
408
        headless: true,
409
        elements: cytoscapeElements
410
    });
411
    for (var i = 0; i < workbook.genes.length; i++) {
412
        var bc = cy.$().bc();
413
        betweennessCentrality.push({
414
            gene: workbook.genes[i],
415
            betweennessCentrality: bc.betweenness("#" + workbook.genes[i].name, null, true)
416
        });
417
        var dijkstra = cy.elements().dijkstra("#" + workbook.genes[i].name, null, true);
418
        for (var j = 0; j < workbook.genes.length; j++) {
419
            shortestPath.push({
420
                source: workbook.genes[i].name,
421
                pathData: {
422
                    target: workbook.genes[j].name,
423
                    shortestPath: dijkstra.distanceTo("#" + workbook.genes[j].name, null, true)
424
                }
425
            });
426
        }
427
    }
428
    return {
429
        betweennessCentrality: betweennessCentrality,
430
        shortestPath: shortestPath
431
    };
432
};
433
*/
434

435
module.exports = function (app) {
3✔
436
    if (app) {
3!
437
        // parse the incoming form data, then parse the spreadsheet. Finally, send back json.
438
        app.post("/upload", function (req, res) {
×
439
            // TODO: Add file validation (make sure that file is an Excel file)
440
            new multiparty.Form().parse(req, function (err, fields, files) {
×
441
                if (err) {
×
442
                    return res.json(
×
443
                        400,
444
                        "There was a problem uploading your file. Please try again."
445
                    );
446
                }
447
                var input;
448
                try {
×
449
                    input = files.file[0].path;
×
450
                } catch (err) {
451
                    return res.json(400, "No upload file selected.");
×
452
                }
453

454
                if (path.extname(input) !== ".xlsx") {
×
455
                    return res.json(
×
456
                        400,
457
                        "This file cannot be loaded because:<br><br> The file is \
458
                        not in a format GRNsight can read." +
459
                            "<br>Please select an Excel Workbook \
460
                        (.xlsx) file. Note that Excel 97-2003 Workbook (.xls) files are not " +
461
                            " able to be read by GRNsight. <br><br>SIF and GraphML files can be loaded \
462
                        using the importer under File > Import." +
463
                            " Additional information about file \
464
                        types that GRNsight supports is in the Documentation."
465
                    );
466
                }
467

468
                // input.meta holds the species and taxon data
469
                return processGRNmap(input, res, app);
×
470
            });
471
        });
472

473
        // Load the demos
474
        app.get("/demo/unweighted", function (req, res) {
×
475
            return demoWorkbooks(
×
476
                "test-files/demo-files/15-genes_28-edges_db5_Dahlquist-data_input.xlsx",
477
                res,
478
                app
479
            );
480
        });
481

482
        app.get("/demo/weighted", function (req, res) {
×
483
            return demoWorkbooks(
×
484
                "test-files/demo-files/15-genes_28-edges_db5_Dahlquist-data_estimation_output.xlsx",
485
                res,
486
                app
487
            );
488
        });
489

490
        app.get("/demo/schadeInput", function (req, res) {
×
491
            return demoWorkbooks(
×
492
                "test-files/demo-files/21-genes_31-edges_Schade-data_input.xlsx",
493
                res,
494
                app
495
            );
496
        });
497

498
        app.get("/demo/schadeOutput", function (req, res) {
×
499
            return demoWorkbooks(
×
500
                "test-files/demo-files/21-genes_31-edges_Schade-data_estimation_output.xlsx",
501
                res,
502
                app
503
            );
504
        });
505

506
        app.get("/demo/ppi", function (req, res) {
×
507
            return demoWorkbooks("test-files/demo-files/18_proteins_81_edges_PPI.xlsx", res, app);
×
508
        });
509
    }
510

511
    // exporting parseNetworkSheet for use in testing. Do not remove!
512
    return {
3✔
513
        grnSightToCytoscape: grnSightToCytoscape,
514
        processGRNmap: processGRNmap,
515
        crossSheetInteractions: crossSheetInteractions,
516
    };
517
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc