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

dondi / GRNsight / 25834941973

14 May 2026 12:46AM UTC coverage: 81.492% (+0.7%) from 80.816%
25834941973

push

github

ceciliazaragoza
now there is whitespace between the border and the node when the nodes are previously outside of the bounds

470 of 582 branches covered (80.76%)

Branch coverage included in aggregate %.

1322 of 1617 relevant lines covered (81.76%)

8849.16 hits per line

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

89.39
/server/controllers/network-sheet-parser.js
1
// var multiparty = require("multiparty");
2
// var path = require("path");
3
// var demoWorkbooks = require(__dirname + "/demo-workbooks");
4

5
const { NETWORK_PPI_MODE, NETWORK_GRN_MODE, CELL_A1_GRN, CELL_A1_PPI } = require("./constants");
3✔
6
const { initWorkbook } = require("./helpers");
3✔
7

8
var semanticChecker = require(__dirname + "/semantic-checker");
3✔
9

10
var constants = require(__dirname + "/workbook-constants");
3✔
11

12
// const NETWORK_SHEET_NAMES = ["network", "network_optimized_weights"];
13

14
// const isNetworkSheet = (sheetName) => {
15
//     return NETWORK_SHEET_NAMES.some(function (network) {
16
//         return (sheetName.toLowerCase() === network);
17
//     });
18
// };
19
// Currently only going to number 76 because currently the network errors out at 75+ genes.
20

21
var addMessageToArray = function (messageArray, message) {
3✔
22
    messageArray.push(message);
297,504✔
23
};
24

25
var addWarning = function (workbook, message) {
3✔
26
    var warningsCount = workbook.warnings.length;
295,659✔
27
    var MAX_WARNINGS = 75;
295,659✔
28
    if (warningsCount < MAX_WARNINGS) {
295,659✔
29
        addMessageToArray(workbook.warnings, message);
1,287✔
30
    } else {
31
        addMessageToArray(workbook.errors, constants.errors.warningsCountError);
294,372✔
32
        return false;
294,372✔
33
    }
34
};
35

36
var addError = function (workbook, message) {
3✔
37
    var errorsCount = workbook.errors.length;
1,845✔
38
    var MAX_ERRORS = 20;
1,845✔
39
    if (errorsCount < MAX_ERRORS) {
1,845✔
40
        addMessageToArray(workbook.errors, message);
1,827✔
41
    } else {
42
        addMessageToArray(workbook.errors, constants.errors.errorsCountError);
18✔
43
        return false;
18✔
44
    }
45
};
46

47
var checkDuplicates = function (errorArray, sourceGenes, targetGenes) {
3✔
48
    // Run through the source genes and check if the gene in slot i is the same as the one next to it
49
    for (var i = 0; i < sourceGenes.length - 1; i++) {
2,760✔
50
        if (sourceGenes[i] === sourceGenes[i + 1]) {
38,613✔
51
            errorArray.push(constants.errors.duplicateGeneError("source", sourceGenes[i]));
24✔
52
        }
53
    }
54
    // Run through the target genes and check if the gene in slot j is the same as the one next to it
55
    for (var j = 0; j < targetGenes.length - 1; j++) {
2,760✔
56
        if (targetGenes[j] === targetGenes[j + 1]) {
38,616✔
57
            errorArray.push(constants.errors.duplicateGeneError("target", targetGenes[j]));
24✔
58
        }
59
    }
60
};
61

62
var addTargetGene = function (workbook, sheet, row, targetGenes, genesList) {
3✔
63
    let currentGene = { name: sheet.data[row][0] };
41,577✔
64
    if (currentGene.name === undefined) {
41,577✔
65
        addWarning(workbook, constants.warnings.missingTargetGeneWarning(row, 0));
102✔
66
    } else if (isNaN(currentGene.name) && typeof currentGene.name !== "string") {
41,475!
67
        addWarning(workbook, constants.warnings.missingTargetGeneWarning(row, 0));
×
68
    } else {
69
        // set currentGeneName to a String so toUpperCase doesn't mess up
70
        currentGene.name = currentGene.name.toString();
41,475✔
71
        targetGenes.push(String(currentGene.name.toUpperCase()));
41,475✔
72
        // Here we check to see if we've already seen the gene name that we're about to store
73
        // Genes may or may not be present due to asymmetry or unorderedness
74
        // If it's in the genesList, it will return a number > 0, so we won't store it
75
        // If it's not there, it will return -1, so we add it.
76
        if (genesList.indexOf(String(currentGene.name.toUpperCase())) === -1) {
41,475✔
77
            genesList.push(String(currentGene.name.toUpperCase()));
270✔
78
            workbook.genes.push(currentGene);
270✔
79
        }
80
    }
81
};
82

83
var parseNetworkSheet = function (sheet, network) {
3✔
84
    var currentLink;
85
    var currentGene;
86
    var sourceGene;
87
    var targetGene;
88
    var sourceGeneNumber;
89
    var targetGeneNumber;
90
    var genesList = []; // This will contain all of the genes in upper case for use in error checking
2,793✔
91
    var sourceGenes = [];
2,793✔
92
    var targetGenes = [];
2,793✔
93
    var columnChecker = [];
2,793✔
94
    var rowData = [];
2,793✔
95

96
    // check for “cols regulators/rows targets” in cell A1
97
    let cellA1 = "";
2,793✔
98
    try {
2,793✔
99
        cellA1 = sheet.data[0][0];
2,793✔
100
    } catch (err) {
101
        const row = 0;
×
102
        const column = 0;
×
103
        addError(network, constants.errors.missingValueError(row, column));
×
104
        return network;
×
105
    }
106

107
    // TODO There are now 2 valid values for cellA1. One indicates GRN, the other is PPI.
108
    // If neither, then we continue with the warning.
109

110
    // Depending on the value of cellA1, we want to make a new property `networkType` which
111
    // will indicate the network type. THe web app then reads this to decide what to do next.
112
    if (cellA1 !== CELL_A1_GRN && cellA1 !== CELL_A1_PPI) {
2,793✔
113
        addError(network, constants.errors.incorrectCellA1WorkbookError(sheet.name));
942✔
114
    }
115

116
    // Get Source Genes
117
    for (let i = 1; i <= sheet.data[0].slice(1).length; i++) {
2,793✔
118
        currentGene = { name: sheet.data[0][i] };
41,679✔
119
        if (currentGene.name === undefined) {
41,679✔
120
            addWarning(network, constants.warnings.missingSourceGeneWarning(0, i));
69✔
121
        } else if (isNaN(currentGene.name) && typeof currentGene.name !== "string") {
41,610!
122
            addWarning(network, constants.warnings.missingSourceGeneWarning(0, i));
×
123
        } else {
124
            // set currentGeneName to a String so toUpperCase doesn't mess up
125
            currentGene.name = currentGene.name.toString();
41,610✔
126
            sourceGenes.push(String(currentGene.name.toUpperCase()));
41,610✔
127
            genesList.push(String(currentGene.name.toUpperCase()));
41,610✔
128
            network.genes.push(currentGene);
41,610✔
129
        }
130
    }
131
    // Set columnCount to undefineds in each column equal to the length of the gene names
132
    columnChecker = new Array(sheet.data[0].length).fill(0);
2,793✔
133

134
    for (var row = 0, column = 1; row < sheet.data.length; row++) {
2,793✔
135
        if (sheet.data[row].length === 0) {
45,126✔
136
            // if the current row is empty
137
            if (addError(network, constants.errors.emptyRowError(row)) === false) {
756✔
138
                return network;
12✔
139
            }
140
        } else if (sheet.data[row].length === 1) {
44,370✔
141
            addTargetGene(network, sheet, row, targetGenes, genesList);
78✔
142
            rowData.push(row);
78✔
143
        } else {
144
            // if the row has data...
145
            // Genes found when row = 0 are targets. Genes found when column = 0 are source genes.
146
            // We set column = 1 in the for loop so it skips row 0 column 0, since that contains no matrix data.
147
            // Yes, the rows and columns use array numbering. That is, they start at 0, not 1.
148
            try {
44,292✔
149
                // This prevents the server from crashing if something goes wrong anywhere in here
150
                if (sheet.data[row].length < sheet.data[0].length) {
44,292✔
151
                    for (let i = sheet.data[row].length - 1; i < sheet.data[0].length - 1; i++) {
72✔
152
                        columnChecker[i]++;
72✔
153
                        addWarning(network, constants.warnings.invalidMatrixDataWarning(row, i));
72✔
154
                    }
155
                }
156
                while (column < sheet.data[row].length) {
44,292✔
157
                    // While we haven't gone through all of the columns in this row...
158
                    if (row !== 0) {
1,192,581✔
159
                        // skip the source genes
160
                        if (column === 0) {
1,150,902✔
161
                            // These genes are the target genes
162
                            try {
41,499✔
163
                                addTargetGene(network, sheet, row, targetGenes, genesList);
41,499✔
164
                            } catch (err) {
165
                                sourceGene = sheet.data[0][column];
×
166
                                targetGene = sheet.data[row][0];
×
167
                                addError(network, constants.errors.corruptGeneError(row, column));
×
168
                                return network;
×
169
                            }
170
                        } else {
171
                            // If we're within the matrix and lookin' at the data...
172
                            try {
1,109,403✔
173
                                if (sheet.data[row][column] === undefined) {
1,109,403✔
174
                                    // SHOULD BE: addError(network, constants.errors.missingValueError(row, column));
175
                                    columnChecker[column - 1]++;
295,278✔
176
                                    addWarning(
295,278✔
177
                                        network,
178
                                        constants.warnings.invalidMatrixDataWarning(row, column)
179
                                    );
180
                                } else if (
814,125✔
181
                                    isNaN(+("" + sheet.data[row][column])) ||
1,628,250✔
182
                                    typeof sheet.data[row][column] !== "number"
183
                                ) {
184
                                    addError(network, constants.errors.dataTypeError(row, column));
18✔
185
                                    return network;
18✔
186
                                } else {
187
                                    // columnChecker[column - 1] = columnChecker[column - 1]++;
188
                                    if (sheet.data[row][column] !== 0) {
814,107✔
189
                                        // We only care about non-zero values
190
                                        // Grab the source and target genes' names
191
                                        sourceGene = sheet.data[0][column];
74,781✔
192
                                        targetGene = sheet.data[row][0];
74,781✔
193
                                        if (sourceGene === undefined || targetGene === undefined) {
74,781✔
194
                                            addWarning(
138✔
195
                                                network,
196
                                                constants.warnings.randomDataWarning(
197
                                                    "undefined",
198
                                                    row,
199
                                                    column
200
                                                )
201
                                            );
202
                                        } else if (
74,643!
203
                                            (isNaN(sourceGene) && typeof sourceGene !== "string") ||
297,852✔
204
                                            (isNaN(targetGene) && typeof targetGene !== "string")
205
                                        ) {
206
                                            addWarning(
×
207
                                                network,
208
                                                constants.warnings.randomDataWarning(
209
                                                    "NaN",
210
                                                    row,
211
                                                    column
212
                                                )
213
                                            );
214
                                        } else {
215
                                            // Grab the source and target genes' numbers
216
                                            sourceGeneNumber = genesList.indexOf(
74,643✔
217
                                                sourceGene.toString().toUpperCase()
218
                                            );
219
                                            targetGeneNumber = genesList.indexOf(
74,643✔
220
                                                targetGene.toString().toUpperCase()
221
                                            );
222
                                            currentLink = {
74,643✔
223
                                                source: sourceGeneNumber,
224
                                                target: targetGeneNumber,
225
                                                value: sheet.data[row][column],
226
                                            };
227
                                            // Here we set the properties of the current link
228
                                            // before we push them to the network
229
                                            if (network.sheetType === "weighted") {
74,643✔
230
                                                if (currentLink.value > 0) {
39,372✔
231
                                                    // If it's a positive number, mark it as an activator
232
                                                    currentLink.type = "arrowhead";
31,716✔
233
                                                    // GRNsight v1 magenta edge color
234
                                                    // currentLink.stroke = "MediumVioletRed";
235
                                                    // Node coloring-consistent red edge color
236
                                                    currentLink.stroke = "rgb(195, 61, 61)";
31,716✔
237
                                                    network.positiveWeights.push(currentLink.value);
31,716✔
238
                                                } else {
239
                                                    // if it's a negative number, mark it as a repressor
240
                                                    currentLink.type = "repressor";
7,656✔
241
                                                    // currentLink.stroke = "DarkTurquoise";
242
                                                    // GRNsight v1 cyan edge color
243
                                                    // Node coloring-consistent blue edge color
244
                                                    currentLink.stroke = "rgb(51, 124, 183)";
7,656✔
245
                                                    network.negativeWeights.push(currentLink.value);
7,656✔
246
                                                }
247
                                            } else if (network.sheetType === "unweighted") {
35,271✔
248
                                                currentLink.type = "arrowhead";
35,271✔
249
                                                currentLink.stroke = "black";
35,271✔
250
                                                if (currentLink.value !== 1) {
35,271✔
251
                                                    addWarning(
3✔
252
                                                        network,
253
                                                        constants.warnings.incorrectlyNamedSheetWarning()
254
                                                    );
255
                                                    currentLink.value = 1;
×
256
                                                }
257
                                                network.positiveWeights.push(currentLink.value);
35,268✔
258
                                            }
259
                                            network.links.push(currentLink);
74,640✔
260
                                        }
261
                                    }
262
                                }
263
                            } catch (err) {
264
                                addError(network, constants.errors.missingValueError(row, column));
3✔
265
                                // SHOULD BE: addError(network, constants.errors.unknownFileError);
266
                                return network;
3✔
267
                            }
268
                        }
269
                    }
270
                    column++; // Let's move on to the next column!
1,192,560✔
271
                } // Once we finish with the current row...
272
                if (column < sourceGenes.length) {
44,271!
273
                    for (let x = column; x < sourceGenes.length - 1; x++) {
×
274
                        columnChecker[column] = columnChecker[column]++;
×
275
                    }
276
                }
277
                column = 0; // let's go back to column 0 on the next row!
44,271✔
278
            } catch (err) {
279
                // We only get here if something goes drastically wrong. We don't want to get here.
280
                addError(network, constants.errors.unknownError);
×
281
                return network;
×
282
            }
283
        }
284
    }
285

286
    if (rowData.length === sheet.data.length - 1) {
2,760✔
287
        addError(network, constants.errors.emptyMatrixDataError(sheet.name));
12✔
288
    } else {
2,748✔
289
        for (let x of rowData) {
2,748✔
290
            addError(network, constants.errors.emptyRowDataError(x, sheet.name));
30✔
291
        }
2,748✔
292
    }
293

294
    for (var i = 0; i < columnChecker.length; i++) {
2,760✔
295
        if (columnChecker[i] >= sheet.data.length - 1) {
142,503✔
296
            if (sheet.data[0][i + 1] === undefined) {
78✔
297
                addError(network, constants.errors.emptyColumnError(i + 1, sheet.name));
42✔
298
            } else {
299
                addError(network, constants.errors.emptyColumnDataError(i + 1, sheet.name));
36✔
300
            }
301
        }
302
    }
303

304
    // Move on to semanticChecker.
305

306
    // We sort them here because gene order is not relevant before this point
307
    // Sorting them now means duplicates will be right next to each other
308
    sourceGenes.sort();
2,760✔
309
    targetGenes.sort();
2,760✔
310

311
    // syntactic duplicate checker for both columns and rows
312
    checkDuplicates(network.errors, sourceGenes, targetGenes);
2,760✔
313

314
    // NOTE: Temporarily commented out pending resolution of #474, and other related issues
315
    // try {
316
    //   network.graphStatisticsReport = graphStatisticsReport(network);
317
    // } catch (err) {
318
    //   console.log ("Graph statistics report failed to be complete.");
319
    // }
320
    return semanticChecker(network);
2,760✔
321
};
322

323
/*
324
 * This method detect the network type of the workbook file either grn or protein-protein-physical-interactions
325
 * If cellA1 = "cols regulators/rows targets" -> workbookType = grn
326
 * If cellA1 = "cols protein1/rows protein2" -> workbookType = "protein-protein-physical-interaction"
327
 * else undefined
328
 */
329

330
exports.workbookType = function (workbookFile) {
3✔
331
    let workbookType;
48✔
332
    for (const sheet of workbookFile) {
297✔
333
        if (
297✔
334
            sheet.name.toLowerCase() === "network" ||
546✔
335
            sheet.name.toLowerCase() === "network_optimized_weights"
336
        ) {
337
            const cellA1 = sheet.data[0][0];
48✔
338

339
            if (cellA1 === CELL_A1_GRN) {
48!
340
                workbookType = NETWORK_GRN_MODE;
48✔
341
            } else if (cellA1 === CELL_A1_PPI) {
×
342
                workbookType = NETWORK_PPI_MODE;
×
343
            } else {
344
                workbookType = undefined;
×
345
            }
346
            break;
48✔
347
        }
348
    }
48✔
349
    return workbookType;
48✔
350
};
351

352
exports.networks = function (workbookFile) {
3✔
353
    const networks = {
1,524✔
354
        network: {},
355
        networkOptimizedWeights: {},
356
        networkWeights: {},
357
    };
1,524✔
358

359
    for (const element of workbookFile) {
12,099✔
360
        // === 'network' for backwards compatibility of test files
361
        if (element.name.toLowerCase() === "network") {
12,099✔
362
            // Here we have found a network sheet containing simple data. We keep looking
363
            // in case there is also a network sheet with optimized weights
364
            networks.network = parseNetworkSheet(
1,332✔
365
                element,
366
                initWorkbook({ sheetType: "unweighted" })
367
            );
368
        } else if (element.name.toLowerCase() === "network_optimized_weights") {
10,767✔
369
            // We found a network sheet with optimized weights, which is the ideal data source.
370
            networks.networkOptimizedWeights = parseNetworkSheet(
714✔
371
                element,
372
                initWorkbook({ sheetType: "weighted" })
373
            );
374
        } else if (element.name.toLowerCase() === "network_weights") {
10,053✔
375
            // We found a network_weights sheet to preserve existing network type sheet data
376
            networks.networkWeights = parseNetworkSheet(
747✔
377
                element,
378
                initWorkbook({ sheetType: "weighted" })
379
            );
380
        }
381
    }
1,524✔
382

383
    if (
1,524✔
384
        Object.keys(networks.network).length === 0 &&
1,716✔
385
        Object.keys(networks.networkOptimizedWeights).length === 0
386
    ) {
387
        networks.network = initWorkbook({ sheetType: "unweighted" });
6✔
388
        addError(networks.network, constants.errors.missingNetworkError);
6✔
389
    }
390
    return networks;
1,524✔
391
};
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