Skip to content

Commit 50e5fc2

Browse files
authored
Merge pull request #96 from gogepp/dev
Release 0.4.19
2 parents 5d63190 + cf5212b commit 50e5fc2

23 files changed

Lines changed: 527 additions & 61 deletions

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
77

8+
## [0.4.19] 2026-03-12
9+
10+
- Properly raises error when failing to add an annotation (#82)
11+
- Fix eggnog seed link (#89)
12+
- Add --silent option to add genome (#92) and add Eggnog
13+
- Allow a 'range' (ex: `5:19`) of columns to be passed when loading expression files (#88)
14+
- Allow '--genome' to be passed when loading expression. This skips trying to predict genome based on first 10 genes (#87)
15+
- Cleanup mongo files at startup to avoid crashes (#86)
16+
- Open dbxref in newTab + allow using any of the gff attribute in the link (#85)
17+
- Add cleaner error when document size is exceeded
18+
- Add optionnal GO json file when loading eggnog, to store used goterms, and avoid requests later.
19+
- Add conf option to display a warning message related to isoforms
20+
821
## [0.4.18] 2025-07-11
922

1023
- Fix GO API call

cli/genoboo.js

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
const fs = require('fs');
55
const commander = require('commander');
66
const { Tail } = require('tail');
7-
const { spawn, execFileSync } = require('child_process');
7+
const { spawn, execFileSync, execSync } = require('child_process');
88
const path = require('path');
99
const asteroid = require('asteroid');
1010
const WebSocket = require('ws');
@@ -64,18 +64,23 @@ class GeneNoteBookConnection {
6464
} else if (jobStatus) {
6565
if (jobStatus === 'failed') {
6666
logger.error('The job failed, something went wrong! (Look at the logs for more details).');
67+
this.connection.disconnect();
68+
process.exit(1);
6769
} else {
6870
logger.log(`Job status: ${jobStatus}`);
6971
}
7072
} else {
7173
logger.error('Undefined server response');
74+
this.connection.disconnect();
75+
process.exit(1);
7276
}
7377
this.connection.disconnect();
7478
})
7579
.catch((error) => {
7680
logger.error(error);
7781
console.log(error);
7882
this.connection.disconnect();
83+
process.exit(1);
7984
});
8085
}
8186
}
@@ -103,6 +108,16 @@ function startMongoDaemon(
103108
execFileSync('mkdir', ['-p', dataFolderPath, logFolderPath]);
104109
const logPath = `${dbPath}/log/mongod.log`;
105110

111+
// Delete log file if it exists to avoid rotation issues
112+
try {
113+
logger.log("Clearing log file if it exists")
114+
fs.unlinkSync(logPath);
115+
} catch (err) {
116+
if (err.code !== "ENOENT") {
117+
throw err;
118+
}
119+
}
120+
106121
logger.log(`Using DB path: ${dbPath}`);
107122
logger.log(`MongoDB data files are in ${dataFolderPath}`);
108123
logger.log(`MongoDB logs are in ${logFolderPath}`);
@@ -124,6 +139,10 @@ function startMongoDaemon(
124139

125140
const mongoDaemon = spawn('mongod', mongodOptionArray);
126141

142+
// wait a bit to make sure the log files are setup
143+
logger.log("Waiting 10 seconds for mongo to start")
144+
execSync("sleep 10");
145+
127146
mongoDaemon.on('error', (err) => {
128147
logger.error(err);
129148
});
@@ -283,7 +302,12 @@ addGenome
283302
'--port [port]',
284303
'Port on which GeneNoteBook is running. Default: 3000'
285304
)
286-
.action((file, { username, password, name, port = 3000, public = false }) => {
305+
.option(
306+
'--silent',
307+
'Keep the upload process silent. Default: false',
308+
false
309+
)
310+
.action((file, { username, password, name, port = 3000, public = false, silent = false }) => {
287311
if (typeof file !== 'string') addGenome.help();
288312
const fileName = path.resolve(file);
289313

@@ -297,6 +321,7 @@ addGenome
297321
fileName,
298322
public,
299323
async: false,
324+
silent
300325
});
301326
})
302327
.on('--help', () => {
@@ -621,9 +646,9 @@ const addExpression = add.command('expression');
621646

622647
addExpression
623648
.description(
624-
'Add gene expression to a running GeneNoteBook server'
649+
'Add gene expression to a running GeneNoteBook server. Will use the first 10 genes to find the genome, using the genome name is passed'
625650
)
626-
.usage('[options] <Kallisto abundance.tsv file>')
651+
.usage('[options] <expression.tsv file>')
627652
.arguments('<file>')
628653
.option('-u, --username <username>', 'GeneNoteBook admin username')
629654
.option('-p, --password <password>', 'GeneNoteBook admin password')
@@ -636,16 +661,20 @@ addExpression
636661
'Description of the experiment'
637662
)
638663
.option(
639-
'--annot <annotation-ame>',
664+
'--annot <annotation-name>',
640665
'Annotation name',
641666
)
667+
.option(
668+
'--genome <genome-name>',
669+
'Genome name',
670+
)
642671
.option(
643672
'-r, --replicas <replicas...>',
644673
'Comma-separated column positions, which are part of the same replica group. Can be set multiple times for multiple groups. The replica group name will be the first column, unless replica-names is set'
645674
)
646675
.option(
647676
'-n, --replica-names <replicaNames...>',
648-
'Name of the replica group. Will defaut to the first column header if not set. Can be set multiple time (for each replica group). Will match replica groups in order, whether they are multiple of single columns.'
677+
'Name of the replica group. Will defaut to the first column header if not set. Can be set multiple time (for each replica group). Will match replica groups in order, whether they are multiple or single columns.'
649678
)
650679
.option(
651680
'--public',
@@ -660,6 +689,7 @@ addExpression
660689
const replicaNames = opts.replicaNames || [];
661690
const isPublic = opts.public;
662691
const annot = opts.annot
692+
const genome = opts.genome
663693
if (!(fileName && username && password)) {
664694
program.help();
665695
}
@@ -669,6 +699,7 @@ addExpression
669699
fileName,
670700
description,
671701
annot,
702+
genome,
672703
replicas,
673704
replicaNames,
674705
isPublic
@@ -818,6 +849,15 @@ addEggnog
818849
.description('Add EggNog-mapper results to a running GeneNoteBook server')
819850
.usage('[options] <EggNog-mapper tsv output file>')
820851
.arguments('<file>')
852+
.option(
853+
'--gofile <gofile>',
854+
'Path to Gene Ontology json file',
855+
)
856+
.option(
857+
'--silent',
858+
'Keep the upload process silent. Default: false',
859+
false
860+
)
821861
.requiredOption(
822862
'-u, --username <adminUsername>',
823863
'GeneNoteBook admin username'
@@ -834,7 +874,7 @@ addEggnog
834874
'--port [port]',
835875
'Port on which GeneNoteBook is running. Default: 3000'
836876
)
837-
.action((file, { username, password, port = 3000, annot }) => {
877+
.action((file, { username, password, port = 3000, annot, gofile, silent = false }) => {
838878
if (typeof file !== 'string') addEggnog.help();
839879

840880
const fileName = path.resolve(file);
@@ -844,7 +884,9 @@ addEggnog
844884

845885
new GeneNoteBookConnection({ username, password, port }).call('addEggnog', {
846886
fileName,
847-
annot: annot
887+
annot: annot,
888+
goFile: gofile,
889+
silent
848890
});
849891
})
850892
.on('--help', () => {

config.json.template

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"public":{
33
"disable_blast": false,
4+
"enable_hectar": false,
45
"disable_user_login": false,
56
"disable_user_registration": false,
67
"disable_header": false,
@@ -14,7 +15,8 @@
1415
],
1516
"protein_dbxref": [
1617
{"url": "https://test_url_protein/#PROTEINID", "label": "my protein label", "assembly": "", "annotation": ""}
17-
]
18+
],
19+
"isoform_filtered": false
1820
},
1921
"externalSearchOptions": {
2022
"url": "http://0.0.0.0:80",

imports/api/genes/eggnog/addEggnog.js

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,75 @@ import logger from '/imports/api/util/logger.js';
66
import { Roles } from 'meteor/alanning:roles';
77
import SimpleSchema from 'simpl-schema';
88
import { Meteor } from 'meteor/meteor';
9+
import { dbxrefCollection } from '/imports/api/genes/dbxrefCollection.js';
10+
11+
import fs from 'fs';
12+
import path from 'path';
913

1014
class EggnogProcessor {
11-
constructor(annot) {
15+
constructor(annot, goFile) {
1216
// Not a bulk mongo suite.
1317
this.genesDb = Genes.rawCollection();
1418
this.nEggnog = 0;
1519
this.annot = annot;
20+
this.goContent = {}
21+
this.addGo = new Set()
22+
this.hasGO = false
23+
this.loadGoContent(goFile)
24+
}
25+
26+
/**
27+
Function that load an option go.json file and store it as dict
28+
*/
29+
loadGoContent(goFile){
30+
if (! goFile) {
31+
return
32+
}
33+
try {
34+
logger.log("Loading GO annotation from :" + goFile)
35+
const raw = fs.readFileSync(goFile, 'utf8');
36+
const goData = JSON.parse(raw);
37+
goData.graphs[0].nodes.forEach(node => {
38+
if (node.id && node.lbl) {
39+
this.goContent[node.id.replace("http://purl.obolibrary.org/obo/", "").replace("_", ":")] = node.lbl;
40+
if (node.meta && node.meta.basicPropertyValues){
41+
node.meta.basicPropertyValues.forEach(bpv => {
42+
if (bpv.pred == "http://www.geneontology.org/formats/oboInOwl#hasAlternativeId" && bpv.val){
43+
this.goContent[bpv.val] = node.lbl
44+
}
45+
})
46+
}
47+
}
48+
})
49+
this.hasGO = true
50+
} catch (error) {
51+
logger.error(error)
52+
logger.warn("Failed to load from " + goFile)
53+
this.hasGO = false
54+
this.goContent = {}
55+
}
56+
}
57+
/** Function that add the GOterms in eggnog row into db */
58+
createGOterms(){
59+
60+
this.addGo.forEach(goID => {
61+
if (!(goID in this.goContent) || !(this.goContent[goID])){
62+
logger.warn(`Missing ${goID} in GO ontology`)
63+
return
64+
}
65+
66+
dbxrefCollection.upsert(
67+
{ dbxrefId: goID},
68+
{ $set: {
69+
dbxrefId: goID,
70+
url: `http://amigo.geneontology.org/amigo/term/${goID}`,
71+
description: this.goContent[goID],
72+
updated: new Date(),
73+
dbType: "go",
74+
}
75+
}
76+
);
77+
})
1678
}
1779

1880
/**
@@ -64,7 +126,7 @@ class EggnogProcessor {
64126
eggNOG_OGs: eggnogOGs,
65127
max_annot_lvl: maxAnnotLvl,
66128
COG_category: cogCategory,
67-
Description: description,
129+
Description: description,
68130
Preferred_name: preferredName,
69131
GOs: gos,
70132
EC: ec,
@@ -118,6 +180,11 @@ class EggnogProcessor {
118180
annotations, // modifier.
119181
);
120182

183+
184+
if (this.hasGO && annotations.GOs){
185+
annotations.GOs.forEach(item => this.addGo.add(item))
186+
}
187+
121188
// Update eggnogId in genes database.
122189
if (typeof documentEggnog.insertedId !== 'undefined') {
123190
// Eggnog _id is created.
@@ -150,20 +217,27 @@ const addEggnog = new ValidatedMethod({
150217
type: String,
151218
optional: true,
152219
},
220+
goFile: {
221+
type: String,
222+
optional: true,
223+
},
224+
silent: {
225+
type: Boolean,
226+
optional: true,
227+
},
153228
}).validator(),
154229
applyOptions: {
155230
noRetry: true,
156231
},
157-
run({ fileName, annot }) {
232+
run({ fileName, annot, goFile, silent }) {
158233
if (!this.userId) {
159234
throw new Meteor.Error('not-authorized');
160235
}
161236
if (!Roles.userIsInRole(this.userId, 'admin')) {
162237
throw new Meteor.Error('not-authorized');
163238
}
164239

165-
logger.log('file :', { fileName });
166-
const job = new Job(jobQueue, 'addEggnog', { fileName, annot });
240+
const job = new Job(jobQueue, 'addEggnog', { fileName, annot, goFile, silent });
167241
const jobId = job.priority('high').save();
168242

169243
let { status } = job.doc;

0 commit comments

Comments
 (0)