1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 describe("Process tests", 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)
17 adminUser = this.adminUser;
19 cy.getUser("user", "Active", "User", false, true)
22 activeUser = this.activeUser;
26 beforeEach(function () {
28 cy.clearLocalStorage();
31 function setupDockerImage(image_name) {
32 // Create a collection that will be used as a docker image for the tests.
33 cy.createCollection(adminUser.token, {
36 ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n",
39 .then(function (dockerImage) {
40 // Give read permissions to the active user on the docker image.
41 cy.createLink(adminUser.token, {
42 link_class: "permission",
44 tail_uuid: activeUser.user.uuid,
45 head_uuid: dockerImage.uuid,
47 .as("dockerImagePermission")
49 // Set-up docker image collection tags
50 cy.createLink(activeUser.token, {
51 link_class: "docker_image_repo+tag",
53 head_uuid: dockerImage.uuid,
54 }).as("dockerImageRepoTag");
55 cy.createLink(activeUser.token, {
56 link_class: "docker_image_hash",
57 name: "sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678",
58 head_uuid: dockerImage.uuid,
59 }).as("dockerImageHash");
62 return cy.getAll("@dockerImage", "@dockerImageRepoTag", "@dockerImageHash", "@dockerImagePermission").then(function ([dockerImage]) {
67 function createContainerRequest(user, name, docker_image, command, reuse = false, state = "Uncommitted") {
68 return setupDockerImage(docker_image).then(function (dockerImage) {
69 return cy.createContainerRequest(user.token, {
72 container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
73 output_path: "stdout.txt",
75 runtime_constraints: {
91 it("shows process logs", function () {
92 const crName = "test_container_request";
93 createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
94 cy.loginAs(activeUser);
95 cy.goToPath(`/processes/${containerRequest.uuid}`);
96 cy.get("[data-cy=process-details]").should("contain", crName);
97 cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
98 cy.createLog(activeUser.token, {
99 object_uuid: containerRequest.container_uuid,
103 event_type: "stdout",
104 }).then(function (log) {
105 cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
110 it("shows process details", function () {
111 createContainerRequest(
113 `test_container_request ${Math.floor(Math.random() * 999999)}`,
115 ["echo", "hello world"],
118 ).then(function (containerRequest) {
119 cy.loginAs(activeUser);
120 cy.goToPath(`/processes/${containerRequest.uuid}`);
121 cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
122 cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
123 cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
126 // Fake submitted by another user
127 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
129 res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
133 createContainerRequest(
135 `test_container_request ${Math.floor(Math.random() * 999999)}`,
137 ["echo", "hello world"],
140 ).then(function (containerRequest) {
141 cy.loginAs(activeUser);
142 cy.goToPath(`/processes/${containerRequest.uuid}`);
143 cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
144 cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
145 cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
149 it("filters process logs by event type", function () {
150 const nodeInfoLogs = [
152 "Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux",
155 "vendor_id : GenuineIntel",
158 "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
160 const crunchRunLogs = [
161 "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
162 "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
163 "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
164 "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
165 "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
168 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
169 "Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
170 "In hac habitasse platea dictumst.",
171 "Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
172 "Interdum et malesuada fames ac ante ipsum primis in faucibus.",
173 "Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
174 "Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
175 "Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
176 "Donec vitae leo id augue gravida bibendum.",
177 "Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
178 "Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
179 "Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
180 "Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.",
181 "Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.",
182 "Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
183 "Duis tristique semper dolor, vitae pulvinar risus.",
184 "Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
185 "Nulla eget mollis ipsum.",
188 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
191 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "node-info", nodeInfoLogs).as("nodeInfoLogs");
192 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "crunch-run", crunchRunLogs).as("crunchRunLogs");
193 cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "stdout", stdoutLogs).as("stdoutLogs");
194 cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
195 cy.loginAs(activeUser);
196 cy.goToPath(`/processes/${containerRequest.uuid}`);
197 // Should show main logs by default
198 cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
199 cy.get("[data-cy=process-logs]")
200 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
201 .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
202 .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
204 cy.get("[data-cy=process-logs-filter]").click();
205 cy.get("body").contains("li", "All logs").click();
206 cy.get("[data-cy=process-logs]")
207 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
208 .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
209 .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
210 // Select 'node-info' logs
211 cy.get("[data-cy=process-logs-filter]").click();
212 cy.get("body").contains("li", "node-info").click();
213 cy.get("[data-cy=process-logs]")
214 .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
215 .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
216 .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
217 // Select 'stdout' logs
218 cy.get("[data-cy=process-logs-filter]").click();
219 cy.get("body").contains("li", "stdout").click();
220 cy.get("[data-cy=process-logs]")
221 .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
222 .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
223 .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
228 it("should show runtime status indicators", function () {
229 // Setup running container with runtime_status error & warning messages
230 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
231 .as("containerRequest")
232 .then(function (containerRequest) {
233 expect(containerRequest.state).to.equal("Committed");
234 expect(containerRequest.container_uuid).not.to.be.equal("");
236 cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
237 expect(queuedContainer.state).to.be.equal("Queued");
239 cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
241 }).then(function (lockedContainer) {
242 expect(lockedContainer.state).to.be.equal("Locked");
244 cy.updateContainer(adminUser.token, lockedContainer.uuid, {
247 error: "Something went wrong",
248 errorDetail: "Process exited with status 1",
249 warning: "Free disk space is low",
252 .as("runningContainer")
253 .then(function (runningContainer) {
254 expect(runningContainer.state).to.be.equal("Running");
255 expect(runningContainer.runtime_status).to.be.deep.equal({
256 error: "Something went wrong",
257 errorDetail: "Process exited with status 1",
258 warning: "Free disk space is low",
263 // Test that the UI shows the error and warning messages
264 cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
265 cy.loginAs(activeUser);
266 cy.goToPath(`/processes/${containerRequest.uuid}`);
267 cy.get("[data-cy=process-runtime-status-error]").should("contain", "Something went wrong").and("contain", "Process exited with status 1");
268 cy.get("[data-cy=process-runtime-status-warning]")
269 .should("contain", "Free disk space is low")
270 .and("contain", "No additional warning details available");
273 // Force container_count for testing
274 let containerCount = 2;
275 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
277 res.body.container_count = containerCount;
281 cy.getAll("@containerRequest").then(function ([containerRequest]) {
282 cy.goToPath(`/processes/${containerRequest.uuid}`);
283 cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
286 cy.getAll("@containerRequest").then(function ([containerRequest]) {
288 cy.goToPath(`/processes/${containerRequest.uuid}`);
289 cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
296 id: "#main/input_file",
297 label: "Label Description",
302 basename: "input1.tar",
304 location: "keep:00000000000000000000000000000000+01/input1.tar",
307 basename: "input1-2.txt",
309 location: "keep:00000000000000000000000000000000+01/input1-2.txt",
312 basename: "input1-3.txt",
314 location: "keep:00000000000000000000000000000000+01/input1-3.txt",
317 basename: "input1-4.txt",
319 location: "keep:00000000000000000000000000000000+01/input1-4.txt",
327 id: "#main/input_dir",
328 doc: "Doc Description",
333 basename: "11111111111111111111111111111111+01",
335 location: "keep:11111111111111111111111111111111+01",
341 id: "#main/input_bool",
342 doc: ["Doc desc 1", "Doc desc 2"],
351 id: "#main/input_int",
360 id: "#main/input_long",
369 id: "#main/input_float",
378 id: "#main/input_double",
387 id: "#main/input_string",
391 input_string: "Hello World",
396 id: "#main/input_file_array",
405 basename: "input2.tar",
407 location: "keep:00000000000000000000000000000000+02/input2.tar",
410 basename: "input3.tar",
412 location: "keep:00000000000000000000000000000000+03/input3.tar",
415 basename: "input3-2.txt",
417 location: "keep:00000000000000000000000000000000+03/input3-2.txt",
422 $import: "import_path",
429 id: "#main/input_dir_array",
438 basename: "11111111111111111111111111111111+02",
440 location: "keep:11111111111111111111111111111111+02",
443 basename: "11111111111111111111111111111111+03",
445 location: "keep:11111111111111111111111111111111+03",
448 $import: "import_path",
455 id: "#main/input_int_array",
467 $import: "import_path",
474 id: "#main/input_long_array",
485 $import: "import_path",
492 id: "#main/input_float_array",
504 $import: "import_path",
511 id: "#main/input_double_array",
518 input_double_array: [
523 $import: "import_path",
530 id: "#main/input_string_array",
537 input_string_array: [
542 $import: "import_path",
549 id: "#main/input_bool_include",
553 input_bool_include: {
554 $include: "include_path",
560 id: "#main/input_int_include",
565 $include: "include_path",
571 id: "#main/input_float_include",
575 input_float_include: {
576 $include: "include_path",
582 id: "#main/input_string_include",
586 input_string_include: {
587 $include: "include_path",
593 id: "#main/input_file_include",
597 input_file_include: {
598 $include: "include_path",
604 id: "#main/input_directory_include",
608 input_directory_include: {
609 $include: "include_path",
615 id: "#main/input_file_url",
620 basename: "index.html",
622 location: "http://example.com/index.html",
628 const testOutputs = [
631 id: "#main/output_file",
632 label: "Label Description",
645 id: "#main/output_file_with_secondary",
646 doc: "Doc Description",
650 output_file_with_secondary: {
651 basename: "main.dat",
653 location: "main.dat",
656 basename: "secondary.dat",
658 location: "secondary.dat",
661 basename: "secondary2.dat",
663 location: "secondary2.dat",
671 id: "#main/output_dir",
672 doc: ["Doc desc 1", "Doc desc 2"],
685 id: "#main/output_bool",
694 id: "#main/output_int",
703 id: "#main/output_long",
712 id: "#main/output_float",
721 id: "#main/output_double",
725 output_double: 100.3,
730 id: "#main/output_string",
734 output_string: "Hello output",
739 id: "#main/output_file_array",
748 basename: "output2.tar",
750 location: "output2.tar",
753 basename: "output3.tar",
755 location: "output3.tar",
762 id: "#main/output_dir_array",
785 id: "#main/output_int_array",
792 output_int_array: [10, 11, 12],
797 id: "#main/output_long_array",
804 output_long_array: [51, 52],
809 id: "#main/output_float_array",
816 output_float_array: [100.2, 100.4, 100.6],
821 id: "#main/output_double_array",
828 output_double_array: [100.1, 100.2, 100.3],
833 id: "#main/output_string_array",
840 output_string_array: ["Hello", "Output", "!"],
845 const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
849 .within($mainRow => {
850 label && cy.contains(label);
853 cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
855 if (Array.isArray(val)) {
856 val.forEach(v => cy.get("@secondaryRows").contains(v));
858 cy.get("@secondaryRows").contains(val);
862 cy.get("@secondaryRows").contains(collection);
866 if (Array.isArray(val)) {
867 val.forEach(v => cy.contains(v));
873 cy.contains(collection);
879 const verifyIOParameterImage = (name, url) => {
884 cy.get('[alt="Inline Preview"]')
885 .should("be.visible")
887 expect($img[0].naturalWidth).to.be.greaterThan(0);
888 expect($img[0].src).contains(url);
893 it("displays IO parameters with keep links and previews", function () {
894 // Create output collection for real files
895 cy.createCollection(adminUser.token, {
896 name: `Test collection ${Math.floor(Math.random() * 999999)}`,
897 owner_uuid: activeUser.user.uuid,
898 }).then(testOutputCollection => {
899 cy.loginAs(activeUser);
901 cy.goToPath(`/collections/${testOutputCollection.uuid}`);
903 cy.get("[data-cy=upload-button]").click();
905 cy.fixture("files/cat.png", "base64").then(content => {
906 cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
907 cy.get("[data-cy=form-submit-btn]").click();
908 cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
909 // Confirm final collection state.
910 cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
913 cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
916 // Get updated collection pdh
917 cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
918 // Add output uuid and inputs to container request
919 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
921 res.body.output_uuid = testOutputCollection.uuid;
922 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
923 content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
925 res.body.mounts["/var/lib/cwl/workflow.json"] = {
930 inputs: testInputs.map(input => input.definition),
931 outputs: testOutputs.map(output => output.definition),
939 // Stub fake output collection
941 { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
945 uuid: testOutputCollection.uuid,
946 portable_data_hash: testOutputCollection.portable_data_hash,
951 // Stub fake output json
953 { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
956 body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
960 // Stub webdav response, points to output json
962 { method: "PROPFIND", url: "*" },
964 fixture: "webdav-propfind-outputs.xml",
969 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
973 cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
974 cy.goToPath(`/processes/${containerRequest.uuid}`);
975 cy.get("[data-cy=process-io-card] h6")
977 .parents("[data-cy=process-io-card]")
979 verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
980 verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
981 verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
982 verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
983 verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
984 verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
985 verifyIOParameter("input_int", null, null, "1");
986 verifyIOParameter("input_long", null, null, "1");
987 verifyIOParameter("input_float", null, null, "1.5");
988 verifyIOParameter("input_double", null, null, "1.3");
989 verifyIOParameter("input_string", null, null, "Hello World");
990 verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
991 verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
992 verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
993 verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
994 verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
995 verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
996 verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
997 verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
998 verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
999 verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
1000 verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
1001 verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
1002 verifyIOParameter("input_bool_include", null, null, "Cannot display value");
1003 verifyIOParameter("input_int_include", null, null, "Cannot display value");
1004 verifyIOParameter("input_float_include", null, null, "Cannot display value");
1005 verifyIOParameter("input_string_include", null, null, "Cannot display value");
1006 verifyIOParameter("input_file_include", null, null, "Cannot display value");
1007 verifyIOParameter("input_directory_include", null, null, "Cannot display value");
1008 verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
1010 cy.get("[data-cy=process-io-card] h6")
1011 .contains("Outputs")
1012 .parents("[data-cy=process-io-card]")
1014 cy.get(ctx).scrollIntoView();
1015 cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
1016 const outPdh = testOutputCollection.portable_data_hash;
1017 verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
1018 verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
1019 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
1020 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
1021 verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
1022 verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
1023 verifyIOParameter("output_bool", null, null, "true");
1024 verifyIOParameter("output_int", null, null, "1");
1025 verifyIOParameter("output_long", null, null, "1");
1026 verifyIOParameter("output_float", null, null, "100.5");
1027 verifyIOParameter("output_double", null, null, "100.3");
1028 verifyIOParameter("output_string", null, null, "Hello output");
1029 verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
1030 verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
1031 verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
1032 verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
1033 verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
1034 verifyIOParameter("output_long_array", null, null, ["51", "52"]);
1035 verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
1036 verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
1037 verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
1042 it("displays IO parameters with no value", function () {
1043 const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
1044 const fakeOutputPDH = "11111111111111111111111111111111+99/";
1046 cy.loginAs(activeUser);
1048 // Add output uuid and inputs to container request
1049 cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
1051 res.body.output_uuid = fakeOutputUUID;
1052 res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
1055 res.body.mounts["/var/lib/cwl/workflow.json"] = {
1060 inputs: testInputs.map(input => input.definition),
1061 outputs: testOutputs.map(output => output.definition),
1069 // Stub fake output collection
1071 { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
1075 uuid: fakeOutputUUID,
1076 portable_data_hash: fakeOutputPDH,
1081 // Stub fake output json
1083 { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
1090 cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
1091 // Stub webdav response, points to output json
1093 { method: "PROPFIND", url: "*" },
1096 body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
1101 createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
1105 cy.getAll("@containerRequest").then(function ([containerRequest]) {
1106 cy.goToPath(`/processes/${containerRequest.uuid}`);
1107 cy.get("[data-cy=process-io-card] h6")
1109 .parents("[data-cy=process-io-card]")
1113 cy.get("tbody tr").each(item => {
1114 cy.wrap(item).contains("No value");
1117 cy.get("[data-cy=process-io-card] h6")
1118 .contains("Outputs")
1119 .parents("[data-cy=process-io-card]")
1121 cy.get("tbody tr").each(item => {
1122 cy.wrap(item).contains("No value");
1128 it("allows copying processes", function () {
1129 const crName = "first_container_request";
1130 const copiedCrName = "copied_container_request";
1131 createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1132 cy.loginAs(activeUser);
1133 cy.goToPath(`/processes/${containerRequest.uuid}`);
1134 cy.get("[data-cy=process-details]").should("contain", crName);
1136 cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
1137 cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
1140 cy.get("[data-cy=form-dialog]").within(() => {
1141 cy.get("input[name=name]").clear().type(copiedCrName);
1142 cy.get("[data-cy=projects-tree-home-tree-picker]").click();
1143 cy.get("[data-cy=form-submit-btn]").click();
1146 cy.get("[data-cy=process-details]").should("contain", copiedCrName);
1147 cy.get("[data-cy=process-details]").find("button").contains("Run");
1150 const getFakeContainer = fakeContainerUuid => ({
1151 href: `/containers/${fakeContainerUuid}`,
1152 kind: "arvados#container",
1153 etag: "ecfosljpnxfari9a8m7e4yv06",
1154 uuid: fakeContainerUuid,
1155 owner_uuid: "zzzzz-tpzed-000000000000000",
1156 created_at: "2023-02-13T15:55:47.308915000Z",
1157 modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
1158 modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
1159 modified_at: "2023-02-15T19:12:45.987086000Z",
1161 "arvados-cwl-runner",
1164 "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
1165 "/var/lib/cwl/workflow.json#main",
1166 "/var/lib/cwl/cwl.input.json",
1168 container_image: "4ad7d11381df349e464694762db14e04+303",
1169 cwd: "/var/spool/cwl",
1173 locked_by_uuid: null,
1176 output_path: "/var/spool/cwl",
1178 runtime_constraints: {
1183 hardware_capability: "",
1185 keep_cache_disk: 2147483648,
1193 scheduling_parameters: {
1198 runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
1199 runtime_auth_scopes: ["all"],
1201 gateway_address: null,
1202 interactive_session_started: false,
1203 output_storage_classes: ["default"],
1204 output_properties: {},
1206 subrequests_cost: 0.0,
1209 it("shows cancel button when appropriate", function () {
1210 // Ignore collection requests
1212 { method: "GET", url: `**/arvados/v1/collections/*` },
1219 // Uncommitted container
1220 const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
1221 createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
1224 // Navigate to process and verify run / cancel button
1225 cy.goToPath(`/processes/${containerRequest.uuid}`);
1227 cy.get("[data-cy=process-details]").should("contain", crUncommitted);
1228 cy.get("[data-cy=process-run-button]").should("exist");
1229 cy.get("[data-cy=process-cancel-button]").should("not.exist");
1233 const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
1234 const fakeCrUuid = "zzzzz-dz642-000000000000001";
1235 createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1236 // Fake container uuid
1237 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1239 res.body.output_uuid = fakeCrUuid;
1240 res.body.priority = 500;
1241 res.body.state = "Committed";
1246 const container = getFakeContainer(fakeCrUuid);
1248 { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
1251 body: { ...container, state: "Queued", priority: 500 },
1255 // Navigate to process and verify cancel button
1256 cy.goToPath(`/processes/${containerRequest.uuid}`);
1258 cy.get("[data-cy=process-details]").should("contain", crQueued);
1259 cy.get("[data-cy=process-cancel-button]").contains("Cancel");
1263 const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
1264 const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
1265 createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1266 // Fake container uuid
1267 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1269 res.body.output_uuid = fakeCrLockedUuid;
1270 res.body.priority = 500;
1271 res.body.state = "Committed";
1276 const container = getFakeContainer(fakeCrLockedUuid);
1278 { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
1281 body: { ...container, state: "Locked", priority: 500 },
1285 // Navigate to process and verify cancel button
1286 cy.goToPath(`/processes/${containerRequest.uuid}`);
1288 cy.get("[data-cy=process-details]").should("contain", crLocked);
1289 cy.get("[data-cy=process-cancel-button]").contains("Cancel");
1292 // On Hold container
1293 const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
1294 const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
1295 createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
1296 // Fake container uuid
1297 cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
1299 res.body.output_uuid = fakeCrOnHoldUuid;
1300 res.body.priority = 0;
1301 res.body.state = "Committed";
1306 const container = getFakeContainer(fakeCrOnHoldUuid);
1308 { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
1311 body: { ...container, state: "Queued", priority: 0 },
1315 // Navigate to process and verify cancel button
1316 cy.goToPath(`/processes/${containerRequest.uuid}`);
1318 cy.get("[data-cy=process-details]").should("contain", crOnHold);
1319 cy.get("[data-cy=process-run-button]").should("exist");
1320 cy.get("[data-cy=process-cancel-button]").should("not.exist");