• 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

98.01
/server/controllers/additional-sheet-parser.js
1
// Parses "optimization_parameters" and 2-column sheets
2
// from GRNmap input or output workbook
3
const {
4
    applyTwoColumnSheetWarnings,
3✔
5
    isValidHeader,
3✔
6
    isValidGeneName,
3✔
7
    validateRowEntry,
3✔
8
    addWarning,
3✔
9
    addError,
3✔
10
} = require("./validators/two-column-warnings");
3✔
11

12
var constants = require(__dirname + "/workbook-constants");
3✔
13

14
const optimizationParametersTypeKey = {
3✔
15
    alpha: "number",
16
    kk_max: "number",
17
    MaxIter: "number",
18
    TolFun: "number",
19
    MaxFunEval: "number",
20
    TolX: "number",
21
    production_function: "string",
22
    L_curve: "number",
23
    estimate_params: "number",
24
    make_graphs: "number",
25
    fix_P: "number",
26
    fix_b: "number",
27
    expression_timepoints: "object",
28
    Strain: "object",
29
    species: "string",
30
    taxon_id: "number",
31
    workbookType: "string",
32
    simulation_timepoints: "object",
33
    b_or_tau: "number",
34
};
35

36
const optimizationDiagnosticsParameters = ["LSE", "Penalty", "min LSE", "iteration count"];
3✔
37

38
const optimizationParametersObjectKey = {
3✔
39
    expression_timepoints: "number",
40
    Strain: "string",
41
    simulation_timepoints: "number",
42
};
43

44
// Optimization Parameters Parser
45
const parseMetaDataSheet = sheet => {
3✔
46
    let meta = {
399✔
47
        data: {},
48
        errors: [],
49
        warnings: [],
50
    };
51
    let paramType;
52
    const isValidHeaderResult = isValidHeader(sheet.data[0], sheet.name);
399✔
53
    if (!isValidHeaderResult.isValid) {
399✔
54
        addWarning(meta, isValidHeaderResult.warning);
6✔
55
    }
56

57
    const isHeaderMissing = isValidHeaderResult.isMissing;
399✔
58
    sheet.data.forEach(function (element, index) {
399✔
59
        if (!isHeaderMissing && index === 0) {
7,506✔
60
            return;
396✔
61
        }
62
        const value = element.slice(1);
7,110✔
63
        // Extract element from array if array contains only 1 value
64
        meta.data[element[0]] = value.length > 1 ? value : value[0];
7,110✔
65
    });
66
    for (let key in meta.data) {
399✔
67
        paramType = optimizationParametersTypeKey[key];
7,110✔
68
        if (paramType === "object") {
7,110✔
69
            paramType = `list of ${optimizationParametersObjectKey[key]}s`;
1,197✔
70
        }
71
        if (meta.data[key] === undefined) {
7,110✔
72
            addWarning(meta, constants.warnings.unknownOptimizationParameter(sheet.name, key));
3✔
73
        } else if (typeof meta.data[key] !== optimizationParametersTypeKey[key]) {
7,107✔
74
            if (
330✔
75
                optimizationParametersTypeKey[key] !== "object" ||
657✔
76
                typeof meta.data[key] !== optimizationParametersObjectKey[key]
77
            ) {
78
                addWarning(
3✔
79
                    meta,
80
                    constants.warnings.invalidOptimizationParameter(sheet.name, key, paramType)
81
                );
82
            }
83
        } else if (optimizationParametersTypeKey[key] === "object") {
6,777✔
84
            for (let val of meta.data[key]) {
11,253✔
85
                if (typeof val !== optimizationParametersObjectKey[key]) {
11,253✔
86
                    // throw error once per object. Makes sure that errors list is not flooded
87
                    addWarning(
3✔
88
                        meta,
89
                        constants.warnings.invalidOptimizationParameter(sheet.name, key, paramType)
90
                    );
91
                    break;
3✔
92
                }
93
            }
870✔
94
        }
95
    }
96
    return meta;
399✔
97
};
98

99
const parseOptimizationDiagnosticsSheet = sheet => {
3✔
100
    let output = {
159✔
101
        data: {
102
            Parameters: {},
103
            MSE: {
104
                "column-headers": [],
105
                Genes: {},
106
            },
107
        },
108
        errors: [],
109
        warnings: [],
110
    };
111
    let currentParameter;
112
    let currentValue;
113
    let currentGene;
114
    let currentMSE = [];
159✔
115
    // Check Headers
116
    const isValidHeaderResult = isValidHeader(sheet.data[0], sheet.name);
159✔
117
    if (!isValidHeaderResult.isValid) {
159✔
118
        addWarning(output, isValidHeaderResult.warning);
3✔
119
    }
120
    // Check Parameter Section
121
    let row = 1;
159✔
122
    // a missing row is the indicator to move onto the MSE
123
    while (sheet.data[row].length > 0) {
159✔
124
        currentParameter = sheet.data[row][0];
762✔
125
        currentValue = sheet.data[row][1];
762✔
126
        if (currentParameter === undefined || currentParameter.replace(/\s+/g, "") === "") {
762✔
127
            if (currentValue === undefined || currentValue.replace(/\s+/g, "") === "") {
123!
128
                // if there is no parameter or value assume that its time to move on
129
                row++;
123✔
130
                break;
123✔
131
            }
132
        }
133
        if (sheet.data[row].length > 2) {
639✔
134
            addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, row + 1));
3✔
135
        }
136
        if (!optimizationDiagnosticsParameters.includes(currentParameter)) {
639✔
137
            if (currentParameter === "Gene") {
3!
138
                row--;
×
139
                break;
×
140
            }
141
            addWarning(
3✔
142
                output,
143
                constants.warnings.unknownOptimizationDiagnosticsParameter(
144
                    sheet.name,
145
                    currentParameter
146
                )
147
            );
148
        } else if (typeof currentValue !== "number") {
636✔
149
            addWarning(
3✔
150
                output,
151
                constants.warnings.invalidOptimizationDiagnosticsValue(sheet.name, currentParameter)
152
            );
153
        } else {
154
            output.data.Parameters[currentParameter] = currentValue;
633✔
155
        }
156
        row++;
639✔
157
    }
158
    // Skip until Gene section
159
    while (sheet.data[row] !== undefined && sheet.data[row].length < 1) {
159✔
160
        row++;
36✔
161
    }
162
    // Check Gene section MSE's
163
    if (sheet.data[row].length > 1) {
159✔
164
        if (sheet.data[row][0] !== "Gene") {
159✔
165
            addWarning(
6✔
166
                output,
167
                constants.warnings.incorrectMSEGeneHeaderWarning(sheet.name, row + 1)
168
            );
169
        }
170
        for (let col = 1; col < sheet.data[row].length; col++) {
159✔
171
            if (!sheet.data[row][col].includes("MSE")) {
354✔
172
                addWarning(
9✔
173
                    output,
174
                    constants.warnings.incorrectMSEHeaderWarning(
175
                        sheet.name,
176
                        sheet.data[row][col],
177
                        row + 1,
178
                        constants.numbersToLetters[col]
179
                    )
180
                );
181
            }
182
            // we still push the header (even tho it's sus) because the gene MSE's are
183
            // dependent on the order of the column headers
184
            output.data.MSE["column-headers"].push(sheet.data[row][col]);
354✔
185
        }
186
        row++;
159✔
187
        // on to the actual genes
188
        while (row < sheet.data.length) {
159✔
189
            if (sheet.data[row].length > output.data.MSE["column-headers"].length + 1) {
945✔
190
                addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, row + 1));
6✔
191
            }
192
            currentGene = sheet.data[row][0];
945✔
193
            // if it's a valid gene set the key = MSE value
194
            const isValidGeneNameResult = isValidGeneName(currentGene, sheet.name, row);
945✔
195
            if (isValidGeneNameResult.isValid) {
945✔
196
                for (let col = 1; col <= output.data.MSE["column-headers"].length; col++) {
945✔
197
                    if (typeof sheet.data[row][col] === "number") {
3,870✔
198
                        currentMSE.push(sheet.data[row][col]);
3,828✔
199
                    } else if (sheet.data[row][col] === undefined) {
42✔
200
                        addWarning(
30✔
201
                            output,
202
                            constants.warnings.missingMSEDataWarning(
203
                                sheet.name,
204
                                row + 1,
205
                                constants.numbersToLetters[col]
206
                            )
207
                        );
208
                    } else {
209
                        addWarning(
12✔
210
                            output,
211
                            constants.warnings.invalidMSEDataWarning(
212
                                sheet.name,
213
                                row + 1,
214
                                constants.numbersToLetters[col]
215
                            )
216
                        );
217
                    }
218
                }
219
                output.data.MSE.Genes[currentGene] = currentMSE;
945✔
220
                currentMSE = [];
945✔
221
            }
222
            row++;
945✔
223
        }
224
    }
225
    return output;
159✔
226
};
227

228
const validData = data => {
3✔
229
    return data !== undefined && data !== null && !(typeof data === "string" && data.trim() === "");
210✔
230
};
231

232
const parseTwoColumnSheet = (sheet, genesInNetwork) => {
3✔
233
    let output = {
1,524✔
234
        data: {},
235
        errors: [],
236
        warnings: [],
237
    };
238

239
    if (!sheet.data || sheet.data.length === 0) {
1,524✔
240
        return output;
9✔
241
    }
242

243
    const genesMissingValue = [];
1,515✔
244
    const valuesMissingGene = [];
1,515✔
245

246
    for (let row = 0; row < sheet.data.length; row++) {
1,515✔
247
        const rowData = sheet.data[row];
9,675✔
248
        const rowNum = row + 1;
9,675✔
249

250
        // Extraneous Data Check
251
        if (rowData.length > 2) {
9,675✔
252
            addWarning(output, constants.warnings.extraneousDataWarning(sheet.name, rowNum));
15✔
253
        }
254

255
        // Header Validation (Row 0)
256
        if (row === 0) {
9,675✔
257
            const headerValidation = isValidHeader(rowData, sheet.name);
1,515✔
258
            if (!headerValidation.isValid) {
1,515✔
259
                addWarning(output, headerValidation.warning);
69✔
260

261
                if (!headerValidation.isMissing) {
69✔
262
                    continue;
36✔
263
                }
264
            } else {
265
                continue;
1,446✔
266
            }
267
        }
268

269
        const geneName = rowData[0];
8,193✔
270
        const geneValue = rowData[1];
8,193✔
271

272
        // Row Data Validation
273
        const result = validateRowEntry(sheet.name, geneName, geneValue, rowNum);
8,193✔
274

275
        if (result.isValid) {
8,193✔
276
            output.data[result.geneName] = result.missingValue ? undefined : result.geneValue;
8,061✔
277
            if (result.missingValue) {
8,061✔
278
                if (validData(result.geneName)) {
90✔
279
                    genesMissingValue.push(result.geneName);
90✔
280
                }
281
            }
282
        } else {
283
            if (result.error) {
132✔
284
                addError(output, result.error);
60✔
285
            }
286
            if (!result.missingValue && validData(result.geneValue)) {
132✔
287
                valuesMissingGene.push(result.geneValue);
105✔
288
            }
289
        }
290
    }
291

292
    applyTwoColumnSheetWarnings(
1,515✔
293
        output,
294
        sheet.name,
295
        genesInNetwork,
296
        valuesMissingGene,
297
        genesMissingValue
298
    );
299

300
    return output;
1,515✔
301
};
302

303
module.exports = function (workbookFile, genesInNetwork) {
3✔
304
    let output = {
417✔
305
        meta: {
306
            data: {},
307
            errors: [],
308
            warnings: [],
309
        }, // optimization_parameters only
310
        twoColumnSheets: {}, // 2-column data
311
        meta2: {}, // optimation_diagnostics only //temporary until where it goes is decided
312
    };
313
    workbookFile.forEach(function (sheet) {
417✔
314
        if (sheet.name === "optimization_parameters") {
4,227✔
315
            output.meta = parseMetaDataSheet(sheet);
399✔
316
            // above line creates an object from the optimization parameters sheet
317
            // these are part of the "meta" property
318
        } else if (constants.TWO_COL_SHEET_NAMES.includes(sheet.name)) {
3,828✔
319
            output.twoColumnSheets[sheet.name] = parseTwoColumnSheet(sheet, genesInNetwork);
1,524✔
320
        } else if (sheet.name === "optimization_diagnostics") {
2,304✔
321
            output.meta2 = parseOptimizationDiagnosticsSheet(sheet);
159✔
322
        }
323
    });
324
    return output;
417✔
325
};
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