Merge branch '21621-io-panel-json-tab-copy' into main. Closes #21621
[arvados.git] / services / workbench2 / cypress / e2e / workflow.cy.js
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 describe('Registered workflow panel tests', function() {
6     let activeUser;
7     let adminUser;
8
9     before(function() {
10         // Only set up common users once. These aren't set up as aliases because
11         // aliases are cleaned up after every test. Also it doesn't make sense
12         // to set the same users on beforeEach() over and over again, so we
13         // separate a little from Cypress' 'Best Practices' here.
14         cy.getUser('admin', 'Admin', 'User', true, true)
15             .as('adminUser').then(function() {
16                 adminUser = this.adminUser;
17             }
18         );
19         cy.getUser('user', 'Active', 'User', false, true)
20             .as('activeUser').then(function() {
21                 activeUser = this.activeUser;
22             }
23         );
24     });
25
26     it('should handle null definition', function() {
27         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
28             .then(function(workflowResource) {
29                 cy.loginAs(activeUser);
30                 cy.goToPath(`/workflows/${workflowResource.uuid}`);
31                 cy.get('[data-cy=registered-workflow-info-panel]').should('contain', workflowResource.name);
32                 cy.get('[data-cy=workflow-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
33             });
34     });
35
36     it('should handle malformed definition', function() {
37         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf", definition: "zap:"}})
38             .then(function(workflowResource) {
39                 cy.loginAs(activeUser);
40                 cy.goToPath(`/workflows/${workflowResource.uuid}`);
41                 cy.get('[data-cy=registered-workflow-info-panel]').should('contain', workflowResource.name);
42                 cy.get('[data-cy=workflow-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
43             });
44     });
45
46     it('should handle malformed run', function() {
47         cy.createResource(activeUser.token, "workflows", {workflow: {
48             name: "Test wf",
49             definition: JSON.stringify({
50                 cwlVersion: "v1.2",
51                 $graph: [
52                     {
53                         "class": "Workflow",
54                         "id": "#main",
55                         "inputs": [],
56                         "outputs": [],
57                         "requirements": [
58                             {
59                                 "class": "SubworkflowFeatureRequirement"
60                             }
61                         ],
62                         "steps": [
63                             {
64                                 "id": "#main/cat1-testcli.cwl (v1.2.0-109-g9b091ed)",
65                                 "in": [],
66                                 "label": "cat1-testcli.cwl (v1.2.0-109-g9b091ed)",
67                                 "out": [
68                                     {
69                                         "id": "#main/step/args"
70                                     }
71                                 ],
72                                 "run": `keep:undefined/bar`
73                             }
74                         ]
75                     }
76                 ],
77                 "cwlVersion": "v1.2",
78                 "http://arvados.org/cwl#gitBranch": "1.2.1_proposed",
79                 "http://arvados.org/cwl#gitCommit": "9b091ed7e0bef98b3312e9478c52b89ba25792de",
80                 "http://arvados.org/cwl#gitCommitter": "GitHub <noreply@github.com>",
81                 "http://arvados.org/cwl#gitDate": "Sun, 11 Sep 2022 21:24:42 +0200",
82                 "http://arvados.org/cwl#gitDescribe": "v1.2.0-109-g9b091ed",
83                 "http://arvados.org/cwl#gitOrigin": "git@github.com:common-workflow-language/cwl-v1.2",
84                 "http://arvados.org/cwl#gitPath": "tests/cat1-testcli.cwl",
85                 "http://arvados.org/cwl#gitStatus": ""
86             })
87         }}).then(function(workflowResource) {
88             cy.loginAs(activeUser);
89             cy.goToPath(`/workflows/${workflowResource.uuid}`);
90             cy.get('[data-cy=registered-workflow-info-panel]').should('contain', workflowResource.name);
91             cy.get('[data-cy=workflow-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
92         });
93     });
94
95     const verifyIOParameter = (name, label, doc, val, collection) => {
96         cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
97             label && cy.contains(label);
98
99             if (val) {
100                 if (Array.isArray(val)) {
101                     val.forEach(v => cy.contains(v));
102                 } else {
103                     cy.contains(val);
104                 }
105             }
106             if (collection) {
107                 cy.contains(collection);
108             }
109         });
110     };
111
112     it('shows workflow details', function() {
113         cy.createCollection(adminUser.token, {
114             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
115             owner_uuid: activeUser.user.uuid,
116             manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
117         })
118             .then(function(collectionResource) {
119                 cy.createResource(activeUser.token, "workflows", {workflow: {
120                     name: "Test wf",
121                     definition: JSON.stringify({
122                         cwlVersion: "v1.2",
123                         $graph: [
124                             {
125                                 "class": "Workflow",
126                                 "hints": [
127                                     {
128                                         "class": "DockerRequirement",
129                                         "dockerPull": "python:2-slim"
130                                     }
131                                 ],
132                                 "id": "#main",
133                                 "inputs": [
134                                     {
135                                         "id": "#main/file1",
136                                         "type": "File"
137                                     },
138                                     {
139                                         "id": "#main/numbering",
140                                         "type": [
141                                             "null",
142                                             "boolean"
143                                         ]
144                                     },
145                                     {
146                                         "default": {
147                                             "basename": "args.py",
148                                             "class": "File",
149                                             "location": "keep:de738550734533c5027997c87dc5488e+53/args.py",
150                                             "nameext": ".py",
151                                             "nameroot": "args",
152                                             "size": 179
153                                         },
154                                         "id": "#main/args.py",
155                                         "type": "File"
156                                     }
157                                 ],
158                                 "outputs": [
159                                     {
160                                         "id": "#main/args",
161                                         "outputSource": "#main/step/args",
162                                         "type": {
163                                             "items": "string",
164                                             "name": "_:b0adccc1-502d-476f-8a5b-c8ef7119e2dc",
165                                             "type": "array"
166                                         }
167                                     }
168                                 ],
169                                 "requirements": [
170                                     {
171                                         "class": "SubworkflowFeatureRequirement"
172                                     }
173                                 ],
174                                 "steps": [
175                                     {
176                                         "id": "#main/cat1-testcli.cwl (v1.2.0-109-g9b091ed)",
177                                         "in": [
178                                             {
179                                                 "id": "#main/step/file1",
180                                                 "source": "#main/file1"
181                                             },
182                                             {
183                                                 "id": "#main/step/numbering",
184                                                 "source": "#main/numbering"
185                                             },
186                                             {
187                                                 "id": "#main/step/args.py",
188                                                 "source": "#main/args.py"
189                                             }
190                                         ],
191                                         "label": "cat1-testcli.cwl (v1.2.0-109-g9b091ed)",
192                                         "out": [
193                                             {
194                                                 "id": "#main/step/args"
195                                             }
196                                         ],
197                                         "run": `keep:${collectionResource.portable_data_hash}/bar`
198                                     }
199                                 ]
200                             }
201                         ],
202                         "cwlVersion": "v1.2",
203                         "http://arvados.org/cwl#gitBranch": "1.2.1_proposed",
204                         "http://arvados.org/cwl#gitCommit": "9b091ed7e0bef98b3312e9478c52b89ba25792de",
205                         "http://arvados.org/cwl#gitCommitter": "GitHub <noreply@github.com>",
206                         "http://arvados.org/cwl#gitDate": "Sun, 11 Sep 2022 21:24:42 +0200",
207                         "http://arvados.org/cwl#gitDescribe": "v1.2.0-109-g9b091ed",
208                         "http://arvados.org/cwl#gitOrigin": "git@github.com:common-workflow-language/cwl-v1.2",
209                         "http://arvados.org/cwl#gitPath": "tests/cat1-testcli.cwl",
210                         "http://arvados.org/cwl#gitStatus": ""
211                     })
212                 }}).then(function(workflowResource) {
213                     cy.loginAs(activeUser);
214                     cy.goToPath(`/workflows/${workflowResource.uuid}`);
215                     cy.get('[data-cy=registered-workflow-info-panel]').should('contain', workflowResource.name);
216                     cy.get('[data-cy=workflow-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
217                     cy.get('[data-cy=registered-workflow-info-panel')
218                         .should('contain', 'gitCommit: 9b091ed7e0bef98b3312e9478c52b89ba25792de')
219
220                     cy.get('[data-cy=process-io-card] h6').contains('Input Parameters')
221                         .parents('[data-cy=process-io-card]').within(() => {
222                             verifyIOParameter('file1', null, '', '', '');
223                             verifyIOParameter('numbering', null, '', '', '');
224                             verifyIOParameter('args.py', null, '', 'args.py', 'de738550734533c5027997c87dc5488e+53');
225                         });
226                     cy.get('[data-cy=process-io-card] h6').contains('Output Parameters')
227                         .parents('[data-cy=process-io-card]').within(() => {
228                             verifyIOParameter('args', null, '', '', '');
229                         });
230                     cy.get('[data-cy=collection-files-panel]').within(() => {
231                         cy.get('[data-cy=collection-files-right-panel]', { timeout: 5000 })
232                             .should('contain', 'bar');
233                     });
234                 });
235             });
236     });
237
238     it('can delete a workflow', function() {
239         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
240             .then(function(workflowResource) {
241                 cy.loginAs(activeUser);
242                 cy.goToPath(`/projects/${activeUser.user.uuid}`);
243                 cy.get('[data-cy=project-panel] table tbody').contains(workflowResource.name).rightclick();
244                 cy.get('[data-cy=context-menu]').contains('Delete Workflow').click();
245                 cy.get('[data-cy=confirmation-dialog-ok-btn]').should('exist').click();
246                 cy.get('[data-cy=project-panel] table tbody').should('not.contain', workflowResource.name);
247             });
248     });
249
250     it('can delete multiple workflows', function() {
251         const wfNames = ["Test wf1", "Test wf2", "Test wf3"];
252
253         wfNames.forEach((wfName) => {
254             cy.createResource(activeUser.token, "workflows", {workflow: {name: wfName}})
255         });
256         
257         cy.loginAs(activeUser);
258
259         wfNames.forEach((wfName) => {
260             cy.get('tr').contains('td', wfName).should('exist').parent('tr').find('input[type="checkbox"]').click();
261         });
262         
263         cy.waitForDom().get('[data-cy=multiselect-button]', {timeout: 10000}).should('be.visible')
264         cy.get('[data-cy=multiselect-button]', {timeout: 10000}).should('have.length', '1').trigger('mouseover');
265         cy.get('body').contains('Delete Workflow', {timeout: 10000}).should('exist')
266         cy.get('[data-cy=multiselect-button]').eq(0).click();
267         cy.get('[data-cy=confirmation-dialog-ok-btn]').should('exist').click();
268
269         wfNames.forEach((wfName) => {
270             cy.get('tr').contains(wfName).should('not.exist');
271         });
272     });
273
274     it('cannot delete readonly workflow', function() {
275         cy.createProject({
276             owningUser: adminUser,
277             targetUser: activeUser,
278             projectName: 'mySharedReadonlyProject',
279             canWrite: false,
280         });
281         cy.getAll('@mySharedReadonlyProject')
282             .then(function ([mySharedReadonlyProject]) {
283                 cy.createResource(adminUser.token, "workflows", {workflow: {name: "Test wf", owner_uuid: mySharedReadonlyProject.uuid}})
284                     .then(function(workflowResource) {
285                         cy.loginAs(activeUser);
286                         cy.goToPath(`/shared-with-me`);
287                         cy.contains("mySharedReadonlyProject").click();
288                         cy.get('[data-cy=project-panel] table tbody').contains(workflowResource.name).rightclick();
289                         cy.get('[data-cy=context-menu]').should("not.contain", 'Delete Workflow');
290                     });
291             });
292     });
293
294     it('shows the appropriate buttons in the multiselect toolbar', () => {
295
296         const msButtonTooltips = [
297             'View details',
298             'Open in new tab',
299             'Copy link to clipboard',
300             'API Details',
301             'Run Workflow',
302             'Delete Workflow',
303         ];
304
305         cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
306             .then(function(workflowResource) {
307                 cy.loginAs(activeUser);
308                 cy.get("[data-cy=side-panel-tree]").contains("Home Projects").click();
309                 cy.waitForDom()
310                 cy.get('[data-cy=data-table-row]').contains(workflowResource.name).should('exist').parent().parent().parent().click()
311                 cy.get('[data-cy=multiselect-button]').should('have.length', msButtonTooltips.length)
312                 for (let i = 0; i < msButtonTooltips.length; i++) {
313                         cy.get('[data-cy=multiselect-button]').eq(i).trigger('mouseover');
314                         cy.get('body').contains(msButtonTooltips[i]).should('exist')
315                         cy.get('[data-cy=multiselect-button]').eq(i).trigger('mouseout');
316                     }
317                 });
318     })
319
320 });