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

dondi / GRNsight / 22110174686

17 Feb 2026 06:12PM UTC coverage: 80.155% (+0.03%) from 80.129%
22110174686

Pull #1325

github

ntran18
Fix eslint being stuck
Pull Request #1325: Fixing warnings issue for two column sheets #1192

425 of 536 branches covered (79.29%)

Branch coverage included in aggregate %.

34 of 39 new or added lines in 3 files covered. (87.18%)

16 existing lines in 2 files now uncovered.

1231 of 1530 relevant lines covered (80.46%)

9140.12 hits per line

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

76.24
/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) {
903✔
31
        return sheetName.includes(suffix);
1,809✔
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,412✔
87
        if (_difference.has(elemB)) {
8,412✔
88
            _difference.delete(elemB);
8,406✔
89
        }
90
    }
594✔
91
    return _difference;
594✔
92
};
93

94
var deepClone = function (object, isArray) {
3✔
95
    var clone = isArray ? [] : {};
1,725✔
96
    if (isArray) {
1,725✔
97
        for (let i of object) {
2,115✔
98
            if (i !== null && typeof i === "object") {
2,115✔
99
                clone.push(deepClone(i, Array.isArray(i)));
1,293✔
100
            } else {
101
                clone.push(i);
822✔
102
            }
103
        }
288✔
104
    } else {
105
        for (let i in object) {
1,437✔
106
            if (object[i] !== null && typeof object[i] === "object") {
5,013✔
107
                clone[i] = deepClone(object[i], Array.isArray(object[i]));
384✔
108
            } else {
109
                clone[i] = object[i];
4,629✔
110
            }
111
        }
112
    }
113
    return clone;
1,725✔
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 genes = networks.network.genes.map(gene => gene.name);
471✔
124

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

129
    var expressionData = parseExpressionSheets(workbookFile);
48✔
130

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

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

167
        if (networks.networkWeights.warnings !== undefined) {
36✔
168
            networks.networkWeights.warnings.forEach(data => workbook.warnings.push(data));
36✔
169
        }
170
    }
171

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

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

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

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

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

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

209
    if (additionalData && additionalData.warnings) {
48!
NEW
210
        workbook.warnings.push(...additionalData.warnings);
×
211
    }
212

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

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

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

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

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

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

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

297
    // Gene Mismatch and Label Error Tests
298

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

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

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

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

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

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

360
    helpers.attachCorsHeader(res, app);
×
361

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

368
    helpers.attachFileHeaders(res, path);
×
369

370
    var workbook = crossSheetInteractions(sheet);
×
371

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

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

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

401
    return result;
×
402
};
403

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

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

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

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

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

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

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

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

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

513
    // exporting parseNetworkSheet for use in testing. Do not remove!
514
    return {
3✔
515
        grnSightToCytoscape: grnSightToCytoscape,
516
        processGRNmap: processGRNmap,
517
        crossSheetInteractions: crossSheetInteractions,
518
    };
519
};
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