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

dondi / GRNsight / 17425127190

03 Sep 2025 06:33AM UTC coverage: 80.379%. Remained the same
17425127190

Pull #1198

github

dondi
Address post-merge linting errors.
Pull Request #1198: Prettier Configuration

410 of 513 branches covered (79.92%)

Branch coverage included in aggregate %.

266 of 335 new or added lines in 18 files covered. (79.4%)

7 existing lines in 4 files now uncovered.

1200 of 1490 relevant lines covered (80.54%)

9339.44 hits per line

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

89.3
/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);
298,665✔
23
};
24

25
var addWarning = function (workbook, message) {
3✔
26
    var warningsCount = workbook.warnings.length;
297,699✔
27
    var MAX_WARNINGS = 75;
297,699✔
28
    if (warningsCount < MAX_WARNINGS) {
297,699✔
29
        addMessageToArray(workbook.warnings, message);
3,327✔
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;
966✔
38
    var MAX_ERRORS = 20;
966✔
39
    if (errorsCount < MAX_ERRORS) {
966✔
40
        addMessageToArray(workbook.errors, message);
948✔
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,334✔
50
        if (sourceGenes[i] === sourceGenes[i + 1]) {
37,905✔
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,334✔
56
        if (targetGenes[j] === targetGenes[j + 1]) {
37,908✔
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] };
40,443✔
64
    if (currentGene.name === undefined) {
40,443✔
65
        addWarning(workbook, constants.warnings.missingTargetGeneWarning(row, 0));
102✔
66
    } else if (isNaN(currentGene.name) && typeof currentGene.name !== "string") {
40,341!
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();
40,341✔
71
        targetGenes.push(String(currentGene.name.toUpperCase()));
40,341✔
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) {
40,341✔
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,367✔
91
    var sourceGenes = [];
2,367✔
92
    var targetGenes = [];
2,367✔
93
    var columnChecker = [];
2,367✔
94
    var rowData = [];
2,367✔
95

96
    // check for “cols regulators/rows targets” in cell A1
97
    let cellA1 = "";
2,367✔
98
    try {
2,367✔
99
        cellA1 = sheet.data[0][0];
2,367✔
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,367✔
113
        addWarning(network, constants.warnings.incorrectCellA1WorkbookWarning(sheet.name));
2,040✔
114
    }
115

116
    // Get Source Genes
117
    for (let i = 1; i <= sheet.data[0].slice(1).length; i++) {
2,367✔
118
        currentGene = { name: sheet.data[0][i] };
40,545✔
119
        if (currentGene.name === undefined) {
40,545✔
120
            addWarning(network, constants.warnings.missingSourceGeneWarning(0, i));
69✔
121
        } else if (isNaN(currentGene.name) && typeof currentGene.name !== "string") {
40,476!
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();
40,476✔
126
            sourceGenes.push(String(currentGene.name.toUpperCase()));
40,476✔
127
            genesList.push(String(currentGene.name.toUpperCase()));
40,476✔
128
            network.genes.push(currentGene);
40,476✔
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,367✔
133

134
    for (var row = 0, column = 1; row < sheet.data.length; row++) {
2,367✔
135
        if (sheet.data[row].length === 0) {
43,629✔
136
            // if the current row is empty
137
            if (addError(network, constants.errors.emptyRowError(row)) === false) {
819✔
138
                return network;
12✔
139
            }
140
        } else if (sheet.data[row].length === 1) {
42,810✔
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 {
42,732✔
149
                // This prevents the server from crashing if something goes wrong anywhere in here
150
                if (sheet.data[row].length < sheet.data[0].length) {
42,732✔
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) {
42,732✔
157
                    // While we haven't gone through all of the columns in this row...
158
                    if (row !== 0) {
1,189,071✔
159
                        // skip the source genes
160
                        if (column === 0) {
1,148,526✔
161
                            // These genes are the target genes
162
                            try {
40,365✔
163
                                addTargetGene(network, sheet, row, targetGenes, genesList);
40,365✔
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,108,161✔
173
                                if (sheet.data[row][column] === undefined) {
1,108,161✔
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 (
812,883✔
181
                                    isNaN(+("" + sheet.data[row][column])) ||
1,625,766✔
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) {
812,865✔
189
                                        // We only care about non-zero values
190
                                        // Grab the source and target genes' names
191
                                        sourceGene = sheet.data[0][column];
72,489✔
192
                                        targetGene = sheet.data[row][0];
72,489✔
193
                                        if (sourceGene === undefined || targetGene === undefined) {
72,489✔
194
                                            addWarning(
138✔
195
                                                network,
196
                                                constants.warnings.randomDataWarning(
197
                                                    "undefined",
198
                                                    row,
199
                                                    column
200
                                                )
201
                                            );
202
                                        } else if (
72,351!
203
                                            (isNaN(sourceGene) && typeof sourceGene !== "string") ||
288,684✔
204
                                            (isNaN(targetGene) && typeof targetGene !== "string")
205
                                        ) {
NEW
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(
72,351✔
217
                                                sourceGene.toString().toUpperCase()
218
                                            );
219
                                            targetGeneNumber = genesList.indexOf(
72,351✔
220
                                                targetGene.toString().toUpperCase()
221
                                            );
222
                                            currentLink = {
72,351✔
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") {
72,351✔
230
                                                if (currentLink.value > 0) {
38,460✔
231
                                                    // If it's a positive number, mark it as an activator
232
                                                    currentLink.type = "arrowhead";
31,074✔
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,074✔
237
                                                    network.positiveWeights.push(currentLink.value);
31,074✔
238
                                                } else {
239
                                                    // if it's a negative number, mark it as a repressor
240
                                                    currentLink.type = "repressor";
7,386✔
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,386✔
245
                                                    network.negativeWeights.push(currentLink.value);
7,386✔
246
                                                }
247
                                            } else if (network.sheetType === "unweighted") {
33,891✔
248
                                                currentLink.type = "arrowhead";
33,891✔
249
                                                currentLink.stroke = "black";
33,891✔
250
                                                if (currentLink.value !== 1) {
33,891✔
251
                                                    addWarning(
3✔
252
                                                        network,
253
                                                        constants.warnings.incorrectlyNamedSheetWarning()
254
                                                    );
UNCOV
255
                                                    currentLink.value = 1;
×
256
                                                }
257
                                                network.positiveWeights.push(currentLink.value);
33,888✔
258
                                            }
259
                                            network.links.push(currentLink);
72,348✔
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,189,050✔
271
                } // Once we finish with the current row...
272
                if (column < sourceGenes.length) {
42,711!
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!
42,711✔
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,334✔
287
        addError(network, constants.errors.emptyMatrixDataError(sheet.name));
12✔
288
    } else {
2,322✔
289
        for (let x of rowData) {
2,322✔
290
            addError(network, constants.errors.emptyRowDataError(x, sheet.name));
30✔
291
        }
2,322✔
292
    }
293

294
    for (var i = 0; i < columnChecker.length; i++) {
2,334✔
295
        if (columnChecker[i] >= sheet.data.length - 1) {
140,943✔
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,334✔
309
    targetGenes.sort();
2,334✔
310

311
    // syntactic duplicate checker for both columns and rows
312
    checkDuplicates(network.errors, sourceGenes, targetGenes);
2,334✔
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,334✔
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/ row 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;
39✔
332
    for (const sheet of workbookFile) {
291✔
333
        if (sheet.name.toLowerCase() === "network") {
291✔
334
            const cellA1 = sheet.data[0][0];
39✔
335

336
            if (cellA1 === CELL_A1_GRN) {
39!
337
                workbookType = NETWORK_GRN_MODE;
39✔
338
            } else if (cellA1 === CELL_A1_PPI) {
×
339
                workbookType = NETWORK_PPI_MODE;
×
340
            } else {
341
                workbookType = undefined;
×
342
            }
343
            break;
39✔
344
        }
345
    }
39✔
346
    return workbookType;
39✔
347
};
348

349
exports.networks = function (workbookFile) {
3✔
350
    const networks = {
1,272✔
351
        network: {},
352
        networkOptimizedWeights: {},
353
        networkWeights: {},
354
    };
1,272✔
355

356
    for (const element of workbookFile) {
9,984✔
357
        // === 'network' for backwards compatibility of test files
358
        if (element.name.toLowerCase() === "network") {
9,984✔
359
            // Here we have found a network sheet containing simple data. We keep looking
360
            // in case there is also a network sheet with optimized weights
361
            networks.network = parseNetworkSheet(
1,080✔
362
                element,
363
                initWorkbook({ sheetType: "unweighted" })
364
            );
365
        } else if (element.name.toLowerCase() === "network_optimized_weights") {
8,904✔
366
            // We found a network sheet with optimized weights, which is the ideal data source.
367
            networks.networkOptimizedWeights = parseNetworkSheet(
624✔
368
                element,
369
                initWorkbook({ sheetType: "weighted" })
370
            );
371
        } else if (element.name.toLowerCase() === "network_weights") {
8,280✔
372
            // We found a network_weights sheet to preserve existing network type sheet data
373
            networks.networkWeights = parseNetworkSheet(
663✔
374
                element,
375
                initWorkbook({ sheetType: "weighted" })
376
            );
377
        }
378
    }
1,272✔
379

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