Skip to content

Commit aed0096

Browse files
authored
Merge pull request #37 from gogepp/dev
Release 0.4.5
2 parents faa78e7 + 9c77c8c commit aed0096

16 files changed

Lines changed: 596 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ 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.5] 2023-09-19
9+
10+
### Added
11+
12+
- Added Hector loader
13+
14+
### Changed
15+
16+
- Changed GO API url due to changes
17+
818
## [0.4.4] 2023-06-23
919

1020
### Changed

cli/genoboo.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,45 @@ Example:
755755
})
756756
.exitOverride(customExitOverride(addEggnog));
757757

758+
// Add Hectar annotations file.
759+
const addHectar = add.command('hectar');
760+
761+
addHectar
762+
.description('Add Hectar results to a running GeneNoteBook server')
763+
.usage('[options] <Hectar tab output file>')
764+
.arguments('<file>')
765+
.requiredOption(
766+
'-u, --username <adminUsername>',
767+
'GeneNoteBook admin username'
768+
)
769+
.requiredOption(
770+
'-p, --password <adminPassword>',
771+
'GeneNoteBook admin password'
772+
)
773+
.option(
774+
'--port [port]',
775+
'Port on which GeneNoteBook is running. Default: 3000'
776+
)
777+
.action((file, { username, password, port = 3000 }) => {
778+
if (typeof file !== 'string') addHectar.help();
779+
780+
const fileName = path.resolve(file);
781+
if (!(fileName && username && password)) {
782+
addHectar.help();
783+
}
784+
785+
new GeneNoteBookConnection({ username, password, port }).call('addHectar', {
786+
fileName,
787+
});
788+
})
789+
.on('--help', () => {
790+
console.log(`
791+
Example:
792+
genenotebook add hectar hectar_annotations.tab -u admin -p admin
793+
`);
794+
})
795+
.exitOverride(customExitOverride(addHectar));
796+
758797
// add orthogroups.
759798
const addOrthogroups = add.command('orthogroups');
760799

imports/api/api.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import './genomes/annotation/addAnnotation.js';
1919
import './genes/interproscan.js';
2020
import './genes/addInterproscan.js';
2121
import './genes/eggnog/addEggnog.js';
22+
import './genes/hectar/addHectar.js';
2223
import './genes/scanGeneAttributes.js';
2324
import './genes/updateAttributeInfo.js';
2425
import './genes/updateGene.js';
@@ -54,5 +55,6 @@ import './jobqueue/process-blast.js';
5455
import './jobqueue/process-download.js';
5556
import './jobqueue/process-addGenome.js';
5657
import './jobqueue/process-eggnog.js';
58+
import './jobqueue/process-hectar.js';
5759
import './jobqueue/process-similarsequences.js';
5860
import './jobqueue/process-orthogroup.js';

imports/api/genes/geneCollection.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ const GeneSchema = new SimpleSchema(
173173
optional: true,
174174
label: 'eggnog DB identifier (_id in eggnog collection)',
175175
},
176+
hectarId: {
177+
type: String,
178+
index: true,
179+
optional: true,
180+
label: 'Hectar identifier (_id in hectar collection)',
181+
},
176182
seqid: {
177183
type: String,
178184
label: 'ID of the sequence on which the gene is, e.g. chr1',
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { hectarCollection } from '/imports/api/genes/hectar/hectarCollection.js';
2+
import jobQueue, { Job } from '/imports/api/jobqueue/jobqueue.js';
3+
import { ValidatedMethod } from 'meteor/mdg:validated-method';
4+
import { Genes } from '/imports/api/genes/geneCollection.js';
5+
import logger from '/imports/api/util/logger.js';
6+
import { Roles } from 'meteor/alanning:roles';
7+
import SimpleSchema from 'simpl-schema';
8+
import { Meteor } from 'meteor/meteor';
9+
10+
class HectarProcessor {
11+
constructor() {
12+
// Not a bulk mongo suite.
13+
this.genesDb = Genes.rawCollection();
14+
this.nHectar = 0;
15+
}
16+
17+
/**
18+
* Function that returns the total number of insertions or updates in the
19+
* hectar collection.
20+
* @function
21+
* @return {Number} Return the total number of insertions or updates of
22+
* hectar.
23+
*/
24+
getNumberHectar() {
25+
return this.nHectar;
26+
}
27+
28+
parse = (line) => {
29+
if (!(line.slice(0,10) === 'protein id' || line.split('\t').length <= 1)) {
30+
// Get all hectar informations line by line and separated by tabs.
31+
const [
32+
proteinId,
33+
predictedTargetingCategory,
34+
signalPeptideScore,
35+
signalPeptideCleavageSite,
36+
typeIISignalAnchorScore,
37+
chloroplastScore,
38+
mitochondrionScore,
39+
otherScore,
40+
] = line.split('\t');
41+
42+
// Organize data in a dictionary.
43+
const annotations = {
44+
protein_id: proteinId,
45+
predicted_targeting_category: predictedTargetingCategory,
46+
signal_peptide_score: signalPeptideScore,
47+
signal_peptide_cleavage_site: signalPeptideCleavageSite,
48+
typeII_signal_anchor_score: typeIISignalAnchorScore,
49+
chloroplast_score: chloroplastScore,
50+
mitochondrion_score: mitochondrionScore,
51+
other_score: otherScore,
52+
};
53+
54+
// Filters undefined data (with a dash) and splits into an array for
55+
// comma-separated data.
56+
for (const [key, value] of Object.entries(annotations)) {
57+
if (value[0] === '-') {
58+
annotations[key] = undefined;
59+
}
60+
if (value.indexOf(',') > -1) {
61+
annotations[key] = value.split(',');
62+
}
63+
}
64+
// If subfeatures is found in genes database (e.g: ID =
65+
// MMUCEDO_000002-T1).
66+
const subfeatureIsFound = Genes.findOne({
67+
$or: [
68+
{ 'subfeatures.ID': proteinId },
69+
{ 'subfeatures.protein_id': proteinId },
70+
],
71+
});
72+
73+
if (typeof subfeatureIsFound !== 'undefined') {
74+
console.log("if loop" + typeof subfeatureIsFound);
75+
// Increment hectar.
76+
this.nHectar += 1;
77+
78+
// Update or insert if no matching documents were found.
79+
const documentHectar = hectarCollection.upsert(
80+
{ protein_id: proteinId }, // selector.
81+
annotations, // modifier.
82+
);
83+
84+
// Update hectarId in genes database.
85+
if (typeof documentHectar.insertedId !== 'undefined') {
86+
// Hectar _id is created.
87+
return this.genesDb.update({
88+
$or: [
89+
{ 'subfeatures.ID': proteinId },
90+
{ 'subfeatures.protein_id': proteinId },
91+
]},
92+
{ $set: { hectarId: documentHectar.insertedId } },
93+
);
94+
} else {
95+
// Hectar already exists.
96+
const hectarIdentifiant = hectarCollection.findOne({ protein_id: proteinId })._id;
97+
return this.genesDb.update(
98+
{ $or: [{'subfeatures.ID': proteinId}, {'subfeatures.protein_id': proteinId}] },
99+
{ $set: { hectarId: hectarIdentifiant } },
100+
);
101+
}
102+
} else {
103+
logger.warn(`
104+
Warning ! ${proteinId} hectar annotation did
105+
not find a matching protein domain in the genes database.
106+
${proteinId} is not added to the hectar database.`);
107+
}
108+
}
109+
};
110+
}
111+
112+
const addHectar = new ValidatedMethod({
113+
name: 'addHectar',
114+
validate: new SimpleSchema({
115+
fileName: { type: String },
116+
}).validator(),
117+
applyOptions: {
118+
noRetry: true,
119+
},
120+
run({ fileName }) {
121+
if (!this.userId) {
122+
throw new Meteor.Error('not-authorized');
123+
}
124+
if (!Roles.userIsInRole(this.userId, 'admin')) {
125+
throw new Meteor.Error('not-authorized');
126+
}
127+
128+
logger.log('file :', { fileName });
129+
const job = new Job(jobQueue, 'addHectar', { fileName });
130+
const jobId = job.priority('high').save();
131+
132+
let { status } = job.doc;
133+
logger.debug(`Job status: ${status}`);
134+
while ((status !== 'completed') && (status !== 'failed')) {
135+
const { doc } = job.refresh();
136+
status = doc.status;
137+
}
138+
return { result: job.doc.result, jobStatus: status};
139+
},
140+
});
141+
142+
export default addHectar;
143+
export { HectarProcessor };
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/* eslint-env mocha */
2+
import { resetDatabase } from 'meteor/xolvio:cleaner';
3+
import chai from 'chai';
4+
import logger from '../../util/logger';
5+
import { hectarCollection } from './hectarCollection';
6+
import addHectar from './addHectar';
7+
import { addTestUsers, addTestGenome } from '../../../startup/server/fixtures/addTestData';
8+
import '../../jobqueue/process-hectar';
9+
10+
describe('hectar', function testHectar() {
11+
let adminId;
12+
let newUserId;
13+
let adminContext;
14+
let userContext;
15+
16+
logger.log('Testing Hectar methods');
17+
18+
beforeEach(() => {
19+
({ adminId, newUserId } = addTestUsers());
20+
adminContext = { userId: adminId };
21+
userContext = { userId: newUserId };
22+
});
23+
24+
afterEach(() => {
25+
resetDatabase();
26+
});
27+
28+
it('Should add Hectar tab file', function importhectar() {
29+
// Increase timeout
30+
this.timeout(20000);
31+
32+
addTestGenome(annot = true);
33+
34+
const hectarParams = {
35+
fileName: 'assets/app/data/Bnigra_hectar.tab',
36+
};
37+
38+
// Should fail for non-logged in
39+
chai.expect(() => {
40+
addHectar._execute({}, hectarParams);
41+
}).to.throw('[not-authorized]');
42+
43+
// Should fail for non admin user
44+
chai.expect(() => {
45+
addHectar._execute(userContext, hectarParams);
46+
}).to.throw('[not-authorized]');
47+
48+
const { result } = addHectar._execute(adminContext, hectarParams);
49+
50+
chai.assert.equal(result.nInserted, 1)
51+
52+
const hecs = hectarCollection.find({ protein_id: 'BniB01g000010.2N.1' }).fetch();
53+
54+
chai.assert.lengthOf(hecs, 1, 'No hectar data found');
55+
56+
const hec = hecs[0];
57+
58+
chai.assert.equal(hec.predicted_targeting_category, 'other localisation');
59+
chai.assert.equal(hec.signal_peptide_score, '0.0583');
60+
chai.assert.equal(hec.typeII_signal_anchor_score, '0.0228');
61+
chai.assert.equal(hec.mitochondrion_score, '0.1032');
62+
chai.assert.equal(hec.other_score, '0.8968');
63+
});
64+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import SimpleSchema from 'simpl-schema';
2+
import { Mongo } from 'meteor/mongo';
3+
4+
const hectarSchema = new SimpleSchema({
5+
protein_id: {
6+
type: String,
7+
label: 'Query sequence name and type.',
8+
},
9+
predicted_targeting_category: {
10+
type: String,
11+
label: 'Predicted sub-cellular localization.',
12+
},
13+
signal_peptide_score: {
14+
type: String,
15+
label: 'Probability (score) to be a signal peptide.',
16+
},
17+
signal_peptide_cleavage_site: {
18+
type: String,
19+
label: 'Predicted cleavage site of signal peptide.',
20+
},
21+
typeII_signal_anchor_score: {
22+
type: String,
23+
label: 'Probability (score) to be a type II signal anchor.',
24+
},
25+
chloroplast_score: {
26+
type: String,
27+
label: 'Probability (score) to be in chloroplast.',
28+
},
29+
mitochondrion_score: {
30+
type: String,
31+
label: 'Probability (score) to be in mitochondrion.',
32+
},
33+
other_score: {
34+
type: String,
35+
label: 'Probability (score) to be elsewhere .',
36+
},
37+
});
38+
39+
const hectarCollection = new Mongo.Collection('hectar');
40+
41+
export { hectarCollection, hectarSchema };
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { HectarProcessor } from '/imports/api/genes/hectar/addHectar.js';
2+
import logger from '/imports/api/util/logger.js';
3+
import jobQueue from './jobqueue.js';
4+
import readline from 'readline';
5+
import fs from 'fs';
6+
7+
jobQueue.processJobs(
8+
'addHectar',
9+
{
10+
concurrency: 4,
11+
payload: 1,
12+
},
13+
async (job, callback) => {
14+
const { fileName } = job.data;
15+
logger.log(`Add ${fileName} hectar file.`);
16+
17+
const lineProcessor = new HectarProcessor();
18+
19+
const rl = readline.createInterface({
20+
input: fs.createReadStream(fileName, 'utf8'),
21+
crlfDelay: Infinity,
22+
});
23+
24+
const { size: fileSize } = await fs.promises.stat(fileName);
25+
let processedBytes = 0;
26+
let processedLines = 0;
27+
let nHectar = 0;
28+
29+
for await (const line of rl) {
30+
processedBytes += line.length + 1; // also count \n
31+
processedLines += 1;
32+
33+
if ((processedLines % 100) === 0) {
34+
await job.progress(
35+
processedBytes,
36+
fileSize,
37+
{ echo: true },
38+
(err) => {
39+
if (err) logger.error(err);
40+
},
41+
);
42+
}
43+
44+
try {
45+
await lineProcessor.parse(line);
46+
nHectar = lineProcessor.getNumberHectar();
47+
} catch (err) {
48+
logger.error(err);
49+
job.fail({ err });
50+
callback();
51+
}
52+
}
53+
logger.log(`Inserted ${nHectar} Hectar`);
54+
job.done({ nInserted: nHectar });
55+
callback();
56+
},
57+
);

0 commit comments

Comments
 (0)