@opencloning/ui 1.4.0 → 1.4.2
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 +20 -0
- package/package.json +3 -3
- package/src/components/assembler/Assembler.cy.jsx +10 -3
- package/src/components/assembler/Assembler.jsx +3 -3
- package/src/components/form/ServerStaticFileSelect.cy.jsx +109 -0
- package/src/components/form/ServerStaticFileSelect.jsx +19 -5
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @opencloning/ui
|
|
2
2
|
|
|
3
|
+
## 1.4.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#620](https://github.com/manulera/OpenCloning_frontend/pull/620) [`1c2dbef`](https://github.com/manulera/OpenCloning_frontend/commit/1c2dbef236d54b4980599cd6acc4fdabced13f1d) Thanks [@manulera](https://github.com/manulera)! - Fix assembler using BsaI by default always
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @opencloning/store@1.4.2
|
|
11
|
+
- @opencloning/utils@1.4.2
|
|
12
|
+
|
|
13
|
+
## 1.4.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#616](https://github.com/manulera/OpenCloning_frontend/pull/616) [`1419d5c`](https://github.com/manulera/OpenCloning_frontend/commit/1419d5cb80f738b737076e4d3b958c31eeb0f7b4) Thanks [@manulera](https://github.com/manulera)! - Allow to "select all" when importing static sequences from server
|
|
18
|
+
|
|
19
|
+
- Updated dependencies []:
|
|
20
|
+
- @opencloning/store@1.4.1
|
|
21
|
+
- @opencloning/utils@1.4.1
|
|
22
|
+
|
|
3
23
|
## 1.4.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencloning/ui",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@emotion/styled": "^11.14.0",
|
|
26
26
|
"@mui/icons-material": "^5.15.17",
|
|
27
27
|
"@mui/material": "^5.15.17",
|
|
28
|
-
"@opencloning/store": "1.4.
|
|
29
|
-
"@opencloning/utils": "1.4.
|
|
28
|
+
"@opencloning/store": "1.4.2",
|
|
29
|
+
"@opencloning/utils": "1.4.2",
|
|
30
30
|
"@teselagen/bio-parsers": "^0.4.34",
|
|
31
31
|
"@teselagen/ove": "^0.8.34",
|
|
32
32
|
"@teselagen/range-utils": "^0.3.20",
|
|
@@ -96,6 +96,7 @@ describe('<AssemblerComponent />', () => {
|
|
|
96
96
|
<AssemblerComponent
|
|
97
97
|
plasmids={mockPlasmids}
|
|
98
98
|
categories={mockCategories}
|
|
99
|
+
assemblyEnzyme="assembly_enzyme"
|
|
99
100
|
/>
|
|
100
101
|
</ConfigProvider>,
|
|
101
102
|
);
|
|
@@ -209,9 +210,15 @@ describe('<AssemblerComponent />', () => {
|
|
|
209
210
|
body: dummyResponse,
|
|
210
211
|
}).as('fetchSourceSuccess');
|
|
211
212
|
// Mock assembly request
|
|
212
|
-
cy.intercept('POST', 'http://localhost:8000/restriction_and_ligation*', {
|
|
213
|
-
|
|
214
|
-
body
|
|
213
|
+
cy.intercept('POST', 'http://localhost:8000/restriction_and_ligation*', (req) => {
|
|
214
|
+
// Check that the value of the enzyme was set in the request body
|
|
215
|
+
expect(req.body).to.have.property('source');
|
|
216
|
+
expect(req.body.source).to.have.property('restriction_enzymes');
|
|
217
|
+
expect(req.body.source.restriction_enzymes).to.include('assembly_enzyme');
|
|
218
|
+
req.reply({
|
|
219
|
+
statusCode: 200,
|
|
220
|
+
body: dummyResponse,
|
|
221
|
+
});
|
|
215
222
|
}).as('assemblySuccess');
|
|
216
223
|
|
|
217
224
|
// Click submit button
|
|
@@ -122,7 +122,7 @@ function AssemblerBox({ item, index, setCategory, setId, categories, plasmids, a
|
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
export function AssemblerComponent({ plasmids, categories }) {
|
|
125
|
+
export function AssemblerComponent({ plasmids, categories, assemblyEnzyme }) {
|
|
126
126
|
|
|
127
127
|
const [requestedAssemblies, setRequestedAssemblies] = React.useState([])
|
|
128
128
|
const [errorMessage, setErrorMessage] = React.useState('')
|
|
@@ -146,7 +146,7 @@ export function AssemblerComponent({ plasmids, categories }) {
|
|
|
146
146
|
const resp = await requestSources(selectedPlasmids)
|
|
147
147
|
errorMessage = 'Error assembling sequences'
|
|
148
148
|
setLoadingMessage('Assembling...')
|
|
149
|
-
const assemblies = await requestAssemblies(resp)
|
|
149
|
+
const assemblies = await requestAssemblies(resp, assemblyEnzyme)
|
|
150
150
|
setRequestedAssemblies(assemblies)
|
|
151
151
|
} catch (e) {
|
|
152
152
|
if (e.assembly) {
|
|
@@ -292,7 +292,7 @@ function Assembler() {
|
|
|
292
292
|
{syntax && <UploadPlasmidsButton addPlasmids={addPlasmids} syntax={syntax} />}
|
|
293
293
|
{syntax && <Button color="error" onClick={clearPlasmids}>Remove uploaded plasmids</Button>}
|
|
294
294
|
</ButtonGroup>
|
|
295
|
-
{syntax && <AssemblerComponent plasmids={plasmids} syntax={syntax} categories={categories} />}
|
|
295
|
+
{syntax && <AssemblerComponent plasmids={plasmids} syntax={syntax} categories={categories} assemblyEnzyme={syntax.assemblyEnzyme} />}
|
|
296
296
|
</>
|
|
297
297
|
)
|
|
298
298
|
}
|
|
@@ -274,6 +274,115 @@ describe('<ServerStaticFileSelect />', () => {
|
|
|
274
274
|
expect(file.size).to.equal(28);
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
|
+
it('allows selecting all sequences with the Select all option when multiple', () => {
|
|
278
|
+
const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
|
|
279
|
+
if (url.endsWith('/index.json')) {
|
|
280
|
+
return Promise.resolve({
|
|
281
|
+
data: dummyIndex,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
if (url.endsWith('/example.fa')) {
|
|
285
|
+
return Promise.resolve({ data: 'ATGC' });
|
|
286
|
+
}
|
|
287
|
+
if (url.endsWith('/example2.gb')) {
|
|
288
|
+
return Promise.resolve({ data: 'ATGCA' });
|
|
289
|
+
}
|
|
290
|
+
if (url.endsWith('/example3.fa')) {
|
|
291
|
+
return Promise.resolve({ data: 'ATGCG' });
|
|
292
|
+
}
|
|
293
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
294
|
+
});
|
|
295
|
+
cy.wrap(httpGet).as('httpGet');
|
|
296
|
+
|
|
297
|
+
const onFileSelected = cy.spy().as('onFileSelected');
|
|
298
|
+
cy.mount(
|
|
299
|
+
<ConfigProvider config={config}>
|
|
300
|
+
<ServerStaticFileSelect
|
|
301
|
+
onFileSelected={onFileSelected}
|
|
302
|
+
multiple
|
|
303
|
+
/>
|
|
304
|
+
</ConfigProvider>,
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
cy.get('@httpGet').should('have.been.calledWithMatch', 'index.json');
|
|
308
|
+
|
|
309
|
+
// Use the Select all option to select all sequences
|
|
310
|
+
cy.get('#option-select').click();
|
|
311
|
+
cy.contains('Select all').click();
|
|
312
|
+
cy.get('body').click(0, 0);
|
|
313
|
+
|
|
314
|
+
// Submit form
|
|
315
|
+
cy.contains('button', 'Submit').click();
|
|
316
|
+
|
|
317
|
+
cy.get('@httpGet').should('have.been.calledWithMatch', 'example.fa');
|
|
318
|
+
cy.get('@httpGet').should('have.been.calledWithMatch', 'example2.gb');
|
|
319
|
+
cy.get('@httpGet').should('have.been.calledWithMatch', 'example3.fa');
|
|
320
|
+
|
|
321
|
+
cy.get('@onFileSelected').should('have.been.calledOnce');
|
|
322
|
+
cy.get('@onFileSelected').should((spy) => {
|
|
323
|
+
const [files] = spy.lastCall.args;
|
|
324
|
+
expect(files).to.have.length(3);
|
|
325
|
+
expect(files[0].name).to.equal('example.fa');
|
|
326
|
+
expect(files[0].type).to.equal('text/plain');
|
|
327
|
+
expect(files[1].name).to.equal('example2.gb');
|
|
328
|
+
expect(files[1].type).to.equal('text/plain');
|
|
329
|
+
expect(files[2].name).to.equal('example3.fa');
|
|
330
|
+
expect(files[2].type).to.equal('text/plain');
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
it('clicking select all only selects the sequences that were filtered by category', () => {
|
|
334
|
+
const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
|
|
335
|
+
if (url.endsWith('/index.json')) {
|
|
336
|
+
return Promise.resolve({
|
|
337
|
+
data: dummyIndex,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
if (url.endsWith('/example.fa')) {
|
|
341
|
+
return Promise.resolve({ data: 'ATGC' });
|
|
342
|
+
}
|
|
343
|
+
if (url.endsWith('/example3.fa')) {
|
|
344
|
+
return Promise.resolve({ data: 'ATGCG' });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
cy.wrap(httpGet).as('httpGet');
|
|
348
|
+
const onFileSelected = cy.spy().as('onFileSelected');
|
|
349
|
+
cy.mount(
|
|
350
|
+
<ConfigProvider config={config}>
|
|
351
|
+
<ServerStaticFileSelect
|
|
352
|
+
onFileSelected={onFileSelected}
|
|
353
|
+
multiple
|
|
354
|
+
/>
|
|
355
|
+
</ConfigProvider>,
|
|
356
|
+
);
|
|
357
|
+
cy.get('@httpGet').should('have.been.calledWithMatch', 'index.json');
|
|
358
|
+
|
|
359
|
+
// Select category
|
|
360
|
+
cy.get('#category-select').click();
|
|
361
|
+
cy.contains('Test category').click();
|
|
362
|
+
|
|
363
|
+
// Select all
|
|
364
|
+
cy.get('#option-select').click();
|
|
365
|
+
cy.contains('Select all').click();
|
|
366
|
+
|
|
367
|
+
// Only one sequence should be shown
|
|
368
|
+
cy.get('#option-select').click();
|
|
369
|
+
cy.contains('Example sequence 1').should('exist');
|
|
370
|
+
cy.contains('Example sequence 2').should('not.exist');
|
|
371
|
+
cy.contains('Example sequence 3').should('exist');
|
|
372
|
+
|
|
373
|
+
// Click outside to close select element
|
|
374
|
+
cy.get('body').click(0, 0);
|
|
375
|
+
cy.contains('button', 'Submit').click();
|
|
376
|
+
|
|
377
|
+
cy.get('@httpGet').should('have.been.calledThrice');
|
|
378
|
+
cy.get('@onFileSelected').should('have.been.calledOnce');
|
|
379
|
+
cy.get('@onFileSelected').should((spy) => {
|
|
380
|
+
const [files] = spy.lastCall.args;
|
|
381
|
+
expect(files).to.have.length(2);
|
|
382
|
+
expect(files[0].name).to.equal('example.fa');
|
|
383
|
+
expect(files[1].name).to.equal('example3.fa');
|
|
384
|
+
});
|
|
385
|
+
})
|
|
277
386
|
it('works with multiple', () => {
|
|
278
387
|
const httpGet = cy.stub(localFilesHttpClient, 'get').callsFake((url) => {
|
|
279
388
|
if (url.endsWith('/index.json')) {
|
|
@@ -16,11 +16,12 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
|
|
|
16
16
|
if (type !== 'sequence') {
|
|
17
17
|
return index.syntaxes;
|
|
18
18
|
}
|
|
19
|
+
const prePendArray = multiple ? ['__all__'] : [];
|
|
19
20
|
if (selectedCategory === '') {
|
|
20
|
-
return index.sequences;
|
|
21
|
+
return [...prePendArray, ...index.sequences];
|
|
21
22
|
}
|
|
22
|
-
return index.sequences.filter((sequence) => sequence.categories?.includes(selectedCategory));
|
|
23
|
-
}, [type, index, selectedCategory]);
|
|
23
|
+
return [...prePendArray, ...index.sequences.filter((sequence) => sequence.categories?.includes(selectedCategory))];
|
|
24
|
+
}, [type, index, selectedCategory, multiple]);
|
|
24
25
|
|
|
25
26
|
const categoryOptions = React.useMemo(() => {
|
|
26
27
|
if (!index?.categories) return [];
|
|
@@ -72,6 +73,19 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
|
|
|
72
73
|
const buttonDisabled = multiple ? selectedOptions.length === 0 : !selectedOptions;
|
|
73
74
|
|
|
74
75
|
const label = type === 'sequence' ? 'Sequence' : 'Syntax';
|
|
76
|
+
|
|
77
|
+
const onOptionsChange = React.useCallback((event, value) => {
|
|
78
|
+
console.log('onOptionsChange', value);
|
|
79
|
+
if (multiple && type === 'sequence') {
|
|
80
|
+
if (value.includes('__all__')) {
|
|
81
|
+
const allSequences = options.filter((option) => option !== '__all__');
|
|
82
|
+
setSelectedOptions(allSequences);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
setSelectedOptions(value);
|
|
87
|
+
}, [multiple, type, options]);
|
|
88
|
+
|
|
75
89
|
return (
|
|
76
90
|
<RequestStatusWrapper requestStatus={indexRequestStatus} retry={indexRetry}>
|
|
77
91
|
{error && <Alert severity="error">{error}</Alert>}
|
|
@@ -98,8 +112,8 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
|
|
|
98
112
|
multiple={multiple}
|
|
99
113
|
options={options}
|
|
100
114
|
value={selectedOptions}
|
|
101
|
-
onChange={
|
|
102
|
-
getOptionLabel={(option) => option?.name || option?.path || ''}
|
|
115
|
+
onChange={onOptionsChange}
|
|
116
|
+
getOptionLabel={(option) => (option === '__all__' ? 'Select all' : option?.name || option?.path || '')}
|
|
103
117
|
disableCloseOnSelect={multiple}
|
|
104
118
|
renderInput={(params) => (
|
|
105
119
|
<TextField
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Version placeholder - replaced at publish time via prepack script
|
|
2
|
-
export const version = "1.4.
|
|
2
|
+
export const version = "1.4.2";
|