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

dondi / GRNsight / 22111382279

17 Feb 2026 06:50PM UTC coverage: 80.145% (+0.02%) from 80.129%
22111382279

Pull #1325

github

ntran18
Remove unused TWO_COLUMN_SHEET_NAME
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%)

1 existing line in 1 file now uncovered.

1230 of 1529 relevant lines covered (80.44%)

9146.09 hits per line

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

92.83
/server/controllers/additional-sheet-parser.js
1
// Parses "optimization_paramters" and 2-column sheets
2
// from GRNmap input or output workbook
3

4
var constants = require(__dirname + "/workbook-constants");
3✔
5

6
const getSheetHeader = (sheetName, column, row) => {
3✔
7
    if (row === 0) {
993✔
8
        if (sheetName === "production_rates" || sheetName === "optimized_production_rates") {
993✔
9
            return "production_rate";
222✔
10
        } else if (sheetName === "degradation_rates") {
771✔
11
            return "degradation_rate";
153✔
12
        } else if (sheetName === "threshold_b" || sheetName === "optimized_threshold_b") {
618✔
13
            return "threshold_b";
222✔
14
        } else if (sheetName === "optimization_parameters") {
396✔
15
            return column === 0 ? "optimization_parameter" : "value";
306✔
16
        } else if (sheetName === "optimization_diagnostics") {
90✔
17
            return column === 0 ? "Parameter" : "Value";
90✔
18
        }
19
    }
20
};
21

22
const optimizationParametersTypeKey = {
3✔
23
    alpha: "number",
24
    kk_max: "number",
25
    MaxIter: "number",
26
    TolFun: "number",
27
    MaxFunEval: "number",
28
    TolX: "number",
29
    production_function: "string",
30
    L_curve: "number",
31
    estimate_params: "number",
32
    make_graphs: "number",
33
    fix_P: "number",
34
    fix_b: "number",
35
    expression_timepoints: "object",
36
    Strain: "object",
37
    species: "string",
38
    taxon_id: "number",
39
    workbookType: "string",
40
    simulation_timepoints: "object",
41
    b_or_tau: "number",
42
};
43

44
const optimizationDiagnosticsParameters = ["LSE", "Penalty", "min LSE", "iteration count"];
3✔
45

46
const optimizationParametersObectKey = {
3✔
47
    expression_timepoints: "number",
48
    Strain: "string",
49
    simulation_timepoints: "number",
50
};
51

52
const addWarning = (workbook, message) => {
3✔
53
    let warningsCount;
54
    if (!Object.keys(workbook).includes("warnings")) {
141!
55
        warningsCount = 0;
×
56
        workbook.warnings = [];
×
57
    } else {
58
        warningsCount = workbook.warnings.length;
141✔
59
    }
60
    const MAX_WARNINGS = 75;
141✔
61
    if (warningsCount < MAX_WARNINGS) {
141!
62
        workbook.warnings.push(message);
141✔
63
    } else {
64
        workbook.errors.push(constants.errors.warningsCountError);
×
65
        return false;
×
66
    }
67
};
68

69
const addError = (output, message) => {
3✔
70
    const errorsCount = output.errors.length;
144✔
71
    const MAX_ERRORS = 20;
144✔
72
    if (errorsCount < MAX_ERRORS) {
144!
73
        output.errors.push(message);
144✔
74
    } else {
75
        output.errors.push(constants.errors.errorsCountError);
×
76
        return false;
×
77
    }
78
};
79

80
const validGeneName = (output, sheetName, gene, row) => {
3✔
81
    var maxGeneLength = 12;
7,221✔
82
    var regex = /[^a-z0-9_\-]/gi;
7,221✔
83
    if (typeof gene !== "string") {
7,221✔
84
        addError(output, constants.errors.invalidGeneTypeError(sheetName, gene, row));
30✔
85
        return false;
30✔
86
    } else if (gene.length > maxGeneLength) {
7,191✔
87
        addError(output, constants.errors.invalidGeneLengthError(sheetName, gene, row));
15✔
88
        return false;
15✔
89
    } else if (gene.match(regex) !== null) {
7,176✔
90
        addError(output, constants.errors.specialCharacterError(sheetName, gene, row));
15✔
91
        return false;
15✔
92
    }
93
    return true;
7,161✔
94
};
95
// Optimization Parameters Parser
96
const parseMetaDataSheet = sheet => {
3✔
97
    let meta = {
150✔
98
        data: {},
99
        errors: [],
100
        warnings: [],
101
    };
102
    let paramType;
103
    if (sheet.data[0][0] === undefined) {
150✔
104
        addError(
3✔
105
            meta,
106
            constants.errors.missingColumnHeaderError(
107
                sheet.name,
108
                constants.numbersToLetters[0],
109
                getSheetHeader(sheet.name, 0, 0)
110
            )
111
        );
112
    } else if (sheet.data[0][0] !== getSheetHeader(sheet.name, 0, 0)) {
147✔
113
        addError(
3✔
114
            meta,
115
            constants.errors.incorrectColumnHeaderError(
116
                sheet.name,
117
                constants.numbersToLetters[0],
118
                getSheetHeader(sheet.name, 0, 0)
119
            )
120
        );
121
    }
122
    if (sheet.data[0][1] === undefined) {
150✔
123
        addError(
3✔
124
            meta,
125
            constants.errors.missingColumnHeaderError(
126
                sheet.name,
127
                constants.numbersToLetters[1],
128
                getSheetHeader(sheet.name, 1, 0)
129
            )
130
        );
131
    } else if (sheet.data[0][1] !== getSheetHeader(sheet.name, 1, 0)) {
147✔
132
        addError(
3✔
133
            meta,
134
            constants.errors.incorrectColumnHeaderError(
135
                sheet.name,
136
                constants.numbersToLetters[1],
137
                getSheetHeader(sheet.name, 1, 0)
138
            )
139
        );
140
    }
141

142
    sheet.data.forEach(function (element, index) {
150✔
143
        if (index !== 0) {
2,766✔
144
            const value = element.slice(1);
2,616✔
145
            // Extract element from array if array contains only 1 value
146
            meta.data[element[0]] = value.length > 1 ? value : value[0];
2,616✔
147
        }
148
    });
149
    for (let key in meta.data) {
150✔
150
        paramType = optimizationParametersTypeKey[key];
2,616✔
151
        if (paramType === "object") {
2,616✔
152
            paramType = `list of ${optimizationParametersObectKey[key]}s`;
450✔
153
        }
154
        if (meta.data[key] === undefined) {
2,616✔
155
            addWarning(meta, constants.warnings.unknownOptimizationParameter(sheet.name, key));
3✔
156
        } else if (typeof meta.data[key] !== optimizationParametersTypeKey[key]) {
2,613✔
157
            if (
66✔
158
                optimizationParametersTypeKey[key] !== "object" ||
129✔
159
                typeof meta.data[key] !== optimizationParametersObectKey[key]
160
            ) {
161
                addWarning(
3✔
162
                    meta,
163
                    constants.warnings.invalidOptimizationParameter(sheet.name, key, paramType)
164
                );
165
            }
166
        } else if (optimizationParametersTypeKey[key] === "object") {
2,547✔
167
            for (let val of meta.data[key]) {
3,663✔
168
                if (typeof val !== optimizationParametersObectKey[key]) {
3,663✔
169
                    // throw error once per object. Makes sure that errors list is not flooded
170
                    addWarning(
3✔
171
                        meta,
172
                        constants.warnings.invalidOptimizationParameter(sheet.name, key, paramType)
173
                    );
174
                    break;
3✔
175
                }
176
            }
387✔
177
        }
178
    }
179
    return meta;
150✔
180
};
181

182
const parseOptimizationDiagnosticsSheet = sheet => {
3✔
183
    let output = {
42✔
184
        data: {
185
            Parameters: {},
186
            MSE: {
187
                "column-headers": [],
188
                Genes: {},
189
            },
190
        },
191
        errors: [],
192
        warnings: [],
193
    };
194
    let currentParameter;
195
    let currentValue;
196
    let currentGene;
197
    let currentMSE = [];
42✔
198
    // Check Headers
199
    if (sheet.data[0].length > 1) {
42✔
200
        if (sheet.data[0][0] !== getSheetHeader(sheet.name, 0, 0)) {
39✔
201
            addError(
3✔
202
                output,
203
                constants.errors.incorrectColumnHeaderError(
204
                    sheet.name,
205
                    constants.numbersToLetters[0],
206
                    getSheetHeader(sheet.name, 0, 0)
207
                )
208
            );
209
        }
210
        if (sheet.data[0][1] !== getSheetHeader(sheet.name, 1, 0)) {
39✔
211
            addError(
3✔
212
                output,
213
                constants.errors.incorrectColumnHeaderError(
214
                    sheet.name,
215
                    constants.numbersToLetters[1],
216
                    getSheetHeader(sheet.name, 1, 0)
217
                )
218
            );
219
        }
220
    } else {
221
        // seems a bit sus, but we'll see if this works properly during testing :\
222
        for (let col = 1; col >= sheet.data[0].length; col--) {
3✔
223
            addError(
6✔
224
                output,
225
                constants.errors.missingColumnHeaderError(
226
                    sheet.name,
227
                    constants.numbersToLetters[col],
228
                    getSheetHeader(sheet.name, col, 0)
229
                )
230
            );
231
        }
232
    }
233
    // Check Parameter Section
234
    let row = 1;
42✔
235
    // a missing row is the indicator to move onto the MSE
236
    while (sheet.data[row].length > 0) {
42✔
237
        currentParameter = sheet.data[row][0];
174✔
238
        currentValue = sheet.data[row][1];
174✔
239
        if (currentParameter === undefined || currentParameter.replace(/\s+/g, "") === "") {
174✔
240
            if (currentValue === undefined || currentValue.replace(/\s+/g, "") === "") {
3!
241
                // if there is no parameter or value assume that its time to move on
242
                row++;
3✔
243
                break;
3✔
244
            }
245
        }
246
        if (sheet.data[row].length > 2) {
171✔
247
            addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, row + 1));
3✔
248
        }
249
        if (!optimizationDiagnosticsParameters.includes(currentParameter)) {
171✔
250
            if (currentParameter === "Gene") {
3!
251
                row--;
×
252
                break;
×
253
            }
254
            addWarning(
3✔
255
                output,
256
                constants.warnings.unknownOptimizationDiagnosticsParameter(
257
                    sheet.name,
258
                    currentParameter
259
                )
260
            );
261
        } else if (typeof currentValue !== "number") {
168✔
262
            addWarning(
3✔
263
                output,
264
                constants.warnings.invalidOptimizationDiagnosticsValue(sheet.name, currentParameter)
265
            );
266
        } else {
267
            output.data.Parameters[currentParameter] = currentValue;
165✔
268
        }
269
        row++;
171✔
270
    }
271
    // Skip until Gene section
272
    while (sheet.data[row] !== undefined && sheet.data[row].length < 1) {
42✔
273
        row++;
39✔
274
    }
275
    // Check Gene section MSE's
276
    if (sheet.data[row].length > 1) {
42✔
277
        if (sheet.data[row][0] !== "Gene") {
42✔
278
            addWarning(
6✔
279
                output,
280
                constants.warnings.incorrectMSEGeneHeaderWarning(sheet.name, row + 1)
281
            );
282
        }
283
        for (let col = 1; col < sheet.data[row].length; col++) {
42✔
284
            if (!sheet.data[row][col].includes("MSE")) {
252✔
285
                addWarning(
9✔
286
                    output,
287
                    constants.warnings.incorrectMSEHeaderWarning(
288
                        sheet.name,
289
                        sheet.data[row][col],
290
                        row + 1,
291
                        constants.numbersToLetters[col]
292
                    )
293
                );
294
            }
295
            // we still push the header (even tho it's sus) because the gene MSE's are
296
            // dependent on the order of the column headers
297
            output.data.MSE["column-headers"].push(sheet.data[row][col]);
252✔
298
        }
299
        row++;
42✔
300
        // on to the actual genes
301
        while (row < sheet.data.length) {
42✔
302
            if (sheet.data[row].length > output.data.MSE["column-headers"].length + 1) {
630✔
303
                addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, row + 1));
6✔
304
            }
305
            currentGene = sheet.data[row][0];
630✔
306
            // if it's a valid gene set the key = MSE value
307
            if (validGeneName(output, sheet.name, currentGene, row)) {
630✔
308
                for (let col = 1; col <= output.data.MSE["column-headers"].length; col++) {
630✔
309
                    if (typeof sheet.data[row][col] === "number") {
3,780✔
310
                        currentMSE.push(sheet.data[row][col]);
3,738✔
311
                    } else if (sheet.data[row][col] === undefined) {
42✔
312
                        addWarning(
30✔
313
                            output,
314
                            constants.warnings.missingMSEDataWarning(
315
                                sheet.name,
316
                                row + 1,
317
                                constants.numbersToLetters[col]
318
                            )
319
                        );
320
                    } else {
321
                        addWarning(
12✔
322
                            output,
323
                            constants.warnings.invalidMSEDataWarning(
324
                                sheet.name,
325
                                row + 1,
326
                                constants.numbersToLetters[col]
327
                            )
328
                        );
329
                    }
330
                }
331
                output.data.MSE.Genes[currentGene] = currentMSE;
630✔
332
                currentMSE = [];
630✔
333
            }
334
            row++;
630✔
335
        }
336
    }
337
    return output;
42✔
338
};
339

340
const parseTwoColumnSheet = (sheet, genesInNetwork) => {
3✔
341
    let output = {
591✔
342
        data: {},
343
        errors: [],
344
        warnings: [],
345
    };
346

347
    if (sheet.data.length === 0) {
591✔
348
        return output;
9✔
349
    }
350

351
    let currentGene;
352
    let currentValue;
353

354
    const genesMissingValue = [];
582✔
355

356
    // check to see if the genes are strings and the values are numbers
357

358
    for (let row = 0; row < sheet.data.length; row++) {
582✔
359
        if (sheet.data[row].length > 2) {
7,173✔
360
            addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, row + 1));
15✔
361
        }
362
        if (row === 0) {
7,173✔
363
            if (sheet.data[row].length > 0) {
582✔
364
                if (sheet.data[row][0] !== "id") {
582✔
365
                    addError(output, constants.errors.idLabelError(sheet.name));
15✔
366
                }
367
            }
368
            if (sheet.data[row].length > 1) {
582✔
369
                if (sheet.data[row][1] !== getSheetHeader(sheet.name, 1, row)) {
552✔
370
                    addError(
15✔
371
                        output,
372
                        constants.errors.incorrectColumnHeaderError(
373
                            sheet.name,
374
                            constants.numbersToLetters[1],
375
                            getSheetHeader(sheet.name, 1, row)
376
                        )
377
                    );
378
                }
379
            } else {
380
                addError(
30✔
381
                    output,
382
                    constants.errors.missingColumnHeaderError(
383
                        sheet.name,
384
                        constants.numbersToLetters[1],
385
                        getSheetHeader(sheet.name, 1, row)
386
                    )
387
                );
388
            }
389
        } else {
390
            currentGene = sheet.data[row][0];
6,591✔
391
            currentValue = sheet.data[row][1];
6,591✔
392

393
            if (validGeneName(output, sheet.name, currentGene, row + 1)) {
6,591✔
394
                if (currentValue === null || currentValue === undefined) {
6,531✔
395
                    genesMissingValue.push(currentGene);
27✔
396
                    output.data[currentGene] = undefined;
27✔
397
                } else {
398
                    if (typeof currentValue === "number") {
6,504!
399
                        output.data[currentGene] = currentValue;
6,504✔
400
                    } else {
NEW
401
                        if (typeof currentValue === "number") {
×
NEW
402
                            output.data[currentGene] = currentValue;
×
NEW
403
                            genesInSheet.push(currentGene);
×
404
                        } else {
NEW
405
                            addError(
×
406
                                output,
407
                                constants.errors.invalidValueError(
408
                                    sheet.name,
409
                                    currentValue,
410
                                    row + 1,
411
                                    getSheetHeader(sheet.name, 1, row)
412
                                )
413
                            );
414
                        }
415
                    }
416
                }
417
            }
418
        }
419
    }
420

421
    const allMissing =
422
        genesInNetwork && genesInNetwork.every(gene => genesMissingValue.includes(gene));
582✔
423
    if (allMissing) {
582✔
424
        addWarning(
9✔
425
            output,
426
            constants.warnings.missingAllValuesForGenes(sheet.name, genesMissingValue)
427
        );
428
    }
429

430
    // Check for missing genes in sheet
431
    if (genesInNetwork) {
582✔
432
        //  Check if the output data keys (genes in sheet) include all genes in the network
433
        const missingGenes = genesInNetwork.filter(g => !Object.keys(output.data).includes(g));
2,268✔
434
        if (missingGenes.length > 0) {
288✔
435
            addWarning(
36✔
436
                output,
437
                constants.warnings.missingGenesInTwoColumnSheetWarningWhenImporting(
438
                    sheet.name,
439
                    missingGenes.join(", ")
440
                )
441
            );
442
        }
443
    }
444

445
    return output;
582✔
446
};
447

448
module.exports = function (workbookFile, genesInNetwork) {
3✔
449
    let output = {
174✔
450
        meta: {
451
            data: {},
452
            errors: [],
453
            warnings: [],
454
        }, // optimization_parameters only
455
        twoColumnSheets: {}, // 2-column data
456
        meta2: {}, // optimation_diagnostics only //temporary until where it goes is decided
457
    };
458
    workbookFile.forEach(function (sheet) {
174✔
459
        if (sheet.name === "optimization_parameters") {
2,034✔
460
            output.meta = parseMetaDataSheet(sheet);
150✔
461
            // above line creates an object from the optimization paramerters sheet
462
            // these are part of the "meta" property
463
        } else if (constants.TWO_COL_SHEET_NAMES.includes(sheet.name)) {
1,884✔
464
            output.twoColumnSheets[sheet.name] = parseTwoColumnSheet(sheet, genesInNetwork);
591✔
465
        } else if (sheet.name === "optimization_diagnostics") {
1,293✔
466
            output.meta2 = parseOptimizationDiagnosticsSheet(sheet);
42✔
467
        }
468
    });
469
    return output;
174✔
470
};
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