@opencloning/utils 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/package.json +5 -5
- package/src/config/urlWhitelist.js +2 -0
- package/src/utils/alignmentUtils.js +17 -0
- package/src/utils/fileParsers.test.js +16 -16
- package/src/utils/readNwrite.js +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @opencloning/utils
|
|
2
2
|
|
|
3
|
+
## 2.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#790](https://github.com/OpenCloning/OpenCloning_frontend/pull/790) [`0b2a21d`](https://github.com/OpenCloning/OpenCloning_frontend/commit/0b2a21dde79f5539579bee39f0de49b01cd6c10c) Thanks [@manulera](https://github.com/manulera)! - Support for fetchin sequences created by the OC mcp server
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @opencloning/store@2.1.1
|
|
11
|
+
|
|
3
12
|
## 2.1.0
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"license": "MIT",
|
|
3
3
|
"name": "@opencloning/utils",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/utils/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -29,14 +29,14 @@
|
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/OpenCloning/
|
|
33
|
-
"directory": "packages/utils"
|
|
32
|
+
"url": "https://github.com/OpenCloning/OpenCloning.git",
|
|
33
|
+
"directory": "js/packages/utils"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opencloning/store": "2.
|
|
36
|
+
"@opencloning/store": "2.2.0",
|
|
37
37
|
"@teselagen/bio-parsers": "^0.4.38",
|
|
38
38
|
"@teselagen/sequence-utils": "^0.3.42",
|
|
39
|
-
"@zip.js/zip.js": "^2.
|
|
39
|
+
"@zip.js/zip.js": "^2.24.0",
|
|
40
40
|
"dom-to-svg": "^0.12.2",
|
|
41
41
|
"lodash-es": "^4.18.1"
|
|
42
42
|
},
|
|
@@ -20,4 +20,6 @@ export default [
|
|
|
20
20
|
'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/taxonomy/taxon_suggest',
|
|
21
21
|
'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/taxon',
|
|
22
22
|
'https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession',
|
|
23
|
+
// OpenCloning MCP server, set at build time
|
|
24
|
+
...(import.meta.env.VITE_MCP_SERVER_URL ? [import.meta.env.VITE_MCP_SERVER_URL] : []),
|
|
23
25
|
];
|
|
@@ -91,6 +91,23 @@ export function buildAlignmentConfig(id, sequenceData, alignmentTracks) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Build the alignment config of sequencing files aligned to a sequence (response of the align_sanger endpoint).
|
|
96
|
+
* @param {Object} sequenceModel - The TextFileSequence that was aligned (its id names the alignment)
|
|
97
|
+
* @param {Object} sequenceData - The reference sequence data
|
|
98
|
+
* @param {Array} parsedFiles - [{ fileName, fileContent }], fileContent is getTeselaJsonFromBase64 output
|
|
99
|
+
* @param {Array} alignedSequences - align_sanger response: the aligned reference, then one aligned string per file
|
|
100
|
+
* @returns {Object} Alignment config for addAlignment
|
|
101
|
+
*/
|
|
102
|
+
export function buildSequencingAlignmentConfig(sequenceModel, sequenceData, parsedFiles, alignedSequences) {
|
|
103
|
+
const referenceTrack = buildReferenceTrack(sequenceData, alignedSequences[0]);
|
|
104
|
+
const otherTracks = parsedFiles.map((f, i) => {
|
|
105
|
+
const alignmentFile = { file_name: f.fileName, alignment: [alignedSequences[0], alignedSequences[i + 1]] }; // eslint-disable-line camelcase
|
|
106
|
+
return buildAlignmentTrack(f.fileContent, alignmentFile);
|
|
107
|
+
});
|
|
108
|
+
return buildAlignmentConfig(sequenceModel.id, sequenceData, [referenceTrack, ...otherTracks]);
|
|
109
|
+
}
|
|
110
|
+
|
|
94
111
|
/**
|
|
95
112
|
* Remove a panel from panelsShown by id.
|
|
96
113
|
* @param {Array} panelsShown - Current panelsShown array (array of arrays)
|
|
@@ -13,7 +13,7 @@ describe('delimitedFileToJson', () => {
|
|
|
13
13
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,CGTG', 'test.csv');
|
|
14
14
|
|
|
15
15
|
const result = await delimitedFileToJson(file);
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
expect(result).toEqual([
|
|
18
18
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
19
19
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -24,7 +24,7 @@ describe('delimitedFileToJson', () => {
|
|
|
24
24
|
const file = createFile('name;sequence\nprimer1;ATGC\nprimer2;CGTG', 'test.csv');
|
|
25
25
|
|
|
26
26
|
const result = await delimitedFileToJson(file);
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
expect(result).toEqual([
|
|
29
29
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
30
30
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -35,7 +35,7 @@ describe('delimitedFileToJson', () => {
|
|
|
35
35
|
const file = createFile('name\tsequence\nprimer1\tATGC\nprimer2\tCGTG', 'test.tsv');
|
|
36
36
|
|
|
37
37
|
const result = await delimitedFileToJson(file);
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
expect(result).toEqual([
|
|
40
40
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
41
41
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -70,7 +70,7 @@ describe('delimitedFileToJson', () => {
|
|
|
70
70
|
const file = createFile('name,sequence\n\nprimer1,ATGC\n \nprimer2,CGTG', 'test.csv');
|
|
71
71
|
|
|
72
72
|
const result = await delimitedFileToJson(file);
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
expect(result).toEqual([
|
|
75
75
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
76
76
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -107,7 +107,7 @@ describe('delimitedFileToJson', () => {
|
|
|
107
107
|
const file = createFile('name,sequence\r\nprimer1,ATGC\r\nprimer2,CGTG', 'test.csv');
|
|
108
108
|
|
|
109
109
|
const result = await delimitedFileToJson(file);
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
expect(result).toEqual([
|
|
112
112
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
113
113
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -118,7 +118,7 @@ describe('delimitedFileToJson', () => {
|
|
|
118
118
|
const file = createFile('name,sequence\rprimer1,ATGC\rprimer2,CGTG', 'test.csv');
|
|
119
119
|
|
|
120
120
|
const result = await delimitedFileToJson(file);
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
expect(result).toEqual([
|
|
123
123
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
124
124
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -129,7 +129,7 @@ describe('delimitedFileToJson', () => {
|
|
|
129
129
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,CGTG', 'test.csv');
|
|
130
130
|
|
|
131
131
|
const result = await delimitedFileToJson(file);
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
expect(result).toEqual([
|
|
134
134
|
{ name: 'primer1', sequence: 'ATGC' },
|
|
135
135
|
{ name: 'primer2', sequence: 'CGTG' }
|
|
@@ -140,7 +140,7 @@ describe('delimitedFileToJson', () => {
|
|
|
140
140
|
const file = createFile('name,sequence\nprimer1,ATGC', 'test.csv');
|
|
141
141
|
|
|
142
142
|
const result = await delimitedFileToJson(file, []);
|
|
143
|
-
|
|
143
|
+
|
|
144
144
|
expect(result).toEqual([
|
|
145
145
|
{ name: 'primer1', sequence: 'ATGC' }
|
|
146
146
|
]);
|
|
@@ -150,7 +150,7 @@ describe('delimitedFileToJson', () => {
|
|
|
150
150
|
const file = createFile('name,sequence\nprimer1,ATGC', 'test.csv');
|
|
151
151
|
|
|
152
152
|
const result = await delimitedFileToJson(file);
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
expect(result).toEqual([
|
|
155
155
|
{ name: 'primer1', sequence: 'ATGC' }
|
|
156
156
|
]);
|
|
@@ -172,7 +172,7 @@ describe('primersFromTextFile', () => {
|
|
|
172
172
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,CGTG', 'test.csv');
|
|
173
173
|
|
|
174
174
|
const result = await primersFromTextFile(file, []);
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
expect(result).toEqual([
|
|
177
177
|
{ name: 'primer1', sequence: 'ATGC', error: '' },
|
|
178
178
|
{ name: 'primer2', sequence: 'CGTG', error: '' }
|
|
@@ -183,7 +183,7 @@ describe('primersFromTextFile', () => {
|
|
|
183
183
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,CGTG', 'test.csv');
|
|
184
184
|
|
|
185
185
|
const result = await primersFromTextFile(file, ['primer1']);
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
expect(result).toEqual([
|
|
188
188
|
{ name: 'primer1', sequence: 'ATGC', error: 'existing' },
|
|
189
189
|
{ name: 'primer2', sequence: 'CGTG', error: '' }
|
|
@@ -194,7 +194,7 @@ describe('primersFromTextFile', () => {
|
|
|
194
194
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,XYZ', 'test.csv');
|
|
195
195
|
|
|
196
196
|
const result = await primersFromTextFile(file, []);
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
expect(result).toEqual([
|
|
199
199
|
{ name: 'primer1', sequence: 'ATGC', error: '' },
|
|
200
200
|
{ name: 'primer2', sequence: 'XYZ', error: 'invalid' }
|
|
@@ -205,7 +205,7 @@ describe('primersFromTextFile', () => {
|
|
|
205
205
|
const file = createFile('name,sequence\nprimer1,XYZ', 'test.csv');
|
|
206
206
|
|
|
207
207
|
const result = await primersFromTextFile(file, ['primer1']);
|
|
208
|
-
|
|
208
|
+
|
|
209
209
|
expect(result).toEqual([
|
|
210
210
|
{ name: 'primer1', sequence: 'XYZ', error: 'existing' }
|
|
211
211
|
]);
|
|
@@ -215,7 +215,7 @@ describe('primersFromTextFile', () => {
|
|
|
215
215
|
const file = createFile('name,sequence\nprimer1,ATGC\nprimer2,XYZ\nprimer3,CGTG', 'test.csv');
|
|
216
216
|
|
|
217
217
|
const result = await primersFromTextFile(file, ['primer1']);
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
expect(result).toEqual([
|
|
220
220
|
{ name: 'primer1', sequence: 'ATGC', error: 'existing' },
|
|
221
221
|
{ name: 'primer2', sequence: 'XYZ', error: 'invalid' },
|
|
@@ -233,7 +233,7 @@ describe('primersFromTextFile', () => {
|
|
|
233
233
|
const file = createFile('name,sequence\nprimer1,ATGC', 'test.csv');
|
|
234
234
|
|
|
235
235
|
const result = await primersFromTextFile(file, []);
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
expect(result).toEqual([
|
|
238
238
|
{ name: 'primer1', sequence: 'ATGC', error: '' }
|
|
239
239
|
]);
|
|
@@ -243,7 +243,7 @@ describe('primersFromTextFile', () => {
|
|
|
243
243
|
const file = createFile('name\tsequence\nprimer1\tATGC', 'test.tsv');
|
|
244
244
|
|
|
245
245
|
const result = await primersFromTextFile(file, []);
|
|
246
|
-
|
|
246
|
+
|
|
247
247
|
expect(result).toEqual([
|
|
248
248
|
{ name: 'primer1', sequence: 'ATGC', error: '' }
|
|
249
249
|
]);
|
package/src/utils/readNwrite.js
CHANGED
|
@@ -158,6 +158,26 @@ export const downloadCloningStrategyAsSvg = async (fileName) => {
|
|
|
158
158
|
downloadTextFile(svgString, fileName);
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Extract the files of a zip whose path (e.g. 'run/A1/A1.fastq') matches filter, as Files named like the
|
|
163
|
+
* file without its folder (e.g. 'A1.fastq'). Directories, hidden files and folders, and anything in
|
|
164
|
+
* __MACOSX/ (e.g. the ._A1.fastq copies that macOS adds) are skipped.
|
|
165
|
+
*/
|
|
166
|
+
export async function extractFilesFromZip(zipFile, filter) {
|
|
167
|
+
const zipReader = new ZipReader(new BlobReader(zipFile));
|
|
168
|
+
try {
|
|
169
|
+
const entries = (await zipReader.getEntries()).filter((entry) => !entry.directory
|
|
170
|
+
&& !entry.filename.split('/').some((part) => part.startsWith('.') || part === '__MACOSX')
|
|
171
|
+
&& filter(entry.filename));
|
|
172
|
+
return await Promise.all(entries.map(async (entry) => new File(
|
|
173
|
+
[await entry.getData(new BlobWriter())],
|
|
174
|
+
entry.filename.split('/').pop(),
|
|
175
|
+
)));
|
|
176
|
+
} finally {
|
|
177
|
+
await zipReader.close();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
161
181
|
export async function parseHistoryFile(file) {
|
|
162
182
|
const isZipFile = file.name.endsWith('.zip');
|
|
163
183
|
const isJsonFile = file.name.endsWith('.json');
|
|
@@ -279,6 +299,16 @@ export async function getTeselaJsonFromBase64(ab1Base64, fileName = null) {
|
|
|
279
299
|
}
|
|
280
300
|
}
|
|
281
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Read a sequencing file (ab1, fasta, genbank, fastq...) and parse it.
|
|
304
|
+
* Returns the base64 content (to store it) and the parsed teselaJson (e.g. teselaJson.sequence is the trace).
|
|
305
|
+
*/
|
|
306
|
+
export async function readSequencingFile(file) {
|
|
307
|
+
const base64str = await file2base64(file);
|
|
308
|
+
const teselaJson = await getTeselaJsonFromBase64(base64str, file.name);
|
|
309
|
+
return { base64str, teselaJson };
|
|
310
|
+
}
|
|
311
|
+
|
|
282
312
|
export function formatTemplate(data, url) {
|
|
283
313
|
const newData = { ...data };
|
|
284
314
|
const segments = url.split('/');
|