1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 const path = require('path');
7 describe('Collection panel tests', function () {
13 // Only set up common users once. These aren't set up as aliases because
14 // aliases are cleaned up after every test. Also it doesn't make sense
15 // to set the same users on beforeEach() over and over again, so we
16 // separate a little from Cypress' 'Best Practices' here.
17 cy.getUser('admin', 'Admin', 'User', true, true)
18 .as('adminUser').then(function () {
19 adminUser = this.adminUser;
22 cy.getUser('collectionuser1', 'Collection', 'User', false, true)
23 .as('activeUser').then(function () {
24 activeUser = this.activeUser;
27 downloadsFolder = Cypress.config('downloadsFolder');
30 beforeEach(function () {
32 cy.clearLocalStorage();
35 it('allows to download mountain duck config for a collection', () => {
36 cy.createCollection(adminUser.token, {
37 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
38 owner_uuid: activeUser.user.uuid,
39 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
41 .as('testCollection').then(function (testCollection) {
42 cy.loginAs(activeUser);
43 cy.goToPath(`/collections/${testCollection.uuid}`);
45 cy.get('[data-cy=collection-panel-options-btn]').click();
46 cy.get('[data-cy=context-menu]').contains('Open with 3rd party client').click();
47 cy.get('[data-cy=download-button').click();
49 const filename = path.join(downloadsFolder, `${testCollection.name}.duck`);
51 cy.readFile(filename, { timeout: 15000 })
53 const childrenCollection = Array.prototype.slice.call(Cypress.$(body).find('dict')[0].children);
57 for (i=0; i < childrenCollection.length; i += j) {
58 map[childrenCollection[i].outerText] = childrenCollection[i + 1].outerText;
61 cy.get('#simple-tabpanel-0').find('a')
63 const [host, port] = a.text().split('@')[1].split('/')[0].split(':');
64 expect(map['Protocol']).to.equal('davs');
65 expect(map['UUID']).to.equal(testCollection.uuid);
66 expect(map['Username']).to.equal(activeUser.user.username);
67 expect(map['Port']).to.equal(port);
68 expect(map['Hostname']).to.equal(host);
70 expect(map['Path']).to.equal(`/c=${testCollection.uuid}`);
74 .then(() => cy.task('clearDownload', { filename }));
78 it('uses the property editor (from edit dialog) with vocabulary terms', function () {
79 cy.createCollection(adminUser.token, {
80 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
81 owner_uuid: activeUser.user.uuid,
82 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
84 .as('testCollection').then(function () {
85 cy.loginAs(activeUser);
86 cy.goToPath(`/collections/${this.testCollection.uuid}`);
88 cy.get('[data-cy=collection-info-panel')
89 .should('contain', this.testCollection.name)
90 .and('not.contain', 'Color: Magenta');
92 cy.get('[data-cy=collection-panel-options-btn]').click();
93 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
94 cy.get('[data-cy=form-dialog]').should('contain', 'Properties');
96 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
97 cy.get('[data-cy=resource-properties-form]').within(() => {
98 cy.get('[data-cy=property-field-key]').within(() => {
99 cy.get('input').type('Color');
101 cy.get('[data-cy=property-field-value]').within(() => {
102 cy.get('input').type('Magenta');
106 // Confirm proper vocabulary labels are displayed on the UI.
107 cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
108 cy.get('[data-cy=form-dialog]').contains('Save').click();
109 cy.get('[data-cy=form-dialog]').should('not.exist');
110 // Confirm proper vocabulary IDs were saved on the backend.
111 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
112 .its('body').as('collection')
114 expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3');
116 // Confirm the property is displayed on the UI.
117 cy.get('[data-cy=collection-info-panel')
118 .should('contain', this.testCollection.name)
119 .and('contain', 'Color: Magenta');
123 it('uses the property editor (from details panel) with vocabulary terms', function () {
124 cy.createCollection(adminUser.token, {
125 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
126 owner_uuid: activeUser.user.uuid,
127 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
129 .as('testCollection').then(function () {
130 cy.loginAs(activeUser);
131 cy.goToPath(`/collections/${this.testCollection.uuid}`);
133 cy.get('[data-cy=collection-info-panel')
134 .should('contain', this.testCollection.name)
135 .and('not.contain', 'Color: Magenta')
136 .and('not.contain', 'Size: S');
137 cy.get('[data-cy=additional-info-icon]').click();
139 cy.get('[data-cy=details-panel]').within(() => {
140 cy.get('[data-cy=property-editor-btn]').click();
142 cy.get('[data-cy=resource-properties-dialog').contains('Edit properties');
144 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
145 cy.get('[data-cy=resource-properties-form]').within(() => {
146 cy.get('[data-cy=property-field-key]').within(() => {
147 cy.get('input').type('Color');
149 cy.get('[data-cy=property-field-value]').within(() => {
150 cy.get('input').type('Magenta');
154 // Confirm proper vocabulary labels are displayed on the UI.
155 cy.get('[data-cy=resource-properties-dialog]')
156 .should('contain', 'Color: Magenta');
157 // Confirm proper vocabulary IDs were saved on the backend.
158 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
159 .its('body').as('collection')
161 expect(this.collection.properties.IDTAGCOLORS).to.equal('IDVALCOLORS3');
164 // Case-insensitive on-blur auto-selection test
165 // Key: Size (IDTAGSIZES) - Value: Small (IDVALSIZES2)
166 cy.get('[data-cy=resource-properties-form]').within(() => {
167 cy.get('[data-cy=property-field-key]').within(() => {
168 cy.get('input').type('sIzE');
170 cy.get('[data-cy=property-field-value]').within(() => {
171 cy.get('input').type('sMaLL');
173 // Cannot "type()" TAB on Cypress so let's click another field
174 // to trigger the onBlur event.
175 cy.get('[data-cy=property-field-key]').click();
178 // Confirm proper vocabulary labels are displayed on the UI.
179 cy.get('[data-cy=resource-properties-dialog]')
180 .should('contain', 'Size: S');
181 // Confirm proper vocabulary IDs were saved on the backend.
182 cy.doRequest('GET', `/arvados/v1/collections/${this.testCollection.uuid}`)
183 .its('body').as('collection')
185 expect(this.collection.properties.IDTAGSIZES).to.equal('IDVALSIZES2');
188 // Close property editor & confirm properties display on the UI.
189 cy.get('[data-cy=resource-properties-dialog]').within(() => {
190 cy.get('[data-cy=close-dialog-btn]').click();
192 cy.get('[data-cy=collection-info-panel')
193 .should('contain', this.testCollection.name)
194 .and('contain', 'Color: Magenta')
195 .and('contain', 'Size: S');
199 it('shows collection by URL', function () {
200 cy.loginAs(activeUser);
201 [true, false].map(function (isWritable) {
202 // Using different file names to avoid test flakyness: the second iteration
203 // on this loop may pass an assertion from the first iteration by looking
204 // for the same file name.
205 const fileName = isWritable ? 'bar' : 'foo';
206 const subDirName = 'subdir';
207 cy.createGroup(adminUser.token, {
208 name: 'Shared project',
209 group_class: 'project',
210 }).as('sharedGroup').then(function () {
211 // Creates the collection using the admin token so we can set up
212 // a bogus manifest text without block signatures.
213 cy.doRequest('GET', '/arvados/v1/config', null, null)
214 .its('body').should((clusterConfig) => {
215 expect(clusterConfig.Collections, "clusterConfig").to.have.property("TrustAllContent", false);
216 expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAV").have.property("ExternalURL");
217 expect(clusterConfig.Services, "clusterConfig").to.have.property("WebDAVDownload").have.property("ExternalURL");
218 const inlineUrl = clusterConfig.Services.WebDAV.ExternalURL !== ""
219 ? clusterConfig.Services.WebDAV.ExternalURL
220 : clusterConfig.Services.WebDAVDownload.ExternalURL;
221 expect(inlineUrl).to.not.contain("*");
223 .createCollection(adminUser.token, {
224 name: 'Test collection',
225 owner_uuid: this.sharedGroup.uuid,
226 properties: { someKey: 'someValue' },
227 manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n./${subDirName} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`
229 .as('testCollection').then(function () {
230 // Share the group with active user.
231 cy.createLink(adminUser.token, {
232 name: isWritable ? 'can_write' : 'can_read',
233 link_class: 'permission',
234 head_uuid: this.sharedGroup.uuid,
235 tail_uuid: activeUser.user.uuid
237 cy.goToPath(`/collections/${this.testCollection.uuid}`);
239 // Check that name & uuid are correct.
240 cy.get('[data-cy=collection-info-panel]')
241 .should('contain', this.testCollection.name)
242 .and('contain', this.testCollection.uuid)
243 .and('not.contain', 'This is an old version');
244 // Check for the read-only icon
245 cy.get('[data-cy=read-only-icon]').should(`${isWritable ? 'not.' : ''}exist`);
246 // Check that both read and write operations are available on
247 // the 'More options' menu.
248 cy.get('[data-cy=collection-panel-options-btn]')
250 cy.get('[data-cy=context-menu]')
251 .should('contain', 'Add to favorites')
252 .and(`${isWritable ? '' : 'not.'}contain`, 'Edit collection');
253 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
254 cy.get('[data-cy=collection-info-panel]')
255 .should('contain', 'someKey: someValue')
256 .and('not.contain', 'anotherKey: anotherValue');
257 // Check that the file listing show both read & write operations
258 cy.get('[data-cy=collection-files-panel]').within(() => {
260 cy.root().should('contain', fileName);
262 cy.get('[data-cy=upload-button]')
263 .should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
266 // Test context menus
267 cy.get('[data-cy=collection-files-panel]')
268 .contains(fileName).rightclick({ force: true });
269 cy.get('[data-cy=context-menu]')
270 .should('contain', 'Download')
271 .and('not.contain', 'Open in new tab')
272 .and('contain', 'Copy to clipboard')
273 .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
274 .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
275 cy.get('body').click(); // Collapse the menu
276 cy.get('[data-cy=collection-files-panel]')
277 .contains(subDirName).rightclick({ force: true });
278 cy.get('[data-cy=context-menu]')
279 .should('not.contain', 'Download')
280 .and('not.contain', 'Open in new tab')
281 .and('contain', 'Copy to clipboard')
282 .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
283 .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
284 cy.get('body').click(); // Collapse the menu
285 // Hamburger 'more options' menu button
286 cy.get('[data-cy=collection-files-panel-options-btn]')
288 cy.get('[data-cy=context-menu]')
289 .should('contain', 'Select all')
291 cy.get('[data-cy=collection-files-panel-options-btn]')
293 cy.get('[data-cy=context-menu]')
294 .should(`${isWritable ? '' : 'not.'}contain`, 'Remove selected')
295 cy.get('body').click(); // Collapse the menu
301 it('renames a file using valid names', function () {
302 function eachPair(lst, func){
303 for(var i=0; i < lst.length - 1; i++){
304 func(lst[i], lst[i + 1])
307 // Creates the collection using the admin token so we can set up
308 // a bogus manifest text without block signatures.
309 cy.createCollection(adminUser.token, {
310 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
311 owner_uuid: activeUser.user.uuid,
312 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
314 .as('testCollection').then(function () {
315 cy.loginAs(activeUser);
316 cy.goToPath(`/collections/${this.testCollection.uuid}`);
319 'bar', // initial name already set
326 'some name with whitespaces',
328 'is this name legal? I hope it is',
336 "G%C3%BCnter's%20file.pdf",
338 'bar' // make sure we can go back to the original name as a last step
340 eachPair(names, (from, to) => {
341 cy.get('[data-cy=collection-files-panel]')
342 .contains(`${from}`).rightclick();
343 cy.get('[data-cy=context-menu]')
346 cy.get('[data-cy=form-dialog]')
347 .should('contain', 'Rename')
350 .type('{selectall}{backspace}')
351 .type(to, { parseSpecialCharSequences: false });
353 cy.get('[data-cy=form-submit-btn]').click();
354 cy.get('[data-cy=collection-files-panel]')
355 .should('not.contain', `${from}`)
356 .and('contain', `${to}`);
361 it('renames a file to a different directory', function () {
362 // Creates the collection using the admin token so we can set up
363 // a bogus manifest text without block signatures.
364 cy.createCollection(adminUser.token, {
365 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
366 owner_uuid: activeUser.user.uuid,
367 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
369 .as('testCollection').then(function () {
370 cy.loginAs(activeUser);
371 cy.goToPath(`/collections/${this.testCollection.uuid}`);
373 ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => {
374 cy.get('[data-cy=collection-files-panel]')
375 .contains('bar').rightclick({force: true});
376 cy.get('[data-cy=context-menu]')
379 cy.get('[data-cy=form-dialog]')
380 .should('contain', 'Rename')
382 cy.get('input').type(`{selectall}{backspace}${subdir}/foo`);
384 cy.get('[data-cy=form-submit-btn]').click();
385 cy.get('[data-cy=collection-files-panel]')
386 .should('not.contain', 'bar')
387 .and('contain', subdir);
389 cy.get('[data-cy=collection-files-panel]').contains(subdir).click();
390 // Rename 'subdir/foo' to 'foo'
392 cy.get('[data-cy=collection-files-panel]')
393 .contains('foo').rightclick();
394 cy.get('[data-cy=context-menu]')
397 cy.get('[data-cy=form-dialog]')
398 .should('contain', 'Rename')
401 .should('have.value', `${subdir}/foo`)
402 .type(`{selectall}{backspace}bar`);
404 cy.get('[data-cy=form-submit-btn]').click();
407 cy.get('[data-cy=collection-files-panel]')
412 cy.get('[data-cy=collection-files-panel]')
413 .should('contain', subdir) // empty dir kept
414 .and('contain', 'bar');
416 cy.get('[data-cy=collection-files-panel]')
417 .contains(subdir).rightclick();
418 cy.get('[data-cy=context-menu]')
421 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
426 it('tries to rename a file with illegal names', function () {
427 // Creates the collection using the admin token so we can set up
428 // a bogus manifest text without block signatures.
429 cy.createCollection(adminUser.token, {
430 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
431 owner_uuid: activeUser.user.uuid,
432 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
434 .as('testCollection').then(function () {
435 cy.loginAs(activeUser);
436 cy.goToPath(`/collections/${this.testCollection.uuid}`);
438 const illegalNamesFromUI = [
439 ['.', "Name cannot be '.' or '..'"],
440 ['..', "Name cannot be '.' or '..'"],
441 ['', 'This field is required'],
442 [' ', 'Leading/trailing whitespaces not allowed'],
443 [' foo', 'Leading/trailing whitespaces not allowed'],
444 ['foo ', 'Leading/trailing whitespaces not allowed'],
445 ['//foo', 'Empty dir name not allowed']
447 illegalNamesFromUI.forEach(([name, errMsg]) => {
448 cy.get('[data-cy=collection-files-panel]')
449 .contains('bar').rightclick();
450 cy.get('[data-cy=context-menu]')
453 cy.get('[data-cy=form-dialog]')
454 .should('contain', 'Rename')
456 cy.get('input').type(`{selectall}{backspace}${name}`);
458 cy.get('[data-cy=form-dialog]')
459 .should('contain', 'Rename')
461 cy.contains(`${errMsg}`);
463 cy.get('[data-cy=form-cancel-btn]').click();
468 it('can correctly display old versions', function () {
469 const colName = `Versioned Collection ${Math.floor(Math.random() * 999999)}`;
471 let oldVersionUuid = '';
472 // Make sure no other collections with this name exist
473 cy.doRequest('GET', '/arvados/v1/collections', null, {
474 filters: `[["name", "=", "${colName}"]]`,
475 include_old_versions: true
477 .its('body.items').as('collections')
479 expect(this.collections).to.be.empty;
481 // Creates the collection using the admin token so we can set up
482 // a bogus manifest text without block signatures.
483 cy.createCollection(adminUser.token, {
485 owner_uuid: activeUser.user.uuid,
486 preserve_version: true,
487 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
489 .as('originalVersion').then(function () {
490 // Change the file name to create a new version.
491 cy.updateCollection(adminUser.token, this.originalVersion.uuid, {
492 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo\n"
494 colUuid = this.originalVersion.uuid;
496 // Confirm that there are 2 versions of the collection
497 cy.doRequest('GET', '/arvados/v1/collections', null, {
498 filters: `[["name", "=", "${colName}"]]`,
499 include_old_versions: true
501 .its('body.items').as('collections')
503 expect(this.collections).to.have.lengthOf(2);
504 this.collections.map(function (aCollection) {
505 expect(aCollection.current_version_uuid).to.equal(colUuid);
506 if (aCollection.uuid !== aCollection.current_version_uuid) {
507 oldVersionUuid = aCollection.uuid;
510 // Check the old version displays as what it is.
511 cy.loginAs(activeUser)
512 cy.goToPath(`/collections/${oldVersionUuid}`);
514 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
515 cy.get('[data-cy=read-only-icon]').should('exist');
516 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
517 cy.get('[data-cy=collection-files-panel]').should('contain', 'bar');
521 it('views & edits storage classes data', function () {
522 const colName= `Test Collection ${Math.floor(Math.random() * 999999)}`;
523 cy.createCollection(adminUser.token, {
525 owner_uuid: activeUser.user.uuid,
526 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
527 }).as('collection').then(function () {
528 expect(this.collection.storage_classes_desired).to.deep.equal(['default'])
530 cy.loginAs(activeUser)
531 cy.goToPath(`/collections/${this.collection.uuid}`);
533 // Initial check: it should show the 'default' storage class
534 cy.get('[data-cy=collection-info-panel]')
535 .should('contain', 'Storage classes')
536 .and('contain', 'default')
537 .and('not.contain', 'foo')
538 .and('not.contain', 'bar');
539 // Edit collection: add storage class 'foo'
540 cy.get('[data-cy=collection-panel-options-btn]').click();
541 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
542 cy.get('[data-cy=form-dialog]')
543 .should('contain', 'Edit Collection')
544 .and('contain', 'Storage classes')
545 .and('contain', 'default')
546 .and('contain', 'foo')
547 .and('contain', 'bar')
549 cy.get('[data-cy=checkbox-foo]').click();
551 cy.get('[data-cy=form-submit-btn]').click();
552 cy.get('[data-cy=collection-info-panel]')
553 .should('contain', 'default')
554 .and('contain', 'foo')
555 .and('not.contain', 'bar');
556 cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
557 .its('body').as('updatedCollection')
559 expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['default', 'foo']);
561 // Edit collection: remove storage class 'default'
562 cy.get('[data-cy=collection-panel-options-btn]').click();
563 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
564 cy.get('[data-cy=form-dialog]')
565 .should('contain', 'Edit Collection')
566 .and('contain', 'Storage classes')
567 .and('contain', 'default')
568 .and('contain', 'foo')
569 .and('contain', 'bar')
571 cy.get('[data-cy=checkbox-default]').click();
573 cy.get('[data-cy=form-submit-btn]').click();
574 cy.get('[data-cy=collection-info-panel]')
575 .should('not.contain', 'default')
576 .and('contain', 'foo')
577 .and('not.contain', 'bar');
578 cy.doRequest('GET', `/arvados/v1/collections/${this.collection.uuid}`)
579 .its('body').as('updatedCollection')
581 expect(this.updatedCollection.storage_classes_desired).to.deep.equal(['foo']);
586 it('moves a collection to a different project', function () {
587 const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
588 const projName = `Test Project ${Math.floor(Math.random() * 999999)}`;
589 const fileName = `Test_File_${Math.floor(Math.random() * 999999)}`;
591 cy.createCollection(adminUser.token, {
593 owner_uuid: activeUser.user.uuid,
594 manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`,
595 }).as('testCollection');
596 cy.createGroup(adminUser.token, {
598 group_class: 'project',
599 owner_uuid: activeUser.user.uuid,
600 }).as('testProject');
602 cy.getAll('@testCollection', '@testProject')
603 .then(function ([testCollection, testProject]) {
604 cy.loginAs(activeUser);
605 cy.goToPath(`/collections/${testCollection.uuid}`);
606 cy.get('[data-cy=collection-files-panel]').should('contain', fileName);
607 cy.get('[data-cy=collection-info-panel]')
608 .should('not.contain', projName)
609 .and('not.contain', testProject.uuid);
610 cy.get('[data-cy=collection-panel-options-btn]').click();
611 cy.get('[data-cy=context-menu]').contains('Move to').click();
612 cy.get('[data-cy=form-dialog]')
613 .should('contain', 'Move to')
615 cy.get('[data-cy=projects-tree-home-tree-picker]')
618 cy.get('[data-cy=projects-tree-home-tree-picker]')
622 cy.get('[data-cy=form-submit-btn]').click();
623 cy.get('[data-cy=snackbar]')
624 .contains('Collection has been moved')
625 cy.get('[data-cy=collection-info-panel]')
626 .contains(projName).and('contain', testProject.uuid);
627 // Double check that the collection is in the project
628 cy.goToPath(`/projects/${testProject.uuid}`);
629 cy.get('[data-cy=project-panel]').should('contain', collName);
633 it('makes a copy of an existing collection', function() {
634 const collName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
635 const copyName = `Copy of: ${collName}`;
637 cy.createCollection(adminUser.token, {
639 owner_uuid: activeUser.user.uuid,
640 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:some-file\n",
641 }).as('collection').then(function () {
642 cy.loginAs(activeUser)
643 cy.goToPath(`/collections/${this.collection.uuid}`);
644 cy.get('[data-cy=collection-files-panel]')
645 .should('contain', 'some-file');
646 cy.get('[data-cy=collection-panel-options-btn]').click();
647 cy.get('[data-cy=context-menu]').contains('Make a copy').click();
648 cy.get('[data-cy=form-dialog]')
649 .should('contain', 'Make a copy')
651 cy.get('[data-cy=projects-tree-home-tree-picker]')
652 .contains('Projects')
654 cy.get('[data-cy=form-submit-btn]').click();
656 cy.get('[data-cy=snackbar]')
657 .contains('Collection has been copied.')
658 cy.get('[data-cy=snackbar-goto-action]').click();
659 cy.get('[data-cy=project-panel]')
660 .contains(copyName).click();
661 cy.get('[data-cy=collection-files-panel]')
662 .should('contain', 'some-file');
666 it('uses the collection version browser to view a previous version', function () {
667 const colName = `Test Collection ${Math.floor(Math.random() * 999999)}`;
669 // Creates the collection using the admin token so we can set up
670 // a bogus manifest text without block signatures.
671 cy.createCollection(adminUser.token, {
673 owner_uuid: activeUser.user.uuid,
674 preserve_version: true,
675 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
677 .as('collection').then(function () {
678 // Visit collection, check basic information
679 cy.loginAs(activeUser)
680 cy.goToPath(`/collections/${this.collection.uuid}`);
682 cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
683 cy.get('[data-cy=read-only-icon]').should('not.exist');
684 cy.get('[data-cy=collection-version-number]').should('contain', '1');
685 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
686 cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
688 // Modify collection, expect version number change
689 cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
690 cy.get('[data-cy=context-menu]').contains('Remove').click();
691 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
692 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
693 cy.get('[data-cy=collection-version-number]').should('contain', '2');
694 cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
696 // Click on version number, check version browser. Click on past version.
697 cy.get('[data-cy=collection-version-browser]').should('not.exist');
698 cy.get('[data-cy=collection-version-number]').contains('2').click();
699 cy.get('[data-cy=collection-version-browser]')
700 .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
702 // Version 1: 6 bytes in size
703 cy.get('[data-cy=collection-version-browser-select-1]')
704 .should('contain', '1')
705 .and('contain', '6 B')
706 .and('contain', adminUser.user.uuid);
707 // Version 2: 3 bytes in size (one file removed)
708 cy.get('[data-cy=collection-version-browser-select-2]')
709 .should('contain', '2')
710 .and('contain', '3 B')
711 .and('contain', activeUser.user.full_name);
712 cy.get('[data-cy=collection-version-browser-select-3]')
713 .should('not.exist');
714 cy.get('[data-cy=collection-version-browser-select-1]')
717 cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
718 cy.get('[data-cy=read-only-icon]').should('exist');
719 cy.get('[data-cy=collection-version-number]').should('contain', '1');
720 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
721 cy.get('[data-cy=collection-files-panel]')
722 .should('contain', 'foo').and('contain', 'bar');
724 // Check that only old collection action are available on context menu
725 cy.get('[data-cy=collection-panel-options-btn]').click();
726 cy.get('[data-cy=context-menu]')
727 .should('contain', 'Restore version')
728 .and('not.contain', 'Add to favorites');
729 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
731 // Click on "head version" link, confirm that it's the latest version.
732 cy.get('[data-cy=collection-info-panel]').contains('head version').click();
733 cy.get('[data-cy=collection-info-panel]')
734 .should('not.contain', 'This is an old version');
735 cy.get('[data-cy=read-only-icon]').should('not.exist');
736 cy.get('[data-cy=collection-version-number]').should('contain', '2');
737 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
738 cy.get('[data-cy=collection-files-panel]').
739 should('not.contain', 'foo').and('contain', 'bar');
741 // Check that old collection action isn't available on context menu
742 cy.get('[data-cy=collection-panel-options-btn]').click()
743 cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
744 cy.get('body').click(); // Collapse the menu avoiding details panel expansion
746 // Make another change, confirm new version.
747 cy.get('[data-cy=collection-panel-options-btn]').click();
748 cy.get('[data-cy=context-menu]').contains('Edit collection').click();
749 cy.get('[data-cy=form-dialog]')
750 .should('contain', 'Edit Collection')
753 cy.get('input').first().type(' renamed');
755 cy.get('[data-cy=form-submit-btn]').click();
756 cy.get('[data-cy=collection-info-panel]')
757 .should('not.contain', 'This is an old version');
758 cy.get('[data-cy=read-only-icon]').should('not.exist');
759 cy.get('[data-cy=collection-version-number]').should('contain', '3');
760 cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
761 cy.get('[data-cy=collection-files-panel]')
762 .should('not.contain', 'foo').and('contain', 'bar');
763 cy.get('[data-cy=collection-version-browser-select-3]')
764 .should('contain', '3').and('contain', '3 B');
766 // Check context menus on version browser
767 cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
768 cy.get('[data-cy=context-menu]')
769 .should('contain', 'Add to favorites')
770 .and('contain', 'Make a copy')
771 .and('contain', 'Edit collection');
772 cy.get('body').click();
773 // (and now an old version...)
774 cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
775 cy.get('[data-cy=context-menu]')
776 .should('not.contain', 'Add to favorites')
777 .and('contain', 'Make a copy')
778 .and('not.contain', 'Edit collection');
779 cy.get('body').click();
781 // Restore first version
782 cy.get('[data-cy=collection-version-browser]').within(() => {
783 cy.get('[data-cy=collection-version-browser-select-1]').click();
785 cy.get('[data-cy=collection-panel-options-btn]').click()
786 cy.get('[data-cy=context-menu]').contains('Restore version').click();
787 cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
788 cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
789 cy.get('[data-cy=collection-info-panel]')
790 .should('not.contain', 'This is an old version');
791 cy.get('[data-cy=collection-version-number]').should('contain', '4');
792 cy.get('[data-cy=collection-info-panel]').should('contain', colName);
793 cy.get('[data-cy=collection-files-panel]')
794 .should('contain', 'foo').and('contain', 'bar');
798 it('creates new collection with properties on home project', function () {
799 cy.loginAs(activeUser);
800 cy.goToPath(`/projects/${activeUser.user.uuid}`);
801 cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
802 cy.get('[data-cy=breadcrumb-last]').should('not.exist');
803 // Create new collection
804 cy.get('[data-cy=side-panel-button]').click();
805 cy.get('[data-cy=side-panel-new-collection]').click();
806 // Name between brackets tests bugfix #17582
807 const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
809 // Select a storage class.
810 cy.get('[data-cy=form-dialog]')
811 .should('contain', 'New collection')
812 .and('contain', 'Storage classes')
813 .and('contain', 'default')
814 .and('contain', 'foo')
815 .and('contain', 'bar')
817 cy.get('[data-cy=parent-field]').within(() => {
818 cy.get('input').should('have.value', 'Home project');
820 cy.get('[data-cy=name-field]').within(() => {
821 cy.get('input').type(collName);
823 cy.get('[data-cy=checkbox-foo]').click();
827 // Key: Color (IDTAGCOLORS) - Value: Magenta (IDVALCOLORS3)
828 cy.get('[data-cy=form-dialog]').should('not.contain', 'Color: Magenta');
829 cy.get('[data-cy=resource-properties-form]').within(() => {
830 cy.get('[data-cy=property-field-key]').within(() => {
831 cy.get('input').type('Color');
833 cy.get('[data-cy=property-field-value]').within(() => {
834 cy.get('input').type('Magenta');
838 // Confirm proper vocabulary labels are displayed on the UI.
839 cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
841 cy.get('[data-cy=form-submit-btn]').click();
842 // Confirm that the user was taken to the newly created collection
843 cy.get('[data-cy=form-dialog]').should('not.exist');
844 cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
845 cy.get('[data-cy=breadcrumb-last]').should('contain', collName);
846 cy.get('[data-cy=collection-info-panel]')
847 .should('contain', 'default')
848 .and('contain', 'foo')
849 .and('contain', 'Color: Magenta')
850 .and('not.contain', 'bar');
851 // Confirm that the collection's properties has the real values.
852 cy.doRequest('GET', '/arvados/v1/collections', null, {
853 filters: `[["name", "=", "${collName}"]]`,
855 .its('body.items').as('collections')
857 expect(this.collections).to.have.lengthOf(1);
858 expect(this.collections[0].properties).to.have.property(
859 'IDTAGCOLORS', 'IDVALCOLORS3');
863 it('shows responsible person for collection if available', () => {
864 cy.createCollection(adminUser.token, {
865 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
866 owner_uuid: activeUser.user.uuid,
867 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
869 .as('testCollection1');
871 cy.createCollection(adminUser.token, {
872 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
873 owner_uuid: adminUser.user.uuid,
874 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
876 .as('testCollection2').then(function (testCollection2) {
877 cy.shareWith(adminUser.token, activeUser.user.uuid, testCollection2.uuid, 'can_write');
880 cy.getAll('@testCollection1', '@testCollection2')
881 .then(function ([testCollection1, testCollection2]) {
882 cy.loginAs(activeUser);
884 cy.goToPath(`/collections/${testCollection1.uuid}`);
885 cy.get('[data-cy=responsible-person-wrapper]')
886 .contains(activeUser.user.uuid);
888 cy.goToPath(`/collections/${testCollection2.uuid}`);
889 cy.get('[data-cy=responsible-person-wrapper]')
890 .contains(adminUser.user.uuid);
894 describe('file upload', () => {
896 cy.createCollection(adminUser.token, {
897 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
898 owner_uuid: activeUser.user.uuid,
899 manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
901 .as('testCollection1');
904 it('allows to cancel running upload', () => {
905 cy.getAll('@testCollection1')
906 .then(function([testCollection1]) {
907 cy.loginAs(activeUser);
909 cy.goToPath(`/collections/${testCollection1.uuid}`);
911 cy.get('[data-cy=upload-button]').click();
913 cy.fixture('files/5mb.bin', 'base64').then(content => {
914 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
915 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
917 cy.get('[data-cy=form-submit-btn]').click();
919 cy.get('button').contains('Cancel').click();
921 cy.get('[data-cy=form-submit-btn]').should('not.exist');
926 it('allows to cancel single file from the running upload', () => {
927 cy.getAll('@testCollection1')
928 .then(function([testCollection1]) {
929 cy.loginAs(activeUser);
931 cy.goToPath(`/collections/${testCollection1.uuid}`);
933 cy.get('[data-cy=upload-button]').click();
935 cy.fixture('files/5mb.bin', 'base64').then(content => {
936 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
937 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
939 cy.get('[data-cy=form-submit-btn]').click();
941 cy.get('button[aria-label=Remove]').eq(1).click();
943 cy.get('[data-cy=form-submit-btn]').should('not.exist');
945 cy.get('[data-cy=collection-files-panel]').contains('5mb_a.bin').should('exist');
950 it('allows to cancel all files from the running upload', () => {
951 cy.getAll('@testCollection1')
952 .then(function([testCollection1]) {
953 cy.loginAs(activeUser);
955 cy.goToPath(`/collections/${testCollection1.uuid}`);
957 cy.get('[data-cy=upload-button]').click();
959 cy.fixture('files/5mb.bin', 'base64').then(content => {
960 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_a.bin');
961 cy.get('[data-cy=drag-and-drop]').upload(content, '5mb_b.bin');
963 cy.get('[data-cy=form-submit-btn]').click();
965 cy.get('button[aria-label=Remove]').should('exist');
966 cy.get('button[aria-label=Remove]').click({ multiple: true, force: true });
968 cy.get('[data-cy=form-submit-btn]').should('not.exist');