@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 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.0",
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.0",
29
- "@opencloning/utils": "1.4.0",
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
- statusCode: 200,
214
- body: dummyResponse,
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={(event, value) => setSelectedOptions(value)}
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.0";
2
+ export const version = "1.4.2";